一文读懂 JWT

最近在做网页登录权限认证的时候,由于我使用的框架是 SpringBoot ,在网上参考了大量资料后,我发现 SpringSecurity + JWT 出现频率很高,于是我去了解了一下 JWT,以下是我做的一些记录。

1 什么是 JWT

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用 JSON 对象在各方之间安全地传输信息。此信息是经过数字签名的,因此可以验证和信任。

以上是来自 https://jwt.io/ 的一段介绍,从中我们得出 JWT 是一个 Token(令牌),用于 web 中数据的安全传输。

利用 Token 进行登录验证的步骤:

  1. 用户输入账号密码点击登录
  2. 后台收到账号密码,验证是否合法用户
  3. 后台验证是合法用户,生成一个 Token返回给用户
  4. 用户收到该 Token 并将其保存在每次请求的请求头中
  5. 后台每次收到请求都去查询请求头中是否含有正确的 Token,只有 Token 验证通过才会返回请求的资源。

这种基于 Token 的认证方式相比较于基于传统的 cookie 和 session 方式更加节约资源,并且对移动端和分布式系统支持更加友好,其优点有:

  • 支持跨域访问:cookie 是不支持跨域的,而 Token 可以放在请求头中传输
  • 无状态:Token 自身包含了用户登录的信息,无需在服务器端存储 session
  • 移动端支持更好:当客户端不是浏览器时,cookie 不被支持,采用 Token 无疑更好
  • 无需考虑 CRSF:不使用 cookie,也就无需考虑 CRSF 的防御

而 JWT 就是上述 Token 的一种具体实现方式,其本质就是一个字符串, 是将用户信息存储到 JSON 中然后经过编码得到的字符串

2 JWT 的结构

JWT 由三部分组成,分别是 Header(头部)、Payload(有效载荷)、Signature(签名),用点(.)将三部分隔开便是 JWT 的结构,形如xxxxx.yyyyyy.zzzzz的字符串

2.1 Header

JWT Header 由两部分组成,是一个描述 JWT 元数据的JSON对象,alg 属性表示签名使用的算法,默认为 HMAC SHA256(写为HS256);typ 属性表示 Token 的类型,统一写为 JWT。最后,使用 Base64 URL 算法将上述 JSON 对象转换为字符串保存

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

2.2 Payload

payload 是 JWT 的主体部分,保存实体(通常是用户)信息,每一个字段就是一个 claim(声明),JWT 为我们提供了一些默认字段

iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT

我们也可以自定义私有字段,比如用来保存用户信息

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

payload 字段会使用 BaseUrl 编码成字符串组成 JWT 的第二个部分

2.3 Signature

签名部分是对上面两部分数据签名,需要使用 base64 编码后的 header 和 payload 数据,通过指定的算法生成哈希,以确保数据不会被篡改。首先,需要指定一个密钥(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用header中指定的签名算法(默认情况下为 HMAC SHA256)根据以下公式生成签名

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

2.4 注意事项

  1. Header 和 Payload 只是简单的利用 Base64 编码,是可逆的,因此不要在 Payload 中存储敏感信息
  2. Signature 使用的是不可逆的加密算法,无法解码出原文,它的作用是校验 Token 有没有被篡改。该算法需要我们自己指定一个密钥,这个密钥存储在服务端,不能泄露
  3. 尽量避免在 JWT 中存储大量信息,因为一些服务器接收的 HTTP 请求头最大不超过 8KB

3 如何使用 JWT

官网中选择开发语言,可以看到官方给我们推荐的库,这里以 Java 为例

比较推荐使用 java-jwt以及jjwt-root(star 最多,用户量大),去官方 GitHub 仓库学习使用方法

Java 中使用 JWT

首先利用你喜欢的方式引入依赖,我这里利用 Maven

<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>4.4.0</version>
</dependency>

创建 JWT

public class JWTTest {
    @Test
    public void testGreateToken(){
        // 指定token过期时间为10秒
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, 10);

        String token = JWT.create()
                .withHeader(new HashMap<>())  // Header
                .withClaim("userId", 1001)  // Payload
                .withClaim("userName", "user")
                .withExpiresAt(calendar.getTime())  // 过期时间
                .sign(Algorithm.HMAC256("YOUR_SECRETKEY"));  // 签名用的secret

        System.out.println(token);
    }
}

解析 JWT

@Test
public void testResolveToken(){
    // 创建解析对象,使用的算法和secret要与创建token时保持一致
    JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("YOUR_SECRETKEY")).build();
    // 解析指定的token
    DecodedJWT decodedJWT = jwtVerifier.verify(token);
    // 获取解析后的token中的payload信息
    Claim userId = decodedJWT.getClaim("userId");
    Claim userName = decodedJWT.getClaim("userName");
    System.out.println(userId.asInt());
    System.out.println(userName.asString());
    // 输出超时时间
    System.out.println(decodedJWT.getExpiresAt());
}

可以将上述方法封装成 JWT 工具类在项目中使用

此外,除了对称加密方式(加密和解密用同样的密钥),还可以选择非对称加密,即指定两把密钥(公钥和私钥),用其中一把密钥加密必须用另外一把密钥解密

实际开发中需要提升 JWT 的安全性:

  • JWT 是在请求头中传递的,为了避免网络劫持推荐使用 HTTPS
  • JWT 可以使用暴力枚举来破解,因此可以定期更换密钥,不要使用过于简单的密钥

4 参考资料

https://jwt.io/introduction

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值