转载自:https://blog.csdn.net/kingtok/article/details/110438558
下列场景中使用JSON Web Token是很有用的:
首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程- -般是一 个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议) ,从而避免敏感信息被嗅探。
后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload (负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同11. zzz. xxx的字符串。token head . payload . singurater
后端将JWT字符串作为登录成功的返回结果返回给前端。 前端可以将返回的结果保存在localStorage或sessionStorage上, 退出登录时前端删除保存的JWT即可。
前端在每次请求时将JWT放入HTTP Header中的Authorization位。 (解决XSS和XSRF问题)
后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)
验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。
简洁(Compact):可以通过URL,POST参 数或者在HTTP header发送,因为数据量小,传输速度也很快
自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
因为Token是 以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
不需要在服务端保存会话信息,特别适用于分布式微服务。
标头通常由两部分组成:令牌的类型(即JWT) 和所使用的签名算法,例如HMAC SHA256或RSA。 它会使用Base64 编码组成JWT 结构的第一部分。
注意:Base64是一 种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
{
"alg" : "HS256"
"typ" : "JWT"
}
令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64 编码组成JWT结构的第二部分
{
"sub" : "HS256"
"name" : "wang"
"admin" : "true"
}
前面两部分都是使用Base64进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的header和payload以及我们提供的一个密钥,然后使用header 中指定的签名算法(HS256) 进行签名。签名的作用是保证JWT没有被篡改过
HMACSHA256 (base64Ur1Encode(header) + "." + base64Ur1Encode(payload) , secret);
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.3.RELEASE
com.chilly
springboot-jwt-2020
0.0.1-SNAPSHOT
springboot-jwt-2020
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.3
com.auth0
java-jwt
3.10.3
mysql
mysql-connector-java
5.1.49
com.alibaba
druid
1.1.23
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.boot
spring-boot-maven-plugin
package com.chilly;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//@SpringBootTest
class SpringbootJwt2020ApplicationTests {
//令牌获取
@Test
void contextLoads() {
Map map = new HashMap<>();
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 2000);
String token = JWT.create().withHeader(map) //header
.withClaim("userId", 21)//payload
.withClaim("username", "xiaochen")//payload
.withExpiresAt(instance.getTime())//指定令牌的过期时间
.sign(Algorithm.HMAC256("!Q@W#E$R")) //签名
;
System.out.println(token);
}
//令牌验证:根据令牌和签名解析数据
//常见异常:
//SignatureVerificationException 签名不一致异常
//TokenExpiredException 令牌过期异常
//AlgorithmMismatchException 算法不匹配异常
//InvalidClaimException 失效的payload异常
@Test
void test() {
// String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTk2NDkxMzMsInVzZXJJZCI6MjEsInVzZXJuYW1lIjoieGlhb2NoZW4ifQ.LmTERviRHnmKpOeXO0f9K2nR1C7AovGfAV6Fmx7tcw0";
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDY4MTYxNzksInVzZXJJZCI6MjEsInVzZXJuYW1lIjoieGlhb2NoZW4ifQ.h1yaTia3FcTjULAc8Xlfa4M3yD-E7z47lcKc7mNw9CM";
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!Q@W#E$R")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("用户Id:" + decodedJWT.getClaim("userId").asInt());
System.out.println("用户名:" + decodedJWT.getClaim("username"));
System.out.println("过期时间:" + decodedJWT.getExpiresAt());
// 用户Id:21
// 用户名:com.auth0.jwt.impl.JsonNodeClaim@1381794
// 过期时间:Tue Dec 01 17:49:39 CST 2020
}
}
封装成工具类使用
package com.chilly.utils;
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;
/**
* 2020 12.1
* ktoking
*/
public class JWTUtils {
private static String SECRET = "token!Q@W#E$R";
/**
* 生产token
*/
public static String getToken(Map map) {
JWTCreator.Builder builder = JWT.create();
//payload
map.forEach((k, v) -> {
builder.withClaim(k, v);
});
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 7); //默认7天过期
builder.withExpiresAt(instance.getTime());//指定令牌的过期时间
String token = builder.sign(Algorithm.HMAC256(SECRET));//签名
return token;
}
/**
* 验证token
*/
public static DecodedJWT verify(String token) {
//如果有任何验证异常,此处都会抛出异常
DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
return decodedJWT;
}
// /**
// * 获取token中的 payload
// */
// public static DecodedJWT getToken(String token) {
// DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);
// return decodedJWT;
// }
}
拦截器验证token
@RestController
@Slf4j
public class UserController {
@Resource
private UserService userService;
@GetMapping("/user/login")
public Map login(User user) {
log.info("用户名:{}", user.getName());
log.info("password: {}", user.getPassword());
Map map = new HashMap<>();
try {
User userDB = userService.login(user);
Map payload = new HashMap<>();
payload.put("id", userDB.getId());
payload.put("name", userDB.getName());
String token = JWTUtils.getToken(payload);
map.put("state", true);
map.put("msg", "登录成功");
map.put("token", token);
return map;
} catch (Exception e) {
e.printStackTrace();
map.put("state", false);
map.put("msg", e.getMessage());
map.put("token", "");
}
return map;
}
@PostMapping("/user/test")
public Map test(HttpServletRequest request) {
String token = request.getHeader("token");
DecodedJWT verify = JWTUtils.verify(token);
String id = verify.getClaim("id").asString();
String name = verify.getClaim("name").asString();
log.info("用户id:{}", id);
log.info("用户名: {}", name);
//TODO 业务逻辑
Map map = new HashMap<>();
map.put("state", true);
map.put("msg", "请求成功");
return map;
}
}
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDAO userDAO;
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public User login(User user) {
User userDB = userDAO.login(user);
if (userDB != null) {
return userDB;
}
throw new RuntimeException("认证失败");
}
}
@Mapper
public interface UserDAO {
User login(User user);
}
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//获取请求头中的令牌
String token = request.getHeader("token");
log.info("当前token为:{}", token);
Map map = new HashMap<>();
try {
JWTUtils.verify(token);
return true;
} catch (SignatureVerificationException e) {
e.printStackTrace();
map.put("msg", "签名不一致");
} catch (TokenExpiredException e) {
e.printStackTrace();
map.put("msg", "令牌过期");
} catch (AlgorithmMismatchException e) {
e.printStackTrace();
map.put("msg", "算法不匹配");
} catch (InvalidClaimException e) {
e.printStackTrace();
map.put("msg", "失效的payload");
} catch (Exception e) {
e.printStackTrace();
map.put("msg", "token无效");
}
map.put("state", false);
//响应到前台: 将map转为json
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/user/test")
.excludePathPatterns("/user/login")
;
}
}
项目源代码 : 项目git地址