什么是JWT
JWT
是一个非常轻巧的规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息,一个JWT由三部分组成,Header头部、Claims载荷、Signature签名
JWT (header)
JWT头部承载两部分信息
- 令牌类型:JWT
- 加密算法:通常直接使用HMAC SHA256
{
"alg": "HS256",
"typ": "JWT"
}
有效载荷 (payload)
有效载荷部分是JWT的主体内容(存放有效信息),同样也是一个json对象,JWT指定了七个默认字段供选择
- 发行人:iss
- 到期时间:exp
- 主题:sub
- 用户(受众):aud
- 生效时间:nbf
- 签发时间:iat
- 唯一标识(编号):jti
默认JWT是未加密的,然和人都可以解读其内容,不要存放私密敏感信息
这个payloadJson对象也会使用base64URL算法转化为字符串保存
{
"ExpiresAt":time.Now().Add(7*24*time.Hour)Unix(), //过期时间
"IssuedAt":time.Now().Unix(), //发放时间
"Issuer":"admin",
"Subject":"user-token"
}
签名 (signature):对前两部分通过指定的算法生成hash,防止被篡改
签名信息有三部分组成,并通过.
连接:
- base64UrlEncode(header)
- base64UrlEncode(payload)
- secret
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Base64URL
Base64URL 是一种能够对 URL 进行 base64 串型化的算法。
Base64中用的三个字符是"+","/“和”=",由于在URL中有特殊含义,会与特殊符号+、/和=冲突,Base64URL会将 =省略、+替换成-,/替换成_。这就是base64URL算法
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
算出签名后,将这三部分用.
连接成一个完整的字符串,构成了最终的JWT
JWT使用
客户端接收服务器返回的JWT,将其存储在Cookie或localStorage中。
此后,客户端将在与服务器交互中都会带JWT。如果将它存储在Cookie中,就可以自动发送,但是不会跨域,因此一般是将它放入HTTP请求的请求头Authorization
字段中。
Authorization: Bearer <token>
当跨域时,也可以将JWT被放置于POST请求的数据主体中。
JWT特点
-
因为json的通用性,所以JWT是可以进行跨语言支持的,所以像java、js、php、go很多语言都可以使用。
-
JWT默认不加密,但可以加密。生成原始令牌后,可以使用改令牌再次对其进行加密。
-
因为有了payload部分,JWT不仅可用于认证,还可用于存储一些业务逻辑所必要分非敏感信息。善用JWT有助于减少服务器请求数据库的次数
-
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
-
它不需要在服务端保存会话信息, 所以它易于应用的扩展
-
JWT 不加密的情况下,不能将私密数据写入 JWT。
-
JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
-
JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
-
为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
参考连接
- http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
- https://github.com/dwyl/learn-json-web-tokens/blob/master/README.md
上代码!!!
//yaml中
# 身份验证配置
# jwt configuration
jwt:
signing-key: 'ly_Vlearn'
type Claims struct {
UserId uint
UUID uuid.UUID
jwt.StandardClaims
}
//登录以后签发jwt
func tokenNext(c *gin.Context, user model.User) (string,error){
j := &middleware.JWT{
// 唯一签名
[]byte(global.GVA_CONFIG.JWT.SigningKey),
}
clams := request.CustomClaims{
UUID: user.UUID,
ID: user.UserId,
StandardClaims: jwt.StandardClaims{
//签名生效时间
NotBefore: int64(time.Now().Unix() - 1000),
//发放时间
IssuedAt:time.Now().Unix(),
//过期时间
ExpiresAt: int64(time.Now().Unix() + 60*60*24*7),
//签名的发行者
Issuer: "ldy",
},
}
token:=jwt.NewWithClaims(jwt.SigningMethodHS256,claims)
tokenString,err:=token.SignedString(jwtkey)
if err != nil {
return "",err
}
return tokenString,nil
}
====================================================================
//验证前端给的Token
func AuthMiddleware()gin.HandlerFunc {
return func(context *gin.Context) {
//获取 header
tokenString:=context.GetHeader("Authorization")
//验证格式
if tokenString==""|| !strings.HasPrefix(tokenString,"Bearer ") {
context.JSON(http.StatusUnauthorized,gin.H{
"code":401,
"msg":"权限不足",
})
context.Abort()
return
}
tokenString=tokenString[7:]
//验证tokenString
token,claims,err:=common.ParseToken(tokenString)
if err != nil|| !token.Valid{
context.JSON(http.StatusUnauthorized,gin.H{
"code":401,
"msg":"权限不足",
})
context.Abort()
return
}
//通过验证
userId:=claims.UserId
DB:=common.GetDB()
var user model.User
DB.First(&user,userId)
//用户不存在
if user.ID==0 {
context.JSON(http.StatusUnauthorized,gin.H{
"code":401,
"msg":"权限不足",
})
context.Abort()
return
}
//将用户信息写入上写文
context.Set("user",user)
context.Next()
}
//具体验证token方法
func ParseToken(tokenStr string) (*jwt.Token,*Claims,error) {
claims:=&Claims{}
token,err:=jwt.ParseWithClaims(tokenStr,claims, func(token *jwt.Token) (i interface{}, err error) {
return jwtkey,nil
})
return token,claims,err
}