JSON Web Token,通过数字签名的方式,以 JSON 对象为载体,在不同的服务终端之间安全地传输信息
JWT 最常见的场景就是授权认证,一旦用户登录,后续每个请求都将包含JWT,系统在每次处理用户请求之前,都要先进行 JWT 安全校验,通过之后再进行处理。
JWT 由 3 部分组成,用 . 拼接
格式是 Header.Payload.Signature
{
'typ': 'JWT',
'alg': 'HS256'
}
包含类型typ和算法alg,类型写死 JWT,算法可以自己决定,这里用HS256举例
实际的有效数据,你要传的业务数据,例如:
Signature是这么算出来的?
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'your_secret');
说明:
1、header和payload先用base64加密用点链接起来,再用header里alg所指定的算法计算摘要
2、your_secret 就是秘钥,加密方和解密方共同知道的,解密的时候要用
当你拿到一段jwt字符串的时候(就是三段式的那串),你能知道什么?
对于 xxx.yyy.zzz,你可以知道
注意:不要整段粘贴进去用base64解密,要分段,否则可能会出现解密不了的情况。
例子
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0ZWFjaGVyIjp7Im5hbWUiOiJNaXNzIExlZSJ9LCJzZXgiOnRydWUsIm5hbWUiOiJTdG9uZSIsImNvdXJzZUxpc3QiOlt7Im5hbWUiOiJNYXRoIiwiY3JlZGl0cyI6NH0seyJuYW1lIjoiRW5nbGlzaCIsImNyZWRpdHMiOjN9XSwiZXhwIjoxNjkxNDExMzcyLCJhZ2UiOjIwfQ.C2LJfyeeS2E0tpS12o-MnQWvn3B7ecK_ul3kudpiAPE
注意一定要分段粘贴进去进行base64解密,不要一整个整体,本例子没有问题,有些别的例子会出现yyy部分不能解密的情况!!!
base64解密第一段:{"typ":"JWT","alg":"HS256"}
base64解密第一段:{"teacher":{"name":"Miss Lee"},"sex":true,"name":"Stone","courseList":[{"name":"Math","credits":4},{"name":"English","credits":3}],"exp":1691411372,"age":20}
base64解密第一段:(乱码,看来是解密不了)
其实实际的使用过程,Java 作为后端仅仅需要写解析jwt的,对于封装,这是前端要干的事情。
package com.wyf.test.jwt;
import io.jsonwebtoken.*;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.*;
public class JWTTest {
private static final String MY_SECRET = "xAy23hz23YK";// 加解密用的秘钥
public static void main(String[] args) throws InterruptedException {
String jwtString = testGetJwtString();
//Thread.sleep(4*1000);// 模拟超市的情况是否能检查出来
System.out.println("\n-------------------\n");
testParseJwtString(jwtString);
}
// 得到加密的字符串,这个一般来说不是Java写的,肯定是前端人员写的,JS(react/vue等)
public static String testGetJwtString() {
/*
格式:Header.Payload.Signature
header的格式是:
{
'typ': 'JWT',
'alg': 'HS256'
}
其中类型typ写死,alg可选,这里选HS256算法
Payload 是实际的数据
Signature的计算方式如下(参考如下用JS写的伪代码):
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'your_secret');
其中 your_secret 就是秘钥,加密方和解密方共同知道的,解密的时候要用
*/
// 设置 token 有效期
long tokenLifespan = 3 * 1000; // 30 秒
Date expireDateTime = new Date(System.currentTimeMillis() + tokenLifespan);
// 用 Java 中引入的 JWT 包里的 API
JwtBuilder jwtBuilder = Jwts.builder();
String jwtString = jwtBuilder
// header
.setHeaderParam("typ", "JWT").setHeaderParam("alg", "HS256")
// payload (claim 其实就是payload的意思,就是你要传的数据)
.setClaims(prepareMapPayloadData())
// 可选,设置 token 有效期
.setExpiration(expireDateTime)
// signature (解密的时候用的secret不对的话会抛出signature无法匹配的异常:io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature.)
.signWith(SignatureAlgorithm.HS256, MY_SECRET)
// 用点连接起来
.compact();
System.out.println("jwtString:" + jwtString);
System.out.println("token duration(Sec):" + (tokenLifespan / 1000));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8"));
System.out.println("expireDateTime:" + simpleDateFormat.format(expireDateTime));
return jwtString;
}
// 我在想这个方法根本就不需要返回payload里的值,如果使用JWT,仅仅是validate一下signature是不是正确的、达到鉴权效果就可以了
public static Object testParseJwtString(String jwtString) {
JwtParser jwtParser = Jwts.parser();
// 设置解密用的秘钥(解密的时候用的secret不对的话会抛出signature无法匹配的异常)
jwtParser.setSigningKey(MY_SECRET);
// parse 的时候就会校验签名,不正确的时候会从这行抛出异常,也会校验token是否超时!如果超时爆出
// Exception in thread "main" io.jsonwebtoken.ExpiredJwtException: JWT expired at 2023-08-07T18:59:42Z. Current time: 2023-08-07T18:59:44Z, a difference of 2416 milliseconds. Allowed clock skew: 0 milliseconds.
Jwt parse = jwtParser.parse(jwtString);
Header header = parse.getHeader();
Object body = parse.getBody();// 其API是设计成用Object接收的
System.out.println("header: " + (header != null ? header.getClass().toString() + ", " + header : "null"));
System.out.println("body: " + (body != null ? body.getClass().toString() + ", " + body : "null"));
return body;
}
/**
* method to convert object to a map
* @param obj
* @return
* @throws IllegalAccessException
*/
public static Map<String, Object> getObjectToMap(Object obj) throws IllegalAccessException {
Map<String, Object> map = new HashMap<String, Object>();
Class<?> cla = obj.getClass();
Field[] fields = cla.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String keyName = field.getName();
Object value = field.get(obj);
if (value == null)
value = "";
map.put(keyName, value);
}
return map;
}
public static Map<String, Object> prepareMapPayloadData() {
Map<String, Object> objectToMap;
try {
objectToMap = getObjectToMap(preparePayloadData());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
System.out.println("payload map: " + objectToMap);
return objectToMap;
}
public static Student preparePayloadData() {
List<Course> courseList = new ArrayList<Course>();
Course course1 = new Course();
course1.setName("Math");
course1.setCredits(4);
Course course2 = new Course();
course2.setName("English");
course2.setCredits(3);
courseList.add(course1);
courseList.add(course2);
Teacher teacher = new Teacher();
teacher.setName("Miss Lee");
Student student = new Student();
student.setName("Stone");
student.setAge(20);
student.setSex(true);
student.setTeacher(teacher);
student.setCourseList(courseList);
return student;
}
public static class Student {
private String name;
private Integer age;
private Boolean sex;
private Teacher teacher;
private List<Course> courseList;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getSex() {
return sex;
}
public void setSex(Boolean sex) {
this.sex = sex;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public List<Course> getCourseList() {
return courseList;
}
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
}
public static class Teacher {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class Course {
private String name;
private Integer credits;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCredits() {
return credits;
}
public void setCredits(Integer credits) {
this.credits = credits;
}
}
}