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

Springboot OAuth2.0测试遇AnonymousAuthenticationToken访问拒绝问题求助

OAuth访问拒绝错误排查与解决

我正在实现一个简单的OAuth流程以加深对其工作原理的理解,但出现了访问拒绝错误。经调试发现,该错误源于AuthenticationTrustResolverImpl中的以下方法:

public boolean isAnonymous(Authentication authentication) {
    return this.anonymousClass != null && authentication != null ? this.anonymousClass.isAssignableFrom(authentication.getClass()) : false;
}

当前请求的认证对象为AnonymousAuthenticationToken:

AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]

以下是我的代码实现:

资源服务器配置类

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class ResourceServerConfig {

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .securityMatcher("/api/**")  // This configuration applies to API endpoints
                .authorizeHttpRequests(authorize ->
                        authorize
                                .requestMatchers("/api/public/**").permitAll()
                                .requestMatchers("/api/**").hasAnyAuthority("SCOPE_api.read","api.read")
                                .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwtConfigurer -> {}))
                .sessionManagement(session ->
                        session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                ).formLogin(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                .anonymous(AbstractHttpConfigurer::disable);

        return http.build();
    }

    // This ensures JWT authorities are properly mapped
    @Bean
    @Order(Ordered.LOWEST_PRECEDENCE)
    public SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .securityMatcher("/**")
                .authorizeHttpRequests(authorize ->
                        authorize
                                .requestMatchers("/", "/public/**", "/oauth2/**", "/login").permitAll()
                                .anyRequest().authenticated()
                )
                // Configure form login, etc. for web pages
                .formLogin(form -> form.loginPage("/login").permitAll());

        return http.build();
    }

}

认证服务器配置类

@Configuration
@EnableWebSecurity
public class AuthServerConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

        http.securityMatcher("/oauth2/**","/login", "/error")
                .getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .oidc(Customizer.withDefaults());  // Enable OpenID Connect 1.0

        http
                .exceptionHandling(exceptions ->
                        exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
                )
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));

        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/**")
                .authorizeHttpRequests(authorize ->
                        authorize.requestMatchers("/api/**").authenticated()
                )
                .formLogin(Customizer.withDefaults());

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails userDetails = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(userDetails);
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("api-client")
                .clientSecret("{noop}secret")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/api-client-oidc")
                .redirectUri("http://127.0.0.1:8080/authorized")
                .scope(OidcScopes.OPENID)
                .scope("api.read")
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                .build();

        return new InMemoryRegisteredClientRepository(registeredClient);
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }
}

控制器

@RestController
@RequestMapping("/api")
public class ProtectedController {
    @GetMapping("/public/hello")
    public String publicHello() {
        return "Hello, this is a public endpoint!";
    }

    @GetMapping("/protected/hello")
    public String protectedHello() {
        return "Hello, this is a protected endpoint!";
    }
}

配置文件

logging:
  level:
    org.springframework.security: TRACE
server:
  port: 8080
spring:
  application:
    name: otp
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://localhost:8080

错误信息

org.springframework.security.authorization.AuthorizationDeniedException: Access Denied
    at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:99) ~[spring-security-web-6.5.0.jar:6.5.0]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.5.0.jar:6.5.0]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:125) ~[spring-security-web-6.5.0.jar:6.5.0]
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) ~[spring-security-web-6.5.0.jar:6.5.0]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.5.0.jar:6.5.0]
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) ~[spring-security-web-6.5.0.jar:6.5.0]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.5.0.jar:6.5.0]
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) ~[spring-security-web-6.5.0.jar:6.5.0]
    at org.springframework.sec

问题原因与解决方法

核心问题

请求访问受保护的/api/protected/hello时,SecurityContext中存在的是AnonymousAuthenticationToken,说明请求未携带有效的JWT令牌,或令牌的认证/解析流程未正常执行。

具体排查与修复

  1. SecurityFilterChain路径匹配冲突
    认证服务器中defaultSecurityFilterChain的securityMatcher("/**")会匹配所有请求,包括/api/**,导致资源服务器的高优先级FilterChain未被触发。

    • 修复:修改AuthServerConfig中的defaultSecurityFilterChain,限制其处理的路径:
      @Bean
      @Order(2)
      public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
          http.securityMatcher("/", "/public/**", "/login")
                  .authorizeHttpRequests(authorize ->
                          authorize.anyRequest().authenticated()
                  )
                  .formLogin(Customizer.withDefaults());
      
          return http.build();
      }
      
  2. JWT Issuer URI配置错误
    配置文件中issuer-uri使用了https://localhost:8080,但服务未启用HTTPS,导致资源服务器无法获取JWKS验证令牌。

    • 修复:修改application.yml中的配置:
      spring:
        security:
          oauth2:
            resourceserver:
              jwt:
                issuer-uri: http://localhost:8080
      
  3. JWT权限映射配置缺失
    默认情况下Spring Security会将JWT中的scope转换为带SCOPE_前缀的权限,需确保转换器配置正确。

    • 修复:在ResourceServerConfig中添加JWT权限转换器:
      @Bean
      public JwtAuthenticationConverter jwtAuthenticationConverter() {
          JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
          grantedAuthoritiesConverter.setAuthorityPrefix("SCOPE_");
          grantedAuthoritiesConverter.setAuthoritiesClaimName("scope");
      
          JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
          jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
          return jwtAuthenticationConverter;
      }
      
      并在资源服务器配置中引用:
      .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwtConfigurer -> 
              jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter())
      ))
      
  4. 确保请求携带有效JWT令牌
    访问受保护端点时,需在请求头中添加Authorization: Bearer <access_token>,令牌可通过授权码流程获取:

    1. 访问授权地址:http://localhost:8080/oauth2/authorize?response_type=code&client_id=api-client&redirect_uri=http://127.0.0.1:8080/authorized&scope=api.read
    2. 使用user/password登录并授权
    3. 从回调地址获取code参数
    4. 发送POST请求到http://localhost:8080/oauth2/token,携带参数:grant_type=authorization_code、code=<获取的code>、redirect_uri=http://127.0.0.1:8080/authorized、client_id=api-client、client_secret=secret
    5. 从响应中获取access_token,用于访问受保护端点。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 22:25:54