前后端分离使用shiro登录认证cookie跨域问题踩坑

基于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,需要自己去扩展。具体解决方式就不写了,可以自己搜索一下。

猜你喜欢

转载自blog.csdn.net/qq_41221596/article/details/131628712