1、Token的引入: Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。 2、Token的定义: Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。 3、使用Token的目的: Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。 4.Token 的优点: 扩展性更强,也更安全点,非常适合用在 Web 应用或者移动应用上。Token 的中文有人翻译成 “令牌”,我觉得挺好,意思就是,你拿着这个令牌,才能过一些关卡。 5.Token一般用在三个地方: ①防止表单重复提交 ②anti csrf攻击(跨站点请求伪造) ③身份验证(单点登录) 了解了Token的意义后,我们就更明确的知道为什么要用他了。
这是本文的重点,在这里我就介绍常用的两种方式。 1、用设备号/设备mac地址作为Token(推荐) 客户端:客户端在登录的时候获取设备的设备号/mac地址,并将其作为参数传递到服务端。 服务端:服务端接收到该参数后,便用一个变量来接收同时将其作为Token保存在数据库,并将该Token设置到session中,客户端每次请求的时候都要统一拦截,并将客户端传递的token和服务器端session中的token进行对比,如果相同则放行,不同则拒绝。 分析:此刻客户端和服务器端就统一了一个唯一的标识Token,而且保证了每一个设备拥有了一个唯一的会话。该方法的缺点是客户端需要带设备号/mac地址作为参数传递,而且服务器端还需要保存;优点是客户端不需重新登录,只要登录一次以后一直可以使用,至于超时的问题是有服务器这边来处理,如何处理?若服务器的Token超时后,服务器只需将客户端传递的Token向数据库中查询,同时并赋值给变量Token,如此,Token的超时又重新计时。 2、用session值作为Token 客户端:客户端只需携带用户名和密码登陆即可。 客户端:客户端接收到用户名和密码后并判断,如果正确了就将本地获取sessionID作为Token返回给客户端,客户端以后只需带上请求数据即可。 分析:这种方式使用的好处是方便,不用存储数据,但是缺点就是当session过期后,客户端必须重新登录才能进行访问数据。 三、使用过程中出现的问题以及解决方案? 刚才我们轻松介绍了Token的两种使用方式,但是在使用过程中我们还出现各种问题,Token第一种方法中我们隐藏了一个在网络不好或者并发请求时会导致多次重复提交数据的问题。 该问题的解决方案:将session和Token套用,如此便可解决,如何套用呢?请看这段解释: session是一个在单个操作人员整个操作过程中,与服务端保持通信的唯一识别信息。在同一操作人员的多次请求中,session始终保证是同一个对象,而不是多个对象,因为可以对其加锁。当同一操作人员多个请求进入时,可以通过session限制只能单向通行 本文正是通过使用session以及在session中加入token,来验证同一个操作人员是否进行了并发重复的请求,在后一个请求到来时,使用session中的token验证请求中的token是否一致,当不一致时,被认为是重复提交,将不准许通过。 这就是解决重复提交的方案。 四、基于 Token 的身份验证方法 使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的: 客户端使用用户名跟密码请求登录 服务端收到请求,去验证用户名与密码 验证成功后,服务端会签发一个 Token,再把这个 Token发送给客户端 客户端收到 Token 以后可以把它存储起来,比如放在Cookie 里或者 Local Storage 里 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
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(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.session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。 2.用户下一次请求还必须在第一次请求的服务器上(只有第一次访问的服务器保存了Session信息),在分布式的应用上,相应的限制了负载均衡器的能力。 3.cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于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 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保存在客户端,从这个方面入手可以联想出很多区分点。建议不要死记硬背这三者的概念和区别,要从认证流程出发思考它们之间的关系。