首页 > 其他分享 >JWT示例与原理

JWT示例与原理

时间:2024-03-29 23:44:55浏览次数:26  
标签:String 示例 JWT privateKey publicKey 签名 signature 原理

简介

JWT(JSON Web Token)是一种去中心化的web认证方案,信息存储在客户端。

数据结构

JWT通常由3部分组成,Header、Payload、Signature。每个部分都是用Base64Url编码后的字符串,每个部分之间由点分割。形如

Header.Payload.Signature

注: Base64Url是Base64的一个变种,主要是将Base64编码之后的"+"使用"-"替换,"/"使用"_"替换。因为字符+和/在URL中不是安全的字符。

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

{
  "alg": "RS256",
  "typ": "JWT"
}

其中alg表示签名算法,typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。

{
    "loginName": "xxxx",
    "nickname": "xxxxx",
    "sex": "男"
}

Signature

Signature是对前面两部分的一个签名,防止数据被篡改。比如使用RS256签名算法,则可以使用以下公式可以得到签名部分。

Signature=rsa(sha256(base64EncodeUrl(Header) + "." + base64EncodeUrl(Payload)), privateKey)

即使用base64Url编码Header、Payload,然后再使用sha256摘要算法取得hash值,最后再用rsa非对称算法使用私钥进行加密。

注意: 发送给客户端的JWT字符串的每一部分都是经过Base64URL编码后的字符串,然后使用点连接

验证JWT是否合法

既然知道了怎么签名的,那么解签就容易了

拿到JWT字符串后,截取前面的header+payload,这里已经是经过base64Url编码之后的结果了,最后一部分则是签名部分。

用rsa算法根据公钥解密签名,便能拿到sha256算法的hash值了,于是用这个值再和header+payload经过sha256 hash后的值比较即可。

注:基于RS256签名算法,签名需要使用私钥,因为token是服务端生成的,第三方使用公钥解签,第三方可以有很多。私钥保密,公钥公开。比如统一认证中心生成JWT字符串,应用a,应用b都能使用公开的公钥来验证签名是否合法的。

签名与解签代码演示

生成合法的RSA配对秘钥用于测试

@Test
public void generateKey() throws Exception {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    // 设置密钥长度,通常为 2048 位
    keyPairGenerator.initialize(2048);
    KeyPair keyPair = keyPairGenerator.generateKeyPair();
    PublicKey publicKey = keyPair.getPublic();
    PrivateKey privateKey = keyPair.getPrivate();

    // 使用base64编码, 使用方使用base64解码得到字节数组即可
    System.out.println("PublicKey: " + Base64.getEncoder().encodeToString(publicKey.getEncoded()));
    System.out.println("PrivateKey: " + Base64.getEncoder().encodeToString(privateKey.getEncoded()));
}

签名与验签

public class SignatureAlgTest {

    /**
     * 注意公钥和私钥必须是合法的RSA配对秘钥
     */
    @Test
    public void testSignAndVerify() throws Exception{
        final String publicKey ="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvzFky4s0gcmGytnaIS4JT/pcuW6Yn565mFjT3V1Qo5AMDMSvL5lp7fixGqtUQWDm1az+Vu6QMbLJAR6HyeNMa9EfkhUJQPFFNg29ydDXqzJjhdfdo9O78V20Pwu7ud7MRCq05COU6FMQ+sZmBrylokMyB5YDyBEB/bMo5pcEaeJAq9cd3ORbLBWKJz8NU6nPSkSJEjX2DGRekH/+lQazmpB+kUg8b7rw6pfYwLcSsWpvcgnHWeExuS7vGLQu2cT3SlAfUu9dp+o5ECQX8OmM7YzyMXuTd8D4ijSV8ZPfLAuktuSjMiX0rTHwxBWSUm6LIjNiF5jW9lWUU92mVPxpvwIDAQAB";
        final String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC/MWTLizSByYbK2dohLglP+ly5bpifnrmYWNPdXVCjkAwMxK8vmWnt+LEaq1RBYObVrP5W7pAxsskBHofJ40xr0R+SFQlA8UU2Db3J0NerMmOF192j07vxXbQ/C7u53sxEKrTkI5ToUxD6xmYGvKWiQzIHlgPIEQH9syjmlwRp4kCr1x3c5FssFYonPw1Tqc9KRIkSNfYMZF6Qf/6VBrOakH6RSDxvuvDql9jAtxKxam9yCcdZ4TG5Lu8YtC7ZxPdKUB9S712n6jkQJBfw6YztjPIxe5N3wPiKNJXxk98sC6S25KMyJfStMfDEFZJSbosiM2IXmNb2VZRT3aZU/Gm/AgMBAAECggEALHel/Es3npoK/iH2ADKPWukdaMlmuPU3ME40lG8wIqKNkuip4BW70+u78Tp44a3Sck8GZpycr9pnsplxtoxliUv9nkHDQbX7xWsjwY0PpBMXn5kJxSEpPKVxFxq5Ai1l79LI+Kin6PLs545+S0HT+i3LtIT5Ay6lemaRdDQahC+CmSlHCTcaz38RnjFLntYGPQcjWZUqN5ROgizwCDmt/ZE2xM9LH9Wrp1lTl5D7vkLmNTIAJ+Bm3ANat0OjhMpQ1Dmc82NYT8BNzWRx25uhsRHcS7+NcVQk35WMiZXRL3+t18saNSQOYXxmlO5qrCIOjyUGi9WNYTrGh8KlgchhSQKBgQDn/XFW+HnknJJIRh3GCValymWgnXbKzGlRuqKIoCjoiIjOBQaBshQ+j3IdwortxBmgHbx4uh2z2xaPGynSwJXVKoHoksY6zlJ4Bb5TaQgxapEcqOlHIfUkZfE6O5W/MHP4ACvxl0lIod4B45YCLRFvDoLneIIBXhvL7ioUt/pxdwKBgQDS+wkwvEGd7T10baxYbXU2iBo43hPHIUU1JwdNaQjrZ8QMyVKm0/mwqvVPn5BqGiO3Jq7QV+vjcAh1LOwJGzo9xLOD2UXaPAtDA9OUHAfL6Od45eVweM3h9/ecL2pmEElPqSS8puzFE6MA5qU1KEyL4x/x9jIQaBAihx7tYheb+QKBgB2fDsm8EFRQaZ0w1rxilN22aiOH95MNZqU432fyi0alqFIl8h69TjhuuHN0U6joUR1Qrq/7k69TWh4LqdtvG7KMKuo3U3hOv9jzYsnjr1gf80dlieO7QkHTgmmdEhHHbgdMfk/qsUDE6kPze0Pr3T4A7FYB3RevnHz9fAIJO8EhAoGARgxBBeRLKOL+l2xeX1GgLAXOJvlcua2LK9WUcBgidP4TsmcZQPh6GzT3k4MX0JJzLzjxq4y1beLhe/35NCDNGnr3WxxFO+rZlltr4O3ZjNL8H0C9B7WkLZVFqZ54hgB8Rq2S2+vUCq61XPQ2/8osd/llvtEN2DKkwMH5+7iovAkCgYEAyge6ONdXEhkTCsIbYbOgzL5CUBAQERt2+KGHZC9AeVQwl9xZUznDqKvdKz3A5io7PybSugMLNhC+5ZERFbXf7itanR+iXD68k0ySQcDPBVysQsIrOWqaIeGwM7vIn45BJLdW/YV/wHX36Bp0Sp+CNxJLosf2VzUDJuFyPh0wIhs=";

        // 需要签名的内容
        final String text = "hello world";

        // 最终的签名字符串(JWT的signature部分), 使用base64URL编码
        String signStr = sign(text, privateKey);

        // 验签
        boolean verify = verifySign(text, signStr, publicKey);
        Assertions.assertTrue(verify);

        // 修改签名字符串(不能直接修改字符串, 自己乱修改的可能不符合base64URL编码规范)
        byte[] signBytes = Base64.getUrlDecoder().decode(signStr);
        byte[] signBytesUpdate = Arrays.copyOf(signBytes, signBytes.length);
        signBytesUpdate[0] = 97;
        String signStrUpdate = Base64.getUrlEncoder().encodeToString(signBytesUpdate);

        // 再次验签, 由于篡改了内容, 因此验签失败
        verify = verifySign(text, signStrUpdate, publicKey);
        Assertions.assertFalse(verify);
    }

    /**
     * 签名
     */
    private String sign(String text, String privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        // 使用私钥签名
        signature.initSign(privateKey(privateKey));
        signature.update(text.getBytes(StandardCharsets.UTF_8));

        byte[] signBytes = signature.sign();
        // 最终的签名字符串(JWT的signature部分), 使用base64URL编码
        return Base64.getUrlEncoder().encodeToString(signBytes);
    }

    /**
     * 验签
     */
    private boolean verifySign(String text, String signStr, String publicKey) throws Exception {
        // 解签验证
        Signature signature = Signature.getInstance("SHA256withRSA");
        // 使用公钥验签
        signature.initVerify(publicKey(publicKey));
        signature.update(text.getBytes(StandardCharsets.UTF_8));
        return signature.verify(Base64.getUrlDecoder().decode(signStr));
    }
    
     /**
     * 使用给定字符串构建公钥实例
     * @param key base64编码的字符串
     */
    private PublicKey publicKey(String key) throws Exception{
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] keyBytes = Base64.getDecoder().decode(key);
        // RSA公钥需要使用X509EncodedKeySpec
        return keyFactory.generatePublic(new X509EncodedKeySpec(keyBytes));
    }

    /**
     * 使用给定字符串构建私钥实例
     * @param key base64编码的字符串
     */
    private PrivateKey privateKey(String key) throws Exception{
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        byte[] keyBytes = Base64.getDecoder().decode(key);
        // RSA私钥需要使用PKCS8EncodedKeySpec
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
    }
}

JWT API使用

使用jjwt库(Java JWT)

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.12.5</version>
</dependency>
public class JwtTest {

    @Test
    public void testCreateAndParse() {
        KeyPair keyPair = Jwts.SIG.RS256.keyPair().build();
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();

        Map<String, Object> customPayload = new HashMap<>();
        customPayload.put("loginName", "wastonl");
        customPayload.put("sex", "男");
        customPayload.put("age", 18);

        long currentTime = System.currentTimeMillis();
        String jwt = Jwts.builder()
                .claims(customPayload)
                // 签发时间
                .issuedAt(new Date(currentTime))
                // 1小时后过期
                .expiration(new Date(currentTime + 1000 * 60 * 60))
                .signWith(privateKey, Jwts.SIG.RS256)
                .compact();
        System.out.println(jwt);

        Jws<Claims> claimsJws = Jwts.parser().verifyWith(publicKey)
                .build()
                .parseSignedClaims(jwt);
        System.out.println(claimsJws.getPayload());
    }
}

标签:String,示例,JWT,privateKey,publicKey,签名,signature,原理
From: https://www.cnblogs.com/wt20/p/18104861

相关文章

  • 第11章 使用类——运算符重载(一)一个简单的运算符重载示例(Time类对象的加法)
    本文章是作者根据史蒂芬·普拉达所著的《C++PrimerPlus》而整理出的读书笔记,如果您在浏览过程中发现了什么错误,烦请告知。另外,此书由浅入深,非常适合有C语言基础的人学习,感兴趣的朋友可以自行阅读此书籍。运算符重载我们先了解下函数重载的概念,函数重载,也叫函数多态,指的是用......
  • CTF题型 nodejs(2) Js沙盒vm&vm2逃逸原理总结&典型例题
    CTF题型nodejs(2)Js沙盒逃逸原理&典型例题文章目录CTF题型nodejs(2)Js沙盒逃逸原理&典型例题一.vm原理以及逃逸1.基本用法2.如何逃逸汇总1)this为对象2)this为null(Object.create(null))a.可用输出直接触发toString方法b.调用属性触发3)Object.create(null)+沙箱......
  • 客快物流大数据项目(八十二):Kudu的读写原理 一般有用 看1
    Kudu的读写原理一、​​​​​​​工作模式Kudu的工作模式如下图,有些在上面的内容中已经介绍了,这里简单标注一下:每个kudutable按照hash或range分区为多个tablet;每个tablet中包含一个MemRowSet以及多个DiskRowSet;每个DiskRowSet包含BaseData以及DeltaStores;Delta......
  • 红外通信原理
    专业词汇红外infrared,简写IR红外线infraredray红外遥控器infraredremotecontrol原理介绍红外收发电路图红外接收管内部框图发射遥控信号由四部分组成:编码部分、红外发射源、红外接收设备、解码部分(EncodePart,IRTransmitterSource,IRMdevice,DecodePart)......
  • FreeRTOS从代码层面进行原理分析(3 任务的切换)
    FreeRTOS分析三—任务的切换我们带着三个问题开始了对FreeRTOS代码的探究。1.FreeRTOS是如何建立任务的呢?2.FreeRTOS是调度和切换任务的呢?3.FreeRTOS是如何保证实时性呢?前两篇文章分别从代码的层面分析了FreeRTOS是如何建立任务以及建立的任务是怎么样被调......
  • 大模型检索增强生成RAG原理介绍
    大家好,我是程序锅。github上的代码封装程度高,不利于小白学习入门。常规的大模型RAG框架有langchain等,但是langchain等框架源码理解困难,debug源码上手难度大。因此,我写了一个人人都能看懂、人人都能修改的大模型RAG框架代码。整体项目结构如下图所示:本篇文章将介绍2.RA......
  • 计数器的原理和应用
    一、计数器的原理和应用 要求:每计数三次,数码管值加一#include<reg51.h>unsignedchars[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};unsignedcharnum=0;voidinitcounter(){ TMOD=0x06;//00000110 TH0=256-3; TL0=256-3; ET0=1; EA=1; TR0=1;}......
  • 客快物流大数据项目(八十一): Kudu原理 有用 看1
    ​Kudu原理一、表与schemaKudu设计是面向结构化存储的,因此Kudu的表需要用户在建表时定义它的Schema信息,这些Schema信息包含:列定义(含类型)PrimaryKey定义(用户指定的若干个列的有序组合)数据的唯一性,依赖于用户所提供的PrimaryKey中的Column组合的值的唯一性。Kudu提供了Alt......
  • URL编码:原理、应用与安全性
    在网络世界中,URL(统一资源定位符)是我们访问网页、发送请求的重要方式。然而,URL中包含的特殊字符、不安全字符以及保留字符可能会导致传输错误或安全风险。为了解决这些问题,URL编码应运而生。本文将从概念介绍、编码规则、编码与解码、常见应用场景、历史演变、安全性考虑、局......
  • 深度学习技巧应用39-深度学习模型训练过程中数据均衡策略SMOTE的详细介绍,以及SMOTE的
    大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用39-深度学习模型训练过程中数据均衡策略SMOTE的详细介绍,以及SMOTE的算法原理与实现,本文介绍了一种用于处理分类数据不平衡问题的过采样方法——SMOTE算法。SMOTE算法通过在少数类的样本之间插值来创建新的样本,从而增加少......