初识JWT
JWT(JSON Web Token)是基于 JSON 的开放标准(RFC 7519),用于在各方之间安全地传递信息。
它由三部分组成:
| 部分 | 用途 | 示例内容 |
|---|---|---|
| Header(头部) | 指定算法和类型 | {“alg”:”HS256″,”typ”:”JWT”} |
| Payload(有效载荷) | 存放声明(Claims),如用户 ID、过期时间等 | {“sub”:”1234567890″,”name”:”Alice”,”exp”:1600000000} |
| Signature(签名) | 保证前两部分不被篡改 | HMACSHA256(Base64Url(Header) + “.” + Base64Url(Payload), Secret) |
Header
Header(头部)通常由两部分组成,分别是令牌的类型和所使用的签名算法(HMAC SHA256、RSA 等),其会组成一个 JSON 对象用于描述其元数据,例如:
{
"alg": "HS256",
"typ": "JWT"
}
- alg 字段表示签名算法,默认是 HMAC SHA256(HS256)
- type 字段表示令牌类型,我们使用的 JWT 令牌类型,在最后会对上面的 JSON 对象进行 base64UrlEncode 算法进行转换成为 JWT 的第一部分。
Base64UrlEncode 是 Base64 算法的变种,在 URL 中,一些个别字符是有特殊意义的,例如:“+”、“/”、“=” 等等,因此在 Base64UrlEncode 算法中,会对其进行替换,例如:
“+”替换为“-”、“/”替换成“_”、“=”会被进行忽略处理,以此来保证 JWT 令牌的在 URL 中的可用性和准确性。
Payload
Payload(有效载荷)存储实际传输的数据
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Payload可以包含自定义字段和通用标准字段
JWT通用标准字段如下
- aud(Audience):受众,也就是接受 JWT 的一方。
- exp(ExpiresAt):所签发的 JWT 过期时间,过期时间必须大于签发时间。
- jti(JWT Id):JWT 的唯一标识。
- iat(IssuedAt):签发时间
- iss(Issuer):JWT 的签发者。
- nbf(Not Before):JWT 的生效时间,如果未到这个时间则为不可用。
- sub(Subject):主题
同样也会对该 JSON 对象进行 base64UrlEncode 算法将其转换为 JWT Token 的第二部分。
注意:敏感信息请不要放到 JWT 中,因为他是可逆的,能够被读取
Signature
Signature(签名)部分是对前面两个部分组合(Header+Payload)进行约定算法和规则的签名,而签名将会用于校验消息在整个过程中有没有被篡改,并且对有使用私钥进行签名的令牌,它还可以验证 JWT 的发送者是否它的真实身份。
在签名的生成上,在应用程序指定了 密钥(secret) 后,会使用传入的指定签名算法(默认是 HMAC SHA256),然后通过下述的签名方式来完成 Signature(签名)部分的生成,如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
因此我们可以看出 JWT 的第三部分是由 Header、Payload 以及 Secret 的算法组成而成的,因此它最终可达到用于校验消息是否被篡改的作用之一,因为如果一旦被篡改,Signature 就会无法对上。
简单使用
Go
Go提供了社区维护的JWT仓库
使用命令:go get github.com/golang-jwt/jwt/v5获取
基本用法案例
type Claims struct {
AppKey string `json:"app_key"`
AppSecret string `json:"app_secret"`
jwt.RegisteredClaims
}
func GetJWTSecret() []byte {
return []byte(global.JWTSetting.Secret)
}
func GenerateToken(appKey, appSecret string) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(global.JWTSetting.Expire)
claims := Claims{
AppKey: utils.EncodeMD5(appKey),
AppSecret: utils.EncodeMD5(appSecret),
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expireTime),
Issuer: global.JWTSetting.Issuer,
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(GetJWTSecret())
return token, err
}
func ParseToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return GetJWTSecret(), nil
})
if err != nil {
return nil, err
}
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}
我们声明的Claims即代表Payload有效载荷
其中可以定义我们自己的业务字段,例如携带用户id等。还有官方标准规范(RFC 7519)中定义的一组通用标准字段
type RegisteredClaims struct {
Issuer string `json:"iss,omitempty"`
Subject string `json:"sub,omitempty"`
Audience ClaimStrings `json:"aud,omitempty"`
ExpiresAt *NumericDate `json:"exp,omitempty"`
NotBefore *NumericDate `json:"nbf,omitempty"`
IssuedAt *NumericDate `json:"iat,omitempty"`
ID string `json:"jti,omitempty"`
}
其中jwt.ParseWithClaims在解析时字段校验规则不同
- 他会对时间敏感字段的默认校验(如
exp,nbf,iat等),例如ExpiresAt存在时会默认对其进行校验 - 而对于身份与空间隔离字段(如
iss,aud等),即使写了也不会默认校验,要么在解密函数中进行校验(在函数后加上jwt.WithIssuer(global.JWTSetting.Issuer)的配置项),要么解密后进行校验
附录
JWT通用标准字段
- Issuer (iss) — 签发者
作用:标记这个 Token 是由哪个服务器、哪个系统或者哪个域名生成的。
场景:比如你之前的配置 Issuer: blog-service。当多租户或微服务网关收到 Token 时,可以通过 iss 判定这个 Token 是不是自家的、或者是哪个第三方认证中心(如 Google、微信)发过来的,从而决定用哪个公钥去验签。 - Subject (sub) — 主题
作用:通常用来存放用户唯一标识(如 User ID)。
场景:在面向用户的系统里,通常会把用户在数据库的自增 ID(如 “10025”)或 UUID 存到 sub 里。当后端通过验证后,直接读取 sub 就能知道当前操作的合法用户是谁。 - Audience (aud) — 受众
作用:指定哪些客户端、业务系统或 API 有权接收并处理这个 Token。
场景:假设你的架构里有“商城系统”和“博客系统”。你登录商城时拿到一个 Token,商城系统签发时设置 aud: “shop-app”。如果你企图拿这个 Token 去请求博客系统,博客系统校验时发现 aud 不是 “blog-app”,就会拒绝访问,防止 Token 被滥用(串门)。 - ExpiresAt (exp) — 过期时间
作用:一个特定的 Unix 时间戳,表示在这个时间之后,Token 彻底失效。
场景:非常核心的安全字段。JWT 具有状态不可控的特性,一旦发出无法轻易撤回。通过设置 exp(比如当前时间 + 2小时),可以保证即便 Token 泄露,黑客也只能在有限时间内利用它。 - NotBefore (nbf) — 生效时间
作用:一个特定的 Unix 时间戳,定义了这个 Token 从什么时间点开始才能被业务系统接受。
场景:比较少用。通常用于“预约/定时激活”的业务。例如系统进行升级,或者给用户发放一个 10 分钟后才生效的临时凭证,在 nbf 时间之前,即使签名正确、没过期,服务器也会拒绝。 - IssuedAt (iat) — 签发时间
作用:一个特定的 Unix 时间戳,记录了这个 Token 被创建出来的时刻。
场景:用来算距离过期还有多久。用于安全审计或密码重置后的 Token 批量失效:比如用户在 12:00 重置了密码,服务器可以设定“所有 iat 早于 12:00 的老 Token 全部拒收”。 - ID (jti) — Token 唯一标识
作用:给这个具体的 Token 分配一个全局唯一的 ID(通常是 UUID)。
场景:主要为了防重放攻击(Replay Attacks)。如果你的接口非常敏感,可以把通过验证的 jti 扔进 Redis 里存 2 小时(跟过期时间一致)。如果黑客拦截了请求想反复提交,服务器查到这个 jti 已经被用过一次了,直接拦截。

评论(0)
暂无评论