Gin框架下的JWT
Gin框架下的JWT
什么是JWT
我们都知道,HTTP协议是无状态的,click here,那么服务端怎么知道用户状态的呢(比如是否登录呢),这里就需要用到中间件来进行用户认证。
中间件认证有这么几种方式
- session
- token
token和session最大的区别就是token是存储在客户端的:
我们都知道,session是存储在服务端的,每次请求到来的时候,通过cookie携带的sessionID与服务器内部存储的在线session进行比对,如果有该sessionID,就说明该用户已登录,但是这样就暴露出session最大的确定,服务端压力过大,同时如果是服务器是一个集群,在负载均衡后很可能处理该请求的服务器并不是存储该请求session的服务器。所以为了解决这些问题,token便产生了。
token和session类似,单不存储在服务端,当用户请求连接时,服务端分发一个token表示该用户的“令牌”,每次用户发器请求都携带这个令牌在请求头中(为保证安全性大部分存储在请求头中),和session一样,客户端只转发,不处理token,客户端收到带有token打请求后解析token,解析出的信息就可以唯一标识某个用户(例如预设的UID)。
JWT,读作:jot ,表示:JSON Web Tokens
JWT 标准的 Token 由三个部分组成:header.payload.signature
:
header(头部)
payload(数据)
signature(签名)
中间用点分隔开,并且都会使用 Base64 编码,所以真正的 Token 看起来像这样:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
Header
每个 JWT token 里面都有一个 header,也就是头部数据。里面包含了使用的算法,这个 JWT 是不是带签名的或者加密的。主要就是说明一下怎么处理这个 JWT token 。
唯一在头部里面要包含的是 alg 这个属性,如果是加密的 JWT,这个属性的值就是使用的签名或者解密用的算法。如果是未加密的 JWT,这个属性的值要设置成 none。
Payload
Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。
例如:UID , timeLogin , Ifadmin等等
Signature
JWT 的最后一部分是 Signature ,这部分内容有三个部分——
- 第一部分 :Base64 编码的header。
- 第二部分:Base64 编码的payload。
- 第三部分:再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端,不得泄露。
处理过程可以理解为
const encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
HMACSHA256(encodedString, 'secret');
基于gin框架的JWT
废话不多说,直接上代码,原理上面讲过,详情请见注释!
package main
import (
"errors"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
)
var mySecret = []byte("haomiao000") // 直接将密钥作为字符串存储(这里只是个例子,通常会非常复杂保证安全性)
// 定义自定义声明结构体并内嵌 jwt.RegisteredClaims
type MyClaims struct {
UserID int64 `json:"user_id"`
jwt.RegisteredClaims
}
// 生成JWT
func GenToken(userID int64) (string, error) {
// 创建自定义声明
claims := MyClaims{
UserID: userID,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)), // 过期时间设置为1小时
Issuer: "jwt", // 签发人
},
}
// 使用指定的签名方法创建签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 使用指定的secret签名并获得完整的编码后的字符串token
return token.SignedString(mySecret)
}
// 解析JWT
func ParseToken(tokenString string) (*MyClaims, error) {
var claims MyClaims
// 解析token
token, err := jwt.ParseWithClaims(tokenString, &claims, keyFunc)
if err != nil {
return nil, fmt.Errorf("parsing token: %w", err)
}
// 校验token
if !token.Valid {
return nil, errors.New("invalid token")
}
return &claims, nil
}
// keyFunc 定义获取密钥的函数
func keyFunc(_ *jwt.Token) (interface{}, error) {
return mySecret, nil
}
func main() {
// 初始化Gin引擎
r := gin.Default()
// 设置路由
r.POST("/login", loginHandler)
authGroup := r.Group("/api")
authGroup.Use(authMiddleware())
authGroup.GET("/user", getUserHandler)
// 启动服务器
r.Run(":8080")
}
// authMiddleware 定义JWT身份验证中间件
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
claims, err := ParseToken(tokenString)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
// 在上下文中设置用户ID
c.Set("userID", claims.UserID)
// 继续处理请求
c.Next()
}
}
// loginHandler 登录处理程序
func loginHandler(c *gin.Context) {
// 这里可以编写身份验证逻辑
// 例如从请求中获取用户名和密码,然后验证用户信息
// 假设身份验证成功后,获取用户ID
userID := int64(123)
// 生成JWT
token, err := GenToken(userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"})
return
}
// 返回JWT给客户端
c.JSON(http.StatusOK, gin.H{"token": token})
}
// getUserHandler 示例处理程序,需要JWT验证
func getUserHandler(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"})
return
}
// 在这里可以使用userID进行进一步的处理,例如从数据库中检索用户信息等
c.JSON(http.StatusOK, gin.H{"userID": userID})
}
- 请求拦截(请求附加token)
-
登录生成token
-
验证token