JWT

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。以下是关于JWT的关键信息:

JWT基本概念

  • 定义:JWT是一种基于JSON的开放标准,用于在各方之间安全地传输声明
  • 用途:常用于身份验证和信息交换
  • 结构:由三部分组成,用点(.)分隔

JWT的三个组成部分

  1. Header(头部)
    • 包含令牌类型和签名算法信息
    • 例如:{"alg": "HS256", "typ": "JWT"}
  2. Payload(载荷)
    • 包含实际要传输的数据(声明)
    • 可以包含标准声明(如iss、exp、sub等)或自定义声明
  3. Signature(签名)
    • 用于验证令牌的完整性
    • 通过签名算法对前两部分进行签名生成

主要特点

  • 无状态:服务端不需要存储会话信息
  • 跨域支持:可以在不同域之间传递
  • 紧凑性:体积小,适合在URL、POST参数或HTTP头中传输
  • 自包含:载荷中包含用户所需的所有信息

常见用途

  • 身份验证令牌
  • 单点登录(SSO)
  • API认证
  • 信息交换

JWT广泛应用于现代Web应用和API安全认证中。

起源

需要了解一门技术,首先从为什么产生开始说起是最好的。JWT主要用于用户登录鉴权,所以我们从最传统的session认证开始说起。

session认证

众所周知,http协议本身是无状态的协议,那就意味着当有用户向系统使用账户名称和密码进行用户认证之后,下一次请求还要再一次用户认证才行。因为我们不能通过http协议知道是哪个用户发出的请求,所以如果要知道是哪个用户发出的请求,那就需要在服务器保存一份用户信息(保存至session),然后在认证成功后返回cookie值传递给浏览器,那么用户在下一次请求时就可以带上cookie值,服务器就可以识别是哪个用户发送的请求,是否已认证,是否登录过期等等。这就是传统的session认证方式。session认证的缺点其实很明显,由于session是保存在服务器里,所以如果分布式部署应用的话,会出现session不能共享的问题,很难扩展。于是乎为了解决session共享的问题,又引入了redis,接着往下看。

token认证

这种方式跟session的方式流程差不多,不同的地方在于保存的是一个token值到redis,token一般是一串随机的字符(比如UUID),value一般是用户ID,并且设置一个过期时间。每次请求服务的时候带上token在请求头,后端接收到token则根据token查一下redis是否存在,如果存在则表示用户已认证,如果token不存在则跳到登录界面让用户重新登录,登录成功后返回一个token值给客户端。优点是多台服务器都是使用redis来存取token,不存在不共享的问题,所以容易扩展。缺点是每次请求都需要查一下redis,会造成redis的压力,还有增加了请求的耗时,每个已登录的用户都要保存一个token在redis,也会消耗redis的存储空间。有没有更好的方式呢?接着往下看。

什么是JWT

JWT(全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。上面说法比较文绉绉,简单点说就是一种认证机制,让后台知道该请求是来自于受信的客户端。

流程描述一下:

  1. 用户使用账号、密码登录应用,登录的请求发送到Authentication Server。 Authentication Server进行用户验证,然后创建JWT字符串返回给客户端。
  2. 客户端请求接口时,在请求头带上JWT。
  3. Application Server验证JWT合法性,如果合法则继续调用应用接口返回结果。可以看出与token方式有一些不同的地方,就是不需要依赖redis,用户信息存储在客户端。所以关键在于生成JWT,和解析JWT这两个地方。

JWT的数据结构

JWT一般是这样一个字符串,分为三个部分,以”.”隔开:xxxxx.yyyyy.zzzzz

JWT第一部分是头部分,它是一个描述JWT元数据的Json对象,通常如下所示。

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

alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256),typ属性表示令牌的类型,JWT令牌统一写为JWT。 最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。

Payload

JWT第二部分是Payload,也是一个Json对象,除了包含需要传递的数据,还有七个默认的字段供选择。 分别是,iss:发行人、exp:到期时间、sub:主题、aud:用户、nbf:在此之前不可用、iat:发布时间、jti:JWT ID用于标识该JWT。 如果自定义字段,可以这样定义:

1
2
3
4
5
6
7
8
{
    //默认字段
    "sub":"主题123",
    //自定义字段
    "name":"java技术爱好者",
    "isAdmin":"true",
    "loginTime":"2021-12-05 12:00:03"
}

需要注意的是,默认情况下JWT是未加密的,任何人都可以解读其内容,因此如果一些敏感信息不要存放在此,以防信息泄露。 JSON对象也使用Base64 URL算法转换为字符串保存。

Signature

JWT第三部分是签名。是这样生成的,首先需要指定一个secret,该secret仅仅保存在服务器中,保证不能让其他用户知道。 然后使用Header指定的算法对Header和Payload进行计算,然后就得出一个签名哈希。也就是Signature。 那么Application Server如何进行验证呢?可以利用JWT前两段,用同一套哈希算法和同一个secret计算一个签名值,然后把计算出来的签名值和收到的JWT第三段比较,如果相同则认证通过。

