JSON Web Token (简称JWT),是一种用于身份验证和授权的开放标准
它由JSON(JavaScript Object Notation)格式组成,使用数字签名或加密方式生成令牌。JWT令牌可以在客户端和服务器之间传输,并用于验证用户的身份和授权访问资源。
JWT通常由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。
头部包含了令牌的类型以及所使用的签名算法。例如,常见的签名算法包括HMAC SHA256和RSA。载荷包含了相关的声明和数据,如用户ID、角色、过期时间等。签名部分由头部、载荷和密钥组成,用于验证令牌的真实性和完整性。
JWT的工作流程如下:当用户在客户端进行身份认证后,服务器会生成一个JWT令牌并将其返回给客户端。客户端在后续的请求中携带该令牌作为身份验证凭证。服务器在接收到请求后,会使用相同的密钥对令牌进行验证,并根据令牌中的声明判断用户的身份和权限。
JWT的优点
优点1是无状态性,即服务器不需要存储任何会话信息。这是因为JWT令牌中已经包含了用户的相关信息,服务器只需要校验令牌的真实性即可。这种无状态的设计使得JWT适用于分布式和微服务架构,减轻了服务器的负担。
优点2是JWT的跨域支持。由于JWT令牌是在 HTTP 头部中进行传递的,因此可以轻松地在不同域之间进行传输。这使得在分布式系统中,不同服务之间的身份验证和授权变得更加容易。
下列场景中使用JSON Web Token是很有用的:
- Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
- Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWT可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
为什么需要JWT
与传统网站web项目使用Cookie-Session模式认证用户不同,JWT是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,用户得到Token令牌后,在后续直接使用令牌即可通过服务器访问用户的信息了。
使用JWT
我们使用 Go 语言社区中的 jwt 相关库来构建我们的应用,例如:github.com/golang-jwt/…。
创建jwt
使用默认声明创建jwt
// 用于签名的字符串 var SigningKey = []byte("example.com")
// GenRegisteredClaims 使用默认声明创建jwt
func GenRegisteredClaims() (string, error) {
claims := &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 8)), // 过期时间
Issuer: "user", // 签发人
}
// 生成token对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 生成签名字符串
return token.SignedString(SigningKey)
}
// 解析jwt
func ValidateRegisteredClaims(tokenString string) bool {
// 解析token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{},
error) {
return SigningKey, nil
})
if err != nil { // 解析token失败
return false
}
return token.Valid
}
生成JWT
func GenToken(username string) (string, error) {
// 创建一个我们自己的声明
claims := CustomClaims{
username, // 自定义字段
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpireDuration)),
Issuer: "my-project", // 签发人
},
}
// 使用指定的签名方法创建签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 使用指定的secret签名并获得完整的编码后的字符串token
return token.SignedString(CustomSecret)
}
解析JWT
// ParseToken 解析JWT
func ParseToken(tokenString string) (*CustomClaims, error) {
// 解析token
// 如果是自定义Claim结构体则需要使用 ParseWithClaims 方法
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (i interface{}, err error) {
// 直接使用标准的Claim则可以直接使用Parse方法
// token, err := jwt.Parse(tokenString, func(token jwt.Token) (i interface{}, err error){
return CustomSecret, nil
})
if err != nil {
return nil, err
}
// 对token对象中的Claim进行类型断言
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { // 校验token
return claims, nil
}
return nil, errors.New("invalid token")