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令牌,或令牌的认证/解析流程未正常执行。
具体排查与修复
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(); }
- 修复:修改
JWT Issuer URI配置错误
配置文件中issuer-uri使用了https://localhost:8080,但服务未启用HTTPS,导致资源服务器无法获取JWKS验证令牌。- 修复:修改
application.yml中的配置:spring: security: oauth2: resourceserver: jwt: issuer-uri: http://localhost:8080
- 修复:修改
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()) ))
- 修复:在
确保请求携带有效JWT令牌
访问受保护端点时,需在请求头中添加Authorization: Bearer <access_token>,令牌可通过授权码流程获取:- 访问授权地址:
http://localhost:8080/oauth2/authorize?response_type=code&client_id=api-client&redirect_uri=http://127.0.0.1:8080/authorized&scope=api.read - 使用
user/password登录并授权 - 从回调地址获取
code参数 - 发送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 - 从响应中获取
access_token,用于访问受保护端点。
- 访问授权地址:
内容的提问来源于stack exchange,提问作者user3310115
相关产品推荐
相关产品推荐

