Spring 6资源服务器无法识别Bearer Access Token的问题排查
问题描述
我正在使用Spring 6.0开发一款支持浏览器和独立客户端访问的应用,采用Keycloak作为OAuth2授权服务器,应用本身作为资源服务器提供数据。
浏览器端访问正常,但独立客户端通过设备授权流或密码授权流从Keycloak获取Access Token后,将其传递给Spring应用时,请求被重定向到Keycloak登录页面,而非验证有效Token。
当前配置
@Configuration public class OAuth2Config { /** * The primary client authentication manager. * * @param clientRegistrationRepository * @param authorizedClientRepository * @return The authorized client manager */ @Bean public OAuth2AuthorizedClientManager clientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository ) { // Use the default OAuth client manager - configurations can be made in // the properties - and set the built provider. // DefaultOAuth2AuthorizedClientManager clientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository ); return clientManager; } }
日志信息
2023-01-04T15:30:19.996-06:00 TRACE 9680 --- [nio-8081-exec-4] o.s.security.web.FilterChainProxy : Trying to match request against DefaultSecurityFilterChain [RequestMatcher=any request, Filters=[org.springframework.security.web.session.DisableEncodeUrlFilter@6c6c93f8, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@38e88e13, org.springframework.security.web.context.SecurityContextHolderFilter@89178b4, org.springframework.security.web.header.HeaderWriterFilter@20ad64c, org.springframework.security.web.csrf.CsrfFilter@1512efe9, org.springframework.security.web.authentication.logout.LogoutFilter@7fa86ddd, org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@59509393, org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter@7ec75228, org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter@1d6dc2b8, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@3314f179, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@3a720ae3, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@64836643, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@42684d86, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1fe37d27, org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter@3fc7abf6, org.springframework.security.web.access.ExceptionTranslationFilter@600b3bee, org.springframework.security.web.access.intercept.AuthorizationFilter@565030b7]] (1/1) 2023-01-04T15:30:19.996-06:00 DEBUG 9680 --- [nio-8081-exec-4] o.s.security.web.FilterChainProxy : Securing GET /users 2023-01-04T15:30:19.996-06:00 TRACE 9680 --- [nio-8081-exec-4] o.s.security.web.FilterChainProxy : Invoking DisableEncodeUrlFilter (1/17) : : 2023-01-04T15:30:20.000-06:00 TRACE 9680 --- [nio-8081-exec-4] o.s.security.web.FilterChainProxy : Invoking AuthorizationFilter (17/17) 2023-01-04T15:30:25.332-06:00 TRACE 9680 --- [nio-8081-exec-4] estMatcherDelegatingAuthorizationManager : Authorizing SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@60be86e5] 2023-01-04T15:30:25.332-06:00 TRACE 9680 --- [nio-8081-exec-4] estMatcherDelegatingAuthorizationManager : Checking authorization on SecurityContextHolderAwareRequestWrapper[ org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@60be86e5] using org.springframework.security.authorization.AuthenticatedAuthorizationManager@3ba6b23 2023-01-04T15:30:25.332-06:00 TRACE 9680 --- [nio-8081-exec-4] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists 2023-01-04T15:30:25.332-06:00 TRACE 9680 --- [nio-8081-exec-4] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication] 2023-01-04T15:30:25.333-06:00 TRACE 9680 --- [nio-8081-exec-4] .s.s.w.c.SupplierDeferredSecurityContext : Created SecurityContextImpl [Null authentication] 2023-01-04T15:30:25.333-06:00 TRACE 9680 --- [nio-8081-exec-4] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=192.168.0.119, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]] 2023-01-04T15:30:25.334-06:00 TRACE 9680 --- [nio-8081-exec-4] o.s.s.w.a.ExceptionTranslationFilter : Sending AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=192.168.0.119, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]] to authentication entry point since access is denied
解决方案
核心问题是仅配置了OAuth2客户端登录逻辑,未开启资源服务器的Token验证机制。当前过滤器链只有浏览器端OAuth2授权码流的处理过滤器,没有解析验证Bearer Token的组件,导致应用无法识别请求中的Token,将其判定为匿名访问进而触发重定向登录。
需要做以下调整:
1. 添加资源服务器配置
创建或修改Spring Security配置类,同时支持浏览器端OAuth2登录和API端Bearer Token验证:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 区分不同访问路径的认证规则 .authorizeHttpRequests(auth -> auth // API接口要求Bearer Token认证 .requestMatchers("/api/**").authenticated() // 浏览器端路径使用OAuth2登录 .anyRequest().permitAll() ) // 配置资源服务器,处理JWT格式的Bearer Token .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt // Keycloak的公钥端点,用于验证Token签名 .jwkSetUri("http://你的Keycloak地址/auth/realms/你的领域/protocol/openid-connect/certs") ) ) // 保留浏览器端的OAuth2登录配置 .oauth2Login(withDefaults()); return http.build(); } // 保留原有的OAuth2AuthorizedClientManager配置(若需客户端自动刷新Token则保留) @Bean public OAuth2AuthorizedClientManager clientManager( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) { DefaultOAuth2AuthorizedClientManager clientManager = new DefaultOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); return clientManager; } }
2. 补充配置文件参数
在application.yml或application.properties中添加资源服务器的必要配置:
spring: security: oauth2: resourceserver: jwt: # 验证Token的发行方,需与Keycloak领域地址一致 issuer-uri: http://你的Keycloak地址/auth/realms/你的领域
3. 确保客户端请求正确携带Token
独立客户端发送请求时,必须在请求头中添加Authorization字段,格式为:
Authorization: Bearer <你的Access Token>
关键说明
- 添加
oauth2ResourceServer().jwt()后,Spring Security会自动加入BearerTokenAuthenticationFilter,负责解析请求头中的Token并完成验证; jwkSetUri是Keycloak提供的公钥接口,用于确认JWT签名的合法性;issuer-uri用于校验Token的发行来源,防止非法Token被通过验证。
内容的提问来源于stack exchange,提问作者cneller
相关产品推荐
相关产品推荐

