基础知识
1. 同源问题
浏览器安全解决方案,对于服务端来说就是限制跨域请求本服务器资源
2.CORS 跨域资源共享
跨域访问资源解决方案,浏览器与服务器遵循一定规则,实现跨域资源访问与共享
a. 资源跨域访问
b. Cookie 共享
3.相关概念
origin:
请求头,用于标识请求来源。跨域请求和post请求会携带origin请求头;获取不到为null
简单请求:
Cors规范定义简单请求的原则是:请求不是以更新(添加、修改和删除)资源为目的,服务端对请求的处理不会导致自身维护资源的改变。对于简单跨域资源请求来说,浏览器将两个步骤(取得授权和获取资源)合二为一,由于不涉及到资源的改变,所以不会带来任何副作用。
对于一个请求,必须同时符合如下要求才被划为简单请求:
Http Method只能为其一:
GET
POST
HEAD
请求头只能在如下范围:
Accept
Accept-Language
Content-Language
Content-Type,其中它的值必须如下其一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
除此之外的请求都为非简单请求(也可称为复杂请求)。非简单请求可能对服务端资源改变,因此Cors规定浏览器在发出此类请求之前必须有一个“预检(Preflight)”机制,这也就是我们熟悉的OPTIONS请求。
请求预检:
顾名思义,它表示在浏览器发出真正请求之前,先发送一个预检请求,这个在Http里就是OPTIONS请求方式。这个请求很特殊,它不包含主体(无请求参数、请求体等),主要就是将一些凭证、授权相关的辅助信息放在请求头里交给服务器去做决策。因此它除了携带Origin请求头外,还会额外携带如下两个请求头:
Access-Control-Request-Method:真正请求的方法
Access-Control-Request-Headers:真正请求的自定义请求头(若没有自定义的就是空呗)
服务端在接收到此类请求后,就可以根据其值做逻辑决策啦。如果允许预检请求通过,返回个200即可,否则返回400或者403呗。
如果预检成功,在响应里应该包含上文提到的响应头Access-Control-Allow-Origin和Access-Control-Expose-Headers,除此之外,服务端还可以做更精细化的控制,这些精细化控制的响应头为:
Access-Control-Allow-Methods:允许实际请求的Http方法(们)
Access-Control-Allow-Headers:允许实际请求的请求头(们)
Access-Control-Max-Age:允许浏览器缓存此结果多久,单位:秒。有了缓存,以后就不用每次请求都发送预检请求啦
说明:以上响应头并不是必须的。若没有此响应头,代表接受所有
Cookie的生命周期:
缺省情况下,Cookie的生命周期是Session级别(会话级别)。若想用Cookie进行状态保存、资源共享,服务端一般都会给其设置一个过期时间maxAge,短则1小时、1天,长则1星期、1个月甚至永久,这就是Cookie的生命(周期)。
Cookie的存储形式,根据其生命周期的不同而不同。这由maxAge属性决定,共有这三种情况:
maxAge > 0:cookie不仅内存里有,还会持久化到硬盘,也叫持久Cookie。这样的话即使你关机重启(甚至过几天再访问),这个cookie依旧存在,请求时依旧会携带
maxAge < 0:一般值为-1,也就临时Cookie。该Cookie只在内存中有(如session级别),一旦关闭浏览器此Cookie将不复存在。值得注意的是:若使用无痕模式访问也是不会携带此Cookie的哟
maxAge = 0:内存中没有,硬盘中也没有了,也就立即删除Cookie。此种case存在的唯一目的:服务浏览器可能的已存在的cookie,让其立马失效(消失)
Cookie的域和路径
Cookie是不可以跨域的,隐私安全机制禁止网站非法获取其他网站(域)的Cookie。概念上咱不用长篇大论,举个例子你应该就懂了:
domain:创建此cookie的服务器主机名(or域名),服务端设置。但是不能将其设置为服务器所属域之外的域(若这都允许的话,你把Cookie的域都设置为baidu.com,那百度每次请求岂不要“累死”)
注:端口和域无关,也就是说Cookie的域是不包括端口的
path:域下的哪些目录可以访问此cookie,默认为/,表示所有目录均可访问此cookie
3. CORS解决方案
1. 简单请求
response 响应头添加: Access-Control-Allow-Origin:* 或请求源地址
2. 非简单请求
浏览器发送预检 option 请求,服务器在响应头中添加
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doOptions(req, resp); resp.setHeader("Access-Control-Allow-Origin","http://localhost:63342"); resp.setHeader("Access-Control-Expose-Headers","token,secret"); // 如果请求中需要包含标准请求头以外的请求头,需要进行配置 resp.setHeader("Access-Control-Allow-Headers","token,secret"); // 一般来讲,让此头的值是上面那个的【子集】(或相同) }
*注意*: 跨域响应头在预检请求和真实请求中都需要添加
3. cookie 共享
cookie的共享在于同一根域下的共享
三个关键词:跨域、Cookie、共享。Cookie是数据载体,跨域是场景,共享是需求
说明:Cookie实现跨域共享要求根域必须是一样才行,比如都是www.baidu.com和map.baidu.com的根域都是 baidu.com。这道理就相当于只有加入了银联的银行才能用银行卡去任意一家银联成员行取钱一样
跨域Cookie共享的关键点
这里要讨论的是跨域中Cookie的存储问题:默认情况下,浏览器是不会去为你保存下跨域请求响应的Cookie的。具体现象是:跨域请求的Response响应了即使有Set-Cookie响应头(且有值),浏览器收到后也是不会保存此cookie的。
要实现Cookie的跨域共享,有3个关键点:
服务端负责在响应中将Set-Cookie发出来(由Access-Control-Allow-Credentials响应头决定)
浏览器端只要响应里有Set-Cookie头,就将此Cookie存储(由异步对象的withCredentials属性决定)
浏览器端发现只要有Cookie,即使是跨域请求也将其带着(由异步对象的withCredentials属性决定)
为了满足这三个关键点,在实施层面就有三要素来指导我们开发来解决此类问题。
共享Cookie的三个要素
1. 跨域情况下服务器设置了set-cookie也不会生效,需要添加响应头:
response.setHeader("Access-Control-Allow-Credentials", "true");
2.跨域情况下,服务器成功设置cookie,浏览器默认也不会保存跨域cookie
XMLHttpRequest对象开启withCredentials
$.ajax({ url: "http://localhost:8080/corscookie", type: "GET", xhrFields: { withCredentials: true }, crossDomain: true });
3. 共享cookie时,请求头 Access-Control-Allow-Origin 不能为通配符