这是我参与「第五届青训营」伴学笔记创作活动的第14天。
之前我们介绍了一种简单使用的jwt-go框架,# GO语言的jwt-go认证框架 | 青训营笔记。现在我们来介绍一下gin里带的jwt框架。这个框架比之前的jwt-go加复杂。但是功能、性能更加强大。
1. 安装 gin-jwt 库
go get -u github.com/appleboy/gin-jwt
2. 创建一个 JWT 的 middleware
package main
import (
"time"
"github.com/appleboy/gin-jwt"
"github.com/gin-gonic/gin"
)
//定义一个登录结构体
type login struct {
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
// User demo
var User = map[string]string{
"user1": "password1",
"user2": "password2",
}
func helloHandler(c *gin.Context) {
claims := jwt.ExtractClaims(c)
user, _ := c.Get(identityKey)
c.JSON(200, gin.H{
"userID": claims["id"],
"userName": user.(*User).UserName,
"text": "Hello World.",
})
}
func main() {
r := gin.Default()
authMiddleware := &jwt.GinJWTMiddleware{ // the jwt middleware,jwt中间件的配置参数见文章末尾。
Realm: "test zone",
Key: []byte("secret key"),//加密秘钥,自己记得即可,随意取
Timeout: time.Hour,//有效时间
MaxRefresh: time.Hour,//最大刷新时间
IdentityKey: identityKey,
IdentityHandler: func(c *gin.Context) interface{} {
claims := jwt.ExtractClaims(c)
return &User{
UserName: claims["id"].(string),
}
},
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(*User); ok {
return jwt.MapClaims{
"id": v.UserName,
}
}
return jwt.MapClaims{}
},
Authenticator: func(c *gin.Context) (interface{}, error) { //验证登录的中间件函数
var loginVals login
if err := c.ShouldBind(&loginVals); err != nil {
return "", jwt.ErrMissingLoginValues
}
userID := loginVals.Username
password := loginVals.Password
if (User[userID] == password) {
return &User{
UserName: userID,
}, nil
}
return nil, jwt.ErrFailedAuthentication
},
Authorizator: func(data interface{}, c *gin.Context) bool { //验证权限的中间件函数
if v, ok := data.(*User); ok && v.UserName == "admin" {
return true
}
return false
},
Unauthorized: func(c *gin.Context, code int, message string) {
c.JSON(code, gin.H{
"code": code,
"message": message,
})
},
TokenLookup: "header: Authorization, query: token, cookie: jwt",
TokenHeadName: "Bearer",
TimeFunc: time.Now,
}
r.POST("/login", authMiddleware.LoginHandler) //单个路由中间件使用方法
r.GET("/refresh_token", authMiddleware.RefreshHandler)
auth := r.Group("/auth") //设置路由组
auth.Use(authMiddleware.MiddlewareFunc()) //中间件使用方法:路由r.use(authMiddleware.funcnamexxx())
{
auth.GET("/hello", helloHandler)
}
if err := r.Run(":8080"); err != nil {
log.Fatal(err)
}
}
3. 发送一个登录请求(也可以使用postman测试,下面所有请求都可以,只要请求参数一致即可。)
curl -X POST http://localhost:8080/login -d '{"username":"user1","password":"password1"}'
4. 返回登录结果
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Mzg5MzkxMjMsImlkIjoidXNlcjEifQ.4m4YjZBzmZQQK_X9bVrKP8W0QVUvk-jK7a-0p8A8O6Y" }
5. 发送一个 hello 请求 (取出token,放入请求头,浏览器或postman会自动放)
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Mzg5MzkxMjMsImlkIjoidXNlcjEifQ.4m4YjZBzmZQQK_X9bVrKP8W0QVUvk-jK7a-0p8A8O6Y" http://localhost:8080/auth/hello
6. 返回请求结果
{ "text": "Hello World.", "userID": "user1", "userName": "user1" }
7. 发送一个刷新 token 的请求
curl -X GET http://localhost:8080/refresh_token -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Mzg5MzkxMjMsImlkIjoidXNlcjEifQ.4m4YjZBzmZQQK_X9bVrKP8W0QVUvk-jK7a-0p8A8O6Y"
8. 返回刷新结果
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1Mzg5MzkxNzQsImlkIjoidXNlcjEifQ.W0zB1FxRXg_lPv7zWG8Hk-d2q3-k-v7z1Z8bQx2Qq3o" }
9.错误提示
- 如果我们不提供正确的 token 值,会返回 401 的错误信息,如下:
{ "code": 401, "message": "Token not found" }
- 如果我们提供了正确的 token 值,但是这个 token 已经过期,会返回 401 的错误信息,如下:
{ "code": 401, "message": "Token is expired" }
- 如果我们提供了正确的 token 值,但是这个 token 的使用者没有访问 /auth/hello 的权限,会返回 401 的错误信息,如下:
{ "code": 401, "message": "Authorization header not found" }
10.中间件里提供的函数的使用说明
1. 登录请求流程(使用 LoginHandler)
提供:LoginHandler() 这是一个提供的函数,可以调用任何登录端点,将触发下面描述的流程。
必需:Authenticator 该函数应验证给定gin上下文中的用户凭据(例如,给定用户电子邮件的密码与哈希密码匹配,以及任何其他身份验证逻辑)。然后,Authenticator应返回一个包含将嵌入jwt令牌中的用户数据的结构或映射。这可能是账号ID,角色,is_verified等之类的东西。在成功认证后,从Authenticator返回的数据作为参数传递到PayloadFunc中,用于将上述用户标识嵌入jwt令牌中。如果返回错误,将使用Unauthorized函数(如下所述)。
可选:PayloadFunc 在成功认证(登陆)后调用此函数。它应将从Authenticator返回的任何内容转换为MapClaims(即map [string] interface {})。这个函数的典型用例是,当Authenticator返回一个持有用户标识符的结构时,该结构需要转换为一个映射。MapClaims应包括一个元素[IdentityKey(默认为“identity”):some_user_identity]。 PayloadFunc返回的MapClaims中的元素将嵌入jwt令牌(作为令牌声明)中。当用户在后续请求中传递令牌时,可以通过使用ExtractClaims返回这些要求。
可选:LoginResponse 在成功使用Authenticator认证,使用从PayloadFunc返回的映射中的标识符创建jwt令牌,并在SendCookie启用时将其设置为cookie之后,调用此函数。它被用于处理任何登录后的逻辑。这可能看起来像是使用gin上下文将令牌的JSON返回给用户。
2.登陆后,该端点上的后续请求需要 jwt 令牌(使用 MiddlewareFunc)
提供:MiddlewareFunc()
这是 gin 中间件,应该在任何需要 jwt 令牌的端点中使用。 如果令牌存在,此中间件将解析请求标头,并检查 jwt 令牌是否有效(未过期,签名正确)。 然后它将调用 IdentityHandler,然后调用 Authorizator。 如果 Authorizator 通过并且所有先前的令牌有效性检查都通过,则中间件将继续请求。 如果这些检查中的任何一个失败,则使用 Unauthorized 函数(如下所述)。
可选:IdentityHandler
此功能的默认值可能足以满足您的需要。 此函数的目的是从嵌入在 jwt 令牌中的声明中获取用户身份,并将此身份值传递给 Authorizator。 此函数假定 [IdentityKey: some_user_identity] 是嵌入在 jwt 令牌声明中的属性之一(由 PayloadFunc 确定)。
可选:Authorizator
给定用户身份值(data参数)和 gin 上下文,此函数应检查用户是否有权到达此端点(在 MiddlewareFunc 适用的端点上)。 此函数可能应该使用 ExtractClaims 来检查用户是否具有足够的权限来访问此端点,而不是在每次请求时都访问数据库。 如果用户被授权继续执行请求,则此函数应返回 true,如果他们未被授权(将调用 Unauthorized),则返回 false。
3.注销请求流程(使用 LogoutHandler)
提供:LogoutHandler()
这是一个提供的函数,可以在任何注销端点上调用,如果设置了 SendCookie,它将清除所有 cookie,然后调用 LogoutResponse。
可选:LogoutResponse
如果注销成功与否,这可能只是将 http 状态代码返回给用户。
4. 刷新请求流程(使用 RefreshHandler)
提供:RefreshHandler
这是一个提供的函数,可以在任何刷新令牌端点上调用。 如果传入的令牌是在 MaxRefreshTime 时间范围内发出的,则此处理程序将创建/设置一个类似于 LoginHandler 的新令牌,并将此令牌传递给 RefreshResponse
可选:RefreshResponse
这可能会将令牌的 JSON 返回给用户,类似于 LoginResponse
5. 登录失败、令牌错误或缺少权限
可选: Unauthorized
在登录、授权用户时出现任何错误,或者当请求中没有令牌或无效令牌时,将发生以下情况。 gin 上下文将根据 DisabledAbort 中止,然后调用 HTTPStatusMessageFunc,默认情况下将错误转换为字符串。 最后将调用 Unauthorized 函数。 此函数应该可能会向用户返回包含 http 错误代码和错误消息的 JSON。