摘要
JWT(JSON Web Token):一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,多用于OAuth2.0业务场景下;(OAuth2.0:授权机制,用来授权第三方应用,获取用户数据)
Cookie-Session验证方式:
- 用户在浏览器端填写用户名和密码,并发送给服务端
- 服务端对用户名和密码校验通过后会生成一份保存当前用户相关信息的session数据和一个与之对应的标识(通常称为session_id)
- 服务端返回响应时将上一步的session_id写入用户浏览器的Cookie
- 后续用户来自该浏览器的每次请求都会自动携带包含session_id的Cookie
- 服务端通过请求中的session_id就能找到之前保存的该用户那份session数据,从而获取该用户的相关信息
该模式弊端:
- 扩展性(scaling)不好,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session;
- 依赖于客户端(浏览器)保存Cookie,并且需要在服务端存储用户的session数据;当在不同的浏览器中访问时,就无法从cookie中获取到session_id;
JWT验证的原理:
- 服务器认证以后,签名生成一个 JSON 对象,发回给用户;之后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。这个对象,可以携带在访问的header中,或者body中、也可以是url中;
- JWT 由三个部分组成:Header(头部)、Payload(负载)、Signature(签名)
header:保存元信息,签名的算法等;
Payload:存放实际需要传递的数据;
Signature:对前两部分的利用header中定义的算法来签名;
JWT缺陷
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次;当不加密时,token中不要携带重要信息;
- 由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输;
gin中使用JWT
包
import jwt "github.com/appleboy/gin-jwt/v2"
初始化
JwtWrapper, err := jwt.New(&jwt.GinJWTMiddleware{
Realm: "",
// 用于在会话上下文中获取Token
// handler可通过`ctx.Get(IdentityKey)`获取Token
IdentityKey: IdentityKey,
// jwt签名私有串
Key: []byte(""),
// 会话刷新最短间隔
Timeout: time.Minute*30,
// 会话最长有效期
MaxRefresh: time.Hour * 12,
// 校验用户名和密码并生成Token
Authenticator: func(c *gin.Context) (interface{}, error) {
token := &Token{
}
return token, nil
},
// 返回登录结果,
LoginResponse: func(c *gin.Context, code int, token string, expire time.Time) {
v, _ := c.Get(LoginKey)//LoginKey;login请求存取键
login := v.(*Login) //登录结构信息
login.Token = token
login.TokenExpire = " "//到期时间
c.JSON(http.StatusOK, global.Response{Code: global.ErrCodeSuccess, Msg: "成功", Data: login})
},
// 返回登出结果
LogoutResponse: func(c *gin.Context, code int) {
c.JSON(http.StatusOK, global.Response{Code: global.ErrCodeSuccess, Msg: "成功"})
},
// 转换Token为键值对,交由jwt一起携带
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(*Token); ok {
return jwt.MapClaims{
"token_field": "token_value",
}
}
return jwt.MapClaims{}
},
// 从http请求中提取Token并返回给上下文
IdentityHandler: func(c *gin.Context) interface{} {
claims := jwt.ExtractClaims(c)
token := &Token{
Key: claims["key"].(string),
}
return token
},
// 用户名密码错误,会话数据非法、超时,调用未授权接口等情况的处理
Unauthorized: func(c *gin.Context, code int, message string) {
ec := global.ErrCodePriviledge
if strings.Contains(message, "expired") {
ec = global.ErrCodeSessionGone
}
c.JSON(http.StatusOK, global.Response{
Code: ec,
Msg: message,
})
},
// token读取位置:cookie优先,其次为http头的Authorization
TokenLookup: "cookie: cooke_key, header: Authorization",
// http头放置token时附带的前缀
TokenHeadName: "",
// 时间获取函数
TimeFunc: time.Now,
// 阻止客户端js获取cookie
CookieHTTPOnly: true,
// 存储jwt令牌的cookie名称
CookieName: CookieName,
SendCookie: true,
})
if err != nil {
panic(fmt.Errorf("create jwt middleware failed: %s", err))
}
注册中间件
// 会话即将失效,尝试刷新
// 注意:如果会话已经持续保活超过JwtWrapper.MaxRefresh指定时间,则必须重新登录
auth.GET("/refresh_token", RefreshHandler)
auth.Use(JwtWrapper.MiddlewareFunc())
{
auth.GET("/hello", helloHandler)
}