跨域资源共享 CORS 简介及 SpringBoot 代码实现

概述

为了避免 XSS、CSRF 等攻击,浏览器使用同源策略对跨域 HTTP 请求进行限制。对于前后端分离的项目,如果前端项目与后端项目部署在两个不同的域下,那么前端访问后端时会产生跨域问题。

跨域资源共享(Cross-origin Resource Sharing,CORS)

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS

http://www.ruanyifeng.com/blog/2016/04/cors.html

跨域资源共享是一个 W3C 标准,它基于 HTTP 标头机制,新增了一组 HTTP 标头字段。CORS 需要浏览器和服务器同时支持,它允许浏览器向跨域服务器发送请求,并由服务器决定哪些源站有权限跨域访问资源。因此,实现 CORS 通信的关键是服务器

目前,所有主流浏览器(IE10及以上)使用 XMLHttpRequest 对象都可支持该功能。CORS 通信过程由浏览器自动完成,浏览器一旦发现 AJAX 请求跨源,会自动在头信息中增加 Origin 字段,说明本次请求来自哪个源(协议+域名+端口)。

浏览器将 CORS 请求分成两类:简单请求和需预检的请求。

简单请求

假如站点 https://foo.example 的网页应用想要访问 https://bar.other 的资源

浏览器一旦发现 AJAX 请求跨域,就会自动在请求头信息中增加 Origin 字段

GET /cors HTTP/1.1
...
Origin: https://foo.example

服务端返回

Access-Control-Allow-Origin: *

表明该资源可以被任意外源访问。

使用 Origin 和 Access-Control-Allow-Origin 就能完成最简单的访问控制。

如果 https://bar.other 的资源持有者想限制他的资源只能通过 https://foo.example 来访问,服务端必须明确 Access-Control-Allow-Origin 的值,而不能使用通配符*

Access-Control-Allow-Origin: https://foo.example

简单请求需要满足以下两个条件:

  • 请求方法是以下三种方法之一:HEAD、GET、POST。

  • HTTP的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain。

img

需预检的请求

需预检的请求必须首先使用 OPTIONS 方法发起一个**预检请求(preflight request)**到服务器,获知服务器是否允许该实际请求。

OPTIONS /doc HTTP/1.1
...
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

实际的 POST 请求不会携带 Access-Control-Request-* 标头,它们仅用于 OPTIONS 请求。

标头字段 Access-Control-Request-Method告知服务器,实际请求将使用 POST 方法。

标头字段 Access-Control-Request-Headers 告知服务器,实际请求将携带两个自定义请求标头字段:X-PINGOTHERContent-Type

服务器据此决定,该实际请求是否被允许。

服务端返回

Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

服务器响应中,标头字段 Access-Control-Allow-Origin: https://foo.example,限制请求的源。

标头字段 Access-Control-Allow-Methods 表明服务器允许客户端使用 POSTGET 方法发起请求。

标头字段 Access-Control-Allow-Headers 表明服务器允许请求中携带字段 X-PINGOTHERContent-Type

标头字段 Access-Control-Max-Age 给定了该预检请求可供缓存的时间长短,单位为秒,默认值是 5 秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。以上例子中,该响应的有效时间为 86400 秒,也就是 24 小时。注意,浏览器自身维护了一个最大有效时间,如果该标头字段的值超过了最大有效时间,将不会生效。

预检请求完成之后,发送实际请求。

当 CORS 请求需要携带 cookie 时,需要服务端配置 Access-Control-Allow-Credentials 头信息,前端也需要设置 withCredentials。

Origin: https://foo.example
Cookie: pageAccess=2

服务端返回

Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true

如果服务器端的响应中未携带 Access-Control-Allow-Credentials: true,浏览器将不会把响应内容返回给请求的发送者。

Springboot 实现 CORS

https://zhuanlan.zhihu.com/p/424835912

Springboot 处理客户端请求的流程如图所示:

请求从浏览器发出,最先通过过滤器链,然后通过Dispatcher Servlet,然后通过拦截器,最后才到达处理请求的控制器上。我们只要让 Springboot 在收到 OPTIONS 试探请求时,过滤器或者在拦截器上给浏览器返回它需要的那几个header信息就可以了。

img

方案一:在控制器上添加 @CrossOrigin 注解让 CorsInterceptor 拦截器处理

细粒度控制

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
// 写在类上对类中所有方法有效
@CrossOrigin(origins = "允许跨域的域名", allowCredentials = "true", allowedHeaders = "*", maxAge = 1800)
public class TestController {
    // 写在方法上对当前方法有效    
    @CrossOrigin(origins = "http://localhost:8081", allowCredentials = "true", allowedHeaders = "*")
    @RequestMapping(value = "/api/test", method = RequestMethod.POST)
    public ResponseEntity test() {
        return ResponseEntity.ok("test");
    }
}

方案二:在 MvcConfigurer 上设置一次全局生效

使用此方法配置之后再使用自定义拦截器时跨域相关配置就会失效。

原因是请求到来时会先进入拦截器中,而不是进入 Mapping 映射中,所以返回的头信息中并没有配置的跨域信息。浏览器就会报跨域异常。

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")				// 允许跨域请求的路径,"/**" 表示全部
                .allowedOrigins("*")			// 允许跨域请求的来源,"*" 表示所有域名来源
                .allowedHeaders("*")			// 允许跨域请求可携带的 header,"*" 表示所有 header。CORS 请求时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在 Access-Control-Expose-Headers 里面指定
                .allowedMethods("*")			// 允许跨域请求的方法,"*" 表示所有
                .allowCredentials(true)			// 是否允许发送 cookie,true-允许 false-不允许,默认false
                .maxAge(86400);					// 预检间隔时间,单位为秒
    }
}
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedHeaders("*")
                .allowedMethods("*")
                .allowCredentials(true)
                .maxAge(86400);
    }
}

方案三:正确解决跨域问题的方法,使用 CorsFilter 过滤器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {
    private CorsConfiguration corsConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setMaxAge(3600L);
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig());
        return new CorsFilter(source);
    }
}

相关内容

静态网站和动态网站

静态网站:不需要服务器端处理,服务器只需要检索请求的文件并将它们交付给客户端。

动态网站:需要数据库或服务器。如果用户可以与它进行交互,那么它就是一个动态网站。

跨站脚本攻击(Cross Site Scripting,XSS)

跨站脚本攻击是代码注入的一种,恶意攻击者在 Web 页面里注入恶意 Script 代码,其他用户在浏览网页时,嵌入其中的 Script 代码会被执行,从而达到恶意攻击用户的目的。

跨站请求伪造(Cross-site Request Forgery, CSRF)

同源策略(Same Origin Policy)

https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy

统一资源标识符(Uniform Resource Identifier,URI)

统一资源定位器(Uniform Resource Locator, URL)

URL:协议 - 子域名 - 主域名 - 端口号 - 请求资源地址

同源:如果两个 URL 的协议、域名、端口号三者都相同,就称这两个URL同源。

跨源 HTTP 请求的一个例子:运行在 https://domain-a.com 的 JavaScript 代码使用 XMLHttpRequest 来发起一个到 https://domain-b.com/data.json 的请求。

出于安全性,浏览器限制脚本内发起的跨源 HTTP 请求。

例如,XMLHttpRequest 遵循同源策略,意味着使用这类 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。

其他跨域解决方案

Nginx 反向代理

使用 Nginx 反向代理实现跨域,是最简单的跨域方式。只需要修改 Nginx 的配置即可解决跨域问题,支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。

JSONP

JSONP 只支持 GET 请求。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值