身份验证与授权(JWT)
Gin 支持通过 JWT(JSON Web Token)来实现用户的身份验证。通过中间件验证用户的身份后,可以控制资源的访问权限。
JWT 认证流程 :
1. 用户登录 → 服务端验证 → 生成 JWT → 返回 Token
2. 客户端存储 Token → 后续请求携带 Token
3. 服务端验证 Token → 允许/拒绝访问
1.代码实现
1.1使用jwt库
安装jwt-go库:
go get github.com/dgrijalva/jwt-go
1.2 项目结构
创建如下的项目结构:
.
├── main.go
├── models/
│ └── user.go
├── handlers/
│ ├── auth.go
│ └── user.go
└── middleware/
└── jwt.go
1.3定义用户模型与密钥
修改models/user.go文件:
// models/user.go
type User struct {
ID uint `json:"id"`
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
// 示例用户数据(实际应从数据库读取)
var Users = []User{
{ID: 1, Username: "admin", Password: "admin123"},
}
// 定义 JWT 密钥(需保密,生产环境应从配置读取)
var JwtSecret = []byte("your-secret-key") // 值需要8位以上
1.4 用户登录与生成 JWT
修改 handlers/auth.go 文件:
// handlers/auth.go
package handlers
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/dgrijalva/jwt-go"
"JwtDemo/models"
)
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
func Login(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
return
}
// 校验用户(示例代码,实际应查询数据库)
var user *models.User
for _, u := range models.Users {
if u.Username == req.Username && u.Password == req.Password {
user = &u
break
}
}
if user == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
return
}
// 生成 JWT
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": user.ID,
"username": user.Username,
"exp": time.Now().Add(time.Hour * 24).Unix(), // 过期时间 24 小时
})
tokenString, err := token.SignedString(models.JwtSecret)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成 Token 失败"})
return
}
c.JSON(http.StatusOK, gin.H{"token": tokenString})
}
1.5编写 JWT 验证中间件
修改middleware/jwt.go 文件:
// middleware/jwt.go
package middleware
import (
"net/http"
"strings"
"github.com/dgrijalva/jwt-go"
"JwtDemo/models"
)
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 从 Header 中获取 Token
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未提供 Token"})
return
}
// 提取 Token(格式:Bearer <token>)
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Token 格式错误"})
return
}
tokenString := parts[1]
// 解析并验证 Token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return models.JwtSecret, nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "无效的 Token"})
return
}
// 将 Claims 存入上下文
if claims, ok := token.Claims.(jwt.MapClaims); ok {
c.Set("user_id", claims["user_id"])
c.Set("username", claims["username"])
}
c.Next()
}
}
1.6注册路由与使用中间件
修改main.go文件:
// main.go
package main
import (
"JwtDemo/handlers"
"JwtDemo/middleware"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 公开路由:登录
r.POST("/login", handlers.Login)
// 受保护路由组
protected := r.Group("/api")
protected.Use(middleware.JWTAuth())
{
protected.GET("/profile", func(c *gin.Context) {
userID, _ := c.Get("user_id")
username, _ := c.Get("username")
c.JSON(200, gin.H{
"user_id": userID,
"username": username,
})
})
}
r.Run(":8080")
}
2.测试接口
2.1模拟登录获取Token
访问地址:
http://localhost:8080/login
请求参数:
{
"username" :"admin",
"password" :"admin123"
}
返回参数:
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDAzMTkyNTIsInVzZXJfaWQiOjEsInVzZXJuYW1lIjoiYWRtaW4ifQ.k8TKg4-hZj5XTi7e6A9riXTA-jmf3K3SwXrdwZnqCf4"}
2.2 使用token访问保护路由
访问地址:
http://localhost:8080/api/profile
添加请求头:
Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDAzMTkyNTIsInVzZXJfaWQiOjEsInVzZXJuYW1lIjoiYWRtaW4ifQ.k8TKg4-hZj5XTi7e6A9riXTA-jmf3K3SwXrdwZnqCf4
注意:jwt的传递是在HTTP请求添加名为Authorization的header,形式如下 Authorization: Bearer <token>
,注意 Bearer <token>
之间有空格
返回参数:
{
"user_id": 1,
"username": "admin"
}
3.使用BasicAuth中间件
BasicAuth
是 Gin 框架内置的中间件,用于实现 HTTP 基本认证(Basic Authentication)。它通过用户名和密码保护指定的路由,适用于简单的权限控制场景(如管理后台、内部工具等)。
演示代码:
package main
import (
"encoding/base64"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
// 定义合法账户
// gin.Accounts 是 map[string]string 的一种快捷方式
accounts := gin.Accounts{
"admin": "admin123",
"user": "user123",
}
// 创建 BasicAuth 中间件
auth := gin.BasicAuth(accounts)
// 受保护的路由组
admin := router.Group("/admin", auth)
{
admin.GET("/secrets", func(c *gin.Context) {
// 获取用户,它是由 BasicAuth 中间件设置的
user := c.MustGet(gin.AuthUserKey).(string)
//fmt.Println(c.MustGet("Authorization").(string))
if secret, ok := accounts[user]; ok {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret, "auth": base64.StdEncoding.EncodeToString([]byte(user + ":" + accounts[user]))})
} else {
c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
}
})
}
// 公开路由
router.GET("/", func(c *gin.Context) {
c.String(200, "首页(无需认证)")
})
router.Run(":8080")
}
测试请求:
curl -u admin:admin123 http://localhost:8080/admin/secrets
客户端需在请求头中携带 Authorization
字段,格式为 Basic base64(username:password)
。
例如,用户 admin
密码 admin123
的认证头为:
Authorization: Basic YWRtaW46YWRtaW4xMjM=
使用 Authorization 来测试