Spring Security OAuth2 Client:代理后授权服务器URI重定向配置问题
解决方案
1. 自定义OAuth2AuthorizationRequestResolver修改授权地址
Spring Security默认的DefaultOAuth2AuthorizationRequestResolver仅处理redirect-uri的动态替换,要让authorization-uri也适配代理后的公网地址,需要自定义 resolver,从请求头中提取公网主机信息,替换内部授权服务器地址。
自定义Resolver实现:
@Component public class ProxyAwareAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { private final DefaultOAuth2AuthorizationRequestResolver delegate; private final String internalAuthServerHost; private final String proxyAuthServerPath; public ProxyAwareAuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository, String authorizationRequestBaseUri, @Value("${auth.server.internal-host}") String internalAuthServerHost, @Value("${auth.server.proxy-path}") String proxyAuthServerPath) { this.delegate = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, authorizationRequestBaseUri); this.internalAuthServerHost = internalAuthServerHost; // 配置为你的内部Keycloak地址,比如authorization-server.example.com this.proxyAuthServerPath = proxyAuthServerPath; // 配置为前端代理授权服务器的路径,比如/auth-server } @Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { OAuth2AuthorizationRequest authorizationRequest = this.delegate.resolve(request); return modifyAuthorizationUri(request, authorizationRequest); } @Override public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) { OAuth2AuthorizationRequest authorizationRequest = this.delegate.resolve(request, clientRegistrationId); return modifyAuthorizationUri(request, authorizationRequest); } private OAuth2AuthorizationRequest modifyAuthorizationUri(HttpServletRequest request, OAuth2AuthorizationRequest authorizationRequest) { if (authorizationRequest == null) { return null; } // 优先从Forwarded头提取公网主机, fallback到X-Forwarded-Host String publicHost = extractPublicHost(request); String publicProto = extractPublicProto(request); // 替换授权地址的内部主机为代理后的公网地址 String originalUri = authorizationRequest.getAuthorizationUri(); String modifiedUri = originalUri.replace( "https://" + internalAuthServerHost, publicProto + "://" + publicHost + proxyAuthServerPath ); // 构建新的授权请求对象 return OAuth2AuthorizationRequest.from(authorizationRequest) .authorizationUri(modifiedUri) .build(); } private String extractPublicHost(HttpServletRequest request) { String forwardedHeader = request.getHeader("Forwarded"); if (forwardedHeader != null) { // 解析Forwarded头格式:host=frontend.example.com;proto=https for (String part : forwardedHeader.split(";")) { if (part.trim().startsWith("host=")) { return part.trim().split("=")[1]; } } } return request.getHeader("X-Forwarded-Host") != null ? request.getHeader("X-Forwarded-Host") : request.getServerName(); } private String extractPublicProto(HttpServletRequest request) { String forwardedHeader = request.getHeader("Forwarded"); if (forwardedHeader != null) { for (String part : forwardedHeader.split(";")) { if (part.trim().startsWith("proto=")) { return part.trim().split("=")[1]; } } } return request.getHeader("X-Forwarded-Proto") != null ? request.getHeader("X-Forwarded-Proto") : "https"; } }
配置SecurityFilterChain使用自定义Resolver:
@Configuration @EnableWebSecurity public class SecurityConfig { private final ProxyAwareAuthorizationRequestResolver authorizationRequestResolver; public SecurityConfig(ProxyAwareAuthorizationRequestResolver authorizationRequestResolver) { this.authorizationRequestResolver = authorizationRequestResolver; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(endpoint -> endpoint .authorizationRequestResolver(authorizationRequestResolver) ) ); return http.build(); } }
2. 配置Spring识别Forwarded头
在application.yml中开启框架级别的Forwarded头处理:
server: forward-headers-strategy: framework
如果是Spring Cloud Gateway,补充全局过滤器确保请求头正确传递:
spring: cloud: gateway: default-filters: - AddRequestHeader=X-Forwarded-Proto, https - AddRequestHeader=X-Forwarded-Host, frontend.example.com
3. 配置NGINX代理授权服务器路径
确保NGINX将前端的/auth-server/**路径转发到内部Keycloak,同时重写路径:
location /auth-server/ { proxy_pass https://authorization-server.example.com/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; # 去掉/auth-server前缀,匹配Keycloak的原始路径 rewrite ^/auth-server/(.*)$ /$1 break; }
完成以上配置后,API网关返回的302重定向Location会变为https://frontend.example.com/auth-server/auth?xxx,前端访问该地址时,NGINX会转发到内部Keycloak,实现仅暴露前端公网地址的需求。
内容的提问来源于stack exchange,提问作者asa
相关产品推荐
相关产品推荐

