[golang]在Gin框架中使用JWT鉴权
什么是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 |
开发准备
初始化项目目录并切换进入
mkdir gin-jwt
cd gin-jwt
使用go mod初始化工程
go mod init gin-jwt
安装依赖
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
touch 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")
}
测试运行
go run main.go
客户端测试。正常的话会有以下输出
$ 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")
}
重新运行测试
go run main.go
客户端测试
$ 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,这个目录将包含连接数据库和数据模型的代码。
mkdir 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
- auth.go
package controllers
import (
"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
- setup.go
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{})
}
- user.go
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
- token/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
}
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()
}
}
参考
[golang]在Gin框架中使用JWT鉴权的更多相关文章
- 在gin框架中使用JWT
在gin框架中使用JWT JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth2.0业务场景下. 什么 ...
- gin框架中使用jwt
生成解析token 如今有很多将身份验证内置到API中的方法 -JSON Web令牌只是其中之一.JSON Web令牌(JWT)作为令牌系统而不是在每次请求时都发送用户名和密码,因此比其他方法(如基本 ...
- golang gin框架中实现一个简单的不是特别精确的秒级限流器
起因 看了两篇关于golang中限流器的帖子: Gin 开发实践:如何实现限流中间件 常用限流策略--漏桶与令牌桶介绍 我照着用,居然没效果-- 时间有限没有深究.这实在是一个很简单的功能,我的需求是 ...
- gin框架中请求路由组的使用
1. gin框架中可以使用路由组来实现对路由的分类 package main import "github.com/gin-gonic/gin" func main() { rou ...
- gin框架中的路由
基本路由 gin框架中采用的路由库是基于httrouter做的 地址为:https://github.com/julienschmidt/httprouter httprouter路由库 点击查看代码 ...
- HTTP基本认证和JWT鉴权
一.HTTP基本认证 Basic Authentication——当浏览器访问使用基本认证的网站的时候, 浏览器会提示你输入用户名和密码. http auth的过程: · 客户端发送http请求 · ...
- Spring Boot 鉴权之—— JWT 鉴权
第一:什么是JWT鉴权 1. JWT即JSON Web Tokens,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519),他可以用来安全的传递信息,因为传递的信息是 ...
- JWT鉴权
一.HTTP基本认证 Basic Authentication--当浏览器访问使用基本认证的网站的时候, 浏览器会提示你输入用户名和密码. http auth的过程: 客户端发送http请求 服务器发 ...
- jwt鉴权学习 (php示例代码)
前段时间听朋友讲起 jwt鉴权,博主我是一脸懵逼,通过朋友坚持不懈的讲解,我终于听懂了,jwt就是登陆token校验嘛 然而事情并不是博主想象的那么简单,在一个艳阳高照,晴空万里的夜晚,博主手贱百度了 ...
- golang gin框架中实现大文件的流式上传
一般来说,通过c.Request.FormFile()获取文件的时候,所有内容都全部读到了内存.如果是个巨大的文件,则可能内存会爆掉:且,有的时候我们需要一边上传一边处理. 以下的代码实现了大文件流式 ...
随机推荐
- 第五章-WAF 绕过
WAF 绕过 1.WAF分类 1.1.软件 WAF 一般被安装到 Web 服务器中直接对其进行防护,能够接触到服务器上的文件,直接检测服务器上是否有不安全的文件和操作等. 常见的软件:安全狗.云盾.云 ...
- nvm环境安装
目录 nvm是什么 使用背景 nvm的坑. nvm,node,npm之间的区别. nvm.nodejs.npm的关系: nvm-windows下载地址 安装 linux . mac 源码包下载地址 解 ...
- C语言:Hello Word(梦开始的地方)
#include <stdio.h>//标准输入输出头文件,下面的printf打印的函数包含在这个头文件里面 int main() { printf("Hello Word!&q ...
- quartzui 的界面管理
基于Quartz.NET3.0的定时任务Web可视化管理.docker打包开箱即用.内置SQLite持久化.语言无关.业务代码零污染.支持 RESTful风格接口.傻瓜式配置 quartzuiquar ...
- RocketMQ事务消息源码解析
RocketMQ提供了事务消息的功能,采用2PC(两阶段协议)+补偿机制(事务回查)的分布式事务功能,通过这种方式能达到分布式事务的最终一致. 一. 概述 半事务消息:指的是发送至broker但是还没 ...
- vue-i18n 初体验
vue-i18n 初体验 使用vue,如何国际化呢?采用 vue-i18n.(i18n,internationalization,i和n中间省略18个字符) vue-i18n 官网地址 https:/ ...
- WPF开发快速入门【5】DataGrid的使用
概述 DataGrid是最常用的一种列表数据展现控件,本文介绍DataGrid的一些常用操作,包括:展示.新增.删除.修改等.以下代码基于Stylet框架实现. 数据展示 DataGrid用于对象列表 ...
- iOS使用SignalR客户端代码典范-桥接web SignalR 客户端库
一.SignalR介绍 SignalR是微软基于.Net提供的一个开源实时Web RPC库,可以用在web实时通信的需求上面,比如聊天,web数据更新 SignalR的接口使用十分简单 由于最近的一个 ...
- exception EXC_RESOURCE - WAKEUPS 分析(二)
一.问题: 直播助手在使用ReplayKit2 Extension的过程中,ReplayKit2的Upload进程工作在后台模式,苹果对处于后台的进程进行了内存和CPU资源的限制. 对于内存: 每种E ...
- 一文教你在MindSpore中实现A2C算法训练
本文分享自华为云社区<MindSpore A2C 强化学习>,作者:irrational. Advantage Actor-Critic (A2C)算法是一个强化学习算法,它结合了策略梯度 ...