Spring授权服务器RP发起的注销不起作用

问题描述 投票:0回答:1

我有一个项目,使用 spring gateway 作为 spring 授权服务器的 oauth 客户端。 除了注销之外,在 oidc 身份验证方面一切都工作正常。由于 CORS,注销不起作用,因为在默认 spring 注销页面上按注销按钮后,原点为 null。如果我将“null”原点添加到允许的原点列表中,或禁用 cors,它会按预期工作。

以下是 Spring Gateway 客户端的配置:

@Configuration
@EnableWebFluxSecurity
public class SecurityConfiguration {

    @Autowired
    public SecurityConfiguration(ReactiveClientRegistrationRepository clientRegistrationRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
    }

    ReactiveClientRegistrationRepository clientRegistrationRepository;

    private ServerLogoutSuccessHandler serverLogoutSuccessHandler() {
        OidcClientInitiatedServerLogoutSuccessHandler successHandler = new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository);
        successHandler.setPostLogoutRedirectUri("{baseUrl}/welcome");
        return successHandler;
    }


    @Bean
    public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
        String[] unprotectedPaths = new String[]{"/api-docs/**", "/swagger-ui.html", "/webjars/swagger-ui/**", "/actuator/**",
                "/oidc/**","/welcome/**", "/customer/registration", "/customer/registration-confirmation/**"};

        http.cors(withDefaults()).csrf(csrfSpec -> csrfSpec.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
                        .csrfTokenRequestHandler(new SpaServerCsrfTokenRequestHandler()))
                .authorizeExchange(authorizeExchangeSpec -> authorizeExchangeSpec.pathMatchers(unprotectedPaths).permitAll()
                        .pathMatchers("/notification/ws-connect").hasAuthority("SCOPE_notification.read")
                        .anyExchange().authenticated())
                .oauth2Login(withDefaults())
                .oauth2ResourceServer(oAuth2ResourceServerSpec -> oAuth2ResourceServerSpec.jwt(withDefaults()))
                .logout(httpSecurityLogoutConfigurer -> httpSecurityLogoutConfigurer.logoutSuccessHandler(serverLogoutSuccessHandler()));

        return http.build();
    }


    static final class SpaServerCsrfTokenRequestHandler extends ServerCsrfTokenRequestAttributeHandler {
        private final ServerCsrfTokenRequestAttributeHandler delegate = new XorServerCsrfTokenRequestAttributeHandler();

        @Override
        public void handle(ServerWebExchange exchange, Mono<CsrfToken> csrfToken) {
            // Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of the CsrfToken when it is rendered in the response body.
            this.delegate.handle(exchange, csrfToken);
        }

        @Override
        public Mono<String> resolveCsrfTokenValue(ServerWebExchange exchange, CsrfToken csrfToken) {
            final var hasHeader = exchange.getRequest().getHeaders().get(csrfToken.getHeaderName()) != null;
            return hasHeader ? super.resolveCsrfTokenValue(exchange, csrfToken) : this.delegate.resolveCsrfTokenValue(exchange, csrfToken);
        }
    }

    @Bean
    public WebFilter csrfCookieWebFilter() {
        return (exchange, chain) -> {
            exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty()).subscribe(o -> ((CsrfToken) o).getToken());
            return chain.filter(exchange);
        };
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(List.of("http://localhost:63342"));
        configuration.setAllowedMethods(List.of(CorsConfiguration.ALL));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(List.of(CorsConfiguration.ALL));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}

以下是 api-gateway 日志:

2024-07-16T22:37:22.392+03:00 DEBUG 90681 --- [api-gateway] [ctor-http-nio-6] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : Trying to match using PathMatcherServerWebExchangeMatcher{pattern='/logout', method=GET}
2024-07-16T22:37:22.392+03:00 DEBUG 90681 --- [api-gateway] [ctor-http-nio-6] athPatternParserServerWebExchangeMatcher : Checking match of request : '/logout'; against '/logout'
2024-07-16T22:37:22.392+03:00 DEBUG 90681 --- [api-gateway] [ctor-http-nio-6] o.s.s.w.s.u.m.OrServerWebExchangeMatcher : matched
2024-07-16T22:37:22.395+03:00 TRACE 90681 --- [api-gateway] [ctor-http-nio-6] o.s.w.s.adapter.HttpWebHandlerAdapter    : [130c2a73-11] Completed 200 OK, headers={masked}
2024-07-16T22:37:22.395+03:00 TRACE 90681 --- [api-gateway] [ctor-http-nio-6] o.s.h.s.r.ReactorHttpHandlerAdapter      : [130c2a73-5, L:/[0:0:0:0:0:0:0:1]:9990 - R:/[0:0:0:0:0:0:0:1]:63469] Handling completed
2024-07-16T22:37:37.036+03:00 TRACE 90681 --- [api-gateway] [ctor-http-nio-6] o.s.w.s.adapter.HttpWebHandlerAdapter    : [130c2a73-12] HTTP POST "/logout", headers={masked}
2024-07-16T22:37:37.038+03:00 DEBUG 90681 --- [api-gateway] [ctor-http-nio-6] o.s.w.c.reactive.DefaultCorsProcessor    : Reject: 'null' origin is not allowed
2024-07-16T22:37:37.038+03:00 TRACE 90681 --- [api-gateway] [ctor-http-nio-6] o.s.w.s.adapter.HttpWebHandlerAdapter    : [130c2a73-12] Completed 403 FORBIDDEN, headers={masked}
2024-07-16T22:37:37.039+03:00 TRACE 90681 --- [api-gateway] [ctor-http-nio-6] o.s.h.s.r.ReactorHttpHandlerAdapter      : [130c2a73-6, L:/[0:0:0:0:0:0:0:1]:9990 - R:/[0:0:0:0:0:0:0:1]:63469] Handling completed
spring-security oauth-2.0 openid-connect spring-cloud-gateway spring-authorization-server
1个回答
0
投票

RP 发起的注销(打开链接并阅读规范)从向依赖方(使用

oauth2Login
配置的 Spring 应用程序)
/logout
端点发出 POST 请求开始。

对于配置有

oauth2Login
的应用程序的任何请求,都会通过会话进行授权(请求必须保存会话 cookie)。因此,发送此请求的前端必须与 RP 具有相同的来源:是 RP 上托管的 Thymeleaf 模板,或者通过相同的反向代理(可以是网关本身)提供服务。所以,对RP的POST请求不应该是跨域请求。对于 SPA,您需要的是反向代理,而不是 CORS 配置。

对于通过会话授权的任何 POST、PUT、PATCH 和 DELETE 请求,应保护其免受 CSRF(包含 CSRF 令牌)。对于单页应用程序,此令牌是从 cookie 中读取的(http-only false)并设置为标头。

POST 请求的答案(网关会话结束后)应包含一个

Location
标头,其中包含用于结束授权服务器上的会话的 URI。如果授权服务器允许提供 SPA 的源(反向代理),则可以让运行 SPA 的浏览器跟踪此位置。否则,您可能必须将网关响应的状态从
302
更改为
2xx
范围内的状态,以便 Javascript 代码可以观察响应、读取
Location
,然后设置
window.location.href
(更改浏览器选项卡源而不是发送跨源请求)。

spring-cloud-gateway的详细

工作
示例(包括注销)用作 SPA 的 OAuth2 BFF(带有
oauth2Login
TokenRelay
过滤器)我写的这篇 Baeldung 文章

© www.soinside.com 2019 - 2024. All rights reserved.