基于Spring Security实现RBAC权限控制的完整步骤
作者:无籽西瓜a
什么是 RBAC
RBAC,全称 Role-Based Access Control,基于角色的访问控制。核心思路很简单:不直接给用户分配权限,而是引入"角色"作为中间层。
用户 →角色→ 权限
举个例子:一家公司里,"实习生"能查看文档,"正式员工"能查看和编辑文档,"部门经理"能查看、编辑、删除文档。你入职的时候,HR 不会一条条给你勾权限,而是直接给你一个角色,权限就跟着来了。
这里面有几个关键关系:
- 一个用户可以拥有多个角色
- 一个角色可以分配给多个用户
- 一个角色可以包含多个权限
- 一个权限可以属于多个角色
还有一种模型叫角色继承——上层角色自动继承下层角色的所有权限。比如"经理"自动拥有"员工"的全部权限,再额外拥有管理权限。这在组织架构复杂的系统中非常实用。
Spring Security 是什么
Spring Security 本质上是一个基于 Filter 的安全框架。请求进入应用时,会经过一系列过滤器链,每个过滤器各司其职:有的负责认证,有的负责授权,有的负责 CSRF 防护。
它解决两个核心问题:
- 认证(Authentication):你是谁?验证用户名密码是否正确。
- 授权(Authorization):你能干什么?检查你有没有权限访问某个接口。
用 RBAC 的思路来说,认证就是确认你的身份,授权就是根据你的角色判断你能不能进这扇门。
整体架构设计
要基于 Spring Security 实现 RBAC,需要这几个核心模块:
config/ → 安全配置、JWT 过滤器 controller/ → 接收请求的入口 service/ → 业务逻辑、用户详情加载 repository/ → 数据库访问 model/ → 用户实体、角色定义 utils/ → 密码加密、JWT 工具
下面按照请求的生命周期来讲,从注册到登录到鉴权,一步步走通。
第一步:定义用户模型和角色
@Data
@Entity
@Table(name = "wzxg_users", uniqueConstraints = @UniqueConstraint(columnNames = "username"))
public class WzxgUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String username;
@Column(nullable = false)
private String password;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private WzxgRole role;
@CreationTimestamp
private LocalDateTime createdTime;
@UpdateTimestamp
private LocalDateTime updatedTime;
public enum WzxgRole {
USER, ADMIN
}
}
@Enumerated(EnumType.STRING) 让角色以字符串形式存到数据库里,比如存的是 "ADMIN" 而不是 0,可读性好,也不容易出错。
数据访问层很简单,一个根据用户名查询的方法就够了:
public interface WzxgUserRepository extends JpaRepository<WzxgUser, Long> {
Optional<WzxgUser> findByUsername(String username);
}
第二步:密码加密
密码绝对不能明文存储。BCrypt 是目前主流的选择,它内置盐值机制,同一个密码每次加密结果都不同,能有效防御彩虹表攻击。
public class WzxgPasswordUtil {
private static final BCryptPasswordEncoder wzxgEncoder = new BCryptPasswordEncoder();
public static String encode(String rawPassword) {
return wzxgEncoder.encode(rawPassword);
}
public static boolean matches(String rawPassword, String encodedPassword) {
return wzxgEncoder.matches(rawPassword, encodedPassword);
}
}
注册时调 encode(),登录时调 matches()
第三步:JWT 工具类
系统采用无状态认证,服务端不存 session,而是通过 JWT 令牌来传递身份信息。
@Component
public class WzxgJwtUtils {
private static final String WZXG_SECRET_KEY = "wzxg_jwt_secret_key_2024";
private static final long WZXG_EXPIRATION = 86400000; // 24小时
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + WZXG_EXPIRATION))
.signWith(SignatureAlgorithm.HS256, WZXG_SECRET_KEY)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(WZXG_SECRET_KEY).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;
}
}
public String extractUsername(String token) {
Claims wzxgClaims = Jwts.parser()
.setSigningKey(WZXG_SECRET_KEY)
.parseClaimsJws(token)
.getBody();
return wzxgClaims.getSubject();
}
}
JWT 是一个自包含的令牌,里面可以塞用户名、角色、组织信息等。这样每次请求过来,解析 token 就能知道用户是谁、有什么权限,不用查数据库。
第四步:让 Spring Security 认识你的用户
Spring Security 有自己的一套用户模型 UserDetails,我们需要实现 UserDetailsService 接口,把数据库里的用户转换成它能理解的格式。
@Service
public class WzxgUserDetailsService implements UserDetailsService {
@Autowired
private WzxgUserRepository wzxgUserRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
WzxgUser wzxgUser = wzxgUserRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
return new org.springframework.security.core.userdetails.User(
wzxgUser.getUsername(),
wzxgUser.getPassword(),
buildAuthorities(wzxgUser.getRole())
);
}
private Collection<? extends GrantedAuthority> buildAuthorities(WzxgUser.WzxgRole role) {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role.name()));
}
}
注意 buildAuthorities 方法里的 "ROLE_" 前缀,这是 Spring Security 的约定。角色 ADMIN 会变成 ROLE_ADMIN,这样后面配置 hasRole("ADMIN") 时框架才能正确匹配。
第五步:JWT 认证过滤器
这是整个流程的关键——每个请求进来时,过滤器从请求头里取出 JWT,验证有效性,然后把用户信息塞进 Spring Security 的上下文。
@Component
public class WzxgJwtAuthFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService wzxgUserDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
try {
String wzxgToken = getToken(request);//从请求头获取jwt token
if (wzxgToken != null) {
String username = getUsername(wzxgToken);
UserDetails wzxgUserDetails = wzxgUserDetailsService.loadUserByUsername(username);//根据用户名加载用户详细信息
UsernamePasswordAuthenticationToken wzxgAuth =
new UsernamePasswordAuthenticationToken(
wzxgUserDetails, null, wzxgUserDetails.getAuthorities());
wzxgAuth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(wzxgAuth);
}
} catch (Exception e) {
}
filterChain.doFilter(request, response);
}
private String getToken(HttpServletRequest request) {
String wzxgBearer = request.getHeader("Authorization");
if (wzxgBearer != null && wzxgBearer.startsWith("Bearer ")) {
return wzxgBearer.substring(7);
}
return null;
}
private String getUsername(String token) {
try {
Claims claims = extractClaimsIgnoreExpiration(token);
return claims != null ? claims.getSubject() : null;
} catch (Exception e) {
return null;
}
}
}
继承 OncePerRequestFilter 保证每个请求只过滤一次。如果 token 无效或不存在,不会报错,只是不设置认证信息,后续的权限检查自然会拦住未认证的请求。
第六步:安全配置——定义规则
最后把所有东西串起来,告诉 Spring Security:哪些接口谁能访问。
@Configuration
@EnableWebSecurity
public class WzxgSecurityConfig {
@Autowired
private WzxgJwtAuthFilter wzxgJwtAuthFilter;
@Bean
public SecurityFilterChain wzxgFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
// 登录注册不需要认证
.requestMatchers("/api/v1/auth/register", "/api/v1/auth/login").permitAll()
// 管理接口只有 ADMIN 能访问
.requestMatchers("/api/v1/manage/**").hasRole("ADMIN")
// 普通业务接口,USER 和 ADMIN 都能访问
.requestMatchers("/api/v1/workspace/**").hasAnyRole("USER", "ADMIN")
// 其余接口都需要认证
.anyRequest().authenticated()
)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 把 JWT 过滤器插到 UsernamePasswordAuthenticationFilter 前面
.addFilterBefore(wzxgJwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
csrf.disable():因为用的是 JWT 无状态认证,不需要 CSRF 防护SessionCreationPolicy.STATELESS:不创建 session,完全靠 tokenaddFilterBefore:确保 JWT 过滤器在 Spring Security 默认的认证过滤器之前执行
完整请求流程
把上面的模块串起来,一个请求的完整生命周期是这样的:
1. 用户注册 → 密码用 BCrypt 加密 → 分配默认角色 USER → 存入数据库 2. 用户登录 → 验证用户名密码 → 生成 JWT Token → 返回给客户端 3. 客户端请求业务接口 → 请求头带上 Authorization: Bearer <token> 4. WzxgJwtAuthFilter 拦截请求 → 提取 token → 验证签名和有效期 → 解析出用户名 → 加载用户信息和角色 → 写入 SecurityContext 5. Spring Security 根据 WzxgSecurityConfig 中的规则 → 检查当前用户的角色是否匹配目标接口的权限要求 → 通过则放行,否则返回 403
以上就是基于Spring Security实现RBAC权限控制的完整步骤的详细内容,更多关于Spring Security RBAC权限控制的资料请关注脚本之家其它相关文章!
