身份验证
在第一次登录之后产生一个有一定有效期的 token,并将其存储于浏览器的 Cookie 或 LocalStorage 之中,之后的请求都携带该 token ,请求到达服务器端后,服务器端用该 token 对请求进行鉴权。
更简单的方法是,直接用密钥对用户信息和时间戳进行签名对称加密,这样就可以省下额外的存储,也可以减少每一次请求时对数据库的查询压力。这种方式,在业界已经有一种标准的实现方式,该方式被称为 JSON Web Token
JWT
在 JWT 中,Token 有三部分组成,中间用 . 隔开,并使用 Base64 编码:
-
header
-
payload
-
signature
header
JWT Token 的 header 中,包含两部分信息:
- Token 的类型
- Token 所使用的加密算法
例如
{
"typ": "JWT",
"alg": "HS256"
}
复制代码
payload
放置token的具体内容,可以放一些标准的字段和一些额外的字段,标准字段有:
- iss:JWT Token 的签发者
- sub:主题
- exp:JWT Token 过期时间
- aud:接收 JWT Token 的一方
- iat:JWT Token 签发时间
- nbf:JWT Token 生效时间
- jti:JWT Token ID
Signature
通过前面两部分生成,生成方法:
- 用 Base64 对 header.payload 进行编码
- 用 Secret(Secret 相当于一个密码,存储在服务端) 对编码后的内容进行加密,加密后的内容即为 Signature
根据ID,用户名生成token
func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {
// Load the jwt secret from the Gin config if the secret isn't specified.
if secret == "" {
secret = viper.GetString("jwt_secret")
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": c.ID,
"username": c.Username,
"nbf": time.Now().Unix(),
"iat": time.Now().Unix(),
})
tokenString, err = token.SignedString([]byte(secret))
return
}
复制代码
将请求头里面的token提取出来
func ParseRequest(c *gin.Context) (*Context, error) {
header := c.Request.Header.Get("Authorization")
secret := viper.GetString("jwt_secret")
if len(header) == 0 {
return &Context{}, ErrMissingHeader
}
var t string
fmt.Sscanf(header, "Bearer %s", &t)
return Parse(t, secret)
}
复制代码
检查secret的格式
func secretFunc(secret string) jwt.Keyfunc {
return func(token *jwt.Token) (interface{}, error) {
// Make sure the `alg` is what we except.
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return []byte(secret), nil
}
}
复制代码
解析token
func Parse(tokenString string, secret string) (*Context, error) {
ctx := &Context{}
token, err := jwt.Parse(tokenString, secretFunc(secret))
if err != nil {
return ctx, err
} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
ctx.ID = uint64(claims["id"].(float64))
ctx.Username = claims["username"].(string)
return ctx, nil
} else {
return ctx, err
}
}
复制代码
测试
用户登录
不带token访问
携带token访问
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END