• jwt的了解和使用以及大致代码分析


    jwt简介

    以下介绍来自官网(https://jwt.io/)
    SON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。此信息可以验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名**(来自官网)**。

    尽管 JWT 可以加密以提供各方之间的保密性,但我们将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则向其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是签名的一方**(来自官网)**。

    使用场景

    • 授权:这是使用 JWT 的最常见方案。用户登录后,每个后续请求都将包含 JWT,允许用户访问使用该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够跨不同域轻松使用**(来自官网)**。

    • 信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。由于 JWT 可以签名(例如,使用公钥/私钥对),因此您可以确定发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改**(来自官网)**)。

    jwt的组成结构

    • Header
    • Payload
    • Signature
      因此,JWT 通常如下所示。
    xxxxx.yyyyy.zzzzz
    
    • 1

    Header

    通常由两部分组成:令牌的类型(即 JWT)和正在使用的签名算法,例如 HMAC SHA256 或 RSA。
    例如:

    {
      "alg": "HS256",
      "typ": "JWT"
    }
    
    • 1
    • 2
    • 3
    • 4

    然后,此JSON被Base64Url编码以形成JWT的第一部

    Payload(有效载荷)

    令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的语句。
    有三种类型的声明:已注册、公共和私人声明。
    已注册声明:这些是一组预定义的声明,这些声明不是必需的,但建议使用,以提供一组有用的、可互操作的声明。其中一些是:iss(发行人),exp(到期时间),sub(主题),aud(受众)等。

    请注意,声明名称只有三个字符长,因为 JWT 应该是紧凑的。

    公共声明:这些可以由使用 JWT 的人随意定义。但为了避免冲突,它们应该在 IANA JSON Web 令牌注册表中定义,或者定义为包含抗冲突命名空间的 URI。

    专用声明:这些是创建的自定义声明,用于在同意使用它们的各方之间共享信息,既不是注册声明也不是公共声明。

    示例有效负载可以是:

    {
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    sub是预定义的声明,name,admin则是自己随便添加的一些定义
    然后对有效负载进行 Base64Url 编码以形成 JSON Web 令牌的第二部分。

    请注意,对于签名令牌,此信息虽然受到篡改保护,但任何人都可以读取。不要将机密信息放在 JWT 的有效负载或标头元素中,除非它已加密。

    签名

    要创建签名部分,您必须获取编码的标头、编码的有效负载、机密、标头中指定的算法并对其进行签名。

    例如,如果要使用 HMAC SHA256 算法,将按以下方式创建签名:

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)
    
    • 1
    • 2
    • 3
    • 4

    签名用于验证消息在此过程中未被更改,并且在使用私钥签名的令牌的情况下,它还可以验证 JWT 的发件人是否是它所说的人。

    把所有东西放在一起
    输出是三个由点分隔的 Base64-URL 字符串,可以在 HTML 和 HTTP 环境中轻松传递,同时与基于 XML 的标准(如 SAML)相比更加紧凑。

    使用java-jwt来生成和解析token

    导入maven依赖

    
    			com.auth0</groupId>
    			java-jwt</artifactId>
    			3.4.0</version>
    		</dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这附上一段我之前写的代码

        public String getToken(Users user) {
            String token="";
            // 2*24*3600 * 1000
            token= JWT.create()
                    .withExpiresAt(new Date(System.currentTimeMillis() + (2*24*3600*1000)))
                    .withAudience(user.getEmployeeId())// 将 login_user 保存到 token 里面
                    .sign(Algorithm.HMAC256(DigestUtils.md5Hex(user.getEmployeePassword()+ Constants.MD5PRIVATEKEY)));// 以 password 作为 token 的密钥
            return token;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这是上面引入的依赖提供的方法
    首先是 JWT.create(),create方法去调用了JWTCreator的init方法

    public static JWTCreator.Builder create() {
            return JWTCreator.init();
        }
    
    • 1
    • 2
    • 3

    然后init方法又new了一个自己的内部类,内部类给头部(headerClaims)和载荷(payloadClaims)声明了引用地址

        /**
         * Initialize a JWTCreator instance.
         *
         * @return a JWTCreator.Builder instance to configure.
         */
        static JWTCreator.Builder init() {
            return new Builder();
        }
    
        /**
         * The Builder class holds the Claims that defines the JWT to be created.
         */
        public static class Builder {
            private final Map<String, Object> payloadClaims;
            private Map<String, Object> headerClaims;
    
            Builder() {
                this.payloadClaims = new HashMap<>();
                this.headerClaims = new HashMap<>();
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    紧接着withExpiresAt()和withAudience()实际上就是给载荷里预定义的声明赋值,withExpiresAt()就是给exp赋值,withAudience()就是给aud赋值

         /**
             * Add a specific Audience ("aud") claim to the Payload.
             *
             * @param audience the Audience value.
             * @return this same Builder instance.
             */
            public Builder withAudience(String... audience) {
                addClaim(PublicClaims.AUDIENCE, audience);
                return this;
            }
    
            /**
             * Add a specific Expires At ("exp") claim to the Payload.
             *
             * @param expiresAt the Expires At value.
             * @return this same Builder instance.
             */
            public Builder withExpiresAt(Date expiresAt) {
                addClaim(PublicClaims.EXPIRES_AT, expiresAt);
                return this;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    它们都调用了addClaim方法,可以看到就是在载荷的那个map中添加key和value

     private void addClaim(String name, Object value) {
                if (value == null) {
                    payloadClaims.remove(name);
                    return;
                }
                payloadClaims.put(name, value);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    前面说过也可以自定义声明添加到载荷这部分,这里也提供了多个方法,这里展示两个

       /**
             * Add a custom Claim value.
             *
             * @param name  the Claim's name.
             * @param value the Claim's value.
             * @return this same Builder instance.
             * @throws IllegalArgumentException if the name is null.
             */
            public Builder withClaim(String name, Boolean value) throws IllegalArgumentException {
                assertNonNull(name);
                addClaim(name, value);
                return this;
            }
    
            /**
             * Add a custom Claim value.
             *
             * @param name  the Claim's name.
             * @param value the Claim's value.
             * @return this same Builder instance.
             * @throws IllegalArgumentException if the name is null.
             */
            public Builder withClaim(String name, Integer value) throws IllegalArgumentException {
                assertNonNull(name);
                addClaim(name, value);
                return this;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    然后是sign()方法,这里这里传入了一个我们指明秘钥的算法,headerClaims.put方法给头部添加了令牌的类型(即 JWT)和正在使用的签名算法

    /**
             * Creates a new JWT and signs is with the given algorithm
             *
             * @param algorithm used to sign the JWT
             * @return a new JWT token
             * @throws IllegalArgumentException if the provided algorithm is null.
             * @throws JWTCreationException     if the claims could not be converted to a valid JSON or there was a problem with the signing key.
             */
            public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCreationException {
                if (algorithm == null) {
                    throw new IllegalArgumentException("The Algorithm cannot be null.");
                }
                headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName());
                headerClaims.put(PublicClaims.TYPE, "JWT");
                String signingKeyId = algorithm.getSigningKeyId();
                if (signingKeyId != null) {
                    withKeyId(signingKeyId);
                }
                return new JWTCreator(algorithm, headerClaims, payloadClaims).sign();
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    然后new 了一个JWTCreator对象

     private JWTCreator(Algorithm algorithm, Map<String, Object> headerClaims, Map<String, Object> payloadClaims) throws JWTCreationException {
            this.algorithm = algorithm;
            try {
                ObjectMapper mapper = new ObjectMapper();
                SimpleModule module = new SimpleModule();
                module.addSerializer(ClaimsHolder.class, new PayloadSerializer());
                mapper.registerModule(module);
                mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
                headerJson = mapper.writeValueAsString(headerClaims);
                payloadJson = mapper.writeValueAsString(new ClaimsHolder(payloadClaims));
            } catch (JsonProcessingException e) {
                throw new JWTCreationException("Some of the Claims couldn't be converted to a valid JSON format.", e);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这里可以看到这两句代码,把头部这个map和载荷这个map都转换成了json串

      headerJson = mapper.writeValueAsString(headerClaims);
      payloadJson = mapper.writeValueAsString(new ClaimsHolder(payloadClaims));
    
    • 1
    • 2

    然后返回,在new一个JWTCreator对象后又调用了这个对象的sign()方法

     private String sign() throws SignatureGenerationException {
            String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8));
            String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8));
            String content = String.format("%s.%s", header, payload);
    
            byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8));
            String signature = Base64.encodeBase64URLSafeString((signatureBytes));
    
            return String.format("%s.%s", content, signature);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看到首先对header也就是头部做了base64编码,然后对载荷 payload也做了base64编码

    String header = Base64.encodeBase64URLSafeString(headerJson.getBytes(StandardCharsets.UTF_8));
    String payload = Base64.encodeBase64URLSafeString(payloadJson.getBytes(StandardCharsets.UTF_8));
    
    • 1
    • 2

    然后使用小数点连接

    String content = String.format("%s.%s", header, payload);
    
    • 1

    然后使用算法对连接后的头部和载荷进行签名,最后形成签名

    byte[] signatureBytes = algorithm.sign(content.getBytes(StandardCharsets.UTF_8));
    
    • 1

    然后用base64对签名进行编码,再把编码后的签名和前面的头部,载荷连接起来

    String signature = Base64.encodeBase64URLSafeString((signatureBytes));
    
    return String.format("%s.%s", content, signature);
    
    • 1
    • 2
    • 3
  • 相关阅读:
    华为年薪千万的大牛机器视觉搞了几十年,没有搞定的定位算法,现在我开源给大家。用于非标自动化行业。
    什么是BI报表?
    网址打包微信小程序源码 wap转微信小程序 网站转小程序源码 网址转小程序开发
    【从零开始学习RabbitMQ | 第二篇】如何确保MQ的可靠性和消费者可靠性
    Ubuntu 常规实践操作(一)
    Selenium经典面试题-多窗口切换解决方案
    21. 合并两个有序链表 --力扣 --JAVA
    新零售项目及离线数仓核心面试,,220807,,
    【靶场搭建】-01- 在kali上搭建DVWA靶机
    水果店圈子:开个水果店前景如何,现在做水果店行业还赚钱吗
  • 原文地址:https://blog.csdn.net/GDFHGFHGFH/article/details/133771234