Spring Boot 3升级后authorizeHttpRequests搭配OAuth2ResourceServer返回403
Spring Boot 3.x OAuth2资源服务器403问题排查与解决
问题场景
从Spring Boot 2.7升级至Spring Boot 3.x,使用Azure B2C作为OAuth2授权服务器,JWT获取正常,但将安全配置中的authorizeRequests替换为authorizeHttpRequests后,所有/api/**路径的请求均返回403;保留authorizeRequests配合其他Spring Boot 3.x更新时,功能正常。
核心原因分析
- 权限缺失导致认证后授权失败:自定义JWT转换器返回的
PreAuthenticatedAuthenticationToken中,用户对象未包含GrantedAuthority权限信息。Spring Security 6.x(对应Spring Boot 3)对授权检查更严格,即使认证通过,无权限的用户会被直接拒绝访问。 - AuthenticationProvider配置方式不兼容:手动构建
AuthenticationManager的方式在Spring Security 6.x中可能无法被过滤器链正确识别,导致自定义的preAuthProvider未参与认证流程。 - 请求匹配规则的细微差异:
authorizeHttpRequests与旧版authorizeRequests的匹配逻辑存在差异,未明确配置的请求路径可能被默认拦截。
解决方案步骤
1. 补充用户权限信息
确保自定义用户对象(ImplementUser)包含有效的权限集合,可在JWT转换器或用户详情服务中添加:
修改Auth0TokenConverter的convert方法
@Override public AbstractAuthenticationToken convert(final Jwt jwt) { var emails = jwt.getClaimAsStringList("emails"); if (emails == null || emails.size() != 1) { throw new RuntimeException("Token中必须且只能包含一个邮箱"); } // 添加默认权限,或从JWT中提取权限信息 var authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); var user = ImplementUser.builder() .username(emails.get(0)) .auth0Id(null) .enabled(true) .accountNonExpired(true) .accountNonLocked(true) .credentialsNonExpired(true) .authorities(authorities) // 设置权限 .build(); return new PreAuthenticatedAuthenticationToken(user, jwt); }
或在CustomPreAuthUserDetailsService中补充权限
如果权限需要从数据库获取,可调整loadUserDetails方法:
@Override public final UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) { var principal = (ImplementUser) token.getPrincipal(); var email = principal.getUsername(); var user = authUserDetailsService.loadUserDetails(email); return implementUser.toBuilder() .firstName(user.getFirstName()) .lastName(user.getLastName()) .globalUserId(user.getGlobalUserId()) .profiles(user.getProfiles()) .activeProfile(user.getActiveProfile()) .defaultProfileId(user.getDefaultProfileId()) .authorities(user.getAuthorities()) // 复用从数据库获取的权限 .build(); }
2. 调整SecurityFilterChain配置
改用Spring Security 6.x推荐的方式配置AuthenticationProvider,并正确使用authorizeHttpRequests:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.cors(config -> config.configurationSource(corsConfigurationSource())) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize .requestMatchers("/api/**").authenticated() .anyRequest().permitAll() // 根据业务需求调整非API路径的权限 ) .exceptionHandling(ex -> ex.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))) .csrf(csrf -> csrf.disable()) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(new Auth0TokenConverter()))) // 直接将自定义Provider添加到过滤器链中 .authenticationProvider(authenticationProvider) .authenticationProvider(preAuthProvider); return http.build(); }
3. 移除手动构建的AuthenticationManager Bean
Spring Security 6.x会自动从SecurityFilterChain中配置的AuthenticationProvider构建AuthenticationManager,无需手动创建:
// 移除以下代码 @Bean public AuthenticationManager authManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); authenticationManagerBuilder.authenticationProvider(authenticationProvider); authenticationManagerBuilder.authenticationProvider(preAuthProvider); return authenticationManagerBuilder.build(); }
关键错误日志翻译
2024-05-20 10:00:00.000 DEBUG 12345 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor : 无法授权过滤器调用 [GET /api/user],属性为 [authenticated] org.springframework.security.access.AccessDeniedException: 访问被拒绝 at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73) at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:117) at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ...
内容的提问来源于stack exchange,提问作者Schokea
相关产品推荐
相关产品推荐

