- JWT组成
格式:标头(header).有效载荷(payload).签名(signature)
注意:jwt中不要放敏感数据,例如密码和身份证号等…- JWT组成部分说明
header: 由令牌类型和使用的签名算法两部分组成,用base64编码处理。
payload: 其中包含声明,即相关实体数据(例如登录用户的非敏感数据),用base64编码处理。
**signature:**使用base64编码后的header和payload以及系统提供的密钥组成,并使用header中的算法进行签名。保证jwt未被篡改。
- JWT组成部分说明
整和Spring boot项目
- 创建springboot 项目(此处省略)。加入JWT的依赖,如下:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
- 在test目录下创建一个测试类。在测试类中写生成token的方法:
@Test
void createToken() {
@Test
void createToken() {
/**生成token*/
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MINUTE,2); //2分钟之后的时间
//HashMap mapHeader = new HashMap();
String token= JWT.create()
//.withHeader(mapHeader) //header部分,可省略,省略时默认使用 HMAC256
.withClaim("userId",3) //payload部分
.withClaim("username","张三") //payload部分
.withExpiresAt(cal.getTime()) //令牌过期时间
.sign(Algorithm.HMAC256("zhangxiaosan")); //signature签名部分,其中的参数zhangxiaosan为密钥,此密钥不可对外公开
System.out.println("生成的token:"+token);
}
- 在测试类中写验证token的方法。
@Test
void checkToken(){
/**验证token*/
String token= "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM1NDk5MDAsInVzZXJJZCI6MywidXNlcm5hbWUiOiLlvKDkuIkifQ.gGFrk0i492Gg83-2D4FBZsfnqhwsBJCgX0tIcju_qqg";
try {
//创建验证对象 HMAC256
JWTVerifier jWTVerifier = JWT.require(Algorithm.HMAC256("zhangxiaosan")).build();
DecodedJWT verify = jWTVerifier.verify(token);
System.out.println("verify:" + verify.getClaim("userId").asInt());
System.out.println("verify:" + verify.getClaim("username").asString());
System.out.println("verify:" + verify.getExpiresAt());
}catch (AlgorithmMismatchException e){
System.out.println("算法不匹配");
}catch (TokenExpiredException e){
System.out.println("token已失效");
}catch (JWTDecodeException e){
System.out.println("解码失败,token无效");
}catch (Exception e){
e.printStackTrace();
}
}
先运行步骤2得到密钥,在将步骤3中的token替换运行后得到以下结果:即验证通过。
将上述的代码封装成工具类
创建文件JWTUtil.java,用于封装jwt需要的方法。
package com.jwt.controller;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
private final static String SING = "zhangxiaosan";
private final static Integer DEFAULT_EXPIRES_TIME = 7 ;
/**
* 生成token
* @param map Map<String,String> 存放在token中的payload中的数据
* @return token String 生成的token
*/
public static String createToken(Map<String,String> map){
/**生成token*/
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE,DEFAULT_EXPIRES_TIME); //7天之后的时间
JWTCreator.Builder builder = JWT.create();
/*将存在payload部分的数据Map遍历存放*/
map.forEach((k,v)->{
if (v != null) {
builder.withClaim(k, v);
}
});
builder.withExpiresAt(cal.getTime()); //令牌过期时间
String token = builder.sign(Algorithm.HMAC256(SING));//signature签名部分,其中的参数为密钥,开始生成token
return token;
}
/**
* 验证签名
* @param token String 需要验证的token
* @return resMap Map 验证结果Map {"code":"0000","msg":"成功"}
* */
public static Map verifyToken(String token){
Map map = new HashMap();
try {
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
map.put("code","0000");
map.put("msg","成功");
map.put("data",verify);
}catch (AlgorithmMismatchException e){
map.put("code","1001");
map.put("msg","验证算法不匹配");
map.put("data",null);
}catch (TokenExpiredException e){
map.put("code","1002");
map.put("msg","token已失效");
map.put("data",null);
}catch (JWTDecodeException e) {
map.put("code","1003");
map.put("msg","解码失败,token无效");
map.put("data",null);
}catch (Exception e){
map.put("code","1000");
map.put("msg","验证异常");
map.put("data",null);
}
return map;
}
/**
* 根据token获取payload中的数据
* @param token String 需要获取数据的token
* */
public static DecodedJWT getDateByToken(String token){
Map map = verifyToken(token);
Object data = map.get("data");
if (data == null){
return null;
}
return (DecodedJWT) data;
}
}
今天,安全大佬龙傲天给我发了一细列优秀的文章:
然后若有所思。然后就更新了一下上面的JWT工具类。
生成token后将token字符串用DES对称加密,验证时候用DES解密,这样前端拿到的就是加密后的token串了。
注意:StringUtil只是判断字符传是否为空
修改后的工具类如下:
package com.jwt.controller;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author 张小三
* @create 2021-05-27 17:02
* @verson 1.0.0
*/
public class JwtUtil2 {
private final static String SING = "zhangxiaosan_sign";
private final static String KEY = "zhangxiaosan_key";
/**
* 生成token
* 默认过期时间为30分钟
* @param map Map<String,String> 存放在token中的payload中的数据
* @return token String 生成的token
*/
public static String createToken(Map<String,String> map){
/**生成token*/
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MINUTE,30); //30分钟之后的时间
JWTCreator.Builder builder = JWT.create();
/*将存在payload部分的数据Map遍历存放*/
map.forEach((k,v)->{
if (v != null) {
builder.withClaim(k, v);
}
});
builder.withExpiresAt(cal.getTime()); //令牌过期时间
String token = builder.sign(Algorithm.HMAC256(SING));//signature签名部分,其中的参数为密钥,开始生成token
return DesUtil.encryptDes(KEY, token).replaceAll("\\r\\n","");
}
/***
* 生成自定义过期时间token
* @param map Map<String,String> 存放在token中的payload中的数据
* @param time Integer 自定义的过期时间,单位为分钟
* @return
*/
public static String createTokenCustomExpireTime(Map<String,String> map,Integer time){
/**生成token*/
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MINUTE,time); //
JWTCreator.Builder builder = JWT.create();
/*将存在payload部分的数据Map遍历存放*/
map.forEach((k,v)->{
if (v != null) {
builder.withClaim(k, v);
}
});
builder.withExpiresAt(cal.getTime()); //令牌过期时间
String token = builder.sign(Algorithm.HMAC256(SING));//signature签名部分,其中的参数为密钥,开始生成token
return DesUtil.encryptDes(KEY, token).replaceAll("\\r\\n","");
}
/**
* 验证签名
* @param token String 需要验证的token
* @return resMap Map 验证结果Map {"code":"0000","msg":"成功"}
* */
public static Map verifyToken(String token){
Map map = new HashMap();
if(StringUtil.isEmpty(token)){
map.put("code","0001");
map.put("msg","token不能为空");
map.put("data",null);
}else{
try {
String tokenStr = DesUtil.decryptDES(KEY, token);
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(tokenStr);
map.put("code","0000");
map.put("msg","成功");
map.put("data",verify);
}catch (AlgorithmMismatchException e){
map.put("code","1001");
map.put("msg","验证算法不匹配");
map.put("data",null);
}catch (TokenExpiredException e){
map.put("code","1002");
map.put("msg","token已失效");
map.put("data",null);
}catch (JWTDecodeException e) {
map.put("code","1003");
map.put("msg","解码失败,token无效");
map.put("data",null);
}catch (Exception e){
map.put("code","1000");
map.put("msg","验证失败");
map.put("data",null);
}
}
return map;
}
/**
* 根据token获取payload中的数据
* @param token String 需要获取数据的token
* */
public static DecodedJWT getDateByToken(String token){
Map map = verifyToken(token);
Object data = map.get("data");
if (data == null){
return null;
}
return (DecodedJWT) data;
}
/**
* 判断token的过期时间
* @param token String token值
*/
public static String getExpireTime(String token){
if(StringUtil.isEmpty(token)) return "token不能为空";
DecodedJWT dateByToken = getDateByToken(token);
Map<String, Claim> claims = dateByToken.getClaims();
Claim exp = claims.get("exp");
Date date = exp.asDate();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
}
Desutil.java
package com.jwt.controller;
import javax.crypto.*;
import javax.crypto.spec.DESKeySpec;
import java.security.SecureRandom;
/**
* @author 张小三
* @create 2021-05-27 17:05
* @verson 1.0.0
*/
public class DesUtil {
private static final String KEY = "zhangxiaosan";
/**
* 加密
* @param key 密钥
* @param data 加密的字符串
*/
public static String encryptDes(String key, String data) {
try {
if (StringUtil.isEmpty(key)) key = KEY;
if(StringUtil.isEmpty(data)) return data;
DESKeySpec desKey = new DESKeySpec(key.getBytes("UTF-8"));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKey);
SecureRandom random = new SecureRandom();
Cipher cipher = Cipher.getInstance("DES");
cipher.init(1, secretKey, random);
return toHexString(cipher.doFinal(data.getBytes("UTF-8")));
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
/**
* 解密
* @param key 解密的key String
* @param data 传入加密后的数据 String
* @return String
*
*/
public static String decryptDES(String key, String data){
try {
if (StringUtil.isEmpty(key)) key = KEY;
if(StringUtil.isEmpty(data)) return "";
DESKeySpec desKey = new DESKeySpec(key.getBytes("UTF-8"));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKey);
SecureRandom random = new SecureRandom();
Cipher cipher = Cipher.getInstance("DES");
cipher.init(2, secretKey, random);
return new String(cipher.doFinal(toByteArray(data)));
} catch (BadPaddingException e) {
return data;
} catch (IllegalBlockSizeException e) {
return data;
} catch (Exception e) {
e.printStackTrace();
}
return data;
}
private static byte[] toByteArray(String hexString) {
byte[] bytes = new byte[hexString.length() / 2];
for(int i = 0; i < hexString.length() / 2; ++i) {
bytes[i] = (byte)(Character.digit(hexString.charAt(i * 2), 16) << 4 | Character.digit(hexString.charAt(i * 2 + 1), 16));
}
return bytes;
}
private static String toHexString(byte[] bytes) {
String hexString = "";
byte[] var2 = bytes;
int var3 = bytes.length;
for(int var4 = 0; var4 < var3; ++var4) {
byte b = var2[var4];
hexString = hexString + toHexString(b);
}
return hexString;
}
private static String toHexString(int i) {
return 1 == Integer.toHexString(i).length() ? "0" + Integer.toHexString(i) : Integer.toHexString(i);
}
private static String toHexString(byte b) {
return toHexString(toInt(b));
}
private static int toInt(byte b) {
return b & 255;
}
}
StringUtil.java
package com.jwt.controller;
import java.math.BigDecimal;
import java.util.regex.Pattern;
public class StringUtil {
public static Boolean isEmpty(String str){
if (str==null || str.trim().isEmpty() || "".equals(str.trim()) || "null".equals(str.trim())){
return true;
}
return false;
}
public static Boolean isNoEmpty(String str){
return !isEmpty(str);
}
/***
* 判断是否为整数数字
*/
public static boolean isInteger(String str) {
Pattern pattern = Pattern.compile("[0-9]*");
return pattern.matcher(str).matches();
}
}
使用例子:
//创建放入token种的数据
Map map = new HashMap();
map.put("username","zhangsan");
//创建token
String token = JwtUtil2.createToken(map);
System.out.println(token);
//获取token过期时间
String expireTime = JwtUtil2.getExpireTime(token);
System.out.println(expireTime);
//验证token
// 0000 成功 返回存入token的数据
// 1001 验证算法不匹配
// 1002 token已失效
// 1003 解码失败,token无效
// 1000 验证失败
Map map1 = JwtUtil2.verifyToken(token);
//获取token中存放的值
DecodedJWT dateByToken = JwtUtil2.getDateByToken(token);
System.out.println(dateByToken.getClaim("username").asString());