咔叽网单游戏基地

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 28|回复: 0

[Golang] 基于gin的golang web开发之认证利器jwt

[复制链接]
  • TA的每日心情
    无聊
    2019-4-21 13:02
  • 签到天数: 3 天

    [LV.2]圆转纯熟

    2万

    主题

    2万

    帖子

    11万

    积分

    帖子管理员

    Rank: 9Rank: 9Rank: 9

    积分
    117124

    灌水之王最佳新人活跃会员热心会员宣传达人

    发表于 2020-12-19 15:09:39 | 显示全部楼层 |阅读模式
    JSON Web Token(JWT)是一种很流行的跨域认证解决方案,JWT基于JSON可以在进行验证的同时附带身份信息,对于前后端分离项目很有帮助。
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
    JWT由三部分组成,每个部分之间用点.隔开,分别称为HEADER、PAYLOAD和VERIFY SIGNATURE。HEADER和PAYLOAD经过base64解码后为JSON明文。
      HEADER包含两个字段,alg指明JWT的签名算法,typ固定为JWT。PAYLOAD中包含JWT的声明信息,标准中定义了iss、sub、aud等声明字段,如果标准声明不够用的话,我们还可以增加自定义声明。要注意两点,第一PAYLOAD只是经过base64编码,几乎就等于是明文,不要包含敏感信息。第二不要在PAYLOAD中放入过多的信息,因为验证通过以后每一个请求都要包含JWT,信息太多的话会造成一些没有必要的资源浪费。VERIFY SIGNATURE为使用HEADER中指定的算法生成的签名。例如alg:HS256签名算法
    HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),密钥)
    了解完JWT的基本原理之后,我们来看一下在gin中是怎么使用JWT的。
    引入gin-jwt中间件
    在Gin中使用jwt有个开源项目gin-jwt,这项目几乎包含了我们要用到的一切。例如定义PAYLOAD中的声明、授权验证的方法、是否使用COOKIE等等。下面来看一下官网给出的例子。
    1. package main
    2. import (
    3.         "log"
    4.         "net/http"
    5.         "os"
    6.         "time"
    7.         jwt "github.com/appleboy/gin-jwt/v2"
    8.         "github.com/gin-gonic/gin"
    9. )
    10. type login struct {
    11.         Username string `form:"username" json:"username" binding:"required"`
    12.         Password string `form:"password" json:"password" binding:"required"`
    13. }
    14. var identityKey = "id"
    15. func helloHandler(c *gin.Context) {
    16.         claims := jwt.ExtractClaims(c)
    17.         user, _ := c.Get(identityKey)
    18.         c.JSON(200, gin.H{
    19.                 "userID":  claims[identityKey],
    20.                 "userName": user.(*User).UserName,
    21.                 "text":   "Hello World.",
    22.         })
    23. }
    24. type User struct {
    25.         UserName string
    26.         FirstName string
    27.         LastName string
    28. }
    29. func main() {
    30.         port := os.Getenv("PORT")
    31.         r := gin.New()
    32.         r.Use(gin.Logger())
    33.         r.Use(gin.Recovery())
    34.         if port == "" {
    35.                 port = "8000"
    36.         }
    37.         authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
    38.                 Realm:    "test zone",
    39.                 Key:     []byte("secret key"),
    40.                 Timeout:   time.Hour,
    41.                 MaxRefresh: time.Hour,
    42.                 IdentityKey: identityKey,
    43.                 PayloadFunc: func(data interface{}) jwt.MapClaims {
    44.                         if v, ok := data.(*User); ok {
    45.                                 return jwt.MapClaims{
    46.                                         identityKey: v.UserName,
    47.                                 }
    48.                         }
    49.                         return jwt.MapClaims{}
    50.                 },
    51.                 IdentityHandler: func(c *gin.Context) interface{} {
    52.                         claims := jwt.ExtractClaims(c)
    53.                         return &User{
    54.                                 UserName: claims[identityKey].(string),
    55.                         }
    56.                 },
    57.                 Authenticator: func(c *gin.Context) (interface{}, error) {
    58.                         var loginVals login
    59.                         if err := c.ShouldBind(&loginVals); err != nil {
    60.                                 return "", jwt.ErrMissingLoginValues
    61.                         }
    62.                         userID := loginVals.Username
    63.                         password := loginVals.Password
    64.                         if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
    65.                                 return &User{
    66.                                         UserName: userID,
    67.                                         LastName: "Bo-Yi",
    68.                                         FirstName: "Wu",
    69.                                 }, nil
    70.                         }
    71.                         return nil, jwt.ErrFailedAuthentication
    72.                 },
    73.                 Authorizator: func(data interface{}, c *gin.Context) bool {
    74.                         if v, ok := data.(*User); ok && v.UserName == "admin" {
    75.                                 return true
    76.                         }
    77.                         return false
    78.                 },
    79.                 Unauthorized: func(c *gin.Context, code int, message string) {
    80.                         c.JSON(code, gin.H{
    81.                                 "code":  code,
    82.                                 "message": message,
    83.                         })
    84.                 },
    85.                 TokenLookup: "header: Authorization, query: token, cookie: jwt",
    86.                 TokenHeadName: "Bearer",
    87.                 TimeFunc: time.Now,
    88.         })
    89.         if err != nil {
    90.                 log.Fatal("JWT Error:" + err.Error())
    91.         }
    92.         errInit := authMiddleware.MiddlewareInit()
    93.         if errInit != nil {
    94.                 log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
    95.         }
    96.         r.POST("/login", authMiddleware.LoginHandler)
    97.         r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) {
    98.                 claims := jwt.ExtractClaims(c)
    99.                 log.Printf("NoRoute claims: %#v\n", claims)
    100.                 c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
    101.         })
    102.         auth := r.Group("/auth")
    103.         auth.GET("/refresh_token", authMiddleware.RefreshHandler)
    104.         auth.Use(authMiddleware.MiddlewareFunc())
    105.         {
    106.                 auth.GET("/hello", helloHandler)
    107.         }
    108.         if err := http.ListenAndServe(":"+port, r); err != nil {
    109.                 log.Fatal(err)
    110.         }
    111. }
    复制代码
    我们可以看到jwt.GinJWTMiddleware用于声明一个中间件。PayloadFunc方法中给默认的PAYLOAD增加了id字段,取值为UserName。Authenticator认证器,我们可以在这里验证用户身份,参数为*gin.Context,所以在这里我们可以像写Gin Handler那样获取到Http请求中的各种内容。Authorizator授权器可以判断判断当前JWT是否有权限继续访问。当然还可以设置像过期时间,密钥,是否设置COOKIE等其他选项。
    登录Handler
    以上例子中配置了路由r.POST("/login", authMiddleware.LoginHandler)下面我们来看一下登录过程是怎样的。
    1. func (mw *GinJWTMiddleware) LoginHandler(c *gin.Context) {
    2.         if mw.Authenticator == nil {
    3.                 mw.unauthorized(c, http.StatusInternalServerError, mw.HTTPStatusMessageFunc(ErrMissingAuthenticatorFunc, c))
    4.                 return
    5.         }
    6.         data, err := mw.Authenticator(c)
    7.         if err != nil {
    8.                 mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, c))
    9.                 return
    10.         }
    11.         // Create the token
    12.         token := jwt.New(jwt.GetSigningMethod(mw.SigningAlgorithm))
    13.         claims := token.Claims.(jwt.MapClaims)
    14.         if mw.PayloadFunc != nil {
    15.                 for key, value := range mw.PayloadFunc(data) {
    16.                         claims[key] = value
    17.                 }
    18.         }
    19.         expire := mw.TimeFunc().Add(mw.Timeout)
    20.         claims["exp"] = expire.Unix()
    21.         claims["orig_iat"] = mw.TimeFunc().Unix()
    22.         tokenString, err := mw.signedString(token)
    23.         if err != nil {
    24.                 mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrFailedTokenCreation, c))
    25.                 return
    26.         }
    27.         // set cookie
    28.         if mw.SendCookie {
    29.                 expireCookie := mw.TimeFunc().Add(mw.CookieMaxAge)
    30.                 maxage := int(expireCookie.Unix() - mw.TimeFunc().Unix())
    31.                 if mw.CookieSameSite != 0 {
    32.                         c.SetSameSite(mw.CookieSameSite)
    33.                 }
    34.                 c.SetCookie(
    35.                         mw.CookieName,
    36.                         tokenString,
    37.                         maxage,
    38.                         "/",
    39.                         mw.CookieDomain,
    40.                         mw.SecureCookie,
    41.                         mw.CookieHTTPOnly,
    42.                 )
    43.         }
    44.         mw.LoginResponse(c, http.StatusOK, tokenString, expire)
    45. }
    复制代码
    LoginHandler整体逻辑还是比较简单的,检查并调用前面设置的Authenticator方法,验证成功的话生成一个新的JWT,调用PayloadFunc方法设置PAYLOAD的自定义字段,根据SendCookie判断是否需要在HTTP中设置COOKIE,最后调用LoginResponse方法设置返回值。
    使用中间件
    jwt-gin包提供了一个标准的Gin中间件,我们可以在需要验证JWT的路由上设置中间件。前面例子中对路由组/auth增加了JWT验证auth.Use(authMiddleware.MiddlewareFunc())。
    1. func (mw *GinJWTMiddleware) MiddlewareFunc() gin.HandlerFunc {
    2.         return func(c *gin.Context) {
    3.                 mw.middlewareImpl(c)
    4.         }
    5. }
    6. func (mw *GinJWTMiddleware) middlewareImpl(c *gin.Context) {
    7.         claims, err := mw.GetClaimsFromJWT(c)
    8.         if err != nil {
    9.                 mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, c))
    10.                 return
    11.         }
    12.         if claims["exp"] == nil {
    13.                 mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrMissingExpField, c))
    14.                 return
    15.         }
    16.         if _, ok := claims["exp"].(float64); !ok {
    17.                 mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrWrongFormatOfExp, c))
    18.                 return
    19.         }
    20.         if int64(claims["exp"].(float64)) < mw.TimeFunc().Unix() {
    21.                 mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrExpiredToken, c))
    22.                 return
    23.         }
    24.         c.Set("JWT_PAYLOAD", claims)
    25.         identity := mw.IdentityHandler(c)
    26.         if identity != nil {
    27.                 c.Set(mw.IdentityKey, identity)
    28.         }
    29.         if !mw.Authorizator(identity, c) {
    30.                 mw.unauthorized(c, http.StatusForbidden, mw.HTTPStatusMessageFunc(ErrForbidden, c))
    31.                 return
    32.         }
    33.         c.Next()
    34. }
    复制代码
    GetClaimsFromJWT方法在当前上下文中获取JWT,失败的话返回未授权。接着会判断JWT是否过期,最后前面设置的Authorizator方法验证是否有权限继续访问。
    到此这篇关于基于gin的golang web开发之认证利器jwt的文章就介绍到这了,更多相关gin的golang web开发内容请搜索咔叽论坛以前的文章或继续浏览下面的相关文章希望大家以后多多支持咔叽论坛!

    原文地址:https://www.jb51.net/article/201193.htm
    回复

    使用道具 举报

    QQ|免责声明|手机版|咔叽网单

    GMT+8, 2021-3-3 05:05

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表