You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security 6中JWT用户部分权限失效问题排查求助

问题原因分析

你的核心问题出在JWT权限解析的字符串处理逻辑上:
当你从claims中获取permissions时,直接调用String.valueOf(claims.get("permissions"))将List类型的权限列表转成了字符串,最终得到的格式是[权限1, 权限2, ..., 权限n](带方括号)。
随后用AuthorityUtils.commaSeparatedStringToAuthorityList()解析时,会把每个分割后的元素当成权限:

  • 靠前的权限会被解析成SFG_UI_SCT_ENTITY_TRANSMIT(正常格式)
  • 最后一个权限会被解析成FB_UI_TRUSTED_CERTS_DELETE](多了右括号])
    这就是为什么最后一个权限无法通过hasAuthority()校验的原因。
解决方案

修改JwtAuthenticationFilter中解析权限的逻辑,直接将claims中的permissions强转为List,再生成标准的GrantedAuthority集合:

@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtSettings settings;
    private static final String AUTHENTICATION_HEADER_NAME = "Authorization";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        final String authHeader = request.getHeader(AUTHENTICATION_HEADER_NAME);
        String jwt = null;

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            jwt = authHeader.substring("Bearer ".length());
        }

        if (jwt != null) {
            try {
                final Claims claims = Jwts.parserBuilder()
                        .setSigningKey(getSignKey())
                        .build()
                        .parseClaimsJws(jwt)
                        .getBody();

                final String username = claims.get("sub", String.class);
                // 直接强转为List<String>,避免生成带方括号的字符串
                final List<String> permissionList = claims.get("permissions", List.class);
                
                // 将权限列表转为GrantedAuthority集合
                Collection<? extends GrantedAuthority> authorities = permissionList.stream()
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList());

                final Authentication auth = new UsernamePasswordAuthenticationToken(username, null, authorities);
                SecurityContextHolder.getContext().setAuthentication(auth);

            } catch (Exception e) {
                throw new BadCredentialsException("Invalid token received");
            }
        }

        filterChain.doFilter(request, response);
    }

    private SecretKey getSignKey() {
        final byte[] keyBytes = settings.getTokenSigningKey().getBytes();
        return Keys.hmacShaKeyFor(keyBytes);
    }
}
额外建议(Spring Security 6官方JWT集成方案)

你当前自定义Filter的方式可行,但Spring Security 6提供了更简洁的官方JWT集成方案,减少自定义代码维护成本:

  1. 添加spring-security-oauth2-resource-server依赖
  2. 配置JwtDecoder Bean:
@Bean
public JwtDecoder jwtDecoder() {
    return NimbusJwtDecoder.withSecretKey(getSignKey()).build();
}

private SecretKey getSignKey() {
    final byte[] keyBytes = settings.getTokenSigningKey().getBytes();
    return Keys.hmacShaKeyFor(keyBytes);
}
  1. 修改SecurityConfig,替换自定义Filter为官方JWT认证:
@Bean
public SecurityFilterChain apiSecurityfilterChain(HttpSecurity http) throws Exception {
    return http
            .csrf(csrf -> csrf.disable())
            .cors(corsCustomizer ->corsCustomizer.configurationSource(request -> {
                CorsConfiguration corsConfiguration  = new CorsConfiguration();
                corsConfiguration.setAllowedOrigins(Collections.singletonList("http://localhost:9080"));
                corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
                corsConfiguration.setAllowCredentials(true);
                corsConfiguration.setExposedHeaders(Arrays.asList("Authorization"));
                corsConfiguration.addAllowedHeader("Content-Disposition");
                corsConfiguration.addAllowedHeader("Access-Control-Allow-Headers");
                corsConfiguration.addAllowedHeader("Access-Control-Expose-Headers");
                corsConfiguration.addExposedHeader("Content-Disposition");
                corsConfiguration.setMaxAge(3600L);
                return corsConfiguration;
            }))
            .authorizeHttpRequests(auth -> {
                auth.requestMatchers("/api/auth/signin", "/api/auth/sso", "/api/version/*")
                        .permitAll()
                        .anyRequest()
                        .authenticated();
            })
            // 替换自定义Filter为官方JWT资源服务器配置
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())))
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .build();
}
  1. 实现JwtAuthenticationConverter,映射JWT中的权限:
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    grantedAuthoritiesConverter.setAuthoritiesClaimName("permissions");
    grantedAuthoritiesConverter.setAuthorityPrefix(""); // 不需要权限前缀可设为空

    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
    return converter;
}

内容的提问来源于stack exchange,提问作者peter greaves

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 11:38:08