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

从 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 处理的方式有所不同。

简单请求

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

  1. 请求方法为 GETHEADPOST 之一。
  2. 允许设置的请求头包括 AcceptAccept-LanguageContent-LanguageContent-Type
  3. Content-Type 的值仅限于 text/plainmultipart/form-dataapplication/x-www-form-urlencoded

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

非简单请求

对于非简单请求,浏览器首先会发起预请求 Preflight Request 检查是否允许跨域,如果允许跨域才会执行真正的请求,流程如下所示。
在这里插入图片描述
预请求的 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 响应码。

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

携带用户身份的跨域请求

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

  • Access-Control-Max-Age:表示跨域检查结果在浏览器中可以缓存的秒数。
  • Access-Control-Expose-Headers:默认情况 JS 只能获取一些基本的响应头,这个字段允许 JS 可以获取除基本响应头的其他响应头。

除此之外,服务端还可以返回值为 true 的响应头 Access-Control-Allow-Credentials,这个响应头可以让浏览器在跨域请求时携带 Cookie 信息,当然了,需要在发起请求时配置 withCredentials=true
在这里插入图片描述如果服务端返回了响应头 Access-Control-Allow-Credentials,此时 Access-Control-Allow-Origin 不能返回 *,否则请求将会失败。

Spring MVC CORS 处理

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

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

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 配置

如果想用一个处理器类处理一个请求,这个处理器类可以实现接口 HttpRequestHandlerController 或者 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 配置的几种方式,希望大家在学习技术的时候也尝试了解其背后的内容,做到知其然,也要知其所以然。如果有问题,欢迎留言。

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鹏cool

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值