Session
工作原理
服务端存储:在传统的 Session 机制中,用户登录后,服务器会为用户创建一个会话对象,并将会话数据(如用户信息、权限等)存储在服务器内存中。
客户端携带 Session ID:服务器会将一个唯一的会话 ID(通常为
JSESSIONID
)通过 Cookie 发送给客户端,客户端在后续请求中自动携带这个Session ID
。
优点
简单易用:不需要客户端做额外的处理,只需要依赖 Cookie 或 URL 参数来传递会话 ID。
服务端控制:所有会话数据保存在服务器端,更容易进行管理(如会话过期、登出等)。
自动过期:会话可以设置超时,过期后客户端需要重新登录。
缺点
会话存储压力:随着用户量的增加,服务端需要维护大量的会话数据(可能需要负担更多内存),尤其在分布式系统中,处理会话信息会变得复杂。
不适合分布式系统:当应用部署在多台服务器上时,需要一个集中式存储(如 Redis)来共享会话信息。
图解
代码实现
@RequestMapping("/savaSessionInfo")
public String savaSessionInfo(HttpSession session, String msg) {
if (StringUtils.isBlank(msg)) {
return "msg不能为空";
}
session.setAttribute("session", msg);
return "保存Session信息成功,sessionId为:" + session.getId();
}
@RequestMapping("/getSessionInfo")
public String getSessionInfo(HttpSession session) {
return "获取的session信息为:" + session.getAttribute("session");
}
Token
工作原理
无状态认证:Token 机制(通常是基于 OAuth2 或 JWT)允许客户端在登录后获取一个 token,该 token 是由服务器签发的,包含了用户的认证信息。
客户端存储:客户端将 token 存储在本地(通常是 LocalStorage 或 Cookie 中),每次请求时将 token 作为请求头(
Authorization: Bearer <token>
)传递给服务端。服务端验证:服务端接收到请求时,从 token 中提取出用户信息(如用户 ID、权限等)并进行验证。
优点
无状态:Token 是自包含的,服务端不需要存储会话数据,验证用户身份时只需要解析 Token。
易于扩展:Token 可以用于分布式和微服务架构,不依赖于会话存储。
支持跨域认证:Token 作为 HTTP Header 传递,支持跨域请求,而 Session 通常通过 Cookie 实现,不适合跨域。
缺点
安全性问题:Token 存储在客户端,容易受到 XSS 攻击,且 token 一旦泄露,可能会被恶意使用。
没有主动失效机制:Token 通常有有效期,过期后需要重新登录,但是没有像 Session 那样可以主动注销。
图解
/**
* 保存 token信息
*
* @param msg
* @return
*/
@RequestMapping("/savaByToken")
public String savaByToken(String msg) {
String token = UUID.randomUUID().toString();
redisManager.setex(RedisConstant.REDIS_TOKEN + token, msg, CommonConstant.EXPIRES_ONE_MIN * 10);
return "保存token信息成功,token为:" + token;
}
/**
* 获取 token信息
*
* @param token
* @return
*/
@RequestMapping("/getByToken")
public String getByToken(String token) {
if (StringUtils.isBlank(token)) {
return "token不能为空";
}
return "获取token信息成功,token为:" + redisManager.get(RedisConstant.REDIS_TOKEN + token);
}
Token+Cookie
工作原理
结合了 Token 和 Cookie:在这种方式中,Token 通常作为 Cookie 中的一个字段(如
token
)存储,而不是 LocalStorage。自动携带 Token:浏览器会自动在每次请求时通过 Cookie 将 Token 携带到服务器。
服务端验证:服务端从请求的 Cookie 中提取 Token,并进行身份验证。
优点
免去手动传递 Token:由于浏览器会自动将 Cookie 中的 Token 携带到每个请求中,开发者不需要显式地在每次请求中添加 Authorization 头。
支持跨页面、跨标签页共享:由于 Cookie 是浏览器的全局状态,跨页面、跨标签页都可以访问 Token。
缺点
XSS 攻击的风险:如果 Token 存储在 Cookie 中,且没有设置
HttpOnly
和Secure
标志,可能会暴露给恶意脚本。CSRF 攻击的风险:如果 Cookie 没有设置
SameSite
属性,可能会导致跨站请求伪造(CSRF)攻击。
提高安全性
HTTP-Only Cookie:通过设置
HttpOnly
属性,阻止 JavaScript 访问 Cookie,减少 XSS 风险。SameSite Cookie:通过设置
SameSite
属性,防止 CSRF 攻击。
图解
代码实现
/**
* 保存 token信息
*
* @param msg
* @return
*/
@RequestMapping("/savaByTokenWithCookie")
public String savaByTokenWithCookie(HttpServletResponse response, String msg) {
String token = UUID.randomUUID().toString();
redisManager.setex(RedisConstant.REDIS_TOKEN + token, msg, CommonConstant.EXPIRES_ONE_MIN * 10);
Cookie cookie = new Cookie("token", token);
cookie.setMaxAge(CommonConstant.EXPIRES_ONE_MIN / 100);
response.addCookie(cookie);
return "保存token信息成功,token为:" + token;
}
/**
* 通过 Cookie获取 token
*
* @param request
* @return
*/
@RequestMapping("/getByTokenWithCookie")
public String getByTokenWithCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return "获取token信息为null";
}
String token = null;
for (Cookie cookie : cookies) {
if ("token".equals(cookie.getName())) {
token = cookie.getValue();
break;
}
}
if (StringUtils.isBlank(token)) {
return "token不能为空";
}
return "保存token信息成功,token为:" + redisManager.get(RedisConstant.REDIS_TOKEN + token);
}
JWT
工作原理
自包含的 Token:JWT 是一种自包含的 Token 格式,通常由三部分组成:
Header:声明签名算法(如
HS256
)。Payload:包含声明(如用户信息、权限等)。
Signature:对 Header 和 Payload 的加密签名,确保数据的完整性和安全性。
存储和传递:JWT 通常通过
Authorization: Bearer <token>
传递在请求头中,或者可以存储在 Cookie 中。
优点
无状态认证:JWT 是自包含的,服务端无需存储会话信息,验证身份只需要解析 Token。
支持跨域认证:由于 JWT 是通过 HTTP Header 传递的,可以方便地用于跨域请求。
灵活性高:JWT 可以携带多种自定义信息,适用于复杂的认证和授权场景。
缺点
过期机制:JWT 的有效期通常较短,过期后客户端需要重新获取(例如通过刷新 Token)。不过可以使用刷新 Token 来延续会话。
泄露风险:JWT 存储在客户端(LocalStorage 或 Cookie)时,可能会被 XSS 攻击获取。
图解
代码实现
/**
* 保存 token信息
*
* @param msg
* @return
*/
@RequestMapping("/savaByJwt")
public String savaByJwt(String msg) {
if (StringUtils.isBlank(msg)) {
return "msg不能为空";
}
String token = jwtManager.createToken(RedisConstant.JWT, msg, CommonConstant.EXPIRES_ONE_MIN * 10);
return "保存jwt的token信息成功,token为:" + token;
}
/**
* 获取 token信息
*
* @param token
* @return
*/
@RequestMapping("/getByJwt")
public String getByJwt(String token) {
if (StringUtils.isBlank(token)) {
return "token不能为空";
}
String tokenData = jwtManager.getTokenData(RedisConstant.JWT, token, String.class);
return "获取token信息成功,token为:" + tokenData;
}
比较总结
总结
Session:适用于传统的单体应用,所有会话信息存储在服务器,较为简单但不适合分布式架构。
Token:适合微服务架构和分布式系统,客户端存储 Token,无状态认证。
Token + Cookie:结合 Token 的无状态认证和 Cookie 的自动携带,方便开发,但需要注意安全问题。
JWT:是一种特殊的 Token 格式,具有自包含性,适合复杂认证和跨域场景,但要注意安全性和过期机制。