JWT
1、什么是JWT
概述
- jsonwebtoken是一个开放标准,它定义了一种紧凑都、自包含的方式,用于在各方之间以JSON对象安全的传输信息。此信息可以验证和信任,因为它是数字签名的,jwt可以使用秘密或使用RSA或ECDSA的公钥/私钥对进行签名。
- JWT简称JSON、Web、Token,也就是通过JSON形式作为web与应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。
- 官网:http://jwt.io/introduction/
JWT的作用
- 授权: 用户的授权,一旦用户登录后获得了token,后续一些需要用户权限的请求就不会拒绝请求。
- 信息交互: JSON Web Token是在各方之间安全传输信息的好方法。因为可以对JWT进行签名(例如:使用公钥/私钥对),所以可以确保发送方信息,由于签名是使用标头和有效负载计算的,因此可以验证内容是否遭到篡改。保证信息的完整性。
2、基于传统的session认证
认证方式
- http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,下一次请求时,用户还要再一次进行用户认证才行。在第一次请求后服务器会创建一个会话即session,为了下一次能够识别到是哪个用户发出的请求,只能在服务器存储一份用户登录信息,这份登录信息会在响应时传给浏览器,即为一个sessionid存储在cookie中,这就是传统的基于session认证。
认证流程
图示:
暴露的问题:
- 每个用户认证一次就要记录一个sessionid,都保存在内存中,服务器的开销很大,且断电就丢失。
- session只存储在本服务器的内存中,在分布式应用场景中是非常不方便的,分布式通常是几个服务之间的调用,使用session认证就相应的限制了负载均衡的能力。
- 因为是基于cookie来进行用户的识别,cookie如果被截获,服务器就很容易受到跨站请求伪造的攻击。
- 前后端分离系统中更加的痛苦,sessionid1还只是一个特征值,表达的信息不够丰富。在后端应用是多节点部署场景中,就要实现session的共享机制,不太方便。也不方便集群应用。
3、基于Jwt认证
认证流程
图示:
- 用户进行认证,向服务器发送请求,服务器响应请求并进行认证,认证通过后生成JWT即token,响应给前端;后面的每次请求都携带JWT,服务器拦截请求验证token的有效性,正确的token执行业务逻辑响应数据,错误的token返回错误信息给前端,前端展示相应的的信息。
Jwt的优势
- 简洁: 数据量小,传输熟读快。
- 自包含: 负载中包含了所有用户所需要的信息,避免多次查询数据库。
- Token是以JSON加密形式保存在客户端的,所以Jwt是跨语言的。
- 特别适用于分布式微服务。
4、JWT的结构
本质为一个字符串
token ====> header.payload.singnature
令牌的组成: 标头(header)
、有效负荷(payload)
、签名(singnature)
JWT通常所示:xxxxxx.yyyyyy.zzzzzz
-
header: 标头通常由两部分组成即令牌的类型(JWT)和使用的算法类型。【通常使用Base64编码组成JWT结构的第一部分】
{ "alg":"HS256", "typ":"JWT" }
-
payload: 有效负载,存储用户的相关的信息和其他数据的声明。【通常使用Base64编码组成JWT结构的第二部分】
{ "username":"qiumin", "password":"123456" }
-
singnature: 签名,与前面两个部分生成JWT,该签名不能让其他人知道,是保证token不被串改,保证信息的安全。【通常使用Base64编码组成JWT结构的第二部分】
header+"."+payload+"."+singnature ---> 组成唯一的token
5、使用JWT
springboot
- 导入JWT的依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
- 生成token
@Test
public void contentLoad(){
HashMap<String,Object> map = new HashMap<>();
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,180);
String token = JWT.create()
.withHeader(map) //header
.withClaim("username", "qiumin") //payload
.withClaim("password", "123456") //payload
.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256("!FhSD!#VDZF%#FDBD"));//签名
System.out.println(token);
}
结果:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6IjEyMzQ1NiIsImV4cCI6MTY1NzQzODUxNSwidXNlcm5hbWUiOiJxaXVtaW4ifQ.1TIUuWP1d1-vHc71oTw2pH5LAxtCehiiRnaK3tIZob8
- 验证解析token
@Test
public void Auth(){
JWTVerifier verifier = JWT.require(Algorithm.HMAC256("!FhSD!#VDZF%#FDBD")).build();
DecodedJWT verify = verifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6IjEyMzQ1NiIsImV4cCI6MTY1NzQzODUxNSwidXNlcm5hbWUiOiJxaXVtaW4ifQ.1TIUuWP1d1-vHc71oTw2pH5LAxtCehiiRnaK3tIZob8");
System.out.println(verify.getHeader());
System.out.println("payload: "+verify.getClaim("username").asString());
System.out.println("payload: "+verify.getClaim("password").asString());
System.out.println("过期时间: "+verify.getExpiresAt());
}
常见的JWT异常:
- SignatureVerificationException: 无效签名。
- TokenExpiredException: token过期。
- AlgorithmMismatchException: token算法不一致。
- InvalidClaimException: 失效payload异常。
封装成工具类JwtUtils
package com.qiumin.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
/**
* @author qiumin
* @classname JWTUtils
* @Description love code
* @date 2022-07-10 15:33
*/
public class JwtUtils {
private static final String SING = "!FhSD!#VhDZF%#FDBD";
/**
* 获得Token
* @param map1 header
* @param map2 payload
* */
public static String getToken(Map<String,Object> map1, Map<String,String> map2){
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,180); //过期时间
JWTCreator.Builder builder = JWT.create();
builder.withHeader(map1); //header
map2.forEach((k,v)->{builder.withClaim(k,v);}); //payload
builder.withExpiresAt(instance.getTime()); //过期时间
String token = builder.sign(Algorithm.HMAC256(SING));//签名
return token;
}
/**
* 验证token
* @param token 令牌
* */
public static void verify(String token){
JWTVerifier build = JWT.require(Algorithm.HMAC256(SING)).build();
build.verify(token);
}
/**
* 获取token中的信息
* @param token 令牌
* */
public static DecodedJWT analysis(String token){
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
}
6、拦截器配置验证token
拦截器
创建拦截器的类,配置WebMvcConfigurer,将自己的自定义拦截器装配到容器中
package com.qiumin.interceptor;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qiumin.utils.JwtUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
/**
* JwtInterceptor拦截器类
*
* @author qiumin
* @classname JwtInterceptor
* @Description love code
* @date 2022-07-14 13:14
*/
public class JwtInterceptor implements HandlerInterceptor {
/**
* 拦截器
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
HashMap<String, Object> map = new HashMap<>();
JwtUtils jwtUtils = new JwtUtils();
try {
jwtUtils.verify(token);
return true;
}catch (SignatureVerificationException 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);
response.setContentType("application/json;charset=UTF-8");
//响应给前端
response.getWriter().println(new ObjectMapper().writeValueAsString(map));
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
配置拦截器
package com.qiumin.config;
import com.qiumin.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author qiumin
* @classname InterceptorConfig
* @Description love code
* @date 2022-07-14 13:37
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor())
.addPathPatterns("/user/test2") //拦截的路径请求
.excludePathPatterns("/user/login"); //放行的请求路径
}
}
qiumin
plements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor())
.addPathPatterns("/user/test2") //拦截的路径请求
.excludePathPatterns("/user/login"); //放行的请求路径
}
}
**qiumin**