Spring Boot 2.7.6中OpenID Redirect URI编码问题及解决方案咨询
Spring Boot 2.7.6 OAuth2客户端Redirect URI编码问题及解决方案
问题场景
我在配置使用OpenID的Spring Boot应用时,遇到了Redirect URI未正确URL编码的问题。当前使用Java 11,只能基于Spring Boot 2.7.6开发。
我的application.yml配置如下:
spring: security: oauth2: client: registration: xxxx: client-id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx client-secret: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx authorization-grant-type: authorization_code redirect-uri: https://www.example.com/home scope: - openid - profile - email provider: xxxx: authorizationUri: https:... ...
但生成的授权URL中,redirect_uri参数未进行URL编码:
https://.....com/authorize ?response_type=code &client_id=... &scope=... &state=...D &redirect_uri=https://www.example.com/home <---- 未URL编码 &nonce=...
期望的编码后结果应为:
&redirect_uri=https%3A%2F%2Fwww.example.com%2Fhome <---- 已编码
尝试过的无效操作
- 手动在配置中对
redirect-uri进行URL编码,结果Spring对其进行了二次编码,生成非法格式(%25是%的URL编码,导致整体为二次编码):
https://.....com/authorize ?response_type=code &client_id=... &scope=... &state=...D &redirect_uri=https%253A%252F%252Fexample.com%252Fhome <---- 二次编码导致格式非法 &nonce=...
- 省略
redirect-uri配置并尝试在授权URI中硬编码,Spring直接提示该参数为必填项。 - 身份提供商不支持自动发现,无法通过OIDC自动配置解决。
针对Spring Boot 2.7.6的解决方案
可以通过自定义OAuth2AuthorizationRequestResolver手动处理redirect_uri的编码逻辑,避免框架的编码异常:
- 创建自定义授权请求解析器类:
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.web.util.UriComponentsBuilder; import javax.servlet.http.HttpServletRequest; import java.util.Map; public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { private final OAuth2AuthorizationRequestResolver delegate; public CustomAuthorizationRequestResolver(OAuth2AuthorizationRequestResolver delegate) { this.delegate = delegate; } @Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { OAuth2AuthorizationRequest authorizationRequest = this.delegate.resolve(request); return customizeAuthorizationRequest(authorizationRequest); } @Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) { OAuth2AuthorizationRequest authorizationRequest = this.delegate.resolve(request, clientRegistrationId); return customizeAuthorizationRequest(authorizationRequest); } private OAuth2AuthorizationRequest customizeAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest) { if (authorizationRequest == null) { return null; } UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(authorizationRequest.getAuthorizationRequestUri()); // 移除原未编码的redirect_uri参数 uriBuilder.replaceQueryParam("redirect_uri"); // 添加正确编码后的redirect_uri String encodedRedirectUri = UriComponentsBuilder.fromUriString(authorizationRequest.getRedirectUri()) .build().encode().toUriString(); uriBuilder.queryParam("redirect_uri", encodedRedirectUri); return OAuth2AuthorizationRequest.from(authorizationRequest) .authorizationRequestUri(uriBuilder.build().toUriString()) .build(); } }
- 在Security配置类中注册自定义解析器:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(authEndpoint -> authEndpoint .authorizationRequestResolver(customAuthorizationRequestResolver(clientRegistrationRepository)) ) ); return http.build(); } private OAuth2AuthorizationRequestResolver customAuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) { DefaultOAuth2AuthorizationRequestResolver defaultResolver = new DefaultOAuth2AuthorizationRequestResolver( clientRegistrationRepository, "/oauth2/authorization"); return new CustomAuthorizationRequestResolver(defaultResolver); } }
该方案会在授权请求生成后,替换掉未编码的redirect_uri参数,用正确编码后的URL替代,彻底避免二次编码问题。
Spring 3.x中的修复情况
在Spring Boot 3.x(对应Spring Security 6.x)中,这个问题已经得到官方修复。框架会自动正确处理redirect_uri的URL编码,无需手动干预:配置原始的redirect-uri值后,框架会自动生成符合规范的编码后的参数,不会出现未编码或二次编码的问题。
内容的提问来源于stack exchange,提问作者Franco G
相关产品推荐
相关产品推荐

