JWT入门,jwt可以解密吗?

JWT

什么是 JWT

JSON Web Token,通过数字签名的方式,以 JSON 对象为载体,在不同的服务终端之间安全地传输信息

  • 官网:https://jwt.io/
  • SDK: https://jwt.io/libraries (含Java和各种语言)
  • Java SDK(上面的SDK链接得到): https://github.com/auth0/java-jwt (java sdk也有很多种,这是其中一种)
  • 可以根据自己的喜好选择 SDK,不同的SDK的API设计有差异,那自然会有好坏
  • 本项目种使用的 SDK 也是官方推荐的其中一种(选官方的比来路不明的好)

JWT 有什么用?

JWT 最常见的场景就是授权认证,一旦用户登录,后续每个请求都将包含JWT,系统在每次处理用户请求之前,都要先进行 JWT 安全校验,通过之后再进行处理。

JWT 的组成

JWT 由 3 部分组成,用 . 拼接

格式是 Header.Payload.Signature

  • Header(头信息)
{
  'typ': 'JWT',
  'alg': 'HS256'
}

包含类型typ和算法alg,类型写死 JWT,算法可以自己决定,这里用HS256举例

  • Payload
实际的有效数据,你要传的业务数据,例如:


  • Signature
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,你可以知道

  • xxx,可以用base64解密xxx
  • yyy,本以为base64不能解密,结果可以,另外,我也遇到过不能解密的,可能是自己额外加密了
  • zzz,不能base64解密

注意:不要整段粘贴进去用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 例子

其实实际的使用过程,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;
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值