SecurityFilterChain中基于角色的授权失效问题求助
问题描述
我正在尝试基于角色保护接口端点,现有ADMIN、ESTUDIANTE(学生)、EMPRESA(企业)三种角色,每个用户仅拥有一种,且角色未存储在数据库中。我希望仅ADMIN角色能新增CarreraEntity实例,但在SecurityFilterChain中配置端点权限时,出现403禁止访问或非授权/无权限用户可访问的异常情况。
SecurityConfig类代码
@Configuration @EnableWebSecurity public class SecurityConfig { private final AuthenticationProvider authProvider; private final JwtAuthenticationFilter jwtAuthenticationFilter; @Autowired public SecurityConfig(AuthenticationProvider authProvider, JwtAuthenticationFilter jwtAuthenticationFilter) { this.authProvider = authProvider; this.jwtAuthenticationFilter = jwtAuthenticationFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authRequest -> authRequest .requestMatchers("/api/v1/auth/**").permitAll() .requestMatchers("/api/v1/auth/contrasenia").hasAnyRole( Role.ADMIN.name(), Role.EMPRESA.name(), Role.ESTUDIANTE.name() ) .requestMatchers(HttpMethod.GET, "/api/v1/carreras/**").permitAll() .requestMatchers(HttpMethod.POST, "/api/v1/carreras/**").hasRole(Role.ADMIN.name()) .anyRequest().authenticated() ) .sessionManagement(sessionManager -> sessionManager .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authenticationProvider(authProvider) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .build(); } }
出现问题的端点代码
@GetMapping public ResponseEntity<Set<CarreraDTO>> getCarreras() { return ResponseEntity.ok(carreraService.getCarreras()); } // TODO: return 201 @PostMapping(consumes = "application/json") public ResponseEntity<CarreraDTO> postCarrera(@RequestBody CarreraRequest request) { return ResponseEntity.ok(carreraService.postCarrera(request)); }
JWT声明中分配角色的代码
public String getToken(UserDetails user) { HashMap<String, Object> claims = new HashMap<>(); String role = user.getAuthorities() .stream() .findFirst() .map(GrantedAuthority::getAuthority) .orElseThrow(); claims.put("role", role); return getToken(claims, user); } private String getToken(HashMap<String,Object> map, UserDetails user) { return Jwts.builder() .claims(map) .subject(user.getUsername()) .issuedAt(new Date(System.currentTimeMillis())) .expiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)) .signWith(this.getKey(), Jwts.SIG.HS256) .compact(); }
JwtAuthenticationFilter类代码
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtService jwtService; private final UserDetailsService userDetailsService; @Autowired public JwtAuthenticationFilter(JwtService jwtService, @Qualifier("userDetailsService") UserDetailsService userDetailsService) { this.jwtService = jwtService; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String token = this.getTokenFromRequest(request); final String username; if (token == null) { filterChain.doFilter(request, response); return; } username = jwtService.getUsernameFromToken(token); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtService.isTokenValid(token, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); } private String getTokenFromRequest(HttpServletRequest request) { final String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); return jwtService.getTokenFromAuthorizationHeader(authHeader).orElse(null); } }
我尝试过使用方法安全注解(@PermitAll、@PreAuthorize),也试过hasAuthority(),但这些方案都无法解决问题,不知道该如何处理。
解决方案
问题核心出在Spring Security角色前缀规则和JWT权限解析逻辑上,以下是针对性修复步骤:
1. 统一权限匹配规则
Spring Security的hasRole()方法会自动给角色名拼接ROLE_前缀校验,如果你Role枚举值是ADMIN而非ROLE_ADMIN,会直接导致权限不匹配。两种修复方式二选一:
- 方式一:修改Role枚举
给所有角色值加上ROLE_前缀:public enum Role { ROLE_ADMIN, ROLE_ESTUDIANTE, ROLE_EMPRESA } - 方式二:改用hasAuthority()
hasAuthority()不会自动添加前缀,直接匹配权限字符串,修改SecurityConfig配置:.requestMatchers("/api/v1/auth/contrasenia").hasAnyAuthority( Role.ADMIN.name(), Role.EMPRESA.name(), Role.ESTUDIANTE.name() ) .requestMatchers(HttpMethod.POST, "/api/v1/carreras/**").hasAuthority(Role.ADMIN.name())
2. 从JWT直接提取权限
当前你依赖UserDetailsService加载权限,但角色未存数据库,UserDetailsService无法正确返回权限。需要直接从JWT的role声明中提取权限:
修改JwtAuthenticationFilter逻辑
if (jwtService.isTokenValid(token, userDetails)) { // 从JWT中提取角色 String role = jwtService.extractRoleFromToken(token); // 构造权限集合 Collection<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority(role)); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, authorities ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); }
在JwtService添加提取角色方法
public String extractRoleFromToken(String token) { Claims claims = Jwts.parser() .verifyWith(getKey()) .build() .parseSignedClaims(token) .getPayload(); return claims.get("role", String.class); }
3. 启用方法安全注解(可选)
如果要使用@PreAuthorize等注解,需在SecurityConfig上添加@EnableMethodSecurity(Spring Security 6+版本):
@Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { // 现有代码不变 }
之后可直接在接口方法上使用:
@PreAuthorize("hasAuthority('ADMIN')") @PostMapping(consumes = "application/json") public ResponseEntity<CarreraDTO> postCarrera(@RequestBody CarreraRequest request) { return ResponseEntity.ok(carreraService.postCarrera(request)); }
4. 调试建议
- 开启Spring Security debug日志,查看权限校验过程:
在application.properties中添加:logging.level.org.springframework.security=DEBUG - 在JwtAuthenticationFilter中打印当前用户权限,确认是否正确加载:
System.out.println("当前用户权限:" + authToken.getAuthorities());
内容的提问来源于stack exchange,提问作者Gerónimo González Martino
相关产品推荐
相关产品推荐

