基于cookie, session的登录认证一般后端需要将session id保存在cookie中,这样下次请求时浏览器带上cookie,服务器才知道是同一个会话,根据session id取出session中保存的用户信息,以确定用户是否已登录。
在前后端分离模式下,前端一般通过ajax请求向服务器请求数据,但是如果前后端部署在不同的域名下,因为一些安全机制,cookie无法跨域携带,所以如果还想基于cookie, session来实现登录认证,就要做一些配置以实现cookie可以跨越携带。
第一步要解决就是跨越问题,如果没有设置允许跨越,那么我们会得到如下错误。Access to XMLHttpRequest at XXX from origin YYY has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource.
允许跨域也很简单,对于springboot项目,你可以用@CrossOrgin注解来解决,或者定义一个fiter,在filter中对HttpServletResponse做如下处理。
response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "*");
跨越解决后,就是cookie跨越携带问题,默认浏览器是不会跨域携带cookie的,也就是说,你在xxx域名下通过ajax访问了yyy域名的一个接口,服务器设置了一个cookie在yyy域名下,下次再用ajax请求yyy这个域名,cookie是带不过去的,怎么办?我们需要设置一下允许跨越携带cookie,这需要页面和服务器同时设置。
对于页面,需要在ajax请求中带上如下参数withCredentials: true。对于服务器,也需要在response中设置response.setHeader("Access-Control-Allow-Credentials", "true");
$.ajax({ url: "http://localhost:8080/test", type:"get", xhrFields: { withCredentials: true }, success:function(res){ alert("ajaxTest success"); console.log(res); //在console中查看数据 }, error:function(){ alert(ajaxTest error); }, });
设置好后,再次请求,你会发现又报了如下错误。Access to XMLHttpRequest at http://localhost:8080/test from origin http://localhost has been blocked by CORS policy: The value of the Access-Control-Allow-Origin header in the response must not be the wildcard * when the requests credentials mode is include. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
也就是说当你设置了withCredentials: true时,是不能把Access-Control-Allow-Origin设置成 "*" 的,需要设置成具体的域名,因为我这里试验的是从localhost向localhost:8080发起跨越请求,所以我改成如下配置,
经此一番设置,cookie终于可以跨越携带了。
还有坑。。。
是的没错,上面我们是成功了,但是还有一些坑。
坑1:application/json等复杂请求不能设置header为*
对于contentType: "application/json;charset=utf-8"的请求,会首先发送一次预检请求,然后再发送正式的post请求,这时,在POST请求时我们可能会有如下错误。
Access to XMLHttpRequest at http://localhost:8080/test from origin http://localhost has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
上面错误是提示我们在预检请求时发现content-type 请求头不被允许,于是我们后面的POST请求被阻止了。虽然我们设置了Access-Control-Allow-Headers=* 但是,我们必须再显式设置一下content-type这个请求头,如下。我这里图方便,设置的是"*, content-type",你可以根据自己需要设置请求头。
坑2: 一级域名不同,chome浏览器仍然无法携带cookie。
即使上面所有的坑我们都填平了,最后可能还是躲不过chrome浏览器严格的安全策略。如果你是从abc.domain.com访问def.domain.com,你不会遇到这个坑,但是如果你的两个一级域名也不相同,你就会遇到。比如从abc.domain1.com请求abc.domain2.com,这时你可能会遇到以下问题。
This Set-cookie header didnt specify a "Samesite" attribute and was defaulted to "Samesite=Lax," and was blocked because it came from a cross-site response which was not the response to a top-level navigation. The Set-Cookie had to have been set with "SameSite=None" to enable cross-site usage.
你明明设置了cookie,但是就是找不到,请求的时候也带不上,查看Set-Cookie这一行,可以发现有个黄色的叹号,鼠标放到叹号上可以看到chrome给我们的提示,因为SameSite设置的原因,chrome认为这个cookie不应该被跨越携带,所以没有设置,自然也就无法携带cookie。目前搜狗浏览器没发现这个问题,但是会在控制台打印warning提示将来可能会有此限制。
解决办法就是设置cookie的SameSite=None。但是遗憾的是,目前的javax.servlet.http.Cookie类中没有设置SameSite的api,需要自己去扩展。具体解决方式就不写了,可以自己搜索一下。