Spring Authorization Server RP发起登出失败:CORS Origin为null异常
问题:Spring Gateway作为OIDC客户端登出时Origin为null导致CORS校验失败
我有一个以Spring Gateway作为Spring Authorization Server的OAuth客户端的项目,OIDC认证功能一切正常,但登出功能出现问题。点击Spring默认登出页面的登出按钮后,请求的Origin为null,导致CORS校验不通过,登出失败。若将"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网关日志
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
补充说明
- Chrome会将localhost下的不同端口视为同站;
- Spring Security使用的JSESSIONID未设置SameSite属性,会在跨源请求中发送,但原始问题并不涉及跨源请求。
内容的提问来源于stack exchange,提问作者IonutB
相关产品推荐
相关产品推荐

