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

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更新时,功能正常。

核心原因分析

  1. 权限缺失导致认证后授权失败:自定义JWT转换器返回的PreAuthenticatedAuthenticationToken中,用户对象未包含GrantedAuthority权限信息。Spring Security 6.x(对应Spring Boot 3)对授权检查更严格,即使认证通过,无权限的用户会被直接拒绝访问。
  2. AuthenticationProvider配置方式不兼容:手动构建AuthenticationManager的方式在Spring Security 6.x中可能无法被过滤器链正确识别,导致自定义的preAuthProvider未参与认证流程。
  3. 请求匹配规则的细微差异: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 13:05:27