什么是JWT?
JWT 全称 JSON Web Token,通过数字签名的方式,以JSON对象为载体,在不同的服务终端之间安全的传输信息。
为什么有JWT
JWT 相较于传统的基于会话(Session-cookie、token)的认证机制,具有以下优势:
-
无需服务器存储状态:传统的基于会话的认证机制需要服务器在会话中存储用户的状态信息,包括用户的登录状态、权限等。而使用 JWT,服务器无需存储任何会话状态信息,所有的认证和授权信息都包含在 JWT 中,使得系统可以更容易地进行水平扩展。
-
跨域支持:由于 JWT 包含了完整的认证和授权信息,因此可以轻松地在多个域之间进行传递和使用,实现跨域授权。
-
适应****微服务架构:在微服务架构中,很多服务是独立部署并且可以横向扩展的,这就需要保证认证和授权的无状态性。使用 JWT 可以满足这种需求,每次请求携带 JWT 即可实现认证和授权。
-
自包含:JWT 包含了认证和授权信息,以及其他自定义的声明,这些信息都被编码在 JWT 中,在服务端解码后使用。JWT 的自包含性减少了对服务端资源的依赖,并提供了统一的安全机制。
-
扩展性:JWT 可以被扩展和定制,可以按照需求添加自定义的声明和数据,灵活性更高。
总结来说,使用 JWT 相较于传统的基于会话的认证机制,可以减少服务器存储开销和管理复杂性,实现跨域支持和水平扩展,并且更适应无状态和微服务架构。
JWT的常见使用情景
1. 用户认证 (Authentication)
- 流程:
- 用户通过用户名和密码登录。
- 服务器验证用户信息后生成 JWT,并将其返回给客户端。
- 客户端将 JWT 存储在浏览器的 localStorage 或 sessionStorage,或作为 cookie 保存。
- 客户端每次向服务器发起请求时,在请求头的
Authorization字段携带 JWT(例如:Authorization: Bearer <token>)。 - 服务器验证 JWT 后处理请求。
- 优点:
- 无需在服务器存储会话状态(无状态认证)。
- 可支持跨域访问。
2. 授权 (Authorization)
在某些应用中,JWT 用于决定用户是否有权限访问某些资源或执行某些操作。
- 示例:
- 用户登录后获得 JWT,JWT 中包含用户角色(如
admin或user)。 - 服务器根据角色信息判断用户是否有权限执行请求。
- 用户登录后获得 JWT,JWT 中包含用户角色(如
3. 信息交换 (Information Exchange)
JWT 是一种安全的方式,用于在双方之间传递信息,信息在签名后不可篡改。
- 应用场景:
- 微服务之间安全地传递用户上下文。
- 在客户端和服务端之间传递额外的元数据。
4. 单点登录 (Single Sign-On, SSO)
JWT 的无状态和易传递特性非常适合 SSO 场景:
- 用户登录后,JWT 在多个独立的服务或应用间流转,用于身份验证和授权。
5. 微服务通信
在分布式系统或微服务架构中,JWT 可用于:
-
服务之间的身份认证。
-
附带额外上下文信息(例如:用户 ID、请求来源)
JWT的结构
JWT 由三部分组成:
-
头部 Header :包含了关于生成该JWT的信息以及所使用的算法类型。通常包括:
-
alg:签名的算法
-
typ:声明令牌的类型,通常为JWT
-
-
载荷 PlayLoad / 声明 Claims : 包含了要传递的数据,例如身份信息和其他附属数据。JWT 官方规定了 7 个字段:
-
iss (Issuer):签发者。
-
sub (Subject):主题。表示令牌的用户或主体
-
aud (Audience):接收者。
-
exp (Expiration time):过期时间。
-
nbf (Not Before):生效时间。
-
iat (Issued At):签发时间。
-
jti (JWT ID):编号。唯一标识,用于防止重放攻击
-
-
签名 Signature :使用密钥对头部和载荷进行签名,以验证其完整性。
在使用中一般只需要在载荷中定义需要的字段和新增自定义字段。
加密后三部分由 . 分割
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InBzcSIsImlzcyI6IlBhbmciLCJleHAiOjE3MzE3Mjc5MzYsIm5iZiI6MTczMTcyNzkzMX0.v4v2FTJlouJum6gAOuhLOT81VhobCGa-DaMad7oYmiA
JWT的不足
-
JWT的加密容易破解,因此不能用JWT传送密码等隐私信息。
-
JWT将信息保存到JSON中运输,并且未提供压缩,这对网络资源占用大。
GO中使用JWT
package main
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
"time"
)
type MyClaims struct {
Username string `json:"username"`
jwt.RegisteredClaims // 这个实现了Claims接口
}
// 文档:https://pkg.go.dev/github.com/golang-jwt/jwt/v5
func main() {
// 定义一个加密key
mySigningKey := []byte("this is a key")
// 使用自定义结构体方式
myClaims := MyClaims{
Username: "psq",
RegisteredClaims: jwt.RegisteredClaims{
NotBefore: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(5 * time.Second)),
Issuer: "Pang",
},
}
// 新建一个token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaims)
fmt.Printf("%v\n", token)
// 得到加密字符串
s, err := token.SignedString(mySigningKey)
if err != nil {
fmt.Println(err)
}
fmt.Println(s)
// ------------------------------ //
// 解密
token, err = jwt.ParseWithClaims(s, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return mySigningKey, nil
})
fmt.Println(token)
if err != nil {
fmt.Println(err)
}
// 取出Claims部分,解析回可用的结构体
claims, ok := token.Claims.(*MyClaims)
if !ok {
fmt.Println("Err!")
}
fmt.Println(claims)
fmt.Println(claims.Username)
// 使用map
tokenWithMap := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": "PSQ-Map",
"exp": jwt.NewNumericDate(time.Now().Add(5 * time.Second)),
"nbf": jwt.NewNumericDate(time.Now()),
"iss": "qiang",
})
// 加密
signedString, _ := tokenWithMap.SignedString(mySigningKey)
// 解密
tokenWithMap, err = jwt.ParseWithClaims(signedString, &jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
return mySigningKey, nil
})
mapClaims := tokenWithMap.Claims.(*jwt.MapClaims)
fmt.Println(mapClaims)
}