JWT 生成和解析 Token

2023年 9月 26日 104.4k 0

什么是 Token

Token(令牌)是一种用于身份验证和授权的机制,它是在用户登录后生成的一段字符串,用于识别用户的身份信息,以便在用户进行后续操作时进行身份验证和授权

Token是服务端生成的一串字符串,用来作为客户端的请求令牌。当用户首次登录成功后,服务端会生成一个token并返回给客户端,此后客户端只需要带上token字段,而无需再上传用户名和密码;但当token过期后,用户又会回到首次登录的状态,需要输入用户名和密码

什么是 JWT

Token的生成和解析通常采用JWT(JSON Web Token)协议,它是一种轻量级的身份验证和授权机制。JWT由三部分组成:头部(Header)载荷(Payload)签名(Signature),其中头部和载荷都是使用Base64 编码JSON 对象,签名是由头部、载荷和密钥生成的哈希值

JWT 的生成过程

  • 创建 JWT 头部(Header)对象,包含算法和类型信息
  • 创建 JWT 载荷(Payload)对象,包含用户信息和过期时间等信息
  • 使用密钥对 JWT 头部和载荷进行签名生成 JWT 签名(Signature)
  • 将 JWT 头部、载荷和签名拼接成一个字符串形式的 JWT
  • Spring Boot 采用了标准的 JWT 头部格式, 如下:
    {
      "alg": "HS256",
      "typ": "JWT"
    }
    因此默认情况下使用的是 SignatureAlgorithm.HS256 对称加密算法
    

    但也支持其他的签名算法,比如:SignatureAlgorithm.RS256非对称加密算法

    需要在 application.properties 或 application.yml 文件中通过对应的属性进行配置---spring.security.jwt.signature-algorithm。例如:如果需要使用 RS256 算法,则可以设置该属性为 RS256

    JWT 生成 token 的代码

    private static final String SECRET = "secret_key";
    private static final long EXPIRATION_TIME = 3600000;
    
    public static String createJwtToken(String userId) {
        Map claims = new HashMap();
        claims.put("userId", userId);
        long timeMillis = System.currentTimeMillis();
        Date expirationDate = new Date(timeMillis + EXPIRATION_TIME);
        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
    }
    

    配置文件里面配置通用变量

    # SECRET 和 EXPIRATION_TIME 也可以在 application.yml 文件中进行配置,这样就可以通用了
    spring:
      security:
        jwt:
          secret: secret_key
          expires: 86400000 # 过期时间,单位为毫秒,这里设置的过期时间为1天
    

    使用配置文件里面配置的通用变量

    // 在代码中获取配置文件的值
    @Value("${spring.security.jwt.secret}")
    private static String SECRET;
    
    @Value("${spring.security.jwt.expires}")
    private static long EXPIRATION_TIME;
    

    方法解析

    builder

    builder: 用于创建 JWT 字符串的静态方法,它是由 Jwts 类提供的。该方法返回一个 JwtBuilder 对象,用于构建JWT并生成 JWT 字符串

    setClaims

    setClaims: 用于设置载荷部分的方法。在 JWT 中,载荷是一个JSON 对象,它包含了一些与用户身份验证和授权相关的信息,例如用户ID、用户名、角色、权限等

    // 用法一
    claims.put("userId", userId);
    return Jwts.builder()
            .setClaims(claims)
            .compact();
                
    // 用法二
    return Jwts.builder()
            .claims("userId", userId)
            .claims("username", username)
            .compact();
    

    setIssuedAt

    setIssuedAt: 用于设置签发时间(issued at)的方法。在 JWT 中,签发时间是一个整数,表示 JWT 的生成时间,通常以秒为单位

    setExpiration

    setExpiration: 用于设置过期时间(expiration)的方法。在 JWT 中,过期时间也是一个整数,表示 JWT 的有效期,通常以秒为单位

    注意:设置签发时间和过期时间的方法传入的时间都是 Date 对象,但 setIssuedAt、setExpiration 会将这个 Date 对象转换为 Unix 时间戳并编码到 JWT 中

    生成时间的多种方法:

    • System.currentTimeMillis(): 获取当前时间的毫秒数,类型为 long, 从而得到时间戳(单位为秒)

    • new Date(): 返回当前时间的 Date 对象,包含当前时间的年、月、日、时、分、秒等信息
      这个方法通常用于获取当前时间的具体信息或用于时间格式化

    • getTime(): Date 对象的方法,用于获取当前时间的时间戳

    • new Date(时间戳): 生成的是一个表示当前时间戳的 Date 对象

    signWith

    signWith: 用于设置签名的方法,可以使用不同的算法来对 JWT 进行签名。JWT 使用签名来保证 JWT 的真实性和完整性,防止 JWT 被篡改或伪造

    signWith 方法接受两个参数,分别是签名算法和密钥
    签名算法的类型必须要和 JWT 头部中的 “alg” 字段一致,否则验证时会提示签名无效
    因此,在使用JWT进行签名时,必须要明确指定签名算法
    
    签名算法是由 SignatureAlgorithm 枚举类提供的,它定义了一系列的签名算法,包括 HMAC 和 RSA
    
    在 signWith 方法中: 如果为对称算法,则密钥应该是一个 byte[] 数组或 String 类型的字符串;
    如果为非对称算法,则密钥应该是一个 Key 类型的对象,例如 RSAPrivateKey 或 RSAPublicKey
    
    对称加密算法,需要使用相同的密钥进行加解密
    非对称加密算法,使用公钥进行加密,私钥进行解密
    
    SignatureAlgorithm.HS512 算法,属于 HMAC-SHA512 对称加密算法
    
    SignatureAlgorithm.HS256 算法,属于 HMAC-SHA256 对称加密算法
    
    SignatureAlgorithm.RS256 算法, 属于 RSA-SHA256 非对称加密算法
    

    compact

    compact: 用于将 JWT 编码为紧凑的字符串形式。即将 JWT 编码为没有空格、没有等号、没有加号、没有斜杠的紧凑字符串形式,以便在 URL 中传输

    方法总览

    方法 含义
    Jwts.builder() 创建一个 JwtBuilder 对象
    JwtBuilder.setClaims() 设置 JWT 的声明(claims)
    JwtBuilder.setIssuer() 设置 JWT 的发行者(issuer)
    JwtBuilder.setSubject() 设置 JWT 的主题(subject)
    JwtBuilder.setAudience() 设置 JWT 的受众(audience)
    JwtBuilder.setIssuedAt() 设置 JWT 的签发时间(issuedAt)
    JwtBuilder.setExpiration() 设置 JWT 的过期时间(expiration)
    JwtBuilder.signWith() 设置 JWT 的签名算法和密钥
    JwtBuilder.compact() 生成 JWT 字符串

    JWT 的解析过程

  • 从 JWT 字符串中解析出头部(Header)、载荷(Payload)和签名(Signature)
  • 验证 JWT 签名是否正确,即:使用密钥对头部和载荷进行签名生成签名(Signature),并与解析出的签名进行比较,如果一致则验证通过
  • 验证 JWT 是否过期,即:检查载荷中的过期时间是否在当前时间之前,如果过期则验证失败
  • 后端获取 token 的方式通常有两种:

    • 通过 HTTP 请求头获取:前端在请求时将 token 放置于 HTTP 请求头中,通常是以Authorization: Bearer 的形式,后端可以通过读取请求头中的Authorization字段获取 token

    • 通过请求参数获取:前端在请求时将 token 以某个参数名的形式传递,例如:token=,后端可以通过解析请求参数获取 token

    一般来说,第一种方式更为常用和安全,因为 HTTP 请求头中的内容不会被浏览器缓存或保存在历史记录中,而请求参数则可能会被保存。因此,建议在实现后端获取 token 的逻辑时,优先考虑使用 HTTP 请求头方式

    private static final String SECRET = "secret_key";
    public static Claims parseToken(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token)
                .getBody();
    }
    

    方法解析

    parser

    parser: 用于解析 JWT 字符串的静态方法,它是由 Jwts 类提供的。该方法接受一个 JWT 字符串,并返回一个 JwtParser 对象,用于解析 JWT 字符串并验证其签名

    setSigningKey

    setSigningKey: 用于设置 JWT 签名密钥的方法,它是由 JwtBuilder 类提供的。该方法接受一个 Key 类型的参数,表示用于签名的密钥

    在生成 JWT 时,需要将密钥传递给 signWith()方法以指定签名算法和密钥
    在验证 JWT 时,也需要将密钥传递给 setSigningKey()方法以验证签名
    
    签名密钥可以是对称密钥(例如HMAC密钥)或非对称密钥(例如RSA密钥对)
    对称密钥适用于需要高性能和低成本的场景
    非对称密钥适用于需要更高的安全性和可扩展性的场景
    

    parseClaimsJws

    parseClaimsJws: 是 JWT 解析方法,用于解析 JWT Token 并获取其中的载荷信息(payload),同时也会验证签名是否正确

    getBody

    getBody: 用于从 JWT 中获取载荷(payload)信息。调用此方法将返回一个 Claims 对象,其中包含了 JWT 载荷中的所有声明信息,可以通过 get 方法获取对应的声明值

    方法总览

    方法 含义
    Jwts.parser() 创建一个 JwtParser 对象
    JwtParser.setSigningKey() 设置 JWT 的签名密钥
    JwtParser.parseClaimsJws() 解析 JWT 字符串并返回一个 Claims 对象,包含了 JWT 中所有的声明(claims)

    使用 JWT 的优点

  • 轻量级,传输效率高
  • 可以保存用户信息,不需要在每次请求时重新获取用户信息
  • 可以防止 CSRF 攻击,因为攻击者无法伪造有效的 JWT
  • 可以跨域使用,因为 JWT 是通过签名验证的,而不需要依赖 cookie 等传统的跨域解决方案
  • 使用 JWT 的缺点

  • JWT 不能撤销,一旦签发了 JWT,就无法撤销,除非等到过期时间到期
  • JWT 中包含用户信息,一旦 JWT 泄露,攻击者就可以获得用户的所有信息
  • JWT 不能修改,一旦 JWT 签发,就无法修改其中的内容,除非等到过期时间到期
  • 使用 JWT 需要密钥,密钥泄露后会导致 JWT 失去安全性
  • 相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论