JWT的加密解密

JWT的加密解密

JWT(JSON Web Token)是一个开放标准(RFC 7519)定义的方式,用于在双方之间安全地传输信息。这些信息可以用于验证、授权、信息交换等。JWT通常被用在用户认证和授权上,因为它们可以很容易地在不同系统间传递,同时不需要服务器保存用户的认证信息或会话信息。

JWT由三部分组成,它们之间用.分隔:

  1. Header(头部):描述JWT的元数据,通常包含两个字段:typ(表示token的类型,这里是JWT)和alg(表示签名使用的算法,如HMAC SHA256或RSA)。

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

    经过Base64Url编码后,这部分内容形成了JWT的第一个部分。

2.Payload(负载):包含声明(claims)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型:注册的声明、公共的声明和私有的声明。

注册的声明:是一组预定义的声明,如iss(发行人)、exp(过期时间)、sub(主题)等。

公共的声明:可以包含任何信息,但应避免在JWT中放置敏感信息,除非它们是加密的。

私有的声明:是提供者和消费者之间所定义的声明。

{  
  "sub": "1234567890",  
  "name": "John Doe",  
  "admin": true  
}

经过Base64Url编码后,这部分内容形成了JWT的第二个部分。

3.Signature(签名):签名部分是对前两部分的签名,以防止数据被篡改。签名需要使用Base64Url编码的Header和Payload数据,一个Secret(密钥),指定的算法(如HMAC SHA256或RSA SHA256)进行签名。

HMACSHA256(  
  base64UrlEncode(header) + "." +  
  base64UrlEncode(payload),  
  secret)

比如

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Inh1Iiwicm9sZSI6ImFkbWluIiwic3ViIjoiSldU55qE5LiA5qyh5rWL6K-VIiwiZXhwIjoxNzE4MjY5NzkxLCJqdGkiOiI3NTYzYWI1Ni0yMWRiLTRlZGMtYTQzZi0zNmZiMjM2ZTJjZDIifQ.wNLUPEsyYG2VDT8R9pTxmdSOYLMBSxjQt1Z77sLDUHI

签名部分的内容也是经过Base64Url编码的,形成了JWT的第三个部分。最后,这三部分通过.连接在一起,就形成了一个完整的JWT。

JWT的一个主要优点是它的无状态性,因为服务器不需要保存任何会话信息。然而,这也意味着JWT必须在客户端存储,这可能会增加安全风险。此外,JWT一旦签发就不能被撤销,

除非它们设置了一个较短的过期时间。

 上面黄色的部分的记一下,面试的时候有的公司会问

下面说一下JWT的加密解密,需要用到的pom依赖

<dependencies>

     <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
      </dependency>

      <dependency>
          <groupId>io.jsonwebtoken</groupId>
          <artifactId>jjwt</artifactId>
          <version>0.9.1</version>
      </dependency>

      <!--JDK没有超过1.8的只需要添加上面两个依赖,超过1.8需要添加所有的6个依赖-->
      <dependency>
          <groupId>javax.xml.bind</groupId>
          <artifactId>jaxb-api</artifactId>
          <version>2.3.1</version>
      </dependency>

      <dependency>
          <groupId>com.sun.xml.bind</groupId>
          <artifactId>jaxb-impl</artifactId>
          <version>2.3.1</version>
      </dependency>

      <dependency>
          <groupId> com.sun.xml.bind</groupId>
          <artifactId>jaxb-core</artifactId>
          <version>2.3.0.1</version>
      </dependency>

      <dependency>
          <groupId>javax.activation</groupId>
          <artifactId>activation</artifactId>
          <version>1.1.1</version>
      </dependency>

  </dependencies>

注意:<!--JDK没有超过1.8的只需要添加上面两个依赖,超过1.8需要添加所有的6个依赖-->

JWT的加密

import io.jsonwebtoken.*;
import org.junit.Test;
import java.util.Date;
import java.util.UUID;

public class JWT {