JWT的优点

  • json格式的通用性,所以JWT可以跨语言支持,比如Java、JavaScript、PHP、Node等等。
  • 可以利用Payload存储一些非敏感的信息。
  • 便于传输,JWT结构简单,字节占用小。
  • 不需要在服务端保存会话信息,易于应用的扩展。

总结

最后讲讲JWT的缺点,任何技术都不是完美的,所以我们得用辩证思维去看待任何一项技术。

  • 安全性没法保证,所以jwt里不能存储敏感数据。因为jwt的payload并没有加密,只是用Base64编码而已。
  • 无法中途废弃。因为一旦签发了一个jwt,在到期之前始终都是有效的,如果用户信息发生更新了,只能等旧的jwt过期后重新签发新的jwt。
  • 续签问题。当签发的jwt保存在客户端,客户端一直在操作页面,按道理应该一直为客户端续长有效时间,否则当jwt有效期到了就会导致用户需要重新登录。那么怎么为jwt续签呢?最简单粗暴就是每次签发新的jwt,但是由于过于暴力,会影响性能。如果要优雅一点,又要引入Redis解决,但是这又把无状态的jwt硬生生变成了有状态的,违背了初衷。

所以印证了那句话,没有最好的技术,只有适合的技术。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

// 好的,以下是一个使用 C# 语言编写的 JWT 示例程序,您可以参考它来编写您自己的 JWT 程序:

using System;
using System.Text;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;

class Program
{
    static void Main(string[] args)
    {
        // 定义 JWT 的签名密钥
        var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("my_secret_key"));

        // 定义 JWT 的签名算法
        var signingCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);

        // 定义 JWT 的负载
        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, "1234567890"),
            new Claim(JwtRegisteredClaimNames.NameId, "John Smith"),
            new Claim(JwtRegisteredClaimNames.Email, "john.smith@example.com"),
            new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
        };

        // 创建 JWT 的描述信息
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(claims),
            Expires = DateTime.UtcNow.AddDays(7),
            SigningCredentials = signingCredentials
        };

        // 创建 JWT
        var tokenHandler = new JwtSecurityTokenHandler();
        var token = tokenHandler.CreateToken(tokenDescriptor);

        // 输出 JWT
        Console.WriteLine(tokenHandler.WriteToken(token));
    }
}
// 在上面的示例程序中,我们首先定义了 JWT 的签名密钥和签名算法。然后我们定义了 JWT 的负载,即包含了一些标准声明和自定义声明。接着我们创建了 JWT 的描述信息,并设置了 JWT 的过期时间和签名凭证。最后我们通过 JwtSecurityTokenHandler 类的 CreateToken 方法创建了 JWT,并通过 WriteToken 方法将 JWT 输出到控制台。

// 希望这个示例程序能够帮助您编写您自己的 JWT 程序!


using System;
using System.Text;
using System.Security.Cryptography;

class Program
{
    static void Main(string[] args)
    {
        // 1. 设置 JWT 的 header 和 payload
        var header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
        var payload = "{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"iat\":1516239022}";

        // 2. 对 header 和 payload 进行 base64 编码
        var encodedHeader = Base64UrlEncode(Encoding.UTF8.GetBytes(header));
        var encodedPayload = Base64UrlEncode(Encoding.UTF8.GetBytes(payload));

        // 3. 使用 HMACSHA256 算法生成签名
        var key = Encoding.UTF8.GetBytes("your-256-bit-secret");
        var hmac = new HMACSHA256(key);
        var signature = hmac.ComputeHash(Encoding.UTF8.GetBytes($"{encodedHeader}.{encodedPayload}"));

        // 4. 对签名进行 base64 编码
        var encodedSignature = Base64UrlEncode(signature);

        // 5. 拼接 JWT
        var jwt = $"{encodedHeader}.{encodedPayload}.{encodedSignature}";

        // 6. 输出 JWT
        Console.WriteLine(jwt);
    }

    static string Base64UrlEncode(byte[] input)
    {
        var output = Convert.ToBase64String(input);
        output = output.Replace("+", "-").Replace("/", "_").Replace("=", "");
        return output;
    }
}

OAuth

在学习OAuth时,会有很多疑问OAuth到底是什么,本文介绍OAuth工作机制.

什么是OAuth?

OAuth 不是一个API或者服务,而是一个验证授权(Authorization)的开放标准,所有人都有基于这个标准实现自己的OAuth。

更具体来说,OAuth是一个标准,app可以用来实现secure delegated access. OAuth基于HTTPS,以及APIs,Service应用使用access token来进行身份验证。

OAuth主要有OAuth 1.0a和OAuth 2.0两个版本,并且二者完全不同,且不兼容。OAuth2.0 是目前广泛使用的版本,我们多数谈论OAuth时,为OAuth2.0。

为什么要有OAuth?

在OAuth之前,HTTP Basic Authentication, 即用户输入用户名,密码的形式进行验证, 这种形式是不安全的。OAuth的出现就是为了解决访问资源的安全性以及灵活性。OAuth使得第三方应用对资源的访问更加安全。