提示:本文摘自「云原生AI实战营」中的「Go项目开发中级实战课」体系课。
在实际开发中,使用令牌认证,还有其他很多好处。掌握令牌认证的原理和实现方法,是 Go 语言开发者,必备的核心技能之一。
由于 miniblog 使用 JWT Token 进行身份认证,为了降低学习难度并为后续代码实现奠定基础,本节课将介绍 JWT 的核心内容。
JWT 认证流程
学习 JWT 的最佳方式是通过其认证流程理解其原理。认证流程如下图所示。
上图展示了 JWT 的认证流程,具体流程如下:
- 客户端(通常是前端)通过用户名和密码进行登录;
- 服务端收到请求后会验证用户名和密码,若与数据库记录不一致,则认证失败,若一致,则认证通过。认证通过后,服务端会签发一个具有有效期的 Token 并返回给客户端;
- 客户端接收到 Token 后会将其缓存,例如存储在浏览器的 Cookie 或本地存储中,方便下次调用时使用;
- 客户端在之后的每次 API 请求中携带缓存的 Token;
- 服务端接收到请求后会验证请求中携带的 Token,验证通过后继续处理业务逻辑并返回数据;
- 如果 Token 快过期,前端会调用 Token 刷新接口续期 Token,避免用户再次登录。之后,会使用续期后的 Token 发送 API 请求。
提示: Go 项目开发中,Token 有效期通常设置为 2 小时。
JWT Token 格式
在 JWT 中,Token 由 Header、Payload、Signature 三部分组成,中间用英文点号(.)隔开,并使用 Base64 编码。JWT Token 示例如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzkwNzgwMDUsImlhdCI6MTczNTQ3ODAwNSwibmJmIjoxNzM1NDc4MDA1LCJ4LXVzZXItaWQiOiJ1c2VyLXc2aXJrZyJ9.GromRG7kK90UfU_Q5iOSHs_xE-zSk0e0HLHqJQUjYMU
(1)Header 介绍
JWT Token 的 Header 中包含两部分信息:Token 的类型和 Token 所使用的加密算法。JWT Header 示例如下:
{
"typ": "JWT",
"alg": "HS256"
}
上述示例表明,Token 类型是 JWT,加密算法为 HS256(alg 支持多种加密算法)。
(2)Payload 载荷介绍
Payload 中携带了 Token 的具体内容,其中包含一些标准字段,当然也可以添加额外字段以表达更丰富的信息。这些信息可以用于更复杂的处理场景,例如记录请求的用户 ID、用户名等。标准字段包括:
iss:JWT Token 的签发者;sub:主题;exp:JWT Token 的过期时间;aud:接收 JWT Token 的一方;iat:JWT Token 的签发时间;nbf:JWT Token 的生效时间;jti:JWT Token 的唯一标识(ID)。
Payload 示例如下所示:
{
"id": 2,
"userID": "user-p7q78j",
"nbf": 1527931805,
"iat": 1527931805
}
(3)Signature 签名介绍
Signature 是 Token 的签名部分,其生成方式如下:
- 使用 Base64 对
header.payload进行编码; - 使用密钥(Secret)对编码后的内容进行加密,加密后的内容即为 Signature。
密钥相当于一个密码,存储在服务端,通常通过配置文件设置密钥的值。
最终生成的 Token 如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzkwNzgwMDUsImlhdCI6MTczNTQ3ODAwNSwibmJmIjoxNzM1NDc4MDA1LCJ4LXVzZXItaWQiOiJ1c2VyLXc2aXJrZyJ9.GromRG7kK90UfU_Q5iOSHs_xE-zSk0e0HLHqJQUjYMU
签名后,服务端会返回生成的 Token。客户端在下次请求时会携带该 Token,服务端收到 Token 后会解析出 header.payload,然后使用相同的加密算法和密码对 header.payload 再次加密,并将加密后的 Token 与收到的 Token 进行比对。如果二者相同,则验证通过;如果不相同,则返回 HTTP 401 Unauthorized 错误。
JWT Token 生成示例
下述代码展示了具体如何生成一个 JWT Token,通过代码可以详细的了解 Token 生成的方式:
#!/bin/bash
# 定义Header
HEADER='{"alg":"HS256","typ":"JWT"}'
# 定义Payload
PAYLOAD='{"exp":1739078005,"iat":1735478005,"nbf":1735478005,"x-user-id":"user-w6irkg"}'
# 定义Secret(用于签名)
SECRET="Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5"
# 1. Base64编码Header
HEADER_BASE64=$(echo -n "${HEADER}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
# 2. Base64编码Payload
PAYLOAD_BASE64=$(echo -n "${PAYLOAD}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
# 3. 拼接Header和Payload为签名数据
SIGNING_INPUT="${HEADER_BASE64}.${PAYLOAD_BASE64}"
# 4. 使用HMAC SHA256算法生成签名
SIGNATURE=$(echo -n "${SIGNING_INPUT}" | openssl dgst -sha256 -hmac "${SECRET}" -binary | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
# 5. 拼接最终的JWT Token
JWT="${SIGNING_INPUT}.${SIGNATURE}"
# 输出JWT Token
echo "Generated JWT Token:"
echo "${JWT}"
miniblog 实现 HTTPS 通信
如果你想学习真实项目中,如何设计和实现 HTTPS 通信,可阅读 github.com/onexstack/m… 源码。