0. 前言
最近学习Golang和Gin框架,初步了解了一下JWT作身份校验,这里做个总结。
1. 认识JWT
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
header 用于描述元信息,例如产生signature的算法:
{
"typ": "JWT",
"alg": "HS256"
}
其中 alg
指定了使用哪种哈希算法来创建signature
payload 用于携带你希望向服务端传递的信息。你既可以往里添加官方字段(这里的“字段” (field) 也可以被称作“声明” claims),例如iss(Issuer), sub(Subject), exp(Expiration time),也可以塞入自定义的字段,比如 email:
{
"email": "who@mail.com"
}
signature 的创建分为:
- 拿到密钥
secret
- 将
header
进行 base64 编码,结果为headerStr
- 将
payload
进行 base64 编码,结果为payloadStr
- 将
headerStr
和payloadStr
用.
拼接为字符串data
- 以
data
和secret
作为参数,用哈希算法计算出签名
2. Token 的生成和解析
2.1 生成 token
首先引入 jwt-go 依赖
import "github.com/dgrijalva/jwt-go"
自定义 payload 声明
type Claims struct {
Email string `json:"email"`
Password string `json:"password"`
jwt.StandardClaims
}
自定义 secret 密钥
var jwtSecret = []byte("mySecret")
生成 token
func GenerateToken(email, password string) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(2 * time.Hour) // 失效时间
claims := Claims{
email,
password,
jwt.StandardClaims{
ExpiresAt: expireTime.Unix(),
Issuer: "custom-issuer",
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(jwtSecret)
return token, err
}
2.2 解析 token
func ParseToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if tokenClaims != nil {
// tokenClaims.Valid 判断expireTime
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}
3. JWT 中间件的使用
3.1 编写中间件函数
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.Request.Header.Get("token")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 40001,
"msg": "请求未携带token, 无权限访问",
})
c.Abort()
return
}
claims, err := ParseToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 40001,
"msg": err.Error(),
})
c.Abort()
return
}
// check if token is valid
if claims == nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 40001,
"msg": "token error",
})
c.Abort()
return
}
// 查询数据库,判断token携带的用户信息是否有效
if authErr := repository.Auth(claims.Email, claims.Password); authErr != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"code": 40001,
"msg": "token error",
})
c.Abort()
return
}
c.Set("claims", claims)
}
}
repository.Auth()
定义为
func Auth(email, password string) error {
var user model.User
var err error
if err = DB.Where("email = ?", email).First(&user).Error; err != nil {
return err
}
if user.Password != password {
return errors.New("incorrect email or password")
}
return nil
}
3.2 JWT 的使用
func main() {
r := gin.Default()
r.Use(JWTAuth())
}
这样,所有路由都需经过JWT验证,也可以对某一 routerGroup 的接口作身份验证