深入解析HTTP无状态协议:Cookie与Session的工作原理及其区别
众所周知,HTTP 是无状态的协议。这表明服务器无法知晓用户的信息。所以,W3C 提出了这样的做法:给每一个用户发放一个通行证,无论谁在访问时都必须携带这个通行证,如此一来,服务器就能通过通行证来确认用户的信息。而这个通行证就是 Cookie。

Cookie 是通过检查用户身上的“通行证”来确认用户身份。Session 则是通过检查服务器上的“客户明细表”来确认用户身份。Session 相当于在服务器中建立了一份“客户明细表”。
HTTP 协议具有无状态的特性,Session 无法通过 HTTP 连接来判断是否为同一个用户。因此,服务器向用户浏览器发送了一个名为 JESSIONID 的 Cookie,其值为 Session 的 id 值。实际上,Session 是依靠 Cookie 来识别是否为同一个用户的。
所以,一般我们单系统实现登录会这样做:
我之前Demo的代码,可以参考一下:
/**
* 用户登陆
*/
@PostMapping(value = "/user/session", produces = {应用的格式为 json 且字符编码为 UTF-8[id_2002720431]
public Result login获取字符串类型的 mobileNo、字符串类型的 password、字符串类型的 inputCaptcha,以及 HttpSession 类型的 session 和 HttpServletResponse 类型的 response 。 {
//判断验证码是否正确
ifWebUtils 对输入的验证码进行验证,即验证 inputCaptcha 是否符合要求。"captcha", session)) {
//判断有没有该用户
User user 是通过 userService 的 userLogin 方法进行登录的,传入的参数为 mobileNo 和 password。
if (user != null) {
设置自动登陆,时间为一个星期。把 token 保存在数据库里。
String 类型的 loginToken 等于 WebUtils 进行 md5 加密后的结果。new获取当前日期并转换为字符串形式,然后加上会话的唯一标识(session.getId())。
设置登录令牌为 loginToken 。
User user1 是通过 userService 的 userUpload 方法上传的用户 user 。
设置 session 的属性,将某个值赋予 session 中的指定属性。具体来说,就是将指定的内容放置在 session 中,以便在后续的请求中可以通过该属性来获取相应的值。"user", user1);
CookieUtil 向 response 中添加了 Cookie。"loginToken",loginToken,604800);
return ResultUtil.success(user1);
} else {
return使用 ResultUtil 类的 error 方法,并传入 ResultEnum.LOGIN_ERROR 作为参数。
}
} else {
return使用 ResultUtil 进行错误处理,错误码为 ResultEnum.CAPTCHA_ERROR 。
}
}
/**
* 用户退出
*/
@DeleteMapping(value = "/session", produces = {"application/json;charset=UTF-8"})
public Result logout使用 HttpSession session、HttpServletRequest request 和 HttpServletResponse response 。 {
//删除session和cookie
session.removeAttribute("user");
CookieUtil 清除了请求中的 Cookie,同时也清除了响应中的 Cookie。"loginToken");
return ResultUtil.success();
}
/**
* @author ozc
* @version 1.0
*
* 拦截器;实现自动登陆功能
*/
public class UserInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
public boolean preHandle该方法接受三个参数,分别是 HttpServletRequest 请求对象、HttpServletResponse 响应对象以及 Object 对象 o 。 throws Exception {
请求的会话中获取到的属性值,将其转换为 User 类型后赋值给 User sessionUser 。 即从请求的会话中获取到一个属性,然后把这个属性转换为 User 类型并赋值给 User sessionUser 。 也就是从请求的会话里获取到一个属性,接着把该属性转为 User 类型并赋予给 User sessionUser 。"user");
// 已经登陆了,放行
if (sessionUser != null) {
return true;
} else {
//得到带过来cookie是否存在
String 类型的 loginToken 是从 request 中通过 CookieUtil 找到名为 Cookie 的值。"loginToken");
if如果登录令牌不是空白字符,则执行以下操作:
//到数据库查询有没有该Cookie
User user 是通过 userService 依据 loginToken 来查找用户的结果。
if (user != null) {
获取会话对象,然后将指定属性设置到该会话中。具体来说,就是通过 request.getSession() 获取到当前的会话对象,接着使用 setAttribute 方法将指定的属性及其对应的值设置到该会话中。"user", user);
return true;
} else {
//没有该Cookie与之对应的用户(Cookie不匹配)
CookieUtil 清除了请求中的 Cookie,同时也清除了响应中的 Cookie。"loginToken");
return false;
}
} else {
没有 cookie,并且也没有进行登陆。这是一个 index 请求,其目的是获取用户信息,因此可以放行。
if请求的请求 URI 包含( );请求的请求 URI 中含有( );请求的请求 URI 具备( );请求的请求 URI 涵盖( );请求的请求 URI 囊括( );请求的请求 URI 包含有( );请求的请求 URI 存有( );请求的请求 URI 具备着( );请求的请求 URI 涵盖着( );请求的请求 URI 囊括着( )。"session")) {
return true;
}
//没有cookie凭证
response 会执行重定向操作,将请求重定向到指定的位置。"/login.html");
return false;
}
}
}
}
总结一下上面代码的思路:
Cookie的作用是什么?和Session有什么区别?
不过 Cookie 和 Session 的应用场景不太一样。
Cookie 通常用于保存用户信息。比如,我们会在 Cookie 中保存已登录用户的一些基本信息,这样下次访问网站时,页面能自动帮你填写登录信息;一般的网站都有保持登录的功能,即下次访问时无需重新登录,这是因为用户登录时,我们会在 Cookie 中存放一个 Token,下次登录只需根据 Token 值查找用户(为保证安全,重新登录通常要重写 Token);登录一次网站后,访问其他页面无需重新登录。Session 的主要作用在于通过服务端来记录用户的状态。像购物车就是一个典型的场景,当你准备把商品添加到购物车时,由于 HTTP 协议是无状态的,所以系统并不清楚是哪个用户在进行操作。而服务端为特定的用户创建了特定的 Session 之后,就能够标识这个用户并且对这个用户进行跟踪了。
Cookie 的数据被保存在客户端,也就是浏览器端。同时,Session 的数据则保存在服务器端。
Cookie 被存储在客户端里。Session 则存储在服务器上。相对而言,Session 的安全性更高一些。如果有一些敏感信息,不要将其写入 Cookie 中。最好把 Cookie 信息进行加密,之后在需要使用的时候再去服务器端进行解密。
三、多系统登录的问题与解决3.1 Session不共享问题
单系统登录功能主要通过 Session 来保存用户信息以实现。我们知晓,多系统可能包含多个 Tomcat,并且 Session 依赖于当前系统的 Tomcat。因此,系统 A 的 Session 与系统 B 的 Session 是不共享的。
系统A的Session和系统B的Session是不共享的
解决系统之间Session不共享问题有一下几种方案:
我们可以将登录功能单独抽取出来,做成一个子系统。
抽取出来成为子系统
抽取出来成为子系统
SSO(登录系统)的逻辑如下:
// 登录功能(SSO单独的服务)
@Override
public TaotaoResult login用户名是 username,密码是 password。 throws Exception {
//根据用户名查询用户信息
TbUserExample example = new TbUserExample();
标准且用户名等于(username);
ListuserMapper 通过 example 进行选择操作,选择的结果存储在 list 中。
if (null == list || list.isEmpty()) {
return TaotaoResult.build(400, "用户不存在");
}
//核对密码
TbUser user = list.get(0);
if如果 DigestUtils.md5DigestAsHex(password.getBytes()) 不等于 user.getPassword() ,那么 {
return TaotaoResult.build(400, "密码错误");
}
//登录成功,把用户信息写入redis
//生成一个用户token
String 类型的 token 等于通过 UUID.randomUUID()方法生成的 toString()结果。
jedisCluster 执行设置操作,将值设置到 USER_TOKEN_KEY 加上某个特定内容的键中。":"使用 JsonUtils 将 user 对象转换为 JSON 字符串,即 JsonUtils.objectToJson(user) 。
//设置session过期时间
jedisCluster 让 USER_TOKEN_KEY 达到过期状态。":"把 SESSION_EXPIRE_TIME 当作会话过期时间来设置的令牌;
return TaotaoResult.ok(token);
}
其他子系统登录时,会向 SSO(登录系统)发起登录请求,SSO 会返回一个 token,这个 token 会被写入到 Cookie 中,下次访问时,就需要把 Cookie 带上。
public TaotaoResult login用户名是 String username,密码是 String password,
使用 HttpServletRequest 请求对象和 HttpServletResponse 响应对象。 {
//请求参数
Map param = new HashMap<>();
param.put("username", username);
param.put("password", password);
//登录处理
TaotaoResult 进行了格式化操作,格式化的对象是 stringResult,最终的结果存储在 result 中,即 TaotaoResult result = TaotaoResult.format(stringResult)。
//登录出错
if (result.getStatus() != 200) {
return result;
}
//登录成功后把取token信息,并写入cookie
获取到的结果中的数据被转换为字符串类型并赋值给了 String 类型的变量 token 。
//写入cookie
CookieUtils 会在请求和响应中设置 Cookie。它会根据给定的请求和响应对象来进行设置操作。通过这种方式,可以在客户端和服务器之间传递和存储相关信息。"TT_TOKEN", token);
//返回成功
return result;
}
总结:
到这里,其实我们会发现其实就两个变化:
3.2 Cookie跨域的问题
我们上面解决了 Session 不能共享的问题,然而实际上还存在另一个问题,那就是 Cookie 是不能跨域的。
比如说,当我们发出请求时,浏览器会自动将 google.com 的 Cookie 传递给 google 的服务器,而不会把其他网站的 Cookie 传递给 google 的服务器。
这意味着,因为域名不一样。用户向系统 A 登录后,系统 A 会返回给浏览器一个 Cookie。当用户再请求系统 B 时,这个由系统 A 返回的 Cookie 不会被带过去。
针对Cookie存在跨域问题,有几种解决方案:
服务端把 Cookie 写入客户端后,客户端会对 Cookie 进行解析操作,从中解析出 Token 。在这之后,每当进行请求时,都需要把这个解析出来的 Token 携带上。
多个域名会共享 Cookie,并且在将 Cookie 写入客户端时会设置 Cookie 的 domain。
把 Token 保存在 SessionStroage 里,这样就不会依赖 Cookie ,也就没有跨域的问题了。
到这里,我们已经可以实现单点登录了。
3.3 CAS原理
说到单点登录时,必然会见到“CAS”(Central Authentication Service)这个名词。接下来讲讲 CAS 是如何运作的。
如果已经将登录单独抽取为一个系统,那么我们还可以这样玩。现在我们拥有两个系统,一个是和另一个系统,其中一个是 SSO。
现在我们有三个系统
用户首先想要访问系统 A 受限的资源,例如购物车功能,而购物车功能需登录后才可访问。接着,系统 A 发现用户未登录,便将其重定向到 sso 认证中心,同时把自己的地址当作参数。请求的地址情况如下:
sso 认证中心察觉到用户未处于登录状态,于是把用户指引到登录页面。用户在登录页面输入用户名和密码来完成登录操作。通过这些步骤,用户与认证中心建立起了全局会话,具体表现为生成一份 Token,并将其写入 Cookie 中,保存在浏览器上。
4步过程
随后,认证中心将重定向至系统 A,并且把 Token 携带过去给到系统 A,重定向的地址为以下内容:
接着,系统 A 前往 sso 认证中心来验证这个 Token 是否正确。若该 Token 正确,那么系统 A 就会与用户建立局部会话,也就是创建 Session。到这里,系统 A 和用户已然处于登录状态了。
第五步和第六步
此时,用户希望访问系统 B 中受限的资源,例如订单功能,而订单功能需要登录后才可访问。系统 B 察觉到用户尚未登录,便将其重定向至 sso 认证中心,并把自身的地址当作参数。请求的地址情况如下:
注意,之前用户与认证中心已建立全局会话,且当时已将 Cookie 保存到浏览器上。所以,这次系统 B 重定向到认证中心时能够带上 Cookie。
认证中心凭借带过来的 Cookie 察觉到已与用户构建了全局会话。接着,认证中心将重定向回系统 B,同时把 Token 携带至系统 B 处。重定向的地址具体如下:
接着,系统 B 前往 sso 认证中心来验证这个 Token 是否准确。若该 Token 准确,那么系统 B 就会与用户构建局部会话(即创建 Session)。至此,系统 B 和用户已然处于登录状态了。
系统B的流程图
看到这里,其实SSO认证中心就类似一个中转站。
参考