1. 跨站(cross-site)、跨域(cross-origin)
以下两个域名就属于跨站,他们具有不同的二级域名pro.com
和 dev.com
。
ocs.pro.com
ocs.dev.com
以下两个域名就属于同站,具有相同的域名test.com
。
ocs.test.com
sts.test.com
2. Cookie 的 SameSite 属性
Chrome 51 开始,浏览器的 Cookie 新增加了一个SameSite属性,用来防止 CSRF 攻击和用户追踪。
Cookie 的 SameSite属性是用于设置Cookie的访问范围的
它可以设置三个值。
- Strict
Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。
- Lax Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
设置了Strict或Lax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。
- None
Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性 (Cookie 只能通过 HTTPS 协议发送) ,否则无效。 下面的设置无效。
Set-Cookie: widget_session=abc123; SameSite=None
下面的设置有效
Set-Cookie: widget_session=abc123; SameSite=None; Secure
3. XMLHttpRequest.withCredentials
XMLHttpRequest.withCredentials属性是一个布尔值,表示跨域请求时,用户信息(比如 Cookie 和认证的 HTTP 头信息)是否会包含在请求之中,默认为false,即向 example.com 发出跨域请求时,不会发送 example.com 设置在本机上的 Cookie(如果有的话)。
如果需要跨域 AJAX 请求发送 Cookie,需要withCredentials属性设为true。注意,同源的请求不需要设置这个属性。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://ocs.test.com/', true);
xhr.withCredentials = true;
xhr.send(null);
为了让这个属性生效,服务器必须显式返回Access-Control-Allow-Credentials这个头信息。
Access-Control-Allow-Credentials: true withCredentials属性打开的话,跨域请求不仅会发送 Cookie,还会设置远程主机指定的 Cookie。反之也成立,如果withCredentials属性没有打开,那么跨域的 AJAX 请求即使明确要求浏览器设置 Cookie,浏览器也会忽略。
注意,脚本总是遵守同源政策,无法从document.cookie或者 HTTP 回应的头信息之中,读取跨域的 Cookie,withCredentials属性不影响这一点。
4. Cookie的访问
同源(same-origin)
无限制,无论XMLHttpRequest.withCredentials
是true
还是false
,浏览器均可存储cookie,XHR请求中均会带上cookie。
顶级导航(top-level navigation),即浏览器地址栏中直接输入地址,浏览器会存储cookie,不论cookie的samesite
的值是多少。
XMLHttpRequest.withCredentials=false,cross-origin,同站(same-site)
这种场景下,cookie不会被浏览器存储。
XMLHttpRequest.withCredentials=false,cross-origin,跨站(cross-site)
这种场景下,cookie不会被浏览器存储。
XMLHttpRequest.withCredentials=true,cross-origin,跨站(cross-site)
对于使用HTTP协议的API返回的cookie,浏览器不会存储,在浏览器开发者工具,网络面板中可以看到set-cookie后有告警图标,鼠标放上后可以看到相关说明。
对于HTTPS协议的API返回的cookie,如果设置了属性:secure; samesite=none
,则浏览器会存储cookie。XHR请求也会带上目标域的cookie。
XMLHttpRequest.withCredentials=true,cross-origin,同站(same-site)
对于使用HTTPS协议的API,浏览器会存储cookie,不论samesite
的值;
对于使用HTTP协议的API,浏览器会存储samesite
的值为Lax
和Strict
的cookie;
XHR请求会带上目标域的cookie;
// Java 设置secure; samesite=none 方法
@RestController
@RequestMapping
public class TestController {
@GetMapping("/test")
public Object test (HttpServletRequest request,
HttpServletResponse response) throws Exception {
ResponseCookie cookie = ResponseCookie.from("myCookie", "myCookieValue") // key & value
.httpOnly(true) // 禁止js读取
.secure(true) // 在http下也传输
.domain("localhost")// 域名
.path("/") // path
.maxAge(Duration.ofHours(1)) // 1个小时候过期
.sameSite("None") // 大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外
.build()
;
// 设置Cookie Header 使用andHeader能设置多个,setHeader只能设置一个SET_COOKIE
response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());
return "ok";
}
}
5 总结
同源时cookie的存储与发送没有问题,顶级导航的情况可以看作是同源场景;
不同源场景,若XMLHttpRequest.withCredentials=false
,则浏览器不会存储cookie;
不同源场景,且XMLHttpRequest.withCredentials=true
,又可分为以下场景:
- same-site
对于使用HTTPS协议的API,浏览器会存储cookie,不论
samesite
的值;对于使用HTTP协议的API,浏览器会存储
samesite
的值为Lax
和Strict
的cookie;XHR请求会带上目标域的cookie;
- cross-site
对于HTTPS协议的API返回的cookie,如果设置了属性:
secure; samesite=none
,则浏览器会存储cookie。XHR请求也会带上目标域的cookie。
跨站一定跨域。