springboot jwt 学习

  • JWT组成
    格式:标头(header).有效载荷(payload).签名(signature)
    注意:jwt中不要放敏感数据,例如密码和身份证号等…
    • JWT组成部分说明
      header: 由令牌类型和使用的签名算法两部分组成,用base64编码处理。
      payload: 其中包含声明,即相关实体数据(例如登录用户的非敏感数据),用base64编码处理。
      **signature:**使用base64编码后的header和payload以及系统提供的密钥组成,并使用header中的算法进行签名。保证jwt未被篡改。

整和Spring boot项目

  1. 创建springboot 项目(此处省略)。加入JWT的依赖,如下:
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </dependency>
  1. 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);
    }
  1. 在测试类中写验证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;
    }


}

今天,安全大佬龙傲天给我发了一细列优秀的文章:

  1. JWT介绍及其安全性分析
  2. jas502n/JWT_Brute: JWT_Brute
  3. 攻击JWT的一些方法
  4. Json Web Token历险记

然后若有所思。然后就更新了一下上面的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());
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小张帅三代

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值