本文根据笔者开发过程(java)中遇到的问题整理而来,旨在减少大家查询无用资料的时间,对于一些原理解释可能不是很完美
首先,先让我们来看看Cookie的一些官方文档(rfc6265):
相信你也看得一脸懵,接下来我说一下里面的关键词
5.2.3. The Domain Attribute(作用域)
5.2.5. The Secure Attribute(Secure属性)
4.1.2.7. The SameSite Attribute(SameSite属性)
操作复现:
环境:Java 11, SpringBoot2.7.0(Tomcat), Edge浏览器, Js/axios(fetch函数)
服务器端Controller层(API)代码:
@PostMapping("/api/v1/login")
public BaseResponse test(HttpSession session,@RequestBody LoginRequest request){//BaseResponse 为自定义的成功响应
//执行登录逻辑
//...
//假设现在登录成功了
log.info("登录请求:{}",request);
Object successUserDO=request;
session.setAttribute("loginUser",successUserDO);
return BaseResponse.success();
}
@GetMapping("/api/v1/detail")
public BaseResponse getLogin(HttpSession session){
//前端获取登录信息和验证登录是否失效
return BaseResponse.success(session.getAttribute("loginUser"));
}
后端Config配置允许跨域:
@Configuration
public class MVCConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
//配置允许跨域的路径
registry.addMapping("/**")
//配置允许访问的跨域资源的请求域名
.allowCredentials(true)
.allowedOriginPatterns("*")
//配置允许访问该跨域资源服务器的请求方法
.allowedMethods("*")
//配置允许请求 头部head的访问
.allowedHeaders("*");
}
}
前端调用代码:
const BaseURL="http://127.0.0.1:8080"
let data={
loginRequest:{
userName:"",
password:""
}
}
function userLogin(){//用户登录
fetch(BaseURL+"/api/v1/login",{
method:"POST",
headers:{
"Content-Type":"application/json",
},
credentials: "include",//前端跨域
mode:"cors",
body:JSON.stringify(data.loginRequest)
}).then(rep=>rep.json()).then((rep)=>{
console.log(rep)
})
}
function getUserDetail(){//获取用户详情
fetch(BaseURL+"/api/v1/detail",{
method:"GET",
headers:{
"Content-Type":"application/json",
}
credentials: "include",//前端跨域
mode:"cors",
}).then(rep=>rep.json()).then((rep)=>{
console.log(rep)
})
}
写完测试,一看:登录成功,但是请求/api/v1/detail的时候,拿到的却是空对象
登录:
获取用户信息:
这时候大概率就是前后端打架的时候了:
"我登录成功了为什么也还是拿不到"
"肯定是前端的问题"
......
我的思路和目前用的两套解决方案
总结原因:
这锅,原因有点复杂,但总的来说,是前后端分离+浏览器更新的锅
分析过程:
前置知识->后端Session:
简要来讲,java后端中的Session本质上是一个K-V键值对,也就是一个Map,key是string类型的,value是也是一
个Map,也就等价于Java中的Session看做对象的话,其实它可以写成
Map,那么,这个session的key(第一个String)是放在浏览器哪里的呢?
结合标题我们可以知道(bushi),它是放在浏览器的Cookie中的
这么做的原因如下:
流程分析以及排错:
当一个http请求后端的时候
服务器的解析流程:
key:Set-Cookie
value: JESSSION=xxxx
图片如下:
然后就给Controller使用实例化好了的Session对象
问题查找:
我们点击Login请求的Cookie
再点击Detail请求的Cookie
发现了吗?每次请求都响应了一个Session,并且没有请求cookie,
也就是说每一次请求对于后端来说都是新的请求!
解释:
因为浏览器有个默认行为:检查Set-Cookie这个响应头是否为可接受的,其中和本问题相关的最重要的的就是一个Set-Cookie的属性:SameSite
,关于SameSite,可以看看MDN上关于这部分的解释:developer.mozilla.org/zh-CN/docs/…
本文不过多说明
只需要知道,在浏览器发展中,已经将这一默认属性替换为SameSite=Lax
了,而要使cookie能跨域发送的需要的是SameSite=None
解决方案:
优点:不用去考虑关于Cookie转session的事了
缺点:要后端自己去写查找转化逻辑代码(还有考虑过期失效的问题,这部分可以用JWT或者是Redis解决),前端也需要每次手动携带(也可以说要手动封装请求)token
2.前后端分离开发,但上线时将前端打包放进后端:
优点:代码方面可以完全不用做额外的操作
缺点:开发的时候需要前端配置代理服务器,上线后可能服务器压力很大(静态资源占用带宽较高)
Tomcat
response.addHeader("Set-Cookie","JSESSIONID="+request.getSession().getId()+";SameSite=None;Secure");
Redis作为Session:
String id = session.getId();
String encode = Base64.encode(id);//Base64编码
response.setHeader("Set-Cookie", "SESSION=" + encode + ";Path=/;SameSite=None;Secure");
优点:可前后端分离式开发,相对于第一种来讲前端
缺点:后端需要有https能访问的域名