别用 Filter 了,试试 Spring 自带的方式处理 CORS 跨域问题

简介: 从 CORS 到 Spring MVC跨源资源共享(CORS) 即 Cross-Origin Resource Sharing,也常被译为跨域资源共享。作为 W3C 的标准,它允许浏览器向跨源服务器发起请求,克服了 AJAX 只能同源使用的限制。

从 CORS 到 Spring MVC


跨源资源共享(CORS) 即 Cross-Origin Resource Sharing,也常被译为跨域资源共享。作为 W3C 的标准,它允许浏览器向跨源服务器发起请求,克服了 AJAX 只能同源使用的限制。


CORS 需要浏览器和服务器同时支持,浏览器发起跨域请求时会自动携带一些请求头,服务器如果允许跨域,也会自动添加一些响应头。作为运行在服务端的 Spring MVC 也对 CORS 提供了支持,并提供了多种解决方案。


认识 CORS,从浏览器同源策略谈起

同源策略是 Netscape 公司在 1995 年引入浏览器的,目前所有浏览器都遵循了同源策略。


浏览器同源策略


同源表示两个网页的 协议、域名、端口号 三者都一致。同源策略最初的目的是为了保护 A 网页设置的 cookie 在 B 网页中不能读取。


设想如果用户同时打开了银行网站和钓鱼网站,如果没有同源策略,钓鱼网站拿到银行网站的 cookie 后发起转账或者读取用户敏感信息,将会很危险。


非同源的网页,对 Cookie、LocalStorage、IndexDB 读取、Dom 文档获取、Ajax 请求有着严格的限制。同源策略规定了只能向同源的网址发起 AJAX 请求。CORS 正是解决跨域 AJAX 的标准方案,相比使用 JSONP 方案来说更为灵活。


CORS 处理流程


浏览器的请求可以分为简单请求和非简单请求两种。浏览器对不同类型的请求进行 CORS 处理的方式有所不同。


简单请求


满足下面三个条件的请求可以被称为简单请求。


请求方法为 GET、HEAD、POST 之一。

允许设置的请求头包括 Accept、Accept-Language、Content-Language、Content-Type。

Content-Type 的值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded。

简单请求流程如下图所示。


image.png

image.png


浏览器发起请求时在请求头添加 Origin 字段,表示当前资源所在的源,服务端收到请求后检查该字段,如果允许该请求则在响应头添加Access-Control-Allow-Origin 字段,否则可以拒绝处理请求并返回错误的 HTTP 响应码,浏览器收到响应后发现没有 Access-Control-Allow-Origin 字段或者 Access-Control-Allow-Origin 字段值有误则会在控制台打印不允许跨域的错误信息。


非简单请求


对于非简单请求,浏览器首先会发起预请求 Preflight Request 检查是否允许跨域,如果允许跨域才会执行真正的请求,流程如下所示。


21.png

预请求的 HTTP 方法为 OPTIONS,携带的请求头如下:


Origin:表示资源所在的源。

Access-Control-Request-Method:表示真实 HTTP 请求方法的请求头。

Access-Control-Request-Headers:表示真实 HTTP 请求方法自定义的请求头 。

如果服务端允许跨域请求,会在响应头添加如下的字段:


Access-Control-Allow-Origin :表示跨域请求允许的源,* 表示允许任何源。

Access-Control-Request-Method:表示跨域请求允许使用的请求方法,可以比请求头中的多。

Access-Control-Request-Headers:表示跨域请求允许携带的请求头。

如果服务端不允许跨域请求,则可以直接返回表示错误的 HTTP 响应码。


浏览器收到预请求响应后检查响应头判断是否允许跨域,如果不允许跨域则直接在控制台打印跨域报错信息。如果允许跨域再正常发起请求,携带请求头 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 以及自定义的请求头。


携带用户身份的跨域请求


服务端检查跨域请求后,除了返回基本的响应头,还可以添加如下额外的响应头:


Access-Control-Max-Age:表示跨域检查结果在浏览器中可以缓存的秒数。

Access-Control-Expose-Headers:默认情况 JS 只能获取一些基本的响应头,这个字段允许 JS 可以获取除基本响应头的其他响应头。

除此之外,服务端还可以返回值为 true 的响应头 Access-Control-Allow-Credentials,这个响应头可以让浏览器在跨域请求时携带 Cookie 信息,当然了,需要在发起请求时配置 withCredentials=true。


image.png


如果服务端返回了响应头 Access-Control-Allow-Credentials,此时 Access-Control-Allow-Origin 不能返回 *,否则请求将会失败。


Spring MVC CORS 处理


由于每个接口都需要处理跨域请求,因此在传统的 Java Web 项目中通常使用 Filter 进行全局处理。


Spring MVC 中进行跨域处理的核心类是 HandlerMapping,当请求到达 DispatchServlet,如果请求是预请求 Spring 会将处理器替换为跨域处理器,如果请求是非预请求 Spring 将在拦截器链前面添加跨域拦截器,然后根据 CORS 配置进行相应的处理。再把 DispatcherServlet 流程图祭出,和 CORS 相关的部分可以见右上角。


22.png


CorsFilter


Filter 是解决跨域的传统方式,Spring 出现前,我们经常会写一个解决跨域的 Filter,当请求到来时向响应头中添加固定的字段。


Spring MVC 提供了一个具有相同功能的 CorsFilter,这样以后我们就不需要每个项目都单独写一个处理跨域的 Filter 了。Spring MVC 中配置 Filter 的方式可以参见【Spring MVC 系列】Spring MVC 中 Filter 配置的 6 种方式,看看你了解哪些。


SpringBoot 环境下配置 CorsFilter 示例如下。


@Configuration
public class WebMvcConfig {
    @Bean
    public Filter corsFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("http://hukp.cn");
        corsConfiguration.addAllowedMethod(HttpMethod.POST);
        corsConfiguration.addAllowedHeader("token");
        corsConfiguration.setExposedHeaders(Arrays.asList("header1", "header2"));
        corsConfiguration.setMaxAge(3600L);
        corsConfiguration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/*", corsConfiguration);
        CorsFilter corsFilter = new CorsFilter(corsConfigurationSource);
        return corsFilter;
    }
}


实例化 CorsFilter 时需要指定一个 CorsConfigurationSource 实例用来获取跨域配置 CorsConfiguration,常用的实现是 UrlBasedCorsConfigurationSource。


全局 CORS 配置


Spring MVC 官方解决 CORS 的做法是在 HandlerMappping 获取处理器链时根据是否为预请求使用 PreFlightHandler 作为处理器或者添加拦截器 CorsInterceptor,具体可以参见源码AbstractHandlerMapping#getCorsHandlerExecutionChain。


对于用户而言,只需要进行 CORS 配置就可以了,而配置分为全局配置和局部配置,Spring 会把这两个配置进行合并。对于全局配置而言有 API 和 XML 两种配置方式。


XML 配置


XML 配置是 Spring 早期提供的支持,和上述 CorsFilter 等价的 CORS 配置如下。


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <mvc:cors>
        <mvc:mapping path="/*"
                     allowed-origins="http://hukp.com"
                     allowed-methods="POST"
                     allowed-headers="token"
                     exposed-headers="header1, header2"
                     max-age="3600"
                     allow-credentials="true"
        />
    </mvc:cors>
</beans>


API 配置


当前注解已经成为 Spring 的主流使用方式,使用 @EnableWebMvc 开启 Web 相关特性后可以通过实现接口 WebMvcConfiger 进行跨域配置,最终这个配置将传递到 AbstractHandlerMapping。和 XML 等价的 API 配置方式如下。


@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/*")
                .allowedOrigins("http://hukp.cn")
                .allowedMethods("POST")
                .allowedHeaders("token")
                .exposedHeaders("header1", "header2")
                .maxAge(3600)
                .allowCredentials(true);
    }
}


局部 CORS 配置


除了全局配置,Spring 还可以针对每个处理器做特殊的配置。


API 配置


如果想用一个处理器类处理一个请求,这个处理器类可以实现接口 HttpRequestHandler、Controller 或者 HandlerFunction,如果想要为这个处理器进行 CORS 处理,还需要实现接口 CorsConfigurationSource。以登录场景为例,示例代码如下。


public class LoginHandler implements Controller, CorsConfigurationSource {
    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 省略相关逻辑
        return new ModelAndView();
    }
    // 获取 CORS 配置
    @Override
    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("http://hukp.cn");
        corsConfiguration.addAllowedMethod(HttpMethod.POST);
        corsConfiguration.addAllowedHeader("token");
        corsConfiguration.setExposedHeaders(Arrays.asList("header1", "header2"));
        corsConfiguration.setMaxAge(3600L);
        corsConfiguration.setAllowCredentials(true);
        return corsConfiguration;
    }
}


注解配置


Spring 添加对注解的支持后,我们使用 @Controller 标注控制器类,然后在类中使用 @RequestMapping 标注处理器方法。针对这种方式,由于请求由方法进行处理,我们没办法实现 CorsConfigurationSource 做跨域配置,但是 Spring 也提供了对应的解决方案。


我们可以使用 @CrossOrigin 注解做跨域配置,可以把这个类加在控制器类或者控制器方法上,控制器类上的 @CrossOrigin 适用于所有的控制器方法,控制器方法上的 @CrossOrigin 适用于自身,如果类和方法上都有 @CrossOrigin 注解,Spring 则会将配置合并。


示例代码如下。


@Controller
@CrossOrigin(origins = "http://hukp.cn",
        allowedHeaders = "token")
public class LoginController {
    @CrossOrigin(methods = RequestMethod.POST,
            exposedHeaders = {"header1", "header2"},
            maxAge = 3600L,
            allowCredentials = "true")
    @PostMapping("/login")
    public ModelAndView login(HttpServletRequest request) {
        // 省略业务逻辑
        return new ModelAndView();
    }
}


当请求 /login 到达时,将使用类和方法上合并后的 CORS 配置。


Spring Security CORS 处理

Spring Boot 环境下如果引入了 Spring Security,Spring 将自动配置 CorsFilter,此时从CorsConfigurationSource 类型的 bean 中读取 CORS 配置,因此将 CorsConfigurationSource 配置为 bean 即可。示例代码如下。


@Configuration
public class WebMvcConfig {
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("http://hukp.cn");
        corsConfiguration.addAllowedMethod(HttpMethod.POST);
        corsConfiguration.addAllowedHeader("token");
        corsConfiguration.setExposedHeaders(Arrays.asList("header1", "header2"));
        corsConfiguration.setMaxAge(3600L);
        corsConfiguration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/*", corsConfiguration);
        return corsConfigurationSource;
    }
}


总结

这篇主要对 CORS 规范进行了简单的介绍,并全面的介绍了 Spring MVC 中进行 CORS 配置的几种方式,希望大家在学习技术的时候也尝试了解其背后的内容,做到知其然,也要知其所以然。如果有问题,欢迎留言。

目录
相关文章
|
2月前
|
存储 缓存 安全
oss跨域资源共享(CORS Configuration)
oss跨域资源共享(CORS Configuration)
55 4
|
3月前
|
前端开发 API 数据安全/隐私保护
Web前端开发中的跨域资源共享(CORS)解决方案
【2月更文挑战第5天】在Web前端开发中,跨域资源共享(CORS)是一个常见的挑战。本文将探讨CORS的概念和原理,并介绍一些常用的解决方案,包括服务器端配置和前端处理方法,帮助开发者更好地应对跨域请求问题。
117 4
|
3月前
|
前端开发 开发者
前端开发中的跨域资源共享(CORS)解决方案探讨
【2月更文挑战第2天】跨域资源共享(CORS)是前端开发中常见的问题,本文将深入探讨CORS的原理及解决方案,包括简单请求、预检请求以及常用的CORS解决方案,为前端开发者提供深入的理解和应对CORS问题的有效方法。
39 1
|
3月前
|
Java
springboot+cors跨域处理
springboot+cors跨域处理
26 0
|
3月前
|
前端开发 JavaScript API
探索前端开发中的跨域资源共享(CORS)
【2月更文挑战第3天】在前端开发中,跨域资源共享(CORS)是一个至关重要的话题。本文将深入探讨CORS的概念、工作原理以及如何在前端项目中正确配置和处理跨域请求,帮助开发者更好地理解和应用CORS技术。
29 7
|
3月前
|
前端开发 安全 JavaScript
前端开发中的跨域资源共享(CORS)机制
【2月更文挑战第3天】 在前端开发中,跨域资源共享(CORS)机制是一个重要的安全性问题。本文将介绍CORS的概念、原理和实现方式,并探讨在前端开发中如何处理跨域资源请求,以及如何提高网站的安全性。
|
2天前
|
缓存 前端开发 安全
Python web框架fastapi中间件的使用,CORS跨域详解
Python web框架fastapi中间件的使用,CORS跨域详解
|
10天前
|
JavaScript 前端开发 安全
JavaScript中跨域资源共享(CORS):原理和解决方案
【4月更文挑战第22天】本文介绍了JavaScript中跨域资源共享(CORS)的原理和解决方案。CORS借助HTTP头部字段允许跨域请求,核心是Access-Control-Allow-Origin响应头。解决方案包括:服务器端设置响应头(如使用Express.js的cors中间件)、使用代理服务器或JSONP。现代Web开发推荐使用CORS,因为它更安全、灵活,而JSONP已逐渐被淘汰。理解并正确实施CORS能提升Web应用性能和安全性。
|
2月前
|
缓存 安全 数据安全/隐私保护
在智能媒体服务中,跨域问题可以通过设置CORS(跨源资源共享)规则来解决
在智能媒体服务中,跨域问题可以通过设置CORS(跨源资源共享)规则来解决
19 4
|
2月前
|
JavaScript 安全 前端开发
js开发:请解释什么是跨域请求(CORS),以及如何解决跨域问题。
CORS是一种W3C标准,用于解决浏览器同源策略导致的跨域数据访问限制。它通过服务器在HTTP响应头添加标志允许特定源进行跨域请求。简单请求无需预检,而预检请求(OPTIONS)用于询问服务器是否接受非简单请求。服务器端配置响应头如`Access-Control-Allow-Origin`等实现CORS策略,客户端JavaScript则正常发起请求。若配置不当,浏览器将阻止跨域访问,保障安全。
24 2