Skip to content

基于角色的访问控制(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,它会:

  1. 从 HTTP 请求头中提取 JWT 令牌
  2. 验证 JWT 并解析出用户信息
  3. 将用户身份和角色存入 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 控制实现步骤

  1. 在 JWT 令牌中存储用户角色
  2. 创建 JWT 过滤器,解析令牌并将用户角色存入 SecurityContext
  3. 在 Spring Security 配置类中定义角色权限,限制不同角色对 API 的访问。
  4. 使用 @PreAuthorize 进行方法级别的访问控制
  5. 测试 JWT 令牌和角色权限,确保访问控制生效。

这样,我们就实现了 基于角色的访问控制(RBAC),确保不同权限的用户只能访问其被授权的资源 🎯🚀。