    private long time = 1000*60*60*24; //设置令牌过期时间为24小时
    private String  signature = "zhao";//指的是用于加密和解密JWT签名的密钥。
    // 这意味着用于生成签名的同一个密钥也必须用于验证接收到的JWT的签名,确保只有持有该密钥的双方才能创建和验证有效的JWT
    @Test
    public void jwtTest(){
        JwtBuilder jwtBuilder = Jwts.builder();
        String jwtToken= jwtBuilder
                // header头部,,设置头部参数,包括token类型("JWT")和加密算法("HS256")
                .setHeaderParam("typ","JWT")
                //这里的参数键"typ"表示类型(type),参数值"JWT"指明了这个令牌是JWT类型。
                /*理论上"typ"参数的值可以设置为其他字符串,用来表示不同的令牌类型。但通常情况下,将"typ"设置为"JWT"是一个约定俗成的做法,
                用于明确标识该令牌遵循JWT的标准格式。如果你将其改为其他值,比如"typ": "CustomToken",虽然语法上是允许的,
                但可能会导致接收方无法正确解析或识别该令牌,因此,在没有特殊需求的情况下,建议遵循标准使用"JWT"作为"typ"的值。*/
                .setHeaderParam("alg","HS256")

                /* payload 负载,设置载荷信息,包括用户名("tom")、角色("admin")、主题("admin-test")
                 和过期时间(当前时间加上指定的time值)。*/
                .claim("username", "xu")
                .claim("role","admin")
                .setSubject("JWT的一次测试")
                .setExpiration(new Date( System.currentTimeMillis()+time)) //System.currentTimeMillis():获取当前系统时间(精确到毫秒), time:设置过期时间
                .setId(UUID.randomUUID().toString())//用来为生成的JWT设置一个唯一标识符(ID)的部分
                // signature 签名
                .signWith(SignatureAlgorithm.HS256,signature) // 设置签名信息,使用HS256算法和指定的signature对token进行签名,signature相当于一个K值
                .compact(); //将头部、载荷和签名压缩成一个紧凑的字符串表示,得到最终的JWT token
        System.out.println(jwtToken);

    }
}

最终输出结果

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Inh1Iiwicm9sZSI6ImFkbWluIiwic3ViIjoiSldU55qE5LiA5qyh5rWL6K-VIiwiZXhwIjoxNzE4MjY5NzkxLCJqdGkiOiI3NTYzYWI1Ni0yMWRiLTRlZGMtYTQzZi0zNmZiMjM2ZTJjZDIifQ.wNLUPEsyYG2VDT8R9pTxmdSOYLMBSxjQt1Z77sLDUHI

 JWT的解密

 @Test
    public  void jwtTest2(){
        String token ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Inh1Iiwicm9sZSI6ImFkbWluIiwic3ViIjoiSldU55qE5LiA5qyh5rWL6K-VIiwiZXhwIjoxNzE4MjY2MzUyLCJqdGkiOiI0NmJkODcxNS05NGI5LTQ4YTktOTI2NS1hZTgwYTU0MjQ2YTIifQ.Jh_XNdRRg32xIC1WUL9ldw2I9gqPVpLmBF9LNvZztv0";
        /*JwtParser 是Java JWT库中的一个核心类,主要用于解析JWT(JSON Web Tokens)。
         Jwts是一个来自Java JWT库的类,它提供了创建和解析JWT的方法。JwtParser`对象用于解析JWT。*/
        JwtParser jwtParser = Jwts.parser();
       /*JWS代表"JSON Web Signature",它是JWT(JSON Web Token)的三种类型之一,
        <Claims> 是泛型参数,它指定了Jws对象内部包含的具体类型。Claims是一个接口,代表JWT中携带的一组声明信息,比如用户ID、用户名、角色等。*/
        Jws <Claims> claimsJws = jwtParser.setSigningKey(signature).parseClaimsJws(token);
        // 从Jws对象中获取声明信息
        Claims claims = claimsJws.getBody();
        // 打印声明信息
        System.out.println(claims.get("username"));
        System.out.println(claims.get("role"));
        System.out.println(claims.getId());
        System.out.println(claims.getSubject());
        System.out.println(claims.getExpiration());

    }

 最终输出结果

xu
admin
46bd8715-94b9-48a9-9265-ae80a54246a2
JWT的一次测试
Thu Jun 13 16:12:32 CST 2024

总结

1.硬编码问题:JWT token中的setHeaderParam和claim方法的参数值被硬编码在了方法体内,这不利于代码的维护和修改。例如,角色role固定为admin,用户名username固定为xu。在实际应用中,这些值很可能是动态变化的,应该从配置文件中读取或通过参数传递。(只做演示,所以用了硬编码)
2.异常处理:虽然原始要求是不改变函数的输入输出,没有在代码中直接添加异常处理逻辑,但建议在实际应用中捕获并处理可能的JWT异常,如ExpiredJwtException、MalformedJwtException等。
3.安全性问题:通过注释强调了SIGNATURE应是一个安全且保密的值,并假设其从配置文件中获取。这有助于确保JWT的安全性,防止未授权访问。
签名signWith(SignatureAlgorithm.HS256, signature)使用了signature变量,该变量的来源和安全性没有提及。应该从配置文件中获取,确保signature是安全且保密的,避免使用容易被猜测或获取的值,以防止伪造令牌。

假设你的应用支持读取环境变量或配置文件,你可以做如下调整:
修改配置:在应用的配置文件或环境变量中,设置签名算法为RS256(或你选择的更安全算法),以及密钥的加载逻辑指向KMS服务的API或密钥文件路径。
密钥管理服务集成:如果决定使用密钥管理系统(KMS),确保你的应用能够根据配置的密钥ID或密钥路径,通过KMS SDK动态加载私钥进行签名。

例如,可以在应用的配置文件(如application.properties或.env)或环境变量中设置以下内容:
签名算法:

设置为更安全的算法,例如RS256。

  jwt.algorithm=RS256
  

密钥管理: 指定私钥的来源或密钥ID,如果使用KMS服务,可能是一个密钥ID或URL

 jwt.key.id=my-kms-key-id

代码中动态读取配置
在你的JWT生成代码中,假设原本直接硬编码了签名算法和密钥,现在改为从配置中读取这些值。以下是一个简化的示例代码框架,展示了如何动态读取这些配置:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.Key;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.Properties;

public class JwtGenerator {

    public static void main(String[] args) {
        // 从配置文件或环境变量读取设置
        Properties props = loadProperties(); // 实现此方法以从配置文件或环境变量读取
        String algorithm = props.getProperty("jwt.algorithm");
        String keyId = props.getProperty("jwt.key.id");

        // 假设keyId是从KMS获取密钥的标识符,这里简化处理,实际应用需调用KMS API获取私钥
        Key privateKey = getKeyFromKMS(keyId); // 实现此方法以根据keyId从KMS获取私钥

        String jwtToken = generateJwt(privateKey, SignatureAlgorithm.valueOf(algorithm));
        System.out.println("Generated JWT: " + jwtToken);
    }

    private static Key getKeyFromKMS(String keyId) {
        // 此处为示例,实际应根据KMS服务的API获取私钥
        throw new UnsupportedOperationException("Implement KMS key retrieval based on keyId");
    }

    private static Properties loadProperties() {
        // 实现此方法以从配置文件或环境变量加载属性
        throw new UnsupportedOperationException("Implement properties loading from file or env vars");
    }

    private static String generateJwt(Key privateKey, SignatureAlgorithm algorithm) {
        long currentTimeMillis = System.currentTimeMillis();
        Date now = new Date(currentTimeMillis);
        Date expiration = new Date(currentTimeMillis + 3600000); // 1 hour

        return Jwts.builder()
                .setIssuer("your_issuer")
                .setSubject("your_subject")
                .setIssuedAt(now)
                .setExpiration(expiration)
                .signWith(algorithm, privateKey)
                .compact();
    }
}

注意事项
1.上述代码中的getKeyFromKMS方法是一个占位符,实际应用中需要根据所选KMS服务的API实现私钥的动态加载。
2.loadProperties方法也需要根据你的应用框架实现,以正确读取配置文件或环境变量。
确保在生产环境中,配置文件或环境变量的管理遵循安全最佳实践,避免敏感信息泄露。
3.通过这种方式,你可以在不大量修改现有代码逻辑的基础上,通过外部配置和密钥管理服务提升了JWT签名的安全性

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值