如何为 Webflux、Spring-Security、okta-spring-boot-starter 和 Okta-React 正确使用 CORS?

Posted

技术标签:

【中文标题】如何为 Webflux、Spring-Security、okta-spring-boot-starter 和 Okta-React 正确使用 CORS?【英文标题】:How to use CORS correctly for Webflux, Spring-Security, okta-spring-boot-starter, and Okta-React? 【发布时间】:2020-09-30 07:09:13 【问题描述】:

我的 webflux api 中有一个 CORS webFilter,当我通过邮递员为飞行前检查清单发出选项请求时,我得到了带有正确标题的响应。但是,当我使用 Axios 和 Okta-React 库从浏览器发出相同的请求以检索访问令牌时,会返回 401 Unauthorized 并且不会返回我要查找的标头。 Axios 是不是在 OPTIONS 请求中不包含 Bearer Token?我是否需要将选项请求列入白名单以不通过 OKTA 身份验证,OKTA 不会处理这个吗?

我在邮递员中使用来自 SPA 的相同访问令牌。 我是否在 Axios 请求中遗漏了某些内容,是否需要使用 Axios 执行 CORS 的任何其他配置?

我很困惑,为什么当从邮递员发送时,webfilter 会激活 OPTIONS 请求,但在浏览器中从 Axios 调用时却没有?

const getExperience = () => 
        const  accessToken  = authState;

            axios(
                method: "get",
                timeout: 10000,
                headers: 
                    "Authorization": `Bearer $accessToken`
                ,
                url: `http://localhost:8080/api/v1/professionals`
            ).then( response => 
                setExperience(response.data);
            );
    
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@EnableWebFluxSecurity
public class SecurityConfiguration 

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) 
        http
            .authorizeExchange()
                .anyExchange().authenticated()
                .and()
            .oauth2ResourceServer()
                .jwt();

        return http.build();
    


import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;

import reactor.core.publisher.Mono;

@Component
public class CustomCorsWebFilter implements WebFilter 

    private static final String ALLOWED_HEADERS = "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN";
    private static final String ALLOWED_METHODS = "GET, PUT, POST, DELETE, OPTIONS";
    private static final String ALLOWED_ORIGIN = "http://localhost:3000";
    private static final String MAX_AGE = "3600";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) 

        if (exchange.getRequest().getMethod() == HttpMethod.OPTIONS) 
            ServerHttpResponse response = exchange.getResponse();
            HttpHeaders headers = response.getHeaders();
            headers.add("Access-Control-Allow-Origin", ALLOWED_ORIGIN);
            headers.add("Access-Control-Allow-Methods", ALLOWED_METHODS);
            headers.add("Access-Control-Max-Age", MAX_AGE);
            headers.add("Access-Control-Allow-Headers", ALLOWED_HEADERS);

            response.setStatusCode(HttpStatus.OK);
            return Mono.empty();
        
        return chain.filter(exchange);
    


【问题讨论】:

【参考方案1】:

这是我用来设置带有WebFlux, React, and Okta 的 CORS 标头的内容。

@Bean 
CorsConfigurationSource corsConfigurationSource() 
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowCredentials(true);
    configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000"));
    configuration.setAllowedMethods(Collections.singletonList("GET"));
    configuration.setAllowedHeaders(Collections.singletonList("*"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;

我还使用了如下过滤器:

@Bean
CorsWebFilter corsWebFilter() 
    CorsConfiguration corsConfig = new CorsConfiguration();
    corsConfig.setAllowCredentials(true);
    corsConfig.setAllowedOrigins(List.of("*"));
    corsConfig.setMaxAge(3600L);
    corsConfig.addAllowedMethod("*");
    corsConfig.addAllowedHeader("*");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", corsConfig);

    return new CorsWebFilter(source);

【讨论】:

这行得通!那么为什么这会起作用,而过滤器却不起作用呢? 我认为最大的区别是您的过滤器没有设置允许凭据为真。我在答案中添加了一个过滤器示例。 啊,由于不允许缺少授权标头,我收到未经授权的响应是有道理的。今天学到了新东西! 只是为了澄清一下,在使用 RestControllers 时,在使用上述配置选项之一后,您是否仍需要 @CrossOrigin 注释? 没有。这样配置就不需要@CrossOrigin了。

以上是关于如何为 Webflux、Spring-Security、okta-spring-boot-starter 和 Okta-React 正确使用 CORS?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 Webflux、Spring-Security、okta-spring-boot-starter 和 Okta-React 正确使用 CORS?

使用Webflux和Spring Cloud时如何用netty替换tomcat?

使用 WebFlux 的反应式编程如何处理依赖的外部 api 调用

低价好课:SpringBoot2.0不容错过的新特性WebFlux响应式编程百度云高清完整

如何在 Spring boot 2 + Webflux + Thymeleaf 中配置 i18n?

Spring WebFlux快速上手——响应式Spring的道法术器