Spring Security角色权限配置后requestMatchers始终返回403禁止访问
核心问题分析
你的代码里存在多个导致403的关键点,逐一拆解:
1. JWT签发时间配置错误
TokenService中把issuedAt设为当前时间加1小时,这会导致令牌的签发时间晚于当前时间,令牌处于未生效状态,直接被Spring Security判定为无效,返回403。
2. hasAnyRole方法使用错误
Spring Security的hasRole()/hasAnyRole()会自动为传入的角色名添加ROLE_前缀。你传入"ROLE_USER",实际会被解析为ROLE_ROLE_USER,与令牌中的角色不匹配。
3. JWT权限解析逻辑不匹配
Spring Security默认会把JWT中scope字段的内容处理为scope权限(自动添加SCOPE_前缀),但你存入的是ROLE_开头的角色,默认解析逻辑无法识别这些角色为合法权限。
4. 冗余配置干扰资源服务器逻辑
你在资源服务器配置中添加了userDetailsService和httpBasic,但OAuth2资源服务器(JWT模式)是自包含令牌认证,不需要加载用户详情或HttpBasic认证,这些配置可能导致认证逻辑冲突。
解决步骤
步骤1:修复JWT签发时间与过期时间
修改TokenService中的JWT生成逻辑,正确设置签发时间和过期时间:
public String generateToken(Authentication authentication){ Instant now = Instant.now(); String scope = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(" ")); JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("self") .issuedAt(now) // 改为当前时间,确保令牌立即生效 .expiresAt(now.plus(1 , ChronoUnit.HOURS)) // 添加正确的过期时间 .subject(authentication.getName()) .claim("scope" , scope) .build(); return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); }
步骤2:修正Security权限配置
改用hasAnyAuthority()方法(不会自动添加前缀),并简化重复的路由规则:
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, JwtAuthenticationConverter jwtAuthenticationConverter) throws Exception{ return httpSecurity .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(a -> { // 合并/student/和/student/**规则 a.requestMatchers("/student/**").hasAnyAuthority("ROLE_USER"); // 合并/students/和/students/**规则 a.requestMatchers("/students/**").hasAnyAuthority("ROLE_ADMIN"); // 简化公开路由配置 a.requestMatchers("/", "/token/**").permitAll(); a.anyRequest().authenticated(); }) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter)) ) .headers(headers -> headers.frameOptions().sameOrigin()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .build(); }
步骤3:自定义JWT权限解析转换器
添加自定义JwtAuthenticationConverter,让Spring Security直接解析scope字段中的角色为GrantedAuthority:
@Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(jwt -> { // 从JWT的scope字段中获取权限列表 String scope = jwt.getClaimAsString("scope"); Collection<String> authorities = scope != null ? Arrays.asList(scope.split(" ")) : Collections.emptyList(); return authorities.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); }); return converter; }
步骤4:移除冗余配置
删除Security配置中的userDetailsService(userDetailsService)和httpBasic(Customizer.withDefaults()),避免干扰JWT认证逻辑。
验证要点
- 生成令牌后,通过JWT解析工具检查:
iat字段为当前时间戳exp字段为当前时间加1小时的时间戳scope字段包含正确的角色(如ROLE_USER或ROLE_ADMIN)
- 用对应角色的令牌访问路由,确认权限控制生效。
内容的提问来源于stack exchange,提问作者Expert_Introvert

