什么是JWT
JWT,全称 JSON Web Token,是一种开放标准(RFC 7519),用于安全地在双方之间通报信息。尤其适用于身份验证和授权场景。JWT 的设计允许信息在各方之间安全地、 compactly(紧凑地)传输,因为其自身包含了所有需要的认证信息,从而淘汰了需要查询数据库或会话存储的需求。
JWT主要由三部门组成,通过.毗连:
- Header(头部):描述JWT的元数据,通常包括类型(通常是JWT)和使用的签名算法(如HS256、RS256等)。
- Payload(载荷):包含声明(claims),即用户的相关信息。这些信息可以是公开的,也可以是私有的,但应避免放入敏感信息,因为该部门可以被解码查看。载荷中的声明可以验证,但不加密。
- Signature(签名):用于验证JWT的完整性和来源。它是通过将Header和Payload分别进行Base64编码后,再与一个秘钥(secret)一起通过指定的算法(如HMAC SHA256)盘算得出的。
JWT的工作流程大致如下:
- 认证阶段:用户向服务器提供凭据(如用户名和密码)。服务器验证凭据无误后,生成一个JWT,其中包含用户标识符和其他声明,并使用秘钥对其进行签名。
- 使用阶段:客户端收到JWT后,可以在后续的每个请求中将其放在HTTP请求头中发送给服务器,以此证实自己的身份。
- 验证阶段:服务器收到JWT后,会使用相同的秘钥验证JWT的签名,确保其未被窜改,并检查过期时间等其他声明,从而决定是否允许实行请求。
JWT的优势在于它的无状态性,服务器不需要存储会话信息,这减轻了服务器的压力,同时也方便了跨域认证。但需要留意的是,JWT的安全性依赖于秘钥的安全保管以及对JWT过期时间等的合理设置。
API设计
这里设计两个公共接口和一个受保护的接口。
API描述/api/login公开接口。用于用户登录/api/register公开接口。用于用户注册/api/admin/user保护接口,需要验证JWT开发预备
初始化项目目次并切换进入使用go mod初始化工程安装依赖- go get -u github.com/gin-gonic/gin
- go get -u gorm.io/gorm
- go get -u gorm.io/driver/postgres
- go get -u github.com/golang-jwt/jwt/v5
- go get -u github.com/joho/godotenv
- go get -u golang.org/x/crypto
复制代码 创建第一个API
一开始我们可以在项目的根目次中创建文件main.go添加以下内容- package main
- import (
- "net/http"
- "github.com/gin-gonic/gin"
- )
- func main() {
- r := gin.Default()
- public := r.Group("/api")
- {
- public.POST("/register", func(c *gin.Context) {
- c.JSON(http.StatusOK, gin.H{
- "data": "test. register api",
- })
- })
- }
- r.Run("0.0.0.0:8000")
- }
复制代码 测试运行客户端测试。正常的话会有以下输出- $ curl -X POST http://127.0.0.1:8000/api/register
- {"data":"test. register api"}
复制代码 美满register接口
如今register接口已经预备好了,但一样平常来说我们会把接口业务逻辑放在单独的文件中,而不是和接口定义写在一块。
创建一个控制器的包目次,并添加文件- mkdir controllers
- touch controllers/auth.go
复制代码 auth.go文件内容- package controllers
- import (
- "net/http"
- "github.com/gin-gonic/gin"
- )
- func Register(c *gin.Context) {
- c.JSON(http.StatusOK, gin.H{
- "data": "hello, this is register endpoint",
- })
- }
复制代码 更新main.go文件- package main
- import (
- "github.com/gin-gonic/gin"
- "gin-jwt/controllers"
- )
- func main() {
- r := gin.Default()
- public := r.Group("/api")
- {
- public.POST("/register", controllers.Register)
- }
- r.Run("0.0.0.0:8000")
- }
复制代码 重新运行测试客户端测试- $ curl -X POST http://127.0.0.1:8000/api/register
- {"data":"hello, this is register endpoint"}
复制代码 解析register的客户端请求
客户端请求register api需要携带用户名和密码的参数,服务端对此做解析。编辑文件controllers/auth.go- package controllers
- import (
- "net/http"
- "github.com/gin-gonic/gin"
- )
- // /api/register的请求体
- type ReqRegister struct {
- Username string `json:"username" binding:"required"`
- Password string `json:"password" binding:"required"`
- }
- func Register(c *gin.Context) {
- var req ReqRegister
- if err := c.ShouldBindBodyWithJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "data": err.Error(),
- })
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "data": req,
- })
- }
复制代码 客户端请求测试- $ curl -X POST http://127.0.0.1:8000/api/register -d '{"username": "zhangsan", "password": "123456"}' -H 'Content-Type=application/json'
- {"data":{"username":"zhangsan","password":"123456"}}
复制代码 毗连关系型数据库
一样平常会将数据保存到专门的数据库中,这里用PostgreSQL来存储数据。Postgres使用docker来安装。安装完postgres后,创建用户和数据库:- create user ginjwt encrypted password 'ginjwt';
- create database ginjwt owner = ginjwt;
复制代码 创建目次models,这个目次将包含毗连数据库和数据模型的代码。编辑文件models/setup.go- package models
- import (
- "fmt"
- "log"
- "os"
- "github.com/joho/godotenv"
- "gorm.io/driver/postgres"
- "gorm.io/gorm"
- )
- var DB *gorm.DB
- func ConnectDatabase() {
- err := godotenv.Load(".env")
- if err != nil {
- log.Fatalf("Error loading .env file. %v\n", err)
- }
- // DbDriver := os.Getenv("DB_DRIVER")
- DbHost := os.Getenv("DB_HOST")
- DbPort := os.Getenv("DB_PORT")
- DbUser := os.Getenv("DB_USER")
- DbPass := os.Getenv("DB_PASS")
- DbName := os.Getenv("DB_NAME")
- dsn := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai password=%s", DbHost, DbPort, DbUser, DbName, DbPass)
- DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
- if err != nil {
- log.Fatalf("Connect to database failed, %v\n", err)
- } else {
- log.Printf("Connect to database success, host: %s, port: %s, user: %s, dbname: %s\n", DbHost, DbPort, DbUser, DbName)
- }
- // 迁移数据表
- DB.AutoMigrate(&User{})
- }
复制代码 新建并编辑环境设置文件.env- DB_HOST=127.0.0.1
- DB_PORT=5432
- DB_USER=ginjwt
- DB_PASS=ginjwt
- DB_NAME=ginjwt
复制代码 创建用户模型,编辑代码文件models/user.go- package models
- import (
- "html"
- "strings"
- "golang.org/x/crypto/bcrypt"
- "gorm.io/gorm"
- )
- type User struct {
- gorm.Model
- Username string `gorm:"size:255;not null;unique" json:"username"`
- Password string `gorm:"size:255;not null;" json:"password"`
- }
- func (u *User) SaveUser() (*User, error) {
- err := DB.Create(&u).Error
- if err != nil {
- return &User{}, err
- }
- return u, nil
- }
- // 使用gorm的hook在保存密码前对密码进行hash
- func (u *User) BeforeSave(tx *gorm.DB) error {
- hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
- if err != nil {
- return err
- }
- u.Password = string(hashedPassword)
- u.Username = html.EscapeString(strings.TrimSpace(u.Username))
- return nil
- }
复制代码 更新main.go- package main
- import (
- "github.com/gin-gonic/gin"
- "gin-jwt/controllers"
- "gin-jwt/models"
- )
- func init() {
- models.ConnectDatabase()
- }
- func main() {
- r := gin.Default()
- public := r.Group("/api")
- {
- public.POST("/register", controllers.Register)
- }
- r.Run("0.0.0.0:8000")
- }
复制代码 更新controllers/auth.go- package controllers
- import (
- "net/http"
- "gin-jwt/models"
- "github.com/gin-gonic/gin"
- )
- // /api/register的请求体
- type ReqRegister struct {
- Username string `json:"username" binding:"required"`
- Password string `json:"password" binding:"required"`
- }
- func Register(c *gin.Context) {
- var req ReqRegister
- if err := c.ShouldBindBodyWithJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "data": err.Error(),
- })
- return
- }
- u := models.User{
- Username: req.Username,
- Password: req.Password,
- }
- _, err := u.SaveUser()
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "data": err.Error(),
- })
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "message": "register success",
- "data": req,
- })
- }
复制代码 重新运行服务端后,客户端测试- $ curl -X POST http://127.0.0.1:8000/api/register -d '{"username": "zhangsan", "password": "123456"}' -H 'Content-Type=application/json'
- {"data":{"username":"zhangsan","password":"123456"},"message":"register success"}
复制代码 添加login接口
登录接口实现的也非常简单,只需要提供用户名和密码参数。服务端接收到客户端的请求后到数据库中去匹配,确认用户是否存在和密码是否正确。假如验证通过则返回一个token,否则返回异常响应。
首先在main.go中注册API- // xxx
- func main() {
- // xxx
- r := gin.Default()
- public := r.Group("/api")
- {
- public.POST("/register", controllers.Register)
- public.POST("/login", controllers.Login)
- }
- }
复制代码 在auth.go中添加Login控制器函数- // api/login 的请求体
- type ReqLogin struct {
- Username string `json:"username" binding:"required"`
- Password string `json:"password" binding:"required"`
- }
- func Login(c *gin.Context) {
- var req ReqLogin
- if err := c.ShouldBindBodyWithJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- u := models.User{
- Username: req.Username,
- Password: req.Password,
- }
- // 调用 models.LoginCheck 对用户名和密码进行验证
- token, err := models.LoginCheck(u.Username, u.Password)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": "username or password is incorrect.",
- })
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "token": token,
- })
- }
复制代码 LoginCheck方法在models/user.go文件中实现- package models
- import (
- "gin-jwt/utils/token"
- "html"
- "strings"
- "golang.org/x/crypto/bcrypt"
- "gorm.io/gorm"
- )
- func VerifyPassword(password, hashedPassword string) error {
- return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
- }
- func LoginCheck(username, password string) (string, error) {
- var err error
- u := User{}
- err = DB.Model(User{}).Where("username = ?", username).Take(&u).Error
- if err != nil {
- return "", err
- }
- err = VerifyPassword(password, u.Password)
- if err != nil && err == bcrypt.ErrMismatchedHashAndPassword {
- return "", err
- }
- token, err := token.GenerateToken(u.ID)
- if err != nil {
- return "", err
- }
- return token, nil
- }
复制代码 这里将token相关的函数放到了单独的模块中,新增相关目次并编辑文件- mkdir -p utils/token
- touch utils/token/token.go
复制代码 以下代码为token.go的内容,包含的几个函数在反面会用到- package token
- import (
- "fmt"
- "os"
- "strconv"
- "strings"
- "time"
- "github.com/gin-gonic/gin"
- "github.com/golang-jwt/jwt/v5"
- )
- func GenerateToken(user_id uint) (string, error) {
- token_lifespan, err := strconv.Atoi(os.Getenv("TOKEN_HOUR_LIFESPAN"))
- if err != nil {
- return "", err
- }
- claims := jwt.MapClaims{}
- claims["authorized"] = true
- claims["user_id"] = user_id
- claims["exp"] = time.Now().Add(time.Hour * time.Duration(token_lifespan)).Unix()
- token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
- return token.SignedString([]byte(os.Getenv("API_SECRET")))
- }
- func TokenValid(c *gin.Context) error {
- tokenString := ExtractToken(c)
- fmt.Println(tokenString)
- _, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
- if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
- return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
- }
- return []byte(os.Getenv("API_SECRET")), nil
- })
- if err != nil {
- return err
- }
- return nil
- }
- // 从请求头中获取token
- func ExtractToken(c *gin.Context) string {
- bearerToken := c.GetHeader("Authorization")
- if len(strings.Split(bearerToken, " ")) == 2 {
- return strings.Split(bearerToken, " ")[1]
- }
- return ""
- }
- // 从jwt中解析出user_id
- func ExtractTokenID(c *gin.Context) (uint, error) {
- tokenString := ExtractToken(c)
- token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
- if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
- return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
- }
- return []byte(os.Getenv("API_SECRET")), nil
- })
- if err != nil {
- return 0, err
- }
- claims, ok := token.Claims.(jwt.MapClaims)
- // 如果jwt有效,将user_id转换为浮点数字符串,然后再转换为 uint32
- if ok && token.Valid {
- uid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 32)
- if err != nil {
- return 0, err
- }
- return uint(uid), nil
- }
- return 0, nil
- }
复制代码 在.env文件中添加两个环境变量的设置。TOKEN_HOUR_LIFESPAN设置token的过期时长,API_SECRET是jwt的密钥。- TOKEN_HOUR_LIFESPAN=1
- API_SECRET="wP3-sN6&gG4-lV8>gJ9)"
复制代码 测试,这里改用python代码进行测试- import requests
- import json
- headers = {
- "Content-Type": "application/json",
- }
- resp = requests.get("http://127.0.0.1:8000/api/admin/user", headers=headers)
- def register(username: str, password: str):
- req_body = {
- "username": username,
- "password": password,
- }
- resp = requests.post("http://127.0.0.1:8000/api/register", data=json.dumps(req_body), headers=headers)
- print(resp.text)
- def login(username: str, password: str):
- req_body = {
- "username": username,
- "password": password,
- }
- resp = requests.post("http://127.0.0.1:8000/api/login", data=json.dumps(req_body), headers=headers)
- print(resp.text)
- if resp.status_code == 200:
- return resp.json()["token"]
- else:
- return ""
- if __name__ == "__main__":
- username = "lisi"
- password = "123456"
- register(username, password)
- token = login(username, password)
- print(token)
复制代码 创建JWT认证中间件
创建中间件目次和代码文件- mkdir middlewares
- touch middlewares/middlewares.go
复制代码 内容如下- package middlewares
- import (
- "gin-jwt/utils/token"
- "net/http"
- "github.com/gin-gonic/gin"
- )
- func JwtAuthMiddleware() gin.HandlerFunc {
- return func(c *gin.Context) {
- err := token.TokenValid(c)
- if err != nil {
- c.String(http.StatusUnauthorized, err.Error())
- c.Abort()
- return
- }
- c.Next()
- }
- }
复制代码 在main.go文件中注册路由的时候使用中间件- func main() {
- models.ConnectDatabase()
- r := gin.Default()
- public := r.Group("/api")
- {
- public.POST("/register", controllers.Register)
- public.POST("/login", controllers.Login)
- }
- protected := r.Group("/api/admin")
- {
- protected.Use(middlewares.JwtAuthMiddleware())
- protected.GET("/user", func(c *gin.Context) {
- c.JSON(http.StatusOK, gin.H{
- "status": "success",
- "message": "authorized",
- })
- })
- }
- r.Run("0.0.0.0:8000")
- }
复制代码 在controllers/auth.go文件中实现CurrentUser- func CurrentUser(c *gin.Context) {
- // 从token中解析出user_id
- user_id, err := token.ExtractTokenID(c)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
- return
- }
- // 根据user_id从数据库查询数据
- u, err := models.GetUserByID(user_id)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "message": "success",
- "data": u,
- })
- }
复制代码 在models/user.go文件中实现GetUserByID- // 返回前将用户密码置空
- func (u *User) PrepareGive() {
- u.Password = ""
- }
- func GetUserByID(uid uint) (User, error) {
- var u User
- if err := DB.First(&u, uid).Error; err != nil {
- return u, errors.New("user not found")
- }
- u.PrepareGive()
- return u, nil
- }
复制代码 至此,一个简单的gin-jwt应用就完成了。
客户端测试python脚本
服务端的三个接口这里用python脚本来测试- import requests
- import json
- headers = {
- # "Authorization": f"Bearer {token}",
- "Content-Type": "application/json",
- }
- resp = requests.get("http://127.0.0.1:8000/api/admin/user", headers=headers)
- def register(username: str, password: str):
- req_body = {
- "username": username,
- "password": password,
- }
- resp = requests.post("http://127.0.0.1:8000/api/register", data=json.dumps(req_body), headers=headers)
- print(resp.text)
- def login(username: str, password: str):
- req_body = {
- "username": username,
- "password": password,
- }
- resp = requests.post("http://127.0.0.1:8000/api/login", data=json.dumps(req_body), headers=headers)
- print(resp.text)
- if resp.status_code == 200:
- return resp.json()["token"]
- else:
- return ""
- def test_protect_api(token: str):
- global headers
- headers["Authorization"] = f"Bearer {token}"
- resp = requests.get("http://127.0.0.1:8000/api/admin/user", headers=headers)
- print(resp.text)
- if __name__ == "__main__":
- username = "lisi"
- password = "123456"
- register(username, password)
- token = login(username, password)
- test_protect_api(token)
复制代码 运行脚本效果- {"message":"register success"}
- {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MTk5NDA0NjAsInVzZXJfaWQiOjZ9.qkzn0Ot9hAb54l3RFbGUohHJ9oezGia5x_oXppbD2jQ"}
- {"data":{"ID":6,"CreatedAt":"2024-07-03T00:14:20.187725+08:00","UpdatedAt":"2024-07-03T00:14:20.187725+08:00","DeletedAt":null,"username":"wangwu","password":""},"message":"success"}
复制代码 完整示例代码
目次结构
- ├── client.py # 客户端测试脚本
- ├── controllers # 控制器相关包
- │ └── auth.go # 控制器方法实现
- ├── gin-jwt.bin # 编译的二进制文件
- ├── go.mod # go 项目文件
- ├── go.sum # go 项目文件
- ├── main.go # 程序入口文件
- ├── middlewares # 中间件相关包
- │ └── middlewares.go # 中间件代码文件
- ├── models # 存储层相关包
- │ ├── setup.go # 配置数据库连接
- │ └── user.go # user模块相关数据交互的代码文件
- ├── README.md # git repo的描述文件
- └── utils # 工具类包
- └── token # token相关工具类包
- └── token.go # token工具的代码文件
复制代码 main.go
- package main
- import (
- "log"
- "github.com/gin-gonic/gin"
- "gin-jwt/controllers"
- "gin-jwt/middlewares"
- "gin-jwt/models"
- "github.com/joho/godotenv"
- )
- func init() {
- err := godotenv.Load(".env")
- if err != nil {
- log.Fatalf("Error loading .env file. %v\n", err)
- }
- }
- func main() {
- models.ConnectDatabase()
- r := gin.Default()
- public := r.Group("/api")
- {
- public.POST("/register", controllers.Register)
- public.POST("/login", controllers.Login)
- }
- protected := r.Group("/api/admin")
- {
- protected.Use(middlewares.JwtAuthMiddleware()) // 在路由组中使用中间件
- protected.GET("/user", controllers.CurrentUser)
- }
- r.Run("0.0.0.0:8000")
- }
复制代码 controllers
- package controllersimport ( "net/http" "gin-jwt/models" "gin-jwt/utils/token" "github.com/gin-gonic/gin")// /api/register的请求体type ReqRegister struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"`}// api/login 的请求体
- type ReqLogin struct {
- Username string `json:"username" binding:"required"`
- Password string `json:"password" binding:"required"`
- }
- func Login(c *gin.Context) {
- var req ReqLogin
- if err := c.ShouldBindBodyWithJSON(&req); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- u := models.User{
- Username: req.Username,
- Password: req.Password,
- }
- // 调用 models.LoginCheck 对用户名和密码进行验证
- token, err := models.LoginCheck(u.Username, u.Password)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": "username or password is incorrect.",
- })
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "token": token,
- })
- }func Register(c *gin.Context) { var req ReqRegister if err := c.ShouldBindBodyWithJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "data": err.Error(), }) return } u := models.User{ Username: req.Username, Password: req.Password, } _, err := u.SaveUser() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "data": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "register success", })}func CurrentUser(c *gin.Context) {
- // 从token中解析出user_id
- user_id, err := token.ExtractTokenID(c)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
- return
- }
- // 根据user_id从数据库查询数据
- u, err := models.GetUserByID(user_id)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
- return
- }
- c.JSON(http.StatusOK, gin.H{
- "message": "success",
- "data": u,
- })
- }
复制代码 models
- package models
- import (
- "fmt"
- "log"
- "os"
- "gorm.io/driver/postgres"
- "gorm.io/gorm"
- )
- var DB *gorm.DB
- func ConnectDatabase() {
- var err error
- DbHost := os.Getenv("DB_HOST")
- DbPort := os.Getenv("DB_PORT")
- DbUser := os.Getenv("DB_USER")
- DbPass := os.Getenv("DB_PASS")
- DbName := os.Getenv("DB_NAME")
- dsn := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai password=%s", DbHost, DbPort, DbUser, DbName, DbPass)
- DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
- if err != nil {
- log.Fatalf("Connect to database failed, %v\n", err)
- } else {
- log.Printf("Connect to database success, host: %s, port: %s, user: %s, dbname: %s\n", DbHost, DbPort, DbUser, DbName)
- }
- // 迁移数据表
- DB.AutoMigrate(&User{})
- }
复制代码- package models
- import (
- "errors"
- "gin-jwt/utils/token"
- "html"
- "strings"
- "golang.org/x/crypto/bcrypt"
- "gorm.io/gorm"
- )
- type User struct {
- gorm.Model
- Username string `gorm:"size:255;not null;unique" json:"username"`
- Password string `gorm:"size:255;not null;" json:"password"`
- }
- func (u *User) SaveUser() (*User, error) {
- err := DB.Create(&u).Error
- if err != nil {
- return &User{}, err
- }
- return u, nil
- }
- // 使用gorm的hook在保存密码前对密码进行hash
- func (u *User) BeforeSave(tx *gorm.DB) error {
- hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
- if err != nil {
- return err
- }
- u.Password = string(hashedPassword)
- u.Username = html.EscapeString(strings.TrimSpace(u.Username))
- return nil
- }
- // 返回前将用户密码置空
- func (u *User) PrepareGive() {
- u.Password = ""
- }
- // 对哈希加密的密码进行比对校验
- func VerifyPassword(password, hashedPassword string) error {
- return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
- }
- func LoginCheck(username, password string) (string, error) {
- var err error
- u := User{}
- err = DB.Model(User{}).Where("username = ?", username).Take(&u).Error
- if err != nil {
- return "", err
- }
- err = VerifyPassword(password, u.Password)
- if err != nil && err == bcrypt.ErrMismatchedHashAndPassword {
- return "", err
- }
- token, err := token.GenerateToken(u.ID)
- if err != nil {
- return "", err
- }
- return token, nil
- }
- func GetUserByID(uid uint) (User, error) {
- var u User
- if err := DB.First(&u, uid).Error; err != nil {
- return u, errors.New("user not found")
- }
- u.PrepareGive()
- return u, nil
- }
复制代码 utils
- package token
- import (
- "fmt"
- "os"
- "strconv"
- "strings"
- "time"
- "github.com/gin-gonic/gin"
- "github.com/golang-jwt/jwt/v5"
- )
- func GenerateToken(user_id uint) (string, error) {
- token_lifespan, err := strconv.Atoi(os.Getenv("TOKEN_HOUR_LIFESPAN"))
- if err != nil {
- return "", err
- }
- claims := jwt.MapClaims{}
- claims["authorized"] = true
- claims["user_id"] = user_id
- claims["exp"] = time.Now().Add(time.Hour * time.Duration(token_lifespan)).Unix()
- token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
- return token.SignedString([]byte(os.Getenv("API_SECRET")))
- }
- func TokenValid(c *gin.Context) error {
- tokenString := ExtractToken(c)
- fmt.Println(tokenString)
- _, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
- if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
- return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
- }
- return []byte(os.Getenv("API_SECRET")), nil
- })
- if err != nil {
- return err
- }
- return nil
- }
- // 从请求头中获取token
- func ExtractToken(c *gin.Context) string {
- bearerToken := c.GetHeader("Authorization")
- if len(strings.Split(bearerToken, " ")) == 2 {
- return strings.Split(bearerToken, " ")[1]
- }
- return ""
- }
- // 从jwt中解析出user_id
- func ExtractTokenID(c *gin.Context) (uint, error) {
- tokenString := ExtractToken(c)
- token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
- if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
- return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
- }
- return []byte(os.Getenv("API_SECRET")), nil
- })
- if err != nil {
- return 0, err
- }
- claims, ok := token.Claims.(jwt.MapClaims)
- // 如果jwt有效,将user_id转换为浮点数字符串,然后再转换为 uint32
- if ok && token.Valid {
- uid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 32)
- if err != nil {
- return 0, err
- }
- return uint(uid), nil
- }
- return 0, nil
- }
复制代码 middlewares
- package middlewares
- import (
- "gin-jwt/utils/token"
- "net/http"
- "github.com/gin-gonic/gin"
- )
- func JwtAuthMiddleware() gin.HandlerFunc {
- return func(c *gin.Context) {
- err := token.TokenValid(c)
- if err != nil {
- c.String(http.StatusUnauthorized, err.Error())
- c.Abort()
- return
- }
- c.Next()
- }
- }
复制代码 参考
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |