参考在gin框架中使用JWT | 李文周的博客 (liwenzhou.com)
JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token 实现方式,目前多用于前后端分离项目和 OAuth2.0 业务场景下。
理解:用于取代传统的Session_id,给登录的用户设置一个全局标识符token。
理解:登陆之后服务器生成SessionId用于唯一标识一个用户,发送给浏览器之后存储在本地cookie中,此后浏览器每次发送请求都带上SessionId告诉服务器他是谁。token也是同样的作用,但是更方便。
在之前的一些web项目中,我们通常使用的是Cookie-Session
模式实现用户认证。相关流程大致如下:
这种方案依赖于客户端(浏览器)保存 Cookie,并且需要在服务端存储用户的session数据。
在移动互联网时代,我们的用户可能使用浏览器也可能使用APP来访问我们的服务,我们的web应用可能是前后端分开部署在不同的端口,有时候我们还需要支持第三方登录,这下Cookie-Session
的模式就有些力不从心了。
JWT就是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,经过签名后得到一个Token(令牌)再发回给用户,用户后续请求只需要带上这个Token,服务端解密之后就能获取该用户的相关信息了。
默认声明对象->生成token对象->生成签名字符串
// 用于签名的字符串
var mySigningKey = []byte("签名字符串")
// GenRegisteredClaims 使用默认声明创建jwt
func GenRegisteredClaims() (string, error) {
// 创建 Claims
claims := &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // 过期时间
Issuer: "签名人", // 签发人
}
// 生成token对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 生成签名字符串
return token.SignedString(mySigningKey)
}
解析返回之前的签名字符串,看token是否有效。
// ParseRegisteredClaims 解析jwt
func ValidateRegisteredClaims(tokenString string) bool {
// 解析token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return mySigningKey, nil
})
if err != nil { // 解析token失败
return false
}
return token.Valid
}
注册指定路由,调用函数
理解:先检验账户密码正确与否,然后调用上面的token生成器用用户名生成token字串,然后打包到token返回。
如果账户密码不对,返回一个json,告诉服务器鉴权失败。
func authHandler(c *gin.Context) {
// 用户发送用户名和密码过来
var user UserInfo
err := c.ShouldBind(&user)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 2001,
"msg": "无效的参数",
})
return
}
// 校验用户名和密码是否正确
if user.Username == "q1mi" && user.Password == "q1mi123" {
// 生成Token
tokenString, _ := GenToken(user.Username)
c.JSON(http.StatusOK, gin.H{
"code": 2000,
"msg": "success",
"data": gin.H{"token": tokenString},
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 2002,
"msg": "鉴权失败",
})
return
}
理解:专门用来检验token的util。客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI,根据业务需求进行检验。比如作者这里对请求头进行切割获取携带的token串,然后利用之前的JWT解析函数看token是否有效。无效则返回,有效就把解析信息加入c *gin.Context,设置一个字段,进行后续操作。
用户通过上面的接口获取Token之后,后续就会携带着Token再来请求我们的其他接口,这个时候就需要对这些请求的Token进行校验操作了,很显然我们应该实现一个检验Token的中间件,具体实现如下:
// JWTAuthMiddleware 基于JWT的认证中间件
func JWTAuthMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
// 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI
// 这里假设Token放在Header的Authorization中,并使用Bearer开头
// 这里的具体实现方式要依据你的实际业务情况决定
authHeader := c.Request.Header.Get("Authorization")
if authHeader == "" {
c.JSON(http.StatusOK, gin.H{
"code": 2003,
"msg": "请求头中auth为空",
})
c.Abort()
return
}
// 按空格分割
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
c.JSON(http.StatusOK, gin.H{
"code": 2004,
"msg": "请求头中auth格式有误",
})
c.Abort()
return
}
// parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它
mc, err := ParseToken(parts[1])
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 2005,
"msg": "无效的Token",
})
c.Abort()
return
}
// 将当前请求的username信息保存到请求的上下文c上
c.Set("username", mc.Username)
c.Next() // 后续的处理函数可以用过c.Get("username")来获取当前请求的用户信息
}
}