JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。
在认证的时候,即当用户使用凭证成功登录成功后,一个JSON Web Token将会被返回,即token。该字符串即时用户凭证了,当用户想访问受保护的路由或者资源时,用户代理通常是浏览器都应该带上该凭证,通常是放在Authorization Header中。服务器会检查该JWT是否有效,如果有效则才能访问成功。JWT可以携带部分数据以减少数据库压力。
1、无状态。不在服务端存储,减少服务端压力。
2、一次性的。在jwt失效之前都是有效的,信息更新重新签发的新的JWT,旧的还没过期,依旧可以绕过验证。需要服务端有额外的逻辑,防止再次使用。引入redis,其实有点违背特点1了。
3、jwt太长。若在jwt中放入其他数据很可能会出现,http请求Header>Body,因而使用开销略大。
JWT(JSON Web Token)是一个非常轻巧的规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息,一个JWT由三部分组成,Header头部、Claims载荷、Signature签名
{ "typ": "JWT","alg": "HS256"}
type StandardClaims struct {
// 受众
Audience ClaimStrings `json:"aud,omitempty"`
// 过期时间
ExpiresAt *Time `json:"exp,omitempty"`
// 编号
ID string `json:"jti,omitempty"`
// 签发时间
IssuedAt *Time `json:"iat,omitempty"`
// 签发人
Issuer string `json:"iss,omitempty"`
// 生效时间
NotBefore *Time `json:"nbf,omitempty"`
// 主题
Subject string `json:"sub,omitempty"`
}
type Token struct {
Raw string // The raw token. Populated when you Parse a token
Method SigningMethod // The signing method used or to be used
Header map[string]interface{} // The first segment of the token
Claims Claims // The second segment of the token
Signature string // The third segment of the token. Populated when you Parse a token
Valid bool // Is the token valid? Populated when you Parse/Verify a token
}
1、初始化一个token,可通过构造父类,在Claims中添加一些附加信息
token := jwt.NewWithClaims(
// 加密方式
jwt.SigningMethodHS256,
jwt.StandardClaims{
// 过期时间
ExpiresAt: jwt.At(time.Now().Add(xxx)),
})
// NewWithClaims creats a new token with a specified signing method and claims type
func NewWithClaims(method SigningMethod, claims Claims) *Token {
return &Token{
Header: map[string]interface{}{
"typ": "JWT",
"alg": method.Alg(),
},
Claims: claims,
Method: method,
}
}
===>token内容如下:
{
"token": {
"Raw": "",
"Method": {
"Name": "HS256",
"Hash": "SHA256"
},
"Header": [
{
"typ": "JWT",
"alg": "HS256"
}
],
"StandardClaims": {
"ExpireAt": "2021-12-24 16:35:14.657703 +0800"
},
"Signature": "",
"Valid": false
}
}
2、密钥加密生成一个三段式(中间使用两个.隔开)字符串,
tokenString, err := token.SignedString(hmacSecret) //案例如下行:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2luZm8iOnsidWlkIjozLCJyb2xlX2lkIjo1LCJ1c2VybmFtZSI6IirnrqHnkIblkZgiLCJsYXN0X2xvZ2luX2F0IjoiMjAyMS0xMS0yNFQxNjo0ODoxMC44Nzk4OTE0KzA4OjAwIiwidXNlcl9hZ2VudCI6IlBvc3RtYW5SdW50aW1lLzcuMjguNCIsImRldmljZV90eXBlIjoiQU5EUk9JRCIsImVuZF9hdCI6eyJUaW1lIjoiMDAwMS0wMS0wMVQwMDowMDowMFoiLCJWYWxpZCI6ZmFsc2V9fSwiZXhwIjoxNjQwMzM1Njk0Ljk1MjY0N30.lqFu8xEvJMq31wpTVwBYjFRLt8xefHf7OxC1q1oy__8
该字符串包含三个部分,
Header头部,
Payload负载即上述Claims
Signature 签名/签证
// SignedString returns the complete, signed token
func (t *Token) SignedString(key interface{}, opts ...SigningOption) (string, error) {
var sig, sstr string
var err error
/*
此方法将token中的Header和Claims分别编码并用点连接,部分源码如下
func (t *Token) SigningString(opts ...SigningOption) (string, error) {
...
inputParts := []interface{}{t.Header, t.Claims}
parts := make([]string, 2)
for i, v := range inputParts {
ctx := CodingContext{FieldDescriptor(i), t.Header}
...
}
return strings.Join(parts, "."), nil
}
*/
if sstr, err = t.SigningString(opts...); err != nil {
return "", err
}
// 对前两部分由.拼接的字符串进行签名
if sig, err = t.Method.Sign(sstr, key); err != nil {
return "", err
}
// 再拼接.
return strings.Join([]string{sstr, sig}, "."), nil
}
服务端解码,直接上代码:
func Parse(tokenString string) (*UserInfo, error) {
token, err := jwt.ParseWithClaims(
tokenString,
&UserClaims{},
func(token *jwt.Token) (interface{}, error) {
if token.Method.(*jwt.SigningMethodHMAC) != alg {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return hmacSecret, nil
},
)
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*UserClaims); ok && token.Valid {
return &claims.UserInfo, nil
} else {
return nil, fmt.Errorf("token非法: %s", tokenString)
}
}