基于角色的访问控制(RBAC)在 Spring Security 中的实现
RBAC(Role-Based Access Control,基于角色的访问控制)主要是通过Spring Security 配合 JWT 进行身份认证和授权来实现的。以下是完整的实现流程。
1. 在 JWT 令牌中存储用户角色
在用户登录成功后,我们会生成一个 JWT 令牌,并在 claims
里包含用户的角色信息。
示例:生成 JWT 令牌
java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtil {
private static final String SECRET_KEY = "your_secret_key";
public static String generateToken(String username, String role) {
return Jwts.builder()
.setSubject(username)
.claim("role", role) // 在令牌中存储角色信息
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1天有效期
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
2. 自定义 JWT 过滤器
在 Spring Security 里,我们需要在每个请求中解析 JWT 并验证角色。为此,我们创建一个 JwtAuthenticationFilter
,它会:
- 从 HTTP 请求头中提取 JWT 令牌。
- 验证 JWT 并解析出用户信息。
- 将用户身份和角色存入 SecurityContext,使其可用于后续授权。
说白了:也就是通过过滤器获取 token 中的角色信息,进而在访问接口的时候判断用户角色和这个接口权限是否匹配!!!
示例:JWT 认证过滤器
java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final String SECRET_KEY = "your_secret_key";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7); // 去掉 "Bearer " 前缀
try {
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
String role = claims.get("role", String.class); // 从 JWT 提取角色
// 设置用户权限
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + role);
User principal = new User(username, "", Collections.singletonList(authority));
// 构建 Spring Security 认证对象
JwtAuthenticationToken authentication = new JwtAuthenticationToken(principal, null, principal.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 设置认证对象到上下文 ——> 这样这个访问的用户就带着权限去访问接口了!
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
SecurityContextHolder.clearContext();
}
}
chain.doFilter(request, response);
}
}
3. 在 Spring Security 配置类中启用 RBAC
在 SecurityConfig
配置类里,我们注册 JwtAuthenticationFilter
,并根据用户角色对 API 进行权限控制。
示例:Spring Security 配置
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/user/signup", "/user/login").permitAll() // 允许访问
.requestMatchers("/admin/**").hasRole("ADMIN") // 只有管理员可访问
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // 用户和管理员都能访问
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // 添加 JWT 认证过滤器
.build();
}
}
解释:
permitAll()
:允许所有用户访问/user/signup
和/user/login
。hasRole("ADMIN")
:只有 ROLE_ADMIN 才能访问/admin/**
相关 API。hasAnyRole("USER", "ADMIN")
:普通用户和管理员都可以访问/user/**
相关 API。addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
:确保 JWT 过滤器在用户名/密码认证之前运行。
4. 方法级别权限控制
除了在 SecurityConfig 里配置 URL 级别的权限控制,我们还可以使用 @PreAuthorize
进行方法级别的访问控制。
示例:控制管理员端点
java
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin")
public class AdminController {
@PreAuthorize("hasRole('ADMIN')") // 只有管理员才能访问
@PostMapping("/approve")
public String approveUser() {
return "User approved!";
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/reject")
public String rejectUser() {
return "User rejected!";
}
}
示例:控制用户端点
java
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@PreAuthorize("hasAnyRole('USER', 'ADMIN')") // 用户和管理员都可以访问
@GetMapping("/profile")
public String getUserProfile() {
return "User profile data";
}
}
5. 测试 RBAC 权限
测试 1:管理员尝试访问受保护的 /admin/approve
请求:
json
POST /admin/approve
Authorization: Bearer <管理员的 JWT 令牌>
返回:
json
{
"message": "User approved!"
}
测试 2:普通用户尝试访问 /admin/approve
请求:
json
POST /admin/approve
Authorization: Bearer <普通用户的 JWT 令牌>
返回:
json
{
"error": "Forbidden",
"message": "Access Denied"
}
(因为该用户没有 ROLE_ADMIN
,所以访问被拒绝)
总结
✅ RBAC 控制实现步骤
- 在 JWT 令牌中存储用户角色。
- 创建 JWT 过滤器,解析令牌并将用户角色存入
SecurityContext
。 - 在 Spring Security 配置类中定义角色权限,限制不同角色对 API 的访问。
- 使用
@PreAuthorize
进行方法级别的访问控制。 - 测试 JWT 令牌和角色权限,确保访问控制生效。
这样,我们就实现了 基于角色的访问控制(RBAC),确保不同权限的用户只能访问其被授权的资源 🎯🚀。