JWT=JSON Web Token,是目前较为风行的分布式认证计划。
认证计划
个别罕用传统的 Cookie/Session 认证计划,认证过程:
- 用户向服务器发送 username+password;
- 服务器验证当前,存储该用户的登录信息,如 userId/role/loginTime 等;
- 服务器向用户返回 session_id,写入用户的 cookie;
- 用户随后的每一次申请,都通过 Cookie,将 session_id 传回服务器;
- 服务器收到 session_id,找到后期保留的数据,由此失去用户的身份 (userId/role 等);
在分布式系统中,常遇到跨域认证的问题,比方 A 网站和 B 网站 是同一家公司的关联服务器。现要求,用户只有在其中一个网站登录,再拜访另一个网站就会主动登录,对该问题,罕用的计划有:
- 传统的 Cookie/Session 计划:将 session 集中长久化并做 cache,每来一个申请,都要用申请中的 sessionId 进行认证;
- JWT 计划:服务端不存储 session 数据,认证信息存储在客户端,服务端仅要 session 的颁发和校验;
JWT(JSON Web Token) 是目前较为风行的跨域认证解决方案。
JWT 的认证过程
JWT 的认证过程是,客户端将用户名和明码传入服务端,服务端通过认证后,将生成一个 JSON 对象,发回给用户,JSON 对象大略的格局:
{
"姓名": "张三",
"角色": "管理员",
"到期工夫": "2018 年 7 月 1 日 0 点 0 分"
}
当前客户端再与服务端通信的时候,都要带上这个 JSON 对象,服务端校验 JSON 对象的内容认证用户。
这样服务端不必保留任何 session 信息,服务端变成无状态的,扩展性较好。
JWT 的数据结构:
- Header:形容 JWT 的元数据,如签名算法;
- Payload:寄存理论传递的数据,除了官网定义的签发人、过期工夫等四段,还能够寄存公有字段,如 userId/userName 等信息;
- Signature:对前两局部的签名,避免数据篡改;
JWT 的长处:服务端便于扩大,因为服务端不存储认证信息,无状态的,十分利于扩大。
JWT 的毛病:JWT 一旦签发,在到期之前始终无效,除非服务端部署额定的逻辑。
golang-jwt 的 demo
http-server 应用 github.com/gin-gonic/gin。
jwt 应用 github.com/dgrijalva/jwt-go。
该 demo 中,client 通过 POST /login 进行登录,而后获取 JWT token,而后通过在 header 中带上 token,POST /order 进行商品下单。
生成 jwt token
JWT token 在用户 /login 的时候,由服务端调配:
type AuthClaim struct {
UserId uint64 `json:"userId"`
jwt.StandardClaims
}
var secret = []byte("It is my secret")
const TokenExpireDuration = 2 * time.Hour
// 生成 JWT token
func GenToken(userId uint64) (string, error) {
c := AuthClaim{
UserId: userId,
StandardClaims: jwt.StandardClaims{ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(),
Issuer: "TEST001",
},
}
// 应用指定的签名办法创立签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
// 应用指定的 secret 签名并取得残缺的编码后的字符串 token
return token.SignedString(secret)
}
校验 jwt token
login 胜利后,用户再申请其它的 request 时,带上该 JWT token,交由服务端的 middleware 认证,认证 OK 后,才会失去解决:
func ParseToken(tokenStr string) (*AuthClaim, error) {token, err := jwt.ParseWithClaims(tokenStr, &AuthClaim{}, func(tk *jwt.Token) (interface{}, error) {return secret, nil})
if err != nil {return nil, err}
if claim, ok := token.Claims.(*AuthClaim); ok && token.Valid {return claim, nil}
return nil, errors.New("Invalid token")
}
JWTAuthMiddleware 的实现
func jwtAuthMiddleware() func(c *gin.Context) {return func(c *gin.Context) {token := c.Request.Header.Get("token")
if token == "" {c.JSON(http.StatusForbidden, "empty token")
c.Abort()
return
}
claim, err := ParseToken(token)
if err != nil {c.JSON(http.StatusForbidden, "Invalid token")
c.Abort()
return
}
c.Set("userId", claim.UserId)
c.Next()}
}
在 gin 中应用 JWTMiddleware:
r := gin.Default()
r.POST("/login", loginHandler)
api := r.Group("/api")
api.Use(jwtAuthMiddleware())
api.POST("/order", orderHandler)
参考:
1.https://mojotv.cn/go/golang-j…
2.https://ruanyifeng.com/blog/2…
3.https://github.com/lwangrabbi…