实习的时候,遇到了这个问题,故在此整理一下跨域访问的问题。这里主要讨论CORS。
1. 关于CORS
CORS是一种访问机制,英文全称是Cross-Origin Resource Sharing,即我们常说的跨域资源共享,
它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持,现在主流浏览器都支持,IE不能低于10。
对于前端来说,CORS和AJAX代码上几乎没差别,浏览器会自动添加一些附加的头信息,所以实现CORS主要在服务器端,只要服务器实现了CORS接口就可以跨域通信。
2. 两种请求
CORS请求分为2类:简单请求和非简单请求。只要满足下面的条件,就是简单请求。两类请求,浏览器处理方式是不一样的。
-
请求方法是这3者之一:
GET
,POST
,HEAD
-
请求头不超过这些字段:
Accept
,Accept-language
,Content-languange
,Last-event-id
,Content-type
(只能为application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)
1)简单请求与响应
浏览器会增加一个Origin
字段,如以下请求,要访问api.alice.com/cors
,源域名为api.bob.com
。
1
2
3
4
5
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
...
Origin
字段用来说明本次请求来自哪个源。服务器根据这个值,决定是否同意这次请求。服务器会进行响应,但是不能通过状态码识别是否出错。
服务器响应会返回以下3个字段:
-
Access-Control-Allow-Origin
: 必需,表明允许的源域名,*
表示接受任意域名请求 -
Access-Control-Allow-Credentials
: 可选,表示是否允许CORS请求发送Cookie,默认为true
-
Access-Control-Expose-Headers
: 可选,由于XMLHttpRequest
只能拿到响应头的6个基本字段(Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
),若要其它字段就必须在这里指定。下面就是响应的一个例子(截取部分)
1 2 3 4
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
#### Cookie与认证(withCredentials
)
CORS请求默认不发送Cookie和HTTP认证消息。若要发送Cookie,一方面服务器要指定Access-Control-Allow-Credentials
为true
,另一方面前端AJAX需要打开withCredentials
属性,否则浏览器不会发送Cookie,也不会处理服务器设置Cookie的请求,
1
2
3
4
```javascript
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
```
此外,若要发送Cookie,Access-Control-Allow-Origin
不能为*
, 必须指定明确的与请求网页一致的域名。
Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传。
2)非简单请求与响应
a) 预检请求
非简单请求是有特殊要求的请求,如请求方法是PUT
,DELETE
,或者Content-Type=application/json
。
在正式通信前,会增加一次HTTP查询请求,称为预检请求(Preflight)。
预检请求先询问服务器:
- 当前网页所在域名是否在服务器许可名单中
- 可以使用那些HTTP动作和头信息字段
请求如下例子:
1
2
3
4
5
6
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
...
除了Origin
字段还有2个其它字段:
Access-Control-Request-Method
:声明该请求使用的方法Access-Control-Request-Header
: 声明请求发送的额外的头字段,如上面的X-Custom-Header
b) 预检响应
若服务端肯定了预检请求,则返回响应,包含CORS相关头信息字段,如下所示:
1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
...
关键字段如下解释:
Access-Control-Allow-Origin
: 见上文Access-Control-Allow-Method
: 必需,CROS支持所有跨域请求的方法Access-Control-Allow-Header
: 若指定了Access-Control-Request-Header
,则是必需的,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段Access-Control-Allow-Credentials
: 见上文Access-Control-Max-Age
: 可选,表明预检请求有效期,单位为秒。回应会被缓存,下次再次跨域请求时,有效期内就不需要再次发送预检请求
若否定预检请求,则也会返回HTTP正常的回应,但是没有任何CORS相关头信息字段,浏览器会报错。
c) 浏览器正常请求和响应
这部分和简单请求是一样的。
3. Spring下的实现
1)配置并返回一个新的CorsFilter
的Bean
在任意配置类创建即可。
创建新的CorsFilter
的Bean
,需要创建CorsConfiguration
类来配置,设置CORS信息,并将其注册到UrlBasedCorConfigurationSource
中(即添加映射路径)。这是全局的。
2)重写WebMvcConfigurer
重写addCorsMappings(CorsRegistry registry)
方法,在registry
中添加CROS配置信息。这也是全局的。
3)在Controller
类或者方法上添加注解@CrossOrigin
如下面的例子,允许Origin
为http://xx-domain.com
,并设置有效期为3600秒。这是局部的。
1
2
3
4
5
6
7
8
9
10
@Controller
public class XXController
{
@RequestMapping("/test")
@CrossOrigin(origins = "http://xx-domain.com", maxAge = 3600)
public String test()
{
return "test";
}
}
4) 手动设置响应头
直接在HttpServletResponse
中添加Header信息即可。如下例子,和3)是等价的。
1
2
3
4
5
6
7
8
9
10
11
@Controller
public class XXController
{
@RequestMapping("/test")
public String test(final HttpServletResponse resp)
{
resp.addHeader("Access-Control-Allow-Origin", "http://xx-domain.com");
resp.addHeader("Access-Control-Max-Age", "3600");
return "test";
}
}
**跨站伪造攻击
见这个博客,整理的很好:http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html