前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手,带你从零封装Gin框架(七):实现登录接口 & jwt 鉴权中间件

手把手,带你从零封装Gin框架(七):实现登录接口 & jwt 鉴权中间件

作者头像
用户10002156
发布2024-01-17 13:41:35
1.4K0
发布2024-01-17 13:41:35
举报
文章被收录于专栏:生活处处有BUG生活处处有BUG

前言

这一篇将使用 jwt-go[1] 包来完成登录接口,颁发 token 令牌,并编写 jwt 中间件对 token 统一鉴权,避免在各个 controller 重复编写鉴权逻辑

安装

代码语言:javascript
复制
go get -u github.com/dgrijalva/jwt-go

定义配置项

新建 config/jwt.go 文件,编写配置

代码语言:javascript
复制
package config

type Jwt struct {
    Secret string `mapstructure:"secret" json:"secret" yaml:"secret"`
    JwtTtl int64 `mapstructure:"jwt_ttl" json:"jwt_ttl" yaml:"jwt_ttl"` // token 有效期(秒)
}

config/config.go 中,添加 Jwt 属性

代码语言:javascript
复制
package config

type Configuration struct {
    App App `mapstructure:"app" json:"app" yaml:"app"`
    Log Log `mapstructure:"log" json:"log" yaml:"log"`
    Database Database `mapstructure:"database" json:"database" yaml:"database"`
    Jwt Jwt `mapstructure:"jwt" json:"jwt" yaml:"jwt"`
}

config.yaml 添加对应配置

代码语言:javascript
复制
jwt:
  secret: 3Bde3BGEbYqtqyEUzW3ry8jKFcaPH17fRmTmqE7MDr05Lwj95uruRKrrkb44TJ4s
  jwt_ttl: 43200

编写颁发 Token 逻辑

新建 app/services/jwt.go 文件,编写

代码语言:javascript
复制
package services

import (
    "github.com/dgrijalva/jwt-go"
    "jassue-gin/global"
    "time"
)

type jwtService struct {
}

var JwtService = new(jwtService)

// 所有需要颁发 token 的用户模型必须实现这个接口
type JwtUser interface {
    GetUid() string
}

// CustomClaims 自定义 Claims
type CustomClaims struct {
    jwt.StandardClaims
}

const (
    TokenType = "bearer"
    AppGuardName = "app"
)

type TokenOutPut struct {
    AccessToken string `json:"access_token"`
    ExpiresIn int `json:"expires_in"`
    TokenType string `json:"token_type"`
}

// CreateToken 生成 Token
func (jwtService *jwtService) CreateToken(GuardName string, user JwtUser) (tokenData TokenOutPut, err error, token *jwt.Token) {
    token = jwt.NewWithClaims(
        jwt.SigningMethodHS256,
        CustomClaims{
            StandardClaims: jwt.StandardClaims{
                ExpiresAt: time.Now().Unix() + global.App.Config.Jwt.JwtTtl,
                Id:        user.GetUid(),
                Issuer:    GuardName, // 用于在中间件中区分不同客户端颁发的 token,避免 token 跨端使用
                NotBefore: time.Now().Unix() - 1000,
            },
        },
    )

    tokenStr, err := token.SignedString([]byte(global.App.Config.Jwt.Secret))

    tokenData = TokenOutPut{
        tokenStr,
        int(global.App.Config.Jwt.JwtTtl),
        TokenType,
    }
    return
}

CreateToken 方法需要接收一个 JwtUser 实例对象,我们需要将 app/models/user.go 用户模型实现 JwtUser 接口, 后续其他的用户模型都可以通过实现 JwtUser 接口,来调用 CreateToken() 颁发 Token

代码语言:javascript
复制
package models

import "strconv"

type User struct {
    ID
    Name string `json:"name" gorm:"not null;comment:用户名称"`
    Mobile string `json:"mobile" gorm:"not null;index;comment:用户手机号"`
    Password string `json:"-" gorm:"not null;default:'';comment:用户密码"`
    Timestamps
    SoftDeletes
}

func (user User) GetUid() string {
    return strconv.Itoa(int(user.ID.ID))
}

实现登录接口

app/common/request/user.go 中,新增 Login 验证器结构体

代码语言:javascript
复制
type Login struct {
    Mobile string `form:"mobile" json:"mobile" binding:"required,mobile"`
    Password string `form:"password" json:"password" binding:"required"`
}

func (login Login) GetMessages() ValidatorMessages {
    return ValidatorMessages{
        "mobile.required": "手机号码不能为空",
        "mobile.mobile": "手机号码格式不正确",
        "password.required": "用户密码不能为空",
    }
}

app/services/user.go 中,编写 Login() 登录逻辑

代码语言:javascript
复制
// Login 登录
func (userService *userService) Login(params request.Login) (err error, user *models.User) {
    err = global.App.DB.Where("mobile = ?", params.Mobile).First(&user).Error
    if err != nil || !utils.BcryptMakeCheck([]byte(params.Password), user.Password) {
        err = errors.New("用户名不存在或密码错误")
    }
    return
}

新建 app/controllers/app/auth.go 文件,编写 Login() 进行入参校验,并调用 UserServiceJwtService 服务,颁发 Token

代码语言:javascript
复制
package app

import (
    "github.com/gin-gonic/gin"
    "jassue-gin/app/common/request"
    "jassue-gin/app/common/response"
    "jassue-gin/app/services"
)

func Login(c *gin.Context) {
    var form request.Login
    if err := c.ShouldBindJSON(&form); err != nil {
        response.ValidateFail(c, request.GetErrorMsg(form, err))
        return
    }

    if err, user := services.UserService.Login(form); err != nil {
        response.BusinessFail(c, err.Error())
    } else {
        tokenData, err, _ := services.JwtService.CreateToken(services.AppGuardName, user)
        if err != nil {
            response.BusinessFail(c, err.Error())
            return
        }
        response.Success(c, tokenData)
    }
}

routes/api.go 中,添加路由

代码语言:javascript
复制
router.POST("/auth/login", app.Login)

使用 Apifox 调用http://localhost:8888/api/auth/login ,如下图,成功返回 Token,登录成功

编写 jwt 鉴权中间件

global/error.go 中,定义 TokenError 错误

代码语言:javascript
复制
type CustomErrors struct {
    // ...
    TokenError CustomError
}

var Errors = CustomErrors{
    // ...
    TokenError: CustomError{40100, "登录授权失效"},
}

app/common/response/response.go 中,编写 TokenFail() ,用于 token 鉴权失败统一返回

代码语言:javascript
复制
func TokenFail(c *gin.Context) {
    FailByError(c, global.Errors.TokenError)
}

新建 app/middleware/jwt.go 文件,编写

代码语言:javascript
复制
package middleware

import (
    "github.com/dgrijalva/jwt-go"
    "github.com/gin-gonic/gin"
    "jassue-gin/app/common/response"
    "jassue-gin/app/services"
    "jassue-gin/global"
)

func JWTAuth(GuardName string) gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenStr := c.Request.Header.Get("Authorization")
        if tokenStr == "" {
            response.TokenFail(c)
            c.Abort()
            return
        }
        tokenStr = tokenStr[len(services.TokenType)+1:]

        // Token 解析校验
        token, err := jwt.ParseWithClaims(tokenStr, &services.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
            return []byte(global.App.Config.Jwt.Secret), nil
        })
        if err != nil {
            response.TokenFail(c)
            c.Abort()
            return
        }

        claims := token.Claims.(*services.CustomClaims)
        // Token 发布者校验
        if claims.Issuer != GuardName {
            response.TokenFail(c)
            c.Abort()
            return
        }

        c.Set("token", token)
        c.Set("id", claims.Id)
    }
}

使用 jwt 中间件,实现获取用户信息接口

routes/api.go 中,使用 JWTAuth 中间件,这样一来,客户端需要使用正确的 Token 才能访问在 authRouter 分组下的路由

代码语言:javascript
复制
func SetApiGroupRoutes(router *gin.RouterGroup) {
    router.POST("/auth/register", app.Register)
    router.POST("/auth/login", app.Login)

    authRouter := router.Group("").Use(middleware.JWTAuth(services.AppGuardName))
    {
        authRouter.POST("/auth/info", app.Info)
    }
}

app/services/user.go 中,编写

代码语言:javascript
复制
// GetUserInfo 获取用户信息
func (userService *userService) GetUserInfo(id string) (err error, user models.User) {
    intId, err := strconv.Atoi(id)
    err = global.App.DB.First(&user, intId).Error
    if err != nil {
        err = errors.New("数据不存在")
    }
    return
}

app/controllers/auth.go中,编写 Info(),通过 JWTAuth 中间件校验 Token 识别的用户 ID 来获取用户信息

代码语言:javascript
复制
func Info(c *gin.Context) {
    err, user := services.UserService.GetUserInfo(c.Keys["id"].(string))
    if err != nil {
        response.BusinessFail(c, err.Error())
        return
    }
    response.Success(c, user)
}

使用 Apifox,先将调用登录接口获取 Token 放入 Authorization 头,再调用接口 http://localhost:8888/api/auth/info

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 生活处处有BUG 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 安装
  • 定义配置项
  • 编写颁发 Token 逻辑
  • 实现登录接口
  • 编写 jwt 鉴权中间件
  • 使用 jwt 中间件,实现获取用户信息接口
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档