Springboot跨域源码分析

Springboot跨域实现

/**
 * @Description 跨域设置
 * @Author BillYu
 * Created by BillYu on 2019/11/18.
 */
@Configuration
public class CorsConfig {
    private CorsConfiguration buildConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        //注意修改匹配路径
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }
}

源码查阅

CorsConfiguration.class主要是一些用于跨域校验的属性值。

public class CorsConfiguration {
    public static final String ALL = "*";
    private static final List<HttpMethod> DEFAULT_METHODS;
    private static final List<String> DEFAULT_PERMIT_ALL;
    private static final List<String> DEFAULT_PERMIT_METHODS;
    @Nullable
    private List<String> allowedOrigins;
    @Nullable
    private List<String> allowedMethods;
    @Nullable
    private List<HttpMethod> resolvedMethods;
    @Nullable
    private List<String> allowedHeaders;
    @Nullable
    private List<String> exposedHeaders;
    @Nullable
    private Boolean allowCredentials;
    @Nullable
    private Long maxAge;

    public CorsConfiguration() {
        this.resolvedMethods = DEFAULT_METHODS;
    }

    public CorsConfiguration(CorsConfiguration other) {
        this.resolvedMethods = DEFAULT_METHODS;
        this.allowedOrigins = other.allowedOrigins;
        this.allowedMethods = other.allowedMethods;
        this.resolvedMethods = other.resolvedMethods;
        this.allowedHeaders = other.allowedHeaders;
        this.exposedHeaders = other.exposedHeaders;
        this.allowCredentials = other.allowCredentials;
        this.maxAge = other.maxAge;
    }
}

CorsFilter.class 处理请求的过滤器

public class CorsFilter extends OncePerRequestFilter {
    private final CorsConfigurationSource configSource;
    private CorsProcessor processor = new DefaultCorsProcessor();

    public CorsFilter(CorsConfigurationSource configSource) {
        Assert.notNull(configSource, "CorsConfigurationSource must not be null");
        this.configSource = configSource;
    }

    public void setCorsProcessor(CorsProcessor processor) {
        Assert.notNull(processor, "CorsProcessor must not be null");
        this.processor = processor;
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);
            if (corsConfiguration != null) {
                boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
                if (!isValid || CorsUtils.isPreFlightRequest(request)) {
                    return;
                }
            }
        }

        filterChain.doFilter(request, response);
    }
}

CorsUtils.isCorsRequest(request)
判断请求头中的Origin是否存在,如果不存在不作跨域校验,直接通过。这样解释了我的一个疑惑,为什么img、script标签请求中没有origin也可通过跨域请求。
processor.processRequest(corsConfiguration, request, response);
是对请求根据配置做具体的校验处理

CorsProcessor的实现类DefaultCorsProcessor

public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (!CorsUtils.isCorsRequest(request)) {
            return true;
        } else {
            ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
            if (this.responseHasCors(serverResponse)) {
                logger.debug("Skip CORS processing: response already contains \"Access-Control-Allow-Origin\" header");
                return true;
            } else {
                ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
                if (WebUtils.isSameOrigin(serverRequest)) {
                    logger.debug("Skip CORS processing: request is from same origin");
                    return true;
                } else {
                    boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
                    if (config == null) {
                        if (preFlightRequest) {
                            this.rejectRequest(serverResponse);
                            return false;
                        } else {
                            return true;
                        }
                    } else {
                        return this.handleInternal(serverRequest, serverResponse, config, preFlightRequest);
                    }
                }
            }
        }
    }

this.responseHasCors(serverResponse)
判断该请求是否已经做过跨域校验,如果已经做过校验会在response的header中添加相关属性,如果含有该属性值则直接通过跨域判断。
WebUtils.isSameOrigin(serverRequest)根据请求头中的其他相关属性判断请求是否同源,如果同源则通过跨域判断。

handleInternal方法对具体的请求头属性和配置做了具体的处理判断。

protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response, CorsConfiguration config, boolean preFlightRequest) throws IOException {
        String requestOrigin = request.getHeaders().getOrigin();
        String allowOrigin = this.checkOrigin(config, requestOrigin);
        HttpHeaders responseHeaders = response.getHeaders();
        responseHeaders.addAll("Vary", Arrays.asList("Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"));
        if (allowOrigin == null) {
            logger.debug("Rejecting CORS request because '" + requestOrigin + "' origin is not allowed");
            this.rejectRequest(response);
            return false;
        } else {
            HttpMethod requestMethod = this.getMethodToUse(request, preFlightRequest);
            List<HttpMethod> allowMethods = this.checkMethods(config, requestMethod);
            if (allowMethods == null) {
                logger.debug("Rejecting CORS request because '" + requestMethod + "' request method is not allowed");
                this.rejectRequest(response);
                return false;
            } else {
                List<String> requestHeaders = this.getHeadersToUse(request, preFlightRequest);
                List<String> allowHeaders = this.checkHeaders(config, requestHeaders);
                if (preFlightRequest && allowHeaders == null) {
                    logger.debug("Rejecting CORS request because '" + requestHeaders + "' request headers are not allowed");
                    this.rejectRequest(response);
                    return false;
                } else {
                    responseHeaders.setAccessControlAllowOrigin(allowOrigin);
                    if (preFlightRequest) {
                        responseHeaders.setAccessControlAllowMethods(allowMethods);
                    }

                    if (preFlightRequest && !allowHeaders.isEmpty()) {
                        responseHeaders.setAccessControlAllowHeaders(allowHeaders);
                    }

                    if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
                        responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
                    }

                    if (Boolean.TRUE.equals(config.getAllowCredentials())) {
                        responseHeaders.setAccessControlAllowCredentials(true);
                    }

                    if (preFlightRequest && config.getMaxAge() != null) {
                        responseHeaders.setAccessControlMaxAge(config.getMaxAge());
                    }

                    response.flush();
                    return true;
                }
            }
        }
    }

当前遇到的需求:

根据用户配置,对资源做可配置化跨域处理。
实现方法:

CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("https://www.runoob.com");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);
        try {
            response.reset();
//            System.out.println("after set"+response.getHeader("Access-Control-Allow-Origin"));
            boolean isValid = new DefaultCorsProcessor().processRequest(corsConfiguration,request,response);
//            System.out.println("isValid:"+isValid);
            if (!isValid || CorsUtils.isPreFlightRequest(request)) {
                return null;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

corsConfiguration中添加用户自定义的跨域配置
response.reset();
因为全局跨域配置过滤器的处理,当前收到的response是进行过跨域标记的(Access-Control-Allow-Origin),调用reset方法将response中的header去除。然后才能进行下面的跨域处理。

发表新评论