Spring Security中HttpSessionOAuth2AuthorizationRequestRepository的removeAuthorizationRequest始终返回null
问题描述
使用Spring Security OAuth2 Client搭配Spring Session Data Redis存储会话数据时,OAuth认证流程中抛出authorization_request_not_found异常,根源是HttpSessionOAuth2AuthorizationRequestRepository类的removeAuthorizationRequest方法始终返回null。
Redis中存储的认证请求数据
Redis中存储的OAuth2认证请求数据格式如下:
{ "authorizationUri":"https://accounts.google.com/o/oauth2/v2/auth", "responseType":"code", "clientId":"***.apps.googleusercontent.com", "redirectUri":"http://localhost:8080/login/oauth2/code/google", "scopes":[ "openid", "profile", "email" ], "state":"y829XucK_617Xrl5O4QEk8EfpvuWkaE7G8MUN0HV5ok=", "additionalParameters":{ "nonce":"Z00EwZrkziuO31XVt4RK1tkApUEOjTG4SpzHp94ASxE" }, "authorizationRequestUri":"https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=***.apps.googleusercontent.com&scope=openid%20profile%20email&state=y829XucK_617Xrl5O4QEk8EfpvuWkaE7G8MUN0HV5ok%3D&redirect_uri=http://localhost:8080/login/oauth2/code/google&nonce=Z00EwZrkziuO31XVt4RK1tkApUEOjTG4SpzHp94ASxE", "attributes":{ "registration_id":"google", "nonce":"86o0mT1_uDmbcwDHciWPsxft2Vlo0WyHXZRda2N9a0a00yb7puxLhPqIUNOov--PUt-HRlqb5rpVYXTahlGsCzPTn6wm7ZaD2gNI13HMPqN-IX4oRfAmWbKpi4-7TbPV" }, "grantType":{ "value":"authorization_code" } }
问题原因分析
HttpSessionOAuth2AuthorizationRequestRepository类的removeAuthorizationRequest方法逻辑存在匹配问题:
@Override public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) { Assert.notNull(request, "request cannot be null"); String stateParameter = this.getStateParameter(request); if (stateParameter == null) { return null; } Map<String, OAuth2AuthorizationRequest> authorizationRequests = this.getAuthorizationRequests(request); OAuth2AuthorizationRequest originalRequest = authorizationRequests.remove(stateParameter); if (authorizationRequests.size() == 0) { request.getSession().removeAttribute(this.sessionAttributeName); } else if (authorizationRequests.size() == 1) { request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests.values().iterator().next()); } else { request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests); } return originalRequest; }
方法第4行获取到的state参数值为"y829XucK_617Xrl5O4QEk8EfpvuWkaE7G8MUN0HV5ok=",但第8行获取的authorizationRequests是一个Map类型,而Redis中实际存储的是单个OAuth2AuthorizationRequest对象(并非以state为key的Map结构),导致用state作为key调用remove方法时返回null。
调用该方法的代码在OAuth2LoginAuthenticationFilter中:
@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap()); if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) { OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository .removeAuthorizationRequest(request, response); if (authorizationRequest == null) { OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } // 剩余代码... }
当authorizationRequest为null时,就会抛出authorization_request_not_found异常。
环境配置
plugins { id 'org.springframework.boot' version '2.7.5' } sourceCompatibility = '11' dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation('org.springframework.session:spring-session-data-redis') { exclude group: 'org.springframework.data', module: 'spring-data-redis' } implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-security' }
异常堆栈信息
2023-02-09 16:35:07.947 WARN 59793 --- [nio-8080-exec-2] l.g.c.CustomAuthenticationFailureHandler : [authorization_request_not_found] org.springframework.security.oauth2.core.OAuth2AuthenticationException: [authorization_request_not_found] at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:173) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:227) at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:178) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112) at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:142) at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:82) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.base/java.lang.Thread.run(Thread.java:829)
补充说明
- 使用Spring Boot 2.7.5、Java 11
- 为初始配置,未进行版本升级
- 存在可复现问题的示例仓库
寻求该问题的解决方案。
内容的提问来源于stack exchange,提问作者I have 10 fingers
相关产品推荐
相关产品推荐

