认识token
一、token是计算机术语:
令牌,令牌是一种能够控制站点占有媒体的特殊帧,以区别数据帧及其他控制帧。token其实说的更通俗点可以叫暗号,在一些数据传输之前,要先进行暗号的核对,不同的暗号被授权不同的数据操作。基于 Token 的身份验证方法
二、在前后端完全分离的情况下,
Vue项目中实现token验证大致思路如下:
1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面
4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
5、每次调后端接口,都要在请求头中加token
6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面
三、具体问题
-
如何生成token?
-
如何验证token?
引出JWT
什么是jwt:
JsonWebToken,通俗的说就是通过JSON的形式作为web应用的令牌,用于在各方之间安全的将信息作为json对象传输,在数据传输过程可以完成数据加密和解密。
jwt可以做什么?
授权:使用jwt最常见的方案,一旦用户登陆,每个后续请求将包括jwt从而允许用户访问该令牌的路由,服务和资源;
信息交换:用的较少;
为什么选择JWT?
传统的认证方式:
http是一种无状态的协议,如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要在一次进行用户认证才行,因为根据http协议,不知道是哪个用户发送的请求,所以为了让我们的应用能识别是哪个用户发送的请求,只能在服务器储存一份用户登陆的信息,这份登陆信息会在登陆后响应给浏览器,浏览器保存为cookie,以便下次发送请求能够被服务器识别是哪个用户,这就是传统的基于session的认证。
传统认证的问题:
- 每个用户认证后,都要在服务器进行记录,以便下一次鉴别,seesion通常都是保存在内存中,随着用户增多,服务器的开销会明显变大;
- 用户认证后,服务器端做认证记录,如果记录都保存在内存中,意味着下次用户还必须访问该服务器,才能拿到授权资源,这样在分布式应用中,限制了负载均衡的能力,也限制了应用的扩展能力;
- 基于cooke进行用户识别,可能会被解惑,用户容易受到跨站请求伪造攻击。
- 在前后端分离的系统中:增加了部署的复杂性,通常用户的一次请求要被转发多次,如果用session,每次携带sessionID到服务器,增加了服务器的压力。
基于JWT的认证过程:
1、前端通过web表单,将自己的用户名和密码发送到后端的接口,这一过程一般是个http post请求,建议的方式是通过ssl加密的传输(https协议),从而避免信息被嗅探;
2、后端核对用户名后,将用户的id等其他信息作为JWT Payload(负载),将其他头部分别进行base64编码拼接后签名,形成一个jwt字符串;响应给前端存放在localStorge或者SessionStorge中,退出登陆删除即可;
3、前端在每次请求时将JWT放在http header中的Authorization位。(解决XSS和SCRF的问题)
4、后端通过JWT自带的方法检测JWT的有效性,
5、也可获取JWT中包含的信息,进行其他逻辑操作。
jwt的优势
简洁:通过url或者post参数或者在http的header中发送,因为数据量小,传输速度也快
自包含:负载中包含了用户所需信息,避免了多次查询数据库
因为token是以json加密存储在客户端的,所以jwt是跨语言的,任何web都支持
不需要再服务端保存会话信息,特别适用于分布式微服务
JWT的结构:
token String ===> header.paylod.singnature
头部 负载 签名
header:由两部分组成:令牌类型和所使用签名的算法,如HMAC、SHA256,他会使用Base64编码组成JWT结构的第一部分。
payload:负载,可以放入信息,也会通过Base64编码组成JWT
signature:前两部分都是Base64编码的,可以直接被前端解开知道里面的东西。Signature需要使用编码的Header和payload以及我们提供的一个密钥,然后使用指定的算法进行签名,以保证jwt没有被修改过
三部分组成即jwt
jwt的使用:
1、引入依赖 java-jwt
2、生成token
Calendar instance = Calendar。getInstande();
instance.add(Calendar.SECOND,90);
String token = JWT.create()
.withClaim("username","张三") //添加负载信息
.withExpiresAT(instance) // 设置过期时间
.sing(Algorithm.HMAC256("!#%sdf*")) //设置签名
3、根据令牌和签名解析数据
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!#%sdf*")).build;
DecodedJWT decodeJWT = jwtVerifier.verify(token)
4、封装成工具类使用:
package com.tnt.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
/**
* @author huangrw
* @program: 后端-netsecurity_exam
* @Description JWT工具类
* @createTime 2021年02月20日 21:31:00
*/
public class JWTUtils {
private static final String SING = "!2323eW@y";
/**
* @Author huangrw
* @Description 生成token
* @Date 21:44 2021/2/20
* @Param [map]
* @return java.lang.String
**/
public static String getToken(Map<String,String> map){
Calendar instance = Calendar.getInstance();
// 默认三天过期
instance.add(Calendar.DATE,3);
// 创建jwt builder
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v)->{
builder.withClaim(k,v);
});
String token = builder.withExpiresAt(instance.getTime()) //指定令牌过期时间
.sign(Algorithm.HMAC256(SING)); //签名
return token;
}
/**
* @Author huangrw
* @Description 验证token
* @Date 22:07 2021/2/20
* @Param [token]
* @return com.auth0.jwt.interfaces.DecodedJWT
**/
public static DecodedJWT verify(String token){
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
}
5、使用拦截器进行校验,
package com.tnt.interceptor;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.javafx.geom.transform.SingularMatrixException;
import com.tnt.util.JWTUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* @author huangrw
* @program: 后端-netsecurity_exam
* @Description JWT拦截器
* @createTime 2021年02月20日 21:50:00
*/
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<String,Object> map = new HashMap<>();
// 解决跨域问题,所有options请求统统放行
if("OPTIONS".equals(request.getMethod().toUpperCase())){
return true;
}
// 获取请求头中的token
String token = request.getHeader("token");
System.out.println(token);
try {
JWTUtils.verify(token);
return true;
} catch (SingularMatrixException e){
e.printStackTrace();
map.put("msg","无效签名");
}catch (TokenExpiredException e){
e.printStackTrace();
map.put("msg","token过期");
}catch ( AlgorithmMismatchException e ){
e.printStackTrace();
map.put("msg","token算法不一致");
}catch (Exception e){
e.printStackTrace();
map.put("msg","token无效");
}
map.put("state",false); // 设置状态
// 将map转换成json jackson
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
6、配置拦截器
package com.tnt.config;
import com.tnt.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author huangrw
* @program: 后端-netsecurity_exam
* @Description JWT拦截器配置
* @createTime 2021年02月20日 22:03:00
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/**") //拦截所有请求
//.excludePathPatterns("/**")
.excludePathPatterns("/api/user/regist") //放行注册请求
.excludePathPatterns("/api/admin/login") //放行管理员登陆请求
.excludePathPatterns("/api/user/login") //放行用户登陆请求
.excludePathPatterns("/doc.html","/swagger-resources"); //放行Knife4j
}
}