前言
http协议本身是一种无状态的协议,我们并不能知道是哪个用户发出的请求,因此产生了Web身份校验问题。cookie、session和token都是为了解决Web身份校验而产生的,初学者往往搞不清三者的概念和区别,本篇文章带大家解析这三者的区别和概念。
一、session和cookie
1.1Web发展史
1、很久以前,Web基本上就是文档的浏览而已, 既然是浏览,作为服务器, 不需要记住是谁请求的,每次请求都是一个新的HTTP协议。
2、但是随着交互式Web应用的兴起,像在线购物网站,需要登录的网站等等,需要管理会话,必须记住哪些人登录系统,哪些人往自己的购物车中放商品, 也就是说我必须把每个人区分开。
解决办法:
给大家发一个会话标识(session id), 说白了就是一个随机的字串,每个人收到的都不一样,每次大家向我发起HTTP请求的时候,把这个字符串给一并捎过来, 这样我就能区分开谁是谁了,这就是所谓的session认证。
1.2session和cookie
session :就是会话。这个就类似于你和一个人交谈,你怎么知道当前和你交谈的是张三而不是李四呢?对方肯定有某种特征(长相等)表明他就是张三。服务器就要给每个客户端分配不同的“身份标识”,然后客户端每次向服务器发请求的时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了。至于客户端怎么保存这个“身份标识”,可以有很多种方式,对于浏览器客户端,大家都默认采用 cookie 的方式。
cookie:非常具体的东西,指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能。cookie由服务器生成,发送给浏览器,浏览器把cookie以kv形式保存到某个目录下的文本文件内,下一次请求会把该cookie发送给服务器。
区别:cookie数据存放在客户的浏览器上,session数据放在服务器上。将重要信息存放在Session中,其他信息如果需要保留,可以放在cookie中。
session认证流程如下:
1、当用户首次访问服务器的时候,服务器为每个用户单独创建一个 Session 对象,并分配一个新的 SessionID,此时 SessionID 通过 cookie 保存在用户端。
2.当用户再次访问服务器的时候,携带保存 SessionID 的 Cookie 给服务器,服务器查询是否存在这个 sessionID,如果存在,即认为用户处于登录状态,如果没有对应的 SessionID,服务器会给分配一个新的 sessionID。
关于session认证代码如下:
1.如果登录成功(账号密码验证成功),往session中存放manage键值对。
public class ControllerLogin {
@Autowired
LoginService loginService;
@RequestMapping(value ="/login")
public CommonMessage test(Manage manage, HttpSession httpSession) {
try {
Manage manageTemp = loginService.login(manage);
if (manageTemp != null) {
httpSession.setAttribute("manage", manageTemp);
return new CommonMessage<Manage>(200, "登录成功", manageTemp);
} else {
return new CommonMessage(300, "登录失败",(Manage)null);
}
} catch (Exception e) {
return new CommonMessage(301, "登录失败", (Manage)null);
}
}
}
2.session认证代码部分,使用拦截器,请求到达之前判断是否处于登录状态(即有没有manage属性值)
public class LoginInterceptor implements HandlerInterceptor {
/* 当请求到达控制器之前被执行 true--继续向下执行,到达下一个拦截器,或控制器 false--不会继续向下执行*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession httpSession=request.getSession();
Manage manage = (Manage) httpSession.getAttribute("manage");
if(manage !=null){
return true;
}else{
response.getWriter().println(203);
return false;
}
}
/*控制器方法执行之后执行*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/*整个请求结束后执行*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
1.3session认证缺点
1.session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
2.用户下一次请求还必须在第一次请求的服务器上(只有第一次访问的服务器保存了Session信息),在分布式的应用上,相应的限制了负载均衡器的能力。
3.cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
二、基于token的鉴权机制
基于Token的身份验证是无状态的,我们不将用户信息存在服务器或Session中,解决了在服务端存储信息时的许多问题。
流程:
1.用户通过用户名和密码发送请求。
2.程序验证,并返回一个签名的token 给客户端。
3.客户端储存token,并且每次用于每次发送请求。
4.服务端验证token并返回数据。
token由三部分组成:
1.第一部分头部(header):声明加密算法(HMAC -HS256)
2.第二部分我们称其为载荷(payload ):保存用户的信息
3.第三部分是签名(signature):需要base64转码后的header和base64转码后的payload连接组成的字符串,然后通过header中声明的加密方式进行加密。
token令牌如何让别人伪造不了?
1.用HMAC-SHA256算法,加上密钥,对数据做一个签名,把这个签名和数据一起作为token, 由于密钥别人不知道,就无法伪造token了。
2.当客户端把这个token发过来的时候,再用同样的HMAC-SHA256算法和同样的密钥,对数据再计算一次签名, 和token中的签名做个比较, 如果相同,就知道客户端已经登录过了,如果不相同,数据部分肯定被人篡改过,则告诉客户端没有认证。
token认证优点:
1.无状态、可扩展:服务器只是生成token , 然后验证token,基于这种无状态和不存储Session信息,负载负载均衡器能够将用户信息从一个服务传到其他服务器上。
2.安全:作为身份认证token安全性比session好,因为每个请求都有签名。
token认证的代码如下:
1.token工具类,用来创建生成token、验证token有效性和获得token 中playload部分数据。
public class TokenUtil {
/* 创建生成token的方法*/
public static String token (Integer id, String account,Integer type){
String token = "";
try {
//过期时间 为1970.1.1 0:0:0 至 过期时间 当前的毫秒值 + 有效时间
Date expireDate = new Date(new Date().getTime() + 10000*1000);
//秘钥及加密算法
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
//设置头部信息
Map<String,Object> header = new HashMap<>();
header.put("typ","JWT");
header.put("alg","HS256");
//携带id,账号信息,生成签名
token = JWT.create()
.withHeader(header)
.withClaim("id",id)
.withClaim("account",account)
.withClaim("type",type)
.withExpiresAt(expireDate)
.sign(algorithm);
}catch (Exception e){
e.printStackTrace();
return null;
}
return token;
}
/*验证token是否有效*/
public static boolean verify(String token){
try {
//验签
Algorithm algorithm = Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE");
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e) {//当传过来的token如果有问题,抛出异常
return false;
}
}
/* 获得token 中playload部分数据*/
public static DecodedJWT getTokenInfo(String token){
return JWT.require(Algorithm.HMAC256("ZCEQIUBFKSJBFJH2020BQWE")).build().verify(token);
}
}
2.拦截器,每次请求到达之前判断token是否有效。
public class LoginInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String toke =request.getHeader("token");
boolean flag=TokenUtil.verify(toke);
if(flag){
return true;
}else{
response.getWriter().println(401);
return false;
}
}
}
3.登录控制器,如果登录成功,则把id,账号名等需要的信息存储在token中,然后放在实体manage中,前端可以直接获取到token然后存储在浏览器中,下次发请求之前把token也一起发送过来即可。
@RestController
@RequestMapping("/api/login")
public class BackLoginController {
@Autowired
AdminService adminService;
@RequestMapping("/login")
public CommonResult login(@RequestBody Manage manage){
CommonResult commonResult=null;
try {
Manage manage1= adminService.login(manage);
if (manage1!=null){
String token= TokenUtil.token(manage1.getId(),manage1.getAccount(),manage1.getType());
manage1.setToken(token);
commonResult=new CommonResult(200,"登录成功",manage1);
return commonResult;
}else {
commonResult=new CommonResult(201,"账号或密码错误",null);
return commonResult;
}
} catch (Exception e) {
e.printStackTrace();
commonResult=new CommonResult(500,"服务器忙",null);
return commonResult;
}
}
}
总结
理解cookie、session和token的关键在于它们三者都是为了解决web身份验证而诞生的。session保存在服务器端,cookie和token保存在客户端,从这个方面入手可以联想出很多区分点。建议不要死记硬背这三者的概念和区别,要从认证流程出发思考它们之间的关系。