什么是JWT?
JSON Web Token (JWT) 是一个开放标准 ( RFC 7519 ),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。该信息可以被验证和信任,因为它是经过数字签名的。JWT 可以使用secret(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
虽然 JWT 可以加密以在各方之间提供保密,但我们将重点关注签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则对其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是对其进行签名的一方。
JWT结构
在其紧凑形式中,JSON Web Tokens 由用点 ( .
)分隔的三个部分组成,它们是:
- 标题(Header)
- 有效负载(Payload)
- 签名(Signature)
因此,JWT 通常如下所示。
xxxxx.yyyyy.zzzzz
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
标题(Header)
标头通常由两部分组成:令牌的类型,即 JWT,以及正在使用的签名算法,例如 HMAC SHA256 或 RSA。它们会组成一个JSON对象,用于描述其元数据。例如:
{
"alg": "HS256",
"typ": "JWT"
}
在上述JSON对象中,alg字段用来表示使用的签名算法。typ字段用来表示使用的令牌类型。然后,这个 JSON 被Base64Url编码以形成 JWT 的第一部分。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
有效负载(Payload)
有效负载是一个JSON对象,主要用于存储在JWT中实际传输的数据。
例如:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
有效负载中包含声明。声明是关于实体(通常是用户)和附加数据的声明。共有三种类型的声明:注册声明、公共声明和私有声明。
-
注册声明:这些是一组预定义的声明,这些声明不是强制性的,而是推荐的,以提供一组有用的、可互操作的声明。
- aud(Audience):受众,即接受JWT的一方。
- exp(ExpiresAt):JWT的过期时间,过期时间必须大于签发时间。
- jti(JWT Id):JWT的唯一标识。
- iat(IssuedAt):签发时间。
- iss(Issuer):JWT的签发者。
- nbf(Not Before):JWT的生效时间,如果时间未到,则不可用。
- sub(Subject):主题。
请注意,声明名称只有三个字符,因为 JWT 是紧凑的。
-
公共声明:这些可以由使用 JWT 的人随意定义。一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
-
私有声明:这些都是使用它们同意并既不是当事人之间建立共享信息的自定义声明注册或公众的权利要求。
然后对有效负载进行Base64Url编码以形成 JSON Web 令牌的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
请注意,对于已签名的令牌,此信息虽然受到防篡改保护,但任何人都可以读取。除非加密,否则不要将机密信息放在 JWT 的负载或标头元素中。
签名(Signature)
签名是对前面两个部分(Header-Playload)进行约定算法和规则的签名。签名一般用于校验消息在整个过程中有没有被篡改,对于使用了secret进行签名的令牌还可以验证JWT的发送者是否是它的真实身份。
例如,如果要使用 HMAC SHA256 算法,则签名将通过以下方式创建:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT应用
在身份验证中,当用户使用其凭据成功登录时,将返回 JSON Web Token。由于令牌是凭证,因此必须非常小心以防止出现安全问题。通常,您不应将令牌保留的时间超过所需的时间。
由于缺乏安全性,您也不应该在浏览器存储中存储敏感的会话数据。
每当用户想要访问受保护的路由或资源时,用户代理应该发送 JWT,通常在使用Bearer模式的Authorization标头中。标题的内容应如下所示:
Authorization: Bearer <token>
在某些情况下,这可以是无状态授权机制。服务器的受保护路由将检查Authorization
标头中的有效 JWT ,如果存在,则用户将被允许访问受保护的资源。如果 JWT 包含必要的数据,则可能会减少为某些操作查询数据库的需要,尽管情况并非总是如此。
如果令牌在Authorization
标头中发送,跨源资源共享 (CORS) 不会成为问题,因为它不使用 cookie。
JWT工作流程
-
首先,客户端向服务端申请JWT令牌,这个过程通常是登录功能。即:由用户名和密码换取JWT令牌。
-
当你访问系统其他的接口时,在HTTP的header中携带JWT令牌。
-
服务端解签验证JWT。
golang 代码实现示例
拉取jwt
库。
go get github.com/golang-jwt/jwt
github上star最多的是
github.com/dgrijalva/jwt-go
,但是该库已经不在维护,源代码迁移到新库github.com/golang-jwt/jwt
中维护。
生成Token
func GenerateToken(username, password string) string {
claims := jwt.MapClaims{
"exp": time.Now().Add(time.Hour * 3).Unix(),
"iat": time.Now().Unix(),
"username": username,
"password": password,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
t, _ := token.SignedString([]byte(os.Getenv(Secret)))
return t
}
校验Token
func ValidateToken(token string) (*jwt.Token, error) {
return jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
//nil secret key
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(os.Getenv("JWT_SECRET")), nil
})
}
NewWithClaims函数源码实现
//新建token
func NewWithClaims(method SigningMethod, claims Claims) *Token {
return &Token{
Header: map[string]interface{}{ //header
"typ": "JWT",
"alg": method.Alg(),
},
Claims: claims, //playload
Method: method, //签名算法
}
}
SignedString方法源码实现
// Get the complete, signed token
func (t *Token) SignedString(key interface{}) (string, error) {
var sig, sstr string
var err error
if sstr, err = t.SigningString(); err != nil {
return "", err
}
if sig, err = t.Method.Sign(sstr, key); err != nil {
return "", err
}
//返回
return strings.Join([]string{sstr, sig}, "."), nil
}
加密部分源码实现
// Encode JWT specific base64url encoding with padding stripped
func EncodeSegment(seg []byte) string {
return base64.RawURLEncoding.EncodeToString(seg)
}
base64加密解密可逆,也就是说token中的内容任何人都可以读取。必要时,加密携带的数据。