转载自 https://blog.csdn.net/codeSquare/article/details/98980234
GO语言Gin包(JWT使用)
JWT 的全称叫做 JSON WEB TOKEN,在目前前后端系统中使用较多。
JWT 是由三段构成的。分别是 HEADER,PAYLOAD,VERIFY SIGNATURE,它们生成的信息通过 . 分割。
https://blog.wangjunfeng.com/post/golang-jwt/#3-签名-signature
HEADER
- header 是由 一个 typ 和 alg 组成,typ 会指明为 JWT,而 alg 是所使用的加密算法。
{
"alg": "HS256",
"typ": "JWT"
}
PAYLOAD
-
payload 是 JWT 的载体,也就是要承载的信息。这段信息是可以自定义的,可以定义要存放什么信息,那些字段。
-
该部分信息不宜过多,它会影响 JWT 生成的大小,还有就是请勿将敏感数据存入该部分,该端数据前端是可以解析获取 token 内信息的。
<\br> -
官方给了七个默认字段,我们可以不全部使用,也可以加入我们需要的字段。
iss: Token签发者。格式是区分大小写的字符串或者uri,用于唯一标识签发token的一方。
sub: Token的主体,即它的所有人。格式是区分大小写的字符串或者uri。
aud: 接收Token的一方。格式为区分大小写的字符串或uri,或者这两种的数组。
exp: Token的过期时间,格式为时间戳。
nbf: 指定Token在nbf时间之前不能使用,即token开始生效的时间,格式为时间戳。
iat: Token的签发时间,格式为时间戳。
jti: 指此Token的唯一标识符字符串。主要用于实现唯一性保证,防止重放。
VERIFY SIGNATURE
- 该部分是由算法计算完成的。
- 对 header 进行 base64Url 编码,对 payload 进行 base64Url 编码,两端完成编码后通过 . 进行连接起来。
base64UrlEncode(header).base64UrlEncode(payload)
- 完成上述步骤后,就要通过 header 里指定的加密算法对上部分进行加密,同时还要插入一个密钥,来确保 JWT 签发是安全的。
三部分都完成后,通过使用 . 将三部分分割,生成了上图所示的 JWT 。
JWT 登录原理
- 简单的说就是当用户登录的时候,服务器校验登录名称和密码是否正确,正确的话,会生成 JWT 返回给客户端。客户端获取到 JWT 后要进行保存,之后的每次请求都会讲 JWT 携带在头部,每次服务器都会获取头部的 JWT 是否正确,如果正确则正确执行该请求,否者验证失败,重新登录。
Gin 生成 JWT
go 语言的 JWT 库 "github.com/dgrijalva/jwt-go"
- 登录方法进行改造。
// 省略代码
expiresTime := time.Now().Unix() + int64(OneDayOfHours)
//OneDayOfHours用来设置过期时间,这里设置的时一天
//可以使用int 60*60*24
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", // 主题
}
//通过 StandardClaims 生成标准的载体,也就是上文提到的七个字段,其中 编号设定为 用户 id。
var jwtSecret = []byte("gin Hello")
//jwtSecret 是我们设定的密钥
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
//通过 HS256 算法生成 tokenClaims ,这就是我们的 HEADER 部分和 PAYLOAD。
token, err := tokenClaims.SignedString(jwtSecret)
//生成token
token = "Bearer "+ token
//将 token 和 Bearer 拼接在一起,同时中间用空格隔开。
//生成 Bearer Token
完整代码
func CreateJwt(ctx *gin.Context) {
// 获取用户
user := &model.User{}
/*
type User struct {
gorm.Model
Username string `json:"username"`
Password string `json:"password"`
Roles []Role `json:"role" gorm:"many2many:roles"`
}
*/
result := &model.Result{
Code: 200,
Message: "登录成功",
Data: nil,
}
/*
type Result struct {
Code int `json:"code" example:"000"`
Message string `json:"message" example:"请求信息"`
Data interface{} `json:"data" `
}
*/
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,
})
}
}
Gin 校验 Token
- 解析 token 方法,parseToken()
- eg:
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()
}
}
访问
GET http://localhost:8080
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjMiLCJleHAiOjE1NjQ3OTQzNjIsImp0aSI6Ilx1MDAwMCIsImlhdCI6MTU2NDc5NDM0MiwiaXNzIjoiZ2luIGhlbGxvIiwibmJmIjoxNTY0Nzk0MzQyLCJzdWIiOiJsb2dpbiJ9.uQxGMsftyVFtYIGwQVm1QB2djw-uMfDbw81E5LMjliU