【商城实战(8)】筑牢权限防线:用户认证与权限管理进阶

devtools/2025/3/6 22:55:24/

商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想深入钻研技术细节,还是探寻商城运营之道,本专栏都能提供从 0 到 1 的系统讲解,助力你打造独具竞争力的电商平台,开启电商实战之旅。

目录

  • 一、引入 Spring Security 框架,配置用户角色与权限
    • 1.1 为什么选择 Spring Security
    • 1.2 引入依赖
    • 1.3 创建安全配置类
    • 1.4 创建用户角色
    • 1.5 设置权限
  • 二、详细讲解 JWT 生成、验证、存储及过期处理机制
    • 2.1 JWT 是什么
    • 2.2 JWT 的结构
    • 2.3 JWT 生成
    • 2.4 JWT 验证
    • 2.5 JWT 存储
    • 2.6 过期处理机制
  • 三、结合实际业务场景,展示如何基于角色权限控制用户访问资源
    • 3.1 业务场景分析
    • 3.2 基于角色的访问控制(RBAC)模型
    • 3.3 数据库设计
    • 3.4 代码实现


一、引入 Spring Security 框架,配置用户角色与权限

在商城系统中,保障用户数据的安全和系统的稳定运行至关重要。用户权限管理与认证机制是实现这一目标的关键环节。引入 Spring Security 框架,能够为我们的商城系统提供强大的安全保障。它不仅能帮助我们实现用户的身份认证,还能方便地进行权限控制,确保不同角色的用户只能访问其被授权的资源。

1.1 为什么选择 Spring Security

Spring Security 是一个基于 Spring 框架的安全认证和授权框架,在众多安全框架中脱颖而出,具有显著优势。它提供了全面的安全功能,涵盖用户认证、授权、防止会话固定攻击、点击劫持、跨站请求伪造等常见攻击防护,能全方位保障应用程序的安全。同时,Spring Security 与 Spring Boot 的集成非常便利,借助 Spring Boot 的自动配置特性,只需引入相关依赖,就能快速搭建起安全认证体系,大大减少了开发工作量和配置的复杂性,提高开发效率,因此成为众多开发者保护 Spring 应用程序安全的首选方案。

1.2 引入依赖

在商城项目的pom.xml文件中添加 Spring Security 依赖,具体代码如下:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

添加上述依赖后,Maven 会自动下载 Spring Security 相关的库及其依赖项,为项目引入 Spring Security 的核心功能,后续就可以基于这些依赖进行安全配置和开发。

1.3 创建安全配置类

在项目的配置包下创建一个继承WebSecurityConfigurerAdapter的配置类,比如命名为SecurityConfig。在这个类中,我们需要重写一些方法来定义安全策略。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 配置哪些URL不需要保护,例如登录页面、静态资源等.antMatchers("/login", "/static/**").permitAll()// 其他请求都需要认证.anyRequest().authenticated().and().formLogin()// 自定义登录页面.loginPage("/login")// 登录成功后的默认跳转页面.defaultSuccessUrl("/home").permitAll().and().logout().permitAll();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

在上述代码中:

  • configure(HttpSecurity http)方法用于配置 HTTP 安全策略。
    • .antMatchers(“/login”, “/static/**”).permitAll()表示/login路径和/static/下的所有静态资源不需要认证,任何用户都可以访问。
    • .anyRequest().authenticated()表示其他所有请求都需要用户进行身份认证。
    • .formLogin()部分配置了表单登录相关的信息,包括自定义登录页面为/login,登录成功后默认跳转到/home页面。
    • .logout().permitAll()表示允许所有用户进行注销操作。
  • passwordEncoder()方法定义了密码编码器,使用BCryptPasswordEncoder对用户密码进行加密存储,提高密码的安全性。

1.4 创建用户角色

在SecurityConfig配置类中重写configure(AuthenticationManagerBuilder auth)方法,使用BCryptPasswordEncoder加密方式创建不同角色用户,如ADMIN和USER。

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {PasswordEncoder passwordEncoder = passwordEncoder();// 创建ADMIN角色用户auth.inMemoryAuthentication().passwordEncoder(passwordEncoder).withUser("admin").password(passwordEncoder.encode("admin123")).roles("ADMIN");// 创建USER角色用户auth.inMemoryAuthentication().passwordEncoder(passwordEncoder).withUser("user").password(passwordEncoder.encode("user123")).roles("USER");}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 省略http配置部分代码}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

在上述代码中,通过auth.inMemoryAuthentication()在内存中创建用户。使用passwordEncoder.encode()方法对用户密码进行加密,然后为用户分配相应的角色。这里创建了admin用户,密码为admin123,角色为ADMIN;user用户,密码为user123,角色为USER。在实际应用中,用户信息通常存储在数据库中,可通过自定义UserDetailsService从数据库中加载用户信息并进行认证。

1.5 设置权限

设置权限可以通过@PreAuthorize注解等方式在类和方法上进行设置,也可以在配置类中通过antMatchers定义不同 URL 路径需要的角色权限。

  1. 使用@PreAuthorize注解在方法上设置权限:首先在SecurityConfig类上添加@EnableGlobalMethodSecurity(prePostEnabled = true)注解,开启基于注解的方法级别安全。
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {// 省略其他配置代码
}

然后在控制器类的方法上使用@PreAuthorize注解设置权限,例如:

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ProductController {@GetMapping("/products")@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")public String getProducts() {return "This is a list of products. Only users with USER or ADMIN role can access.";}@GetMapping("/admin/products")@PreAuthorize("hasRole('ADMIN')")public String getAdminProducts() {return "This is a list of products for admin. Only ADMIN role can access.";}
}

在上述代码中:

  • @PreAuthorize(“hasRole(‘USER’) or hasRole(‘ADMIN’)”)表示只有拥有USER或ADMIN角色的用户才能访问/products接口。
  • @PreAuthorize(“hasRole(‘ADMIN’)”)表示只有拥有ADMIN角色的用户才能访问/admin/products接口。
  1. 在配置类中通过antMatchers定义 URL 路径权限:在SecurityConfig类的configure(HttpSecurity http)方法中,还可以通过antMatchers进一步细化 URL 路径的权限控制。
@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login", "/static/**").permitAll().antMatchers("/user/**").hasRole("USER").antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated().and()// 省略其他配置代码
}

在上述代码中:

  • .antMatchers(“/user/**”).hasRole(“USER”)表示只有拥有USER角色的用户才能访问/user/开头的所有路径。
  • .antMatchers(“/admin/**”).hasRole(“ADMIN”)表示只有拥有ADMIN角色的用户才能访问/admin/开头的所有路径。

通过上述方式,我们可以灵活地为不同角色的用户设置不同的访问权限,确保商城系统资源的安全访问。无论是在方法级别还是 URL 路径级别进行权限控制,都能根据实际业务需求进行合理配置,提高系统的安全性和可靠性。

二、详细讲解 JWT 生成、验证、存储及过期处理机制

2.1 JWT 是什么

JWT,全称 JSON Web Token,是一种基于 JSON 的开放标准(RFC 7519) ,用于在网络应用间安全地传递声明。在商城系统中,JWT 可以携带用户的身份信息、权限信息等,实现用户身份的验证和权限的控制。它具有紧凑、自包含的特点,能够在不同的系统之间轻松传递,并且可以通过数字签名来确保信息的完整性和真实性。与传统的基于 Session 的认证方式不同,JWT 是无状态的,服务器不需要存储用户的会话信息,这使得它非常适合分布式系统和前后端分离的应用架构。

2.2 JWT 的结构

JWT 由三部分组成,分别是 Header(头部)、Payload(载荷)和 Signature(签名),它们之间通过英文句号 “.” 连接。

  • Header(头部):通常包含两部分信息,即令牌的类型(typ)和签名算法(alg)。例如:
{"typ": "JWT","alg": "HS256"
}

上述代码表示这是一个 JWT 令牌,使用的签名算法是 HS256。然后,将这个 JSON 对象进行 Base64Url 编码,就得到了 JWT 的第一部分。Base64Url 编码是一种用于将二进制数据转换为文本格式的编码方式,它在 Base64 编码的基础上,对一些特殊字符进行了替换,使其更适合在 URL 中传输。

  • Payload(载荷):是 JWT 的主体部分,用于存放实际需要传递的数据。它包含三种类型的声明:注册声明(Registered Claims)、公共声明(Public Claims)和私有声明(Private Claims)。
    • 注册声明是预定义的一些声明,虽然不是必须的,但建议使用,常见的有:
      • iss(issuer):JWT 的签发者,在商城系统中可以是认证服务器。
      • exp(expiration time):JWT 的过期时间,是一个 Unix 时间戳,用于控制 JWT 的有效时长。
      • sub(subject):JWT 所面向的用户,在商城中可以是用户的唯一标识。
      • aud(audience):JWT 的接收方,可以是特定的服务或应用。
    • 公共声明是由 JWT 的使用者自定义的声明,只要不与注册声明冲突即可。比如在商城中,可以自定义一个userRole声明来表示用户角色。
    • 私有声明是在特定的应用场景下,由应用开发者自定义的声明,用于满足特定的业务需求。例如:
{"sub": "1234567890","name": "John Doe","userRole": "USER","iat": 1516239022
}

将上述 JSON 对象进行 Base64Url 编码,就得到了 JWT 的第二部分。需要注意的是,Payload 部分默认是不加密的,因此不要将敏感信息(如用户密码)存放在其中。

  • Signature(签名):用于验证 JWT 的完整性和真实性,防止 JWT 被篡改。签名的生成需要使用编码后的 Header、编码后的 Payload、一个密钥(Secret)以及 Header 中指定的签名算法。以 HS256 算法为例,签名的生成公式如下:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret
)

服务器在接收到 JWT 后,会使用相同的密钥和签名算法,根据接收到的 Header 和 Payload 重新计算签名,并与 JWT 中的签名进行对比。如果两者一致,则说明 JWT 没有被篡改,是可信的;否则,说明 JWT 可能被篡改,应拒绝该请求。将计算得到的签名进行 Base64Url 编码,就得到了 JWT 的第三部分。

2.3 JWT 生成

在 Java 中,我们可以使用jjwt库来生成 JWT。首先,在pom.xml文件中添加jjwt依赖:

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.2</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.2</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.2</version><scope>runtime</scope>
</dependency>

然后,编写生成 JWT 的代码示例:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;public class JwtUtil {// 密钥,应妥善保管,不要硬编码在代码中,这里仅为示例private static final String SECRET = "your_secret_key";// 过期时间,这里设置为1小时private static final long EXPIRATION_TIME = 1 * 60 * 60 * 1000;public static String generateToken(String username, String role) {Claims claims = Jwts.claims();claims.put("sub", username);claims.put("userRole", role);return Jwts.builder().setClaims(claims).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)).signWith(SignatureAlgorithm.HS256, SECRET).compact();}
}

在上述代码中:

  • Jwts.claims()创建一个Claims对象,用于存放载荷信息。
  • claims.put(“sub”, username)和claims.put(“userRole”, role)分别设置了sub(用户标识)和userRole(用户角色)这两个载荷信息。
  • Jwts.builder()开始构建 JWT。
  • .setClaims(claims)将之前设置的载荷信息添加到 JWT 中。
  • .setIssuedAt(new Date())设置 JWT 的签发时间为当前时间。
  • .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))设置 JWT 的过期时间为当前时间加上 1 小时。
  • .signWith(SignatureAlgorithm.HS256, SECRET)使用 HS256 算法和指定的密钥对 JWT 进行签名。
  • .compact()生成最终的 JWT 字符串。

2.4 JWT 验证

同样使用jjwt库来实现 JWT 的验证,代码示例如下:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;public class JwtUtil {private static final String SECRET = "your_secret_key";public static boolean validateToken(String token) {try {Claims claims = Jwts.parserBuilder().setSigningKey(SECRET).build().parseClaimsJws(token).getBody();Date expiration = claims.getExpiration();Date now = new Date();// 检查JWT是否过期if (expiration.before(now)) {return false;}return true;} catch (SignatureException e) {// 签名验证失败return false;} catch (Exception e) {// 其他异常return false;}}public static Claims getClaims(String token) {return Jwts.parserBuilder().setSigningKey(SECRET).build().parseClaimsJws(token).getBody();}
}

在上述代码中:

  • validateToken(String token)方法用于验证 JWT 的有效性。
    • Jwts.parserBuilder().setSigningKey(SECRET).build().parseClaimsJws(token).getBody()尝试解析 JWT,并获取其中的载荷信息。如果签名验证失败,会抛出SignatureException异常。
    • 然后检查 JWT 的过期时间expiration是否早于当前时间now,如果是,则说明 JWT 已过期,返回false;否则返回true。
  • getClaims(String token)方法用于获取 JWT 中的载荷信息,在需要获取用户身份、角色等信息时可以调用该方法。

2.5 JWT 存储

JWT 在客户端的存储方式主要有以下几种:

  • Local Storage:可以使用浏览器提供的localStorage来存储 JWT。例如:
localStorage.setItem('jwtToken', 'your_token_here');

优点是存储方便,并且在页面刷新后仍然存在。缺点是安全性相对较低,因为localStorage中的数据可以被客户端脚本读取,存在被 XSS 攻击窃取的风险。适用于对安全性要求不是特别高,且需要在页面刷新后保持用户登录状态的场景。

  • Session Storage:类似于localStorage,但sessionStorage中的数据仅在当前会话期间有效,浏览器关闭后数据会被清除。例如:
sessionStorage.setItem('jwtToken', 'your_token_here');

优点是在会话结束后,JWT 会自动消失,降低了被攻击的风险。缺点是页面刷新后,数据依然存在,如果用户在同一浏览器中打开多个标签页,可能会导致数据共享。适用于对安全性要求较高,且只需要在当前会话中保持用户登录状态的场景。

  • Cookie:可以将 JWT 存储在 Cookie 中,并通过设置HttpOnly和Secure属性来提高安全性。HttpOnly属性可以防止 Cookie 被客户端脚本访问,Secure属性可以确保 Cookie 仅在 HTTPS 连接下传输。例如:
document.cookie = "jwtToken=your_token_here; expires=Wed, 01 Dec 2022 00:00:00 UTC; path=/; HttpOnly; Secure";

优点是安全性较高,能够有效防止 XSS 攻击。缺点是 Cookie 的大小有限,并且每次 HTTP 请求都会携带 Cookie,可能会增加请求的大小。适用于对安全性要求非常高,且需要在不同页面和会话中保持用户登录状态的场景。

2.6 过期处理机制

  1. 过期时间设置:在生成 JWT 时,可以通过设置exp(过期时间)声明来指定 JWT 的有效期。如前面生成 JWT 的代码中:
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))

这里将过期时间设置为当前时间加上EXPIRATION_TIME(1 小时)。

  1. 过期后的处理策略
  • 前端检测过期:前端在每次发送请求时,可以先检查 JWT 是否过期。例如,在使用 Axios 发送请求时,可以在请求拦截器中添加如下代码:
import axios from 'axios';axios.interceptors.request.use(config => {const token = localStorage.getItem('jwtToken');if (token) {const decoded = JSON.parse(atob(token.split('.')[1]));const now = Date.now() / 1000;if (decoded.exp && decoded.exp < now) {// JWT已过期,处理逻辑,如跳转到登录页面window.location.href = '/login';return;}config.headers.Authorization = `Bearer ${token}`;}return config;
}, error => {return Promise.reject(error);
});

在上述代码中,先从localStorage中获取 JWT,然后对 JWT 的第二部分(Payload)进行 Base64 解码,得到载荷信息。通过检查exp声明和当前时间,判断 JWT 是否过期。如果过期,则跳转到登录页面。

  • 后端拒绝过期 JWT 请求:后端在接收到请求后,会验证 JWT 的有效性,包括签名验证和过期时间验证。如果 JWT 已过期,后端应返回相应的错误信息,如 HTTP 401 Unauthorized 状态码。在 Spring Boot 中,可以通过自定义过滤器来实现这一功能:
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public class JwtAuthenticationFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {try {String token = extractToken(request);if (token != null && JwtUtil.validateToken(token)) {filterChain.doFilter(request, response);} else {response.setStatus(HttpStatus.UNAUTHORIZED.value());}} catch (ExpiredJwtException e) {response.setStatus(HttpStatus.UNAUTHORIZED.value());} catch (SignatureException e) {response.setStatus(HttpStatus.UNAUTHORIZED.value());}}private String extractToken(HttpServletRequest request) {String bearerToken = request.getHeader("Authorization");if (bearerToken != null && bearerToken.startsWith("Bearer ")) {return bearerToken.substring(7);}return null;}
}

在上述代码中,JwtAuthenticationFilter过滤器会在每次请求时被调用。首先从请求头中提取 JWT,然后调用JwtUtil.validateToken(token)方法验证 JWT 的有效性。如果 JWT 过期或签名验证失败,会设置响应状态码为 401,表示未授权。

  1. 刷新 token 方案:为了避免用户频繁登录,可以使用刷新 token 来延长用户的登录状态。具体做法是:在生成 JWT 时,同时生成一个刷新 token,将其存储在客户端(例如存储在 HttpOnly Cookie 中)。当 JWT 过期时,前端使用刷新 token 向后端请求新的 JWT。后端验证刷新 token 的有效性,如果有效,则生成新的 JWT 和刷新 token 返回给前端。这样可以在保证安全性的前提下,提高用户体验。例如,在后端可以定义一个专门的接口来处理刷新 token 的请求:
import io.jsonwebtoken.Claims;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;@RestController
public class TokenRefreshController {@PostMapping("/refresh-token")public void refreshToken(@RequestHeader("Refresh-Token") String refreshToken, HttpServletResponse response) {if (JwtUtil.validateRefreshToken(refreshToken)) {Claims claims = JwtUtil.getClaimsFromRefreshToken(refreshToken);String username = claims.getSubject();String role = (String) claims.get("userRole");String newToken = JwtUtil.generateToken(username, role);String newRefreshToken = JwtUtil.generateRefreshToken(username);response.setHeader("Authorization", "Bearer " + newToken);response.setHeader("Refresh-Token", newRefreshToken);} else {response.setStatus(HttpStatus.UNAUTHORIZED.value());}}
}

在上述代码中,/refresh - token接口接收前端传递的刷新 token,验证其有效性后,根据刷新 token 中的用户信息生成新的 JWT 和刷新 token,并返回给前端。JwtUtil.validateRefreshToken(refreshToken)和JwtUtil.getClaimsFromRefreshToken(refreshToken)方法需要根据实际情况进行实现,用于验证刷新 token 的有效性和获取其中的载荷信息。

三、结合实际业务场景,展示如何基于角色权限控制用户访问资源

3.1 业务场景分析

在商城系统中,不同角色的用户承担着不同的职责,因此需要赋予他们不同的操作权限,以确保系统的安全和稳定运行。

  • 普通用户:普通用户是商城的主要消费群体,他们在商城中的核心需求是购物。因此,普通用户具有浏览商品的权限,能够查看商品的详细信息,包括商品名称、价格、图片、描述、规格参数等,以便做出购买决策;拥有将心仪商品添加到购物车的权限,方便集中管理和结算;可以在购物车中选择商品进行下单操作,填写收货地址、选择支付方式等完成购买流程;能够查看自己的订单历史,包括订单状态(待付款、待发货、已发货、已完成等)、订单详情(购买商品信息、价格、数量等),方便跟踪订单进度和进行售后查询。
  • 管理员:管理员负责商城的整体运营和管理,需要全面掌控商城的各项事务。他们可以对商品进行全方位管理,包括添加新商品,录入商品的各种信息;编辑商品信息,如修改价格、库存、描述等;删除不再销售的商品;能够管理用户信息,查看所有用户的注册信息,包括用户名、密码(加密存储)、联系方式、注册时间等,对异常用户进行处理,如封禁违规用户;有权限查看和管理所有订单,包括修改订单状态,处理退款、售后等问题。
  • 商家:商家是商品的提供者,主要负责管理自己店铺内的商品相关事务。他们可以管理自己店铺下的商品库存,实时更新商品库存数量,当库存不足时进行补货提醒;对商品信息进行编辑,根据实际情况修改商品的价格、描述、图片等信息,以更好地展示商品和吸引顾客。

3.2 基于角色的访问控制(RBAC)模型

RBAC 模型的核心原理是将权限与角色关联,而不是直接与用户关联 。在该模型中,角色是权限的集合,用户通过被分配到一个或多个角色来获得相应的权限。这种模型包含以下几个关键组件:

  • 用户(User):是需要访问系统资源的个体,在商城系统中,普通用户、管理员、商家等都属于用户范畴。
  • 角色(Role):代表一组权限的集合,对应商城中的普通用户角色、管理员角色、商家角色等,每个角色具有特定的职责和权限范围。
  • 权限(Permission):是对特定资源的访问能力,如在商城中,浏览商品、管理商品、管理订单等操作都属于权限。
  • 角色分配(Role Assignment):将角色分配给用户的过程,例如将管理员角色分配给特定的用户账号,使其拥有管理员权限。
  • 权限分配(Permission Assignment):将权限分配给角色的过程,比如为管理员角色分配管理商品、管理用户、管理订单等权限。

用户与角色、角色与权限之间是多对多的关系。一个用户可以被分配多个角色,例如某个用户既可以是普通用户角色,也可能因为参与商家运营而拥有商家角色;一个角色也可以拥有多个权限,如管理员角色拥有管理商品、用户、订单等多种权限。这种多对多的关系使得 RBAC 模型具有很高的灵活性,能够适应复杂的业务场景,方便对用户权限进行管理和调整。

3.3 数据库设计

为了实现基于角色权限控制,需要设计以下数据库表:

  • 用户表(user):用于存储用户的基本信息。
CREATE TABLE user (user_id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL,password VARCHAR(100) NOT NULL,email VARCHAR(50),phone VARCHAR(20)
);

其中,user_id是用户的唯一标识,username为用户名,password存储加密后的用户密码,email和phone分别为用户的邮箱和电话。

  • 角色表(role):存储系统中的各种角色。
CREATE TABLE role (role_id INT AUTO_INCREMENT PRIMARY KEY,role_name VARCHAR(50) NOT NULL
);

role_id是角色的唯一标识,role_name表示角色名称,如ROLE_USER(普通用户角色)、ROLE_ADMIN(管理员角色)、ROLE_SELLER(商家角色)等。

  • 权限表(permission):记录系统中的各种权限。
CREATE TABLE permission (permission_id INT AUTO_INCREMENT PRIMARY KEY,permission_name VARCHAR(50) NOT NULL,permission_description VARCHAR(100)
);

permission_id是权限的唯一标识,permission_name为权限名称,例如VIEW_PRODUCT(浏览商品权限)、MANAGE_PRODUCT(管理商品权限)等,permission_description用于对权限进行详细描述。

  • 用户 - 角色表(user_role):用于建立用户与角色之间的关联关系。
CREATE TABLE user_role (user_id INT,role_id INT,PRIMARY KEY (user_id, role_id),FOREIGN KEY (user_id) REFERENCES user(user_id),FOREIGN KEY (role_id) REFERENCES role(role_id)
);

通过user_id和role_id分别关联user表和role表,一个用户可以对应多个角色,一个角色也可以对应多个用户。

  • 角色 - 权限表(role_permission):用于建立角色与权限之间的关联关系。
CREATE TABLE role_permission (role_id INT,permission_id INT,PRIMARY KEY (role_id, permission_id),FOREIGN KEY (role_id) REFERENCES role(role_id),FOREIGN KEY (permission_id) REFERENCES permission(permission_id)
);

通过role_id和permission_id分别关联role表和permission表,一个角色可以对应多个权限,一个权限也可以对应多个角色。通过这种数据库表设计,能够清晰地实现基于角色的权限控制,方便在系统中对用户权限进行管理和查询。

3.4 代码实现

在商城项目中,以 Spring Boot 框架为例,展示基于角色权限控制用户访问资源的代码实现。假设我们有一个商品控制器ProductController,其中包含查看商品列表和添加商品的方法。

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class ProductController {// 普通用户和管理员都可以查看商品列表@GetMapping("/products")@PreAuthorize("hasRole('USER') or hasRole('ADMIN')")public String getProducts() {return "This is a list of products. Only users with USER or ADMIN role can access.";}// 只有管理员可以添加商品@PostMapping("/products/add")@PreAuthorize("hasRole('ADMIN')")public String addProduct() {return "Product added successfully. Only ADMIN role can access this operation.";}
}

在上述代码中:

  • @PreAuthorize(“hasRole(‘USER’) or hasRole(‘ADMIN’)”)注解表示只有拥有USER或ADMIN角色的用户才能访问/products接口,即普通用户和管理员都可以查看商品列表。
  • @PreAuthorize(“hasRole(‘ADMIN’)”)注解表示只有拥有ADMIN角色的用户才能访问/products/add接口,即只有管理员可以执行添加商品的操作。当用户尝试访问这些接口时,Spring Security 会根据用户的角色信息进行权限验证,如果用户没有相应的权限,将返回 HTTP 403 Forbidden 状态码,表示禁止访问。这样就实现了基于角色权限控制用户对商城资源的访问。

http://www.ppmy.cn/devtools/165090.html

相关文章

Win10 用户、组与内置安全主体概念详解

一、‌用户&#xff08;User&#xff09;‌ ‌定义‌ 用户是操作系统中的身份标识&#xff0c;用于区分不同操作者并控制资源访问权限。每个用户拥有独立的安全标识符&#xff08;SID&#xff09;‌。 ‌分类‌ ‌内置用户‌&#xff1a; ‌Administrator‌&#xff1a;系统…

Kubernetes(K8S)部署 Redis Cluster 集群

以下将详细介绍如何使用 Kubernetes&#xff08;K8S&#xff09;部署 Redis Cluster 集群&#xff0c;并给出相应的 YAML 代码。 1. 准备工作 在开始部署之前&#xff0c;需要确保已经安装并配置好 Kubernetes 集群&#xff0c;并且 kubectl 可以正常与集群通信。 2. 部署 R…

【Mac】git使用再学习

目录 前言 如何使用github建立自己的代码库 第一步&#xff1a;建立本地git与远程github的联系 生成密钥 将密钥加入github 第二步&#xff1a;创建github仓库并clone到本地 第三步&#xff1a;上传文件 常见的git命令 git commit git branch git merge/git rebase …

【AI神经网络与人脑神经系统的关联及借鉴分析】

AI神经网络与人脑神经系统的关联及借鉴分析 一、结构与功能模拟&#xff1a;从生物神经元到人工单元 生物神经元模型 人脑神经元通过电化学信号传递信息&#xff0c;当输入信号超过阈值时触发动作电位&#xff08;"全有或全无"法则&#xff09;。其动态过程可用Hodg…

1688平台API接口实战:Python实现店铺全量商品数据抓取

在电商数据驱动决策的时代&#xff0c;1688作为国内最大的B2B批发平台&#xff0c;其开放的API接口为商家提供了高效获取商品数据的通道。本文将以Python语言为例&#xff0c;详解如何通过官方接口实现店铺所有商品的自动化抓取。&#xff08;综合参考&#xff09; 一、接口核…

探索DeFi世界:用Python开发去中心化金融应用

探索DeFi世界:用Python开发去中心化金融应用 在区块链技术快速发展的今天,去中心化金融(DeFi)正在改变传统金融行业的格局。作为一名自媒体创作者和技术爱好者,我希望通过本文分享如何用Python开发去中心化金融应用,帮助读者深入了解DeFi的潜力和技术实现方式。 什么是…

qsort函数的模拟实现

文章目录 冒泡排序回调函数qsort函数简介qsort函数的使用qsort函数的模拟实现 冒泡排序 冒泡排序顾名思义就是用来给数据排序的一种方法&#xff0c;假设有一整型数组&#xff0c;如果要将这个数组中的元素按从小到大或从大到小的顺序排序&#xff0c;就可以用冒泡排序来完成。…

python小游戏-坦克大战

完整的游戏状态管理&#xff1a; 生命值系统 得分系统 游戏结束条件 重新开始功能 增强的坦克功能&#xff1a; 坦克旋转 无敌时间 不同类型的坦克&#xff08;玩家/敌人&#xff09; 改进的碰撞系统&#xff1a; 子弹与墙壁碰撞 子弹与坦克碰撞 子弹与基地碰撞 游…