demo里提供了三个接口,认证取token,刷新token,获取信息,token过期前也会在header里写上新token(便于客户端更换)
package main
import (
"fmt"
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
const (
TOKEN_SECRET_KEY = "secret" // 密钥
TOKEN_EXPIRE_TIME = 2 * time.Hour // 2小时过期
TOKEN_REFRESH_TIME = 10 * time.Minute // 接近过期时会在header里面加上新token,客户端可以识别也可以自行拉取新
)
var tb *TokenBucket
func init() {
tb = NewTokenBucket(100, 5000)
}
func main() {
// 创建一个Gin引擎
r := gin.Default()
// 限流
r.Use(rateMiddWare(tb))
// 登录接口
r.POST("/login", loginHandler)
// 受保护接口
v1 := r.Group("/v1").Use(authReqMiddWare())
{
v1.GET("/user", userHandler)
v1.GET("/refresh-token", refreshTokenHandler)
}
// 监听并在8080端口上启动服务
r.Run(":8080")
}
/**
* 登陆
* curl 127.0.0.1:8080/login -X POST
* return {"token":"eyJhbGciO..."}
*/
func loginHandler(c *gin.Context) {
// TODO 验证账户密码
// account + passwd 需要从db中拉取信息校验
// 获取用户信息
userId := "123"
userName := "test 123"
// 签名JWT
tokenString, err := generateJWTToken(userId, userName)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to generate token"})
return
}
// 返回JWT给客户端
c.JSON(http.StatusOK, gin.H{"token": tokenString})
}
/**
* 用户信息
* curl 127.0.0.1:8080/v1/user -H "token:eyJhbGciO..."
* return {"UserId":"123","UserName":"test 123","exp":1694741333,"nbf":1694734133,"iat":1694734133}
*/
func userHandler(c *gin.Context) {
claims, bool := c.Get("claims")
if bool {
// TODO 其他用户信息可以用UID查 缓存 和 数据库
// findbyId()
c.JSON(http.StatusOK, claims)
return
}
c.JSON(http.StatusOK, gin.H{"message": "not found"})
}
/**
* 刷新token
* curl 127.0.0.1:8080/v1/refresh-token -H "token:eyJhbGciO..."
* return {"token":"eyJhbGciO..."}
*/
func refreshTokenHandler(c *gin.Context) {
claims, bool := c.Get("claims")
if !bool {
c.JSON(http.StatusOK, gin.H{"message": "not found claims"})
return
}
fmt.Println(claims)
val, ok := claims.(*jwtClaims)
if !ok {
c.JSON(http.StatusOK, gin.H{"message": "not found"})
return
}
tokenString, err := generateJWTToken(val.UserId, val.UserName)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to generate token"})
return
}
c.JSON(http.StatusOK, gin.H{"token": tokenString})
return
}
//jwt
type jwtClaims struct {
UserId string
UserName string
jwt.RegisteredClaims // jwt中标准格式
}
/**
* 校验token
* 如果想从服务端控制发出的token,可以通过redis标记也能达到让指定token提前过期的目的
*/
func authReqMiddWare() gin.HandlerFunc {
return func(c *gin.Context) {
// 读取TOKEN
tokenStr := c.GetHeader("token")
if tokenStr == "" {
c.JSON(http.StatusForbidden, gin.H{"message": "Token not exist"})
c.Abort()
return
}
// 解析token
token, err := jwt.ParseWithClaims(tokenStr, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(TOKEN_SECRET_KEY), nil
})
if err != nil {
c.JSON(http.StatusForbidden, gin.H{"message": err.Error()})
c.Abort()
return
}
claims, ok := token.Claims.(*jwtClaims)
// 这里默认会检查ExpiresAt是否过期
if ok && token.Valid {
now := time.Now()
// 检查过期时间,对快要过期的添加http header `refresh-token`
if t := claims.ExpiresAt.Time.Add(-TOKEN_REFRESH_TIME); t.Before(now) {
tokenString, err := generateJWTToken(claims.UserId, claims.UserName)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to generate token"})
c.Abort()
return
}
c.Header("refresh-token", tokenString) //
}
c.Set("claims", claims)
}
}
}
// 生成JWT token
func generateJWTToken(userId, userName string) (string, error) {
now := time.Now()
claims := jwtClaims{
UserId: userId,
UserName: userName,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: &jwt.NumericDate{Time: time.Now().Add(TOKEN_EXPIRE_TIME)}, // 过期时间
IssuedAt: jwt.NewNumericDate(now), // 签发时间
NotBefore: jwt.NewNumericDate(now), // 生效时间
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(TOKEN_SECRET_KEY))
}
// 限流
func rateMiddWare(tb *TokenBucket) gin.HandlerFunc {
return func(c *gin.Context) {
if !tb.AllowRequest() {
c.JSON(http.StatusTooManyRequests, gin.H{"message": http.StatusText(http.StatusTooManyRequests)})
c.Abort()
return
}
}
}
// 令牌
type TokenBucket struct {
cap int // 桶容量
rate float64 // 每秒生产个数
tokenNum int // 当前计数
lastTime time.Time // 上一个产生时间
mu sync.Mutex
}
func NewTokenBucket(cap int, rate float64) *TokenBucket {
return &TokenBucket{
cap: cap,
rate: rate,
tokenNum: cap,
lastTime: time.Now(),
}
}
// 拿令牌
func (tb *TokenBucket) AllowRequest() bool {
tb.mu.Lock()
defer tb.mu.Unlock()
now := time.Now()
second := now.Sub(tb.lastTime).Seconds() // 计算经过多少秒
newTokens := int(second * tb.rate) // 计算产生的令牌数量
if newTokens > 0 {
tb.tokenNum = tb.tokenNum + newTokens
if tb.tokenNum > tb.cap { // 不能超过容量
tb.tokenNum = tb.cap
}
tb.lastTime = now
}
if tb.tokenNum > 0 {
tb.tokenNum--
return true
}
return false
}