文章目录
一、跨域介绍
1、什么是跨域
- 同源策略(Sameoriginpolicy)是浏览器最核心也最基本的安全功能
- 一个页面发起的ajax请求,只能是与当前页域名相同的路径,这能有效的阻止跨站攻击
- 所谓同源(即指在同一个域)就是两个页面具有相同的
协议(protocol),主机(host)和端口号
2、为什么会产生跨域
- 前后端分离模式下,客户端请求前端服务器获取视图资源
- 客户端向后端服务器获取数据资源
前端
服务器的协议,IP和端口和后端
服务器不一样
,就产生了跨域
二、简单请求和非简单请求
CORS
是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)- 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而
克服了AJAX只能同源使用的限制
- 浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)
1、简单请求
只要同时满足以下两大条件,就属于简单请求
- 请求方法是以下三种方法之一
- HEAD
- GET
- POST
- HTTP的头信息不超出以下几种字段
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限制三个值
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
基本流程
- 对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个
Origin
字段
举例
- 浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个
Origin
字段 - Origin字段用来说明,本次请求来自哪个源(
协议 + 域名 + 端口
)。服务器根据这个值,决定是否同意这次请求
shell
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
- 如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应
- 浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文)
- 从而抛出一个错误,但是这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200
- 如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段
shell
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-
开头
- Access-Control-Allow-Origin
- 该字段是必须的
- 要么是请求时Origin字段的值
- 要么是一个*,表示接受任意域名的请求
- Access-Control-Allow-Credentials
- 该字段可选
- 它的值是一个布尔值,表示是否允许发送Cookie
- 默认情况下,Cookie不包括在CORS请求之中
- 设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器
- 注意:如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名
- Access-Control-Expose-Headers
- 该字段可选
- CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
- 如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定
- 上面的例子指定,可以返回FooBar字段的值
2、非简单请求
2.1、预检请求
- 非简单请求是那种对服务器有特殊要求的请求
- 比如请求方法是PUT或DELETE
- 或者Content-Type字段的类型是
application/json
- 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为
"预检"请求
- 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段
- 只有得到肯定答复,浏览器才会发出正式的请求,否则就报错
举例
- HTTP请求的方法是
PUT
,并且发送一个自定义头信息X-Custom-Header
- 下面是这个"预检"请求的HTTP头信息
bash
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
- "预检"请求用的请求方法是
OPTIONS
,表示这个请求是用来询问的。头信息里面,关键字段是Origin
,表示请求来自哪个源 - Access-Control-Request-Method
- 该字段是必须的
- 用来列出浏览器的CORS请求会用到哪些
HTTP方法
- Access-Control-Request-Headers
- 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的
头信息字段
- 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的
2.2、预检请求的回应
服务器收到"预检"请求以后,检查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,确认允许跨源请求,就可以做出回应
bash
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
- 关键的是Access-Control-Allow-Origin字段
- 表示http://api.bob.com可以请求数据
- 该字段也可以设为星号,表示同意任意跨源请求
- Access-Control-Allow-Methods
- 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法
- 注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求
- Access-Control-Allow-Credentials
- 该字段与简单请求时的含义相同
- Access-Control-Max-Age
- 该字段可选,用来指定本次预检请求的
有效期
,单位为秒 - 上面结果中,有效期是20天(1728000秒)
- 在此期间,不用发出另一条预检请求
- 该字段可选,用来指定本次预检请求的
- 如果服务器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段
- 浏览器就会认定,服务器不同意预检请求
- 控制台会打印出如下的报错信息
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
2.3、浏览器的正常请求和回应
- 一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个
Origin
头信息字段 - 服务器的回应,也都会有一个
Access-Control-Allow-Origin
头信息字段
"预检"请求之后,浏览器的正常CORS请求
- 头信息的Origin字段是浏览器自动添加的
bash
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
服务器正常的回应
- Access-Control-Allow-Origin字段是每次回应都必定包含的
bash
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
三、@CrossOrigin注解
1、@CrossOrigin源码
- @CrossOrigin可以用在被
RequestMapping修饰的方法
和Controller类上
- 添加此注解即可实现跨域请求
java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {
@AliasFor("origins")
String[] value() default {};
@AliasFor("value")
String[] origins() default {};
/**
* @since 5.3
*/
String[] originPatterns() default {};
String[] allowedHeaders() default {};
String[] exposedHeaders() default {};
RequestMethod[] methods() default {};
String allowCredentials() default "";
long maxAge() default -1;
}
@CrossOrigin属性介绍
- origins和value
- 支持的源,origins和value都是相同的配置,互为别名,
默认配置是"*"
- 表示服务器支持所有源的跨域请求,安全信息较低
- 最好根据实际情况设置对应的信息(协议 + 域名 + 端口)
- 支持的源,origins和value都是相同的配置,互为别名,
- originPatterns
- 同样表示支持的源,Spring 5.3 引入的属性,默认为空
- 与origins二选一,该字段为list,也就是可以配置多个
- allowedHeaders
- 允许跨域的请求头信息,
默认为"*"
表示允许所有的请求头 - CORS默认支持的请求头为:Cache-Control、Content-Language、Expires、Last-Modified、Pragma
- 如果你需要携带其他的请求头需要设置该属性
- 允许跨域的请求头信息,
- exposedHeaders
- 服务器允许客户端访问的相应头,默认为空
- 表示只允许访问:Cache-Control、Content-Language、Expires、Last-Modified、Pragm
- 如果需要客户端访问其他的相应头需要设置该属性
- methods
- 服务器允许的Http Request类型,默认是允许GET、POST、HEAD
- allowCredentials
- 浏览器是否需要把凭证(如:cookies、CSRF tokens)发送到服务器,默认是关闭的
- 该选项开启后会与配置的源建立高度信任的关系,并且还会暴露一些敏感信息,所以
开启该选项时origin不允许设置为"*"
- maxAge
- "预检"结果的缓存时间,单位是秒
- 默认
1800s
,在缓存时间内同一请求不需要"预检"请求
@CrossOrigin注解比较适用于较细粒度的跨域控制
2、CorsRegistry方式
java
@Configuration
//创建WebConfig类实现WebMvcConfigurer接口,通过CorsRegistry设置跨域信息
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
}
}
3、CorsFilter过滤器
在跨域过滤器里配置一下跨域头部,* 是通配符即允许所有
java
@Configuration
public class GatewayCorsConfiguation {
@Bean
public CorsFilter corsFilter(){
// 初始化cors配置对象
CorsConfiguration configuration = new CorsConfiguration();
// 允许使用cookie,但是使用cookie是addAllowedOrigin必须是具体的地址,不能是*
configuration.setAllowCredentials(true);
// configuration.addAllowedOrigin("*");
configuration.addAllowedOrigin("http://manage.leyou.com");
//允许的请求方式,get,put,post,delete
configuration.addAllowedMethod("*");
//允许的头信息
configuration.addAllowedHeader("*");
//初始化cors的源对象配置
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",configuration);
//返回新的CorsFilter.
return new CorsFilter(corsConfigurationSource);
}
}
4、自定义过滤器
java
@WebFilter("/*")
public class CrosFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request =(HttpServletRequest) servletRequest;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With");
// 非预检请求,放行即可,预检请求,则到此结束,不需要放行
if(!request.getMethod().equalsIgnoreCase("OPTIONS")){
filterChain.doFilter(servletRequest, servletResponse);
}
}
}