跨域访问(基于Spring)

Posted by keys961 on May 14, 2018

实习的时候,遇到了这个问题,故在此整理一下跨域访问的问题。这里主要讨论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-urlencodedmultipart/form-datatext/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-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma),若要其它字段就必须在这里指定。

    下面就是响应的一个例子(截取部分)

    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-Credentialstrue,另一方面前端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)配置并返回一个新的CorsFilterBean

在任意配置类创建即可。

创建新的CorsFilterBean,需要创建CorsConfiguration类来配置,设置CORS信息,并将其注册到UrlBasedCorConfigurationSource中(即添加映射路径)。这是全局的。

2)重写WebMvcConfigurer

重写addCorsMappings(CorsRegistry registry)方法,在registry中添加CROS配置信息。这也是全局的。

3)在Controller类或者方法上添加注解@CrossOrigin

如下面的例子,允许Originhttp://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