文章首发于 我的博客 ISLAND 和 我的微信公众号 代码猎奇站前端
在先后端分离的项目中,愈来愈多的项目采用 JWT
代替传统的 cookie
,这里咱们来使用 JWT
结合 Gin
来做为一个登陆受权和权限校验。mysql
JWT 的全称叫作 JSON WEB TOKEN,在目前先后端系统中使用较多。git
JWT 是由三段构成的。分别是 HEADER,PAYLOAD,VERIFY SIGNATURE,它们生成的信息经过 .
分割。github
header 是由 一个 typ
和 alg
组成,typ
会指明为 JWT,而 alg
是所使用的加密算法。算法
{
"alg": "HS256",
"typ": "JWT"
}
复制代码
payload 是 JWT 的载体,也就是咱们要承载的信息。这段信息是咱们能够自定义的,能够定义咱们要存放什么信息,那些字段。该部分信息不宜过多,它会影响 JWT 生成的大小,还有就是请勿将敏感数据存入该部分,该端数据前端是能够解析获取 token 内信息的。sql
官方给了七个默认字段,咱们能够不所有使用,也能够加入咱们须要的字段。json
名称 | 含义 |
---|---|
Audience | 表示JWT的受众 |
ExpiresAt | 失效时间 |
Id | 签发编号 |
IssuedAt | 签发时间 |
Issuer | 签发人 |
NotBefore | 生效时间 |
Subject | 主题 |
这也是 JWT 的最后一段,该部分是由算法计算完成的。后端
对刚刚的 header 进行 base64Url 编码,对 payload 进行 base64Url 编码,两端完成编码后经过 .
进行链接起来。安全
base64UrlEncode(header).base64UrlEncode(payload)
复制代码
完成上述步骤后,就要经过咱们 header 里指定的加密算法对上部分进行加密,同时咱们还要插入咱们的一个密钥,来确保个人 JWT 签发是安全的。服务器
这即是咱们的第三部分。
当三部分都完成后,经过使用 .
将三部分分割,生成了上图所示的 JWT 。
简单的说就是当用户登陆的时候,服务器校验登陆名称和密码是否正确,正确的话,会生成 JWT 返回给客户端。客户端获取到 JWT 后要进行保存,以后的每次请求都会讲 JWT 携带在头部,每次服务器都会获取头部的 JWT 是否正确,若是正确则正确执行该请求,否者验证失败,从新登陆。
go 语言的 JWT 库有不少。jwt.io 上也给出了不少 。这里使用 jwt-go
"github.com/dgrijalva/jwt-go"
咱们对登陆方法进行改造。
// 省略代码
expiresTime := time.Now().Unix() + int64(config.OneDayOfHours)
claims := jwt.StandardClaims{
Audience: user.Username, // 受众
ExpiresAt: expiresTime, // 失效时间
Id: string(user.ID), // 编号
IssuedAt: time.Now().Unix(), // 签发时间
Issuer: "gin hello", // 签发人
NotBefore: time.Now().Unix(), // 生效时间
Subject: "login", // 主题
}
var jwtSecret = []byte(config.Secret)
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 省略代码
复制代码
这里的 config.OneDayOfHours
设定了过时时间,这里设定了一天。经过 StandardClaims
生成标准的载体,也就是上文提到的七个字段,其中 编号设定为 用户 id。其中的 jwtSecret
是咱们设定的密钥,
咱们这里经过 HS256 算法生成 tokenClaims
,这就是咱们的 HEADER 部分和 PAYLOAD。
token, err := tokenClaims.SignedString(jwtSecret)
复制代码
这样便生成了咱们的 token 。咱们要将咱们的 token 和 Bearer 拼接在一块儿,同时中间用空格隔开。
token = "Bearer "+ token
复制代码
生成 Bearer Token 。
当咱们用户进行登陆的时候,就能够经过该片断生成 JWT。
下面是完整代码:
func CreateJwt(ctx *gin.Context) {
// 获取用户
user := &model.User{}
result := &model.Result{
Code: 200,
Message: "登陆成功",
Data: nil,
}
if e := ctx.BindJSON(&user); e != nil {
result.Message = "数据绑定失败"
result.Code = http.StatusUnauthorized
ctx.JSON(http.StatusUnauthorized, gin.H{
"result": result,
})
}
u := user.QueryByUsername()
if u.Password == user.Password {
expiresTime := time.Now().Unix() + int64(config.OneDayOfHours)
claims := jwt.StandardClaims{
Audience: user.Username, // 受众
ExpiresAt: expiresTime, // 失效时间
Id: string(user.ID), // 编号
IssuedAt: time.Now().Unix(), // 签发时间
Issuer: "gin hello", // 签发人
NotBefore: time.Now().Unix(), // 生效时间
Subject: "login", // 主题
}
var jwtSecret = []byte(config.Secret)
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
if token, err := tokenClaims.SignedString(jwtSecret); err == nil {
result.Message = "登陆成功"
result.Data = "Bearer " + token
result.Code = http.StatusOK
ctx.JSON(result.Code, gin.H{
"result": result,
})
} else {
result.Message = "登陆失败"
result.Code = http.StatusOK
ctx.JSON(result.Code, gin.H{
"result": result,
})
}
} else {
result.Message = "登陆失败"
result.Code = http.StatusOK
ctx.JSON(result.Code, gin.H{
"result": result,
})
}
}
复制代码
经过 .http 请求测试,结果以下
{
"result": {
"code": 200,
"message": "登陆成功",
"data": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjMiLCJleHAiOjE1NjQ3OTY0MzksImp0aSI6Ilx1MDAwMCIsImlhdCI6MTU2NDc5NjQxOSwiaXNzIjoiZ2luIGhlbGxvIiwibmJmIjoxNTY0Nzk2NDE5LCJzdWIiOiJsb2dpbiJ9.CpacmfBSMgmK2TgrT-KwNB60bsvwgyryGQ0pWZr8laU"
}
}
复制代码
这个便完成了token的生成。
那么,接下来就须要完成 token 的验证。
还记得以前咱们验证用户是否受权采用的办法吗?是的,在中间件里查看用户 cookie。一样的方法,咱们这里校验用户 JWT 是否有效。
编写咱们的中间件。
新创建 middleware/Auth.go
首先先编写咱们的解析 token 方法,parseToken()
func parseToken(token string) (*jwt.StandardClaims, error) {
jwtToken, err := jwt.ParseWithClaims(token, &jwt.StandardClaims{}, func(token *jwt.Token) (i interface{}, e error) {
return []byte(config.Secret), nil
})
if err == nil && jwtToken != nil {
if claim, ok := jwtToken.Claims.(*jwt.StandardClaims); ok && jwtToken.Valid {
return claim, nil
}
}
return nil, err
}
复制代码
经过传入咱们的 token , 来对 token 进行解析。
完整的中间件代码
func Auth() gin.HandlerFunc {
return func(context *gin.Context) {
result := model.Result{
Code: http.StatusUnauthorized,
Message: "没法认证,从新登陆",
Data: nil,
}
auth := context.Request.Header.Get("Authorization")
if len(auth) == 0 {
context.Abort()
context.JSON(http.StatusUnauthorized, gin.H{
"result": result,
})
}
auth = strings.Fields(auth)[1]
// 校验token
_, err := parseToken(auth)
if err != nil {
context.Abort()
result.Message = "token 过时" + err.Error()
context.JSON(http.StatusUnauthorized, gin.H{
"result": result,
})
} else {
println("token 正确")
}
context.Next()
}
}
复制代码
首先在请求头获取 token ,而后对先把 token 进行解析,将 Bearer 和 JWT 拆分出来,将 JWT 进行校验。
咱们只须要对咱们须要校验的路由进行添加中间件校验便可。
router.GET("/", middleware.Auth(), func(context *gin.Context) {
context.JSON(http.StatusOK, time.Now().Unix())
})
复制代码
当咱们访问 /
的时候就须要携带 token 了
GET http://localhost:8080
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjMiLCJleHAiOjE1NjQ3OTQzNjIsImp0aSI6Ilx1MDAwMCIsImlhdCI6MTU2NDc5NDM0MiwiaXNzIjoiZ2luIGhlbGxvIiwibmJmIjoxNTY0Nzk0MzQyLCJzdWIiOiJsb2dpbiJ9.uQxGMsftyVFtYIGwQVm1QB2djw-uMfDbw81E5LMjliU
复制代码
本章节对什么是 JWT,Gin 中如何使用 JWT 作了介绍。可是不要过于迷信 JWT,JWT 还有不少问题,好比说 JWT 失效只能是时间过时,若是修改密码或者帐户注销等操做须要咱们另外添加逻辑判断。适合的地方选用适合的技术才能发挥最大的优点。
Gin(一):Hello
Gin(二):路由Router
Gin(三):模板tmpl
Gin(四):表单提交校验和模型绑定
Gin(五):链接MySQL
Gin(六):文件的上传
Gin(七):中间件的使用和定义
Gin(八):Cookie的使用
Gin(九):生成restful接口
Gin(十):集成 Swagger
Gin(十一)集成ORM-gorm
Gin(十二)集成JWT