You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 02:02:06