mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-08-26 16:46:14 +08:00
发布v2.6.10版本,更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md
This commit is contained in:
@@ -23,15 +23,9 @@ func Tpl(name, tpl string) string {
|
||||
// 最终效果:/应用名称/插件模块名称/xxx/xxx。如果你不喜欢现在的路由风格,可以自行调整
|
||||
func RouterPrefix(ctx context.Context, app, name string) string {
|
||||
var prefix = "/"
|
||||
switch app {
|
||||
case consts.AppAdmin:
|
||||
prefix = g.Cfg().MustGet(ctx, "router.admin.prefix", "/admin").String()
|
||||
case consts.AppApi:
|
||||
prefix = g.Cfg().MustGet(ctx, "router.api.prefix", "/api").String()
|
||||
case consts.AppHome:
|
||||
prefix = g.Cfg().MustGet(ctx, "router.home.prefix", "/home").String()
|
||||
case consts.AppWebSocket:
|
||||
prefix = g.Cfg().MustGet(ctx, "router.ws.prefix", "/socket").String()
|
||||
|
||||
if app != "" {
|
||||
prefix = g.Cfg().MustGet(ctx, "router."+app+".prefix", "/"+app+"").String()
|
||||
}
|
||||
|
||||
return prefix + "/" + name
|
||||
|
@@ -1,113 +0,0 @@
|
||||
// Package jwt
|
||||
// @Link https://github.com/bufanyun/hotgo
|
||||
// @Copyright Copyright (c) 2023 HotGo CLI
|
||||
// @Author Ms <133814250@qq.com>
|
||||
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
j "github.com/dgrijalva/jwt-go"
|
||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/cache"
|
||||
"hotgo/internal/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenerateLoginToken 为指定用户生成token
|
||||
func GenerateLoginToken(ctx context.Context, user *model.Identity, isRefresh bool) (string, error) {
|
||||
var (
|
||||
jwtVersion = g.Cfg().MustGet(ctx, "jwt.version", "1.0")
|
||||
jwtSign = g.Cfg().MustGet(ctx, "jwt.sign", "hotGo")
|
||||
token = j.NewWithClaims(j.SigningMethodHS256, j.MapClaims{
|
||||
"id": user.Id,
|
||||
"pid": user.Pid,
|
||||
"deptId": user.DeptId,
|
||||
"roleId": user.RoleId,
|
||||
"roleKey": user.RoleKey,
|
||||
"username": user.Username,
|
||||
"realName": user.RealName,
|
||||
"avatar": user.Avatar,
|
||||
"email": user.Email,
|
||||
"mobile": user.Mobile,
|
||||
"exp": user.Exp,
|
||||
"expires": user.Expires,
|
||||
"app": user.App,
|
||||
"isRefresh": isRefresh,
|
||||
"jwtVersion": jwtVersion.String(),
|
||||
})
|
||||
)
|
||||
|
||||
tokenString, err := token.SignedString(jwtSign.Bytes())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var (
|
||||
tokenStringMd5 = gmd5.MustEncryptString(tokenString)
|
||||
// 绑定登录token
|
||||
key = consts.CacheJwtToken + tokenStringMd5
|
||||
// 将有效期转为持续时间,单位:秒
|
||||
expires, _ = time.ParseDuration(fmt.Sprintf("+%vs", user.Expires))
|
||||
)
|
||||
|
||||
err = cache.Instance().Set(ctx, key, tokenString, expires)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = cache.Instance().Set(ctx, consts.CacheJwtUserBind+user.App+":"+gconv.String(user.Id), key, expires)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tokenString, err
|
||||
}
|
||||
|
||||
// ParseToken 解析token
|
||||
func ParseToken(tokenString string, secret []byte) (j.MapClaims, error) {
|
||||
if tokenString == "" {
|
||||
err := gerror.New("token 为空")
|
||||
return nil, err
|
||||
}
|
||||
token, err := j.Parse(tokenString, func(token *j.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*j.SigningMethodHMAC); !ok {
|
||||
return nil, gerror.Newf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return secret, nil
|
||||
})
|
||||
|
||||
if token == nil {
|
||||
err := gerror.New("token不存在")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(j.MapClaims); ok && token.Valid {
|
||||
return claims, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// GetAuthorization 获取authorization
|
||||
func GetAuthorization(r *ghttp.Request) string {
|
||||
// 默认从请求头获取
|
||||
var authorization = r.Header.Get("Authorization")
|
||||
|
||||
// 如果请求头不存在则从get参数获取
|
||||
if authorization == "" {
|
||||
return r.Get("authorization").String()
|
||||
}
|
||||
|
||||
return gstr.Replace(authorization, "Bearer ", "")
|
||||
}
|
||||
|
||||
func GenAuthKey(token string) string {
|
||||
return gmd5.MustEncryptString(token)
|
||||
}
|
@@ -8,8 +8,8 @@ package location
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/axgle/mahonia"
|
||||
"github.com/gogf/gf/v2/container/gmap"
|
||||
"github.com/gogf/gf/v2/encoding/gcharset"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
@@ -67,19 +67,18 @@ func WhoisLocation(ctx context.Context, ip string) (*IpLocationData, error) {
|
||||
|
||||
defer response.Close()
|
||||
|
||||
var (
|
||||
whoisData *WhoisRegionData
|
||||
enc = mahonia.NewDecoder("gbk")
|
||||
data = enc.ConvertString(response.ReadAllString())
|
||||
)
|
||||
str, err := gcharset.ToUTF8("GBK", response.ReadAllString())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = gconv.Struct(data, &whoisData); err != nil {
|
||||
var whoisData *WhoisRegionData
|
||||
if err = gconv.Struct([]byte(str), &whoisData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IpLocationData{
|
||||
Ip: whoisData.Ip,
|
||||
//Country string `json:"country"`
|
||||
Ip: whoisData.Ip,
|
||||
Region: whoisData.Addr,
|
||||
Province: whoisData.Pro,
|
||||
ProvinceCode: gconv.Int64(whoisData.ProCode),
|
||||
|
299
server/internal/library/token/token.go
Normal file
299
server/internal/library/token/token.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"hotgo/internal/consts"
|
||||
"hotgo/internal/library/cache"
|
||||
"hotgo/internal/library/contexts"
|
||||
"hotgo/internal/model"
|
||||
"hotgo/utility/simple"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
*model.Identity
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
ExpireAt int64 `json:"exp"` // token过期时间
|
||||
RefreshAt int64 `json:"ra"` // 刷新时间
|
||||
RefreshCount int64 `json:"rc"` // 刷新次数
|
||||
}
|
||||
|
||||
var (
|
||||
config *model.TokenConfig
|
||||
errorLogin = gerror.New("登录身份已失效,请重新登录!")
|
||||
errorMultiLogin = gerror.New("账号存在异地登录,如非本人操作请及时修改登录密码!")
|
||||
)
|
||||
|
||||
func SetConfig(c *model.TokenConfig) {
|
||||
config = c
|
||||
}
|
||||
|
||||
func GetConfig() *model.TokenConfig {
|
||||
return config
|
||||
}
|
||||
|
||||
// Login 登录
|
||||
func Login(ctx context.Context, user *model.Identity) (string, int64, error) {
|
||||
claims := Claims{
|
||||
user,
|
||||
jwt.RegisteredClaims{},
|
||||
}
|
||||
|
||||
header, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(config.SecretKey))
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
var (
|
||||
now = gtime.Now()
|
||||
// 认证key
|
||||
authKey = GetAuthKey(header)
|
||||
// 登录token
|
||||
tokenKey = GetTokenKey(user.App, authKey)
|
||||
// 身份绑定
|
||||
bindKey = GetBindKey(user.App, user.Id)
|
||||
// 有效时长
|
||||
duration = time.Second * gconv.Duration(config.Expires)
|
||||
)
|
||||
|
||||
token := &Token{
|
||||
ExpireAt: now.Unix() + config.Expires,
|
||||
RefreshAt: now.Unix(),
|
||||
RefreshCount: 0,
|
||||
}
|
||||
|
||||
if err = cache.Instance().Set(ctx, tokenKey, token, duration); err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
if err = cache.Instance().Set(ctx, bindKey, tokenKey, duration); err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
return header, config.Expires, nil
|
||||
}
|
||||
|
||||
// Logout 注销登录
|
||||
func Logout(r *ghttp.Request) (err error) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
header = GetAuthorization(r)
|
||||
)
|
||||
|
||||
if header == "" {
|
||||
err = errorLogin
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := parseToken(ctx, header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
// 认证key
|
||||
authKey = GetAuthKey(header)
|
||||
// 登录token
|
||||
tokenKey = GetTokenKey(contexts.GetModule(ctx), authKey)
|
||||
// 身份绑定
|
||||
bindKey = GetBindKey(contexts.GetModule(ctx), claims.Id)
|
||||
)
|
||||
|
||||
// 删除token
|
||||
if _, err = cache.Instance().Remove(ctx, tokenKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !config.MultiLogin {
|
||||
if _, err = cache.Instance().Remove(ctx, bindKey); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseLoginUser 解析登录用户信息
|
||||
func ParseLoginUser(r *ghttp.Request) (user *model.Identity, err error) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
header = GetAuthorization(r)
|
||||
)
|
||||
|
||||
if header == "" {
|
||||
err = errorLogin
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := parseToken(ctx, header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
authKey = GetAuthKey(header)
|
||||
// 登录token
|
||||
tokenKey = GetTokenKey(claims.App, authKey)
|
||||
// 身份绑定
|
||||
bindKey = GetBindKey(claims.App, claims.Id)
|
||||
)
|
||||
|
||||
// 检查token是否存在
|
||||
tk, err := cache.Instance().Get(ctx, tokenKey)
|
||||
if err != nil {
|
||||
g.Log().Debugf(ctx, "get tokenKey err:%+v", err)
|
||||
err = errorLogin
|
||||
return
|
||||
}
|
||||
|
||||
if tk.IsEmpty() {
|
||||
g.Log().Debug(ctx, "token isEmpty")
|
||||
err = errorLogin
|
||||
return
|
||||
}
|
||||
|
||||
var token *Token
|
||||
if err = tk.Scan(&token); err != nil {
|
||||
g.Log().Debugf(ctx, "token scan err:%+v", err)
|
||||
err = errorLogin
|
||||
return
|
||||
}
|
||||
|
||||
if token == nil {
|
||||
g.Log().Debugf(ctx, "token = nil")
|
||||
err = errorLogin
|
||||
return
|
||||
}
|
||||
|
||||
now := gtime.Now()
|
||||
if token.ExpireAt < now.Unix() {
|
||||
g.Log().Debugf(ctx, "token expired.")
|
||||
err = errorLogin
|
||||
return
|
||||
}
|
||||
|
||||
// 是否允许多端登录
|
||||
if !config.MultiLogin {
|
||||
origin, err := cache.Instance().Get(ctx, bindKey)
|
||||
if err != nil {
|
||||
g.Log().Debugf(ctx, "bindKey get err:%+v", err)
|
||||
err = errorLogin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if origin == nil || origin.IsEmpty() {
|
||||
g.Log().Debug(ctx, "bindKey isEmpty")
|
||||
err = errorLogin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tokenKey != origin.String() {
|
||||
g.Log().Debugf(ctx, "bindKey offsite login tokenKey:%v, origin:%v", tokenKey, origin.String())
|
||||
err = errorMultiLogin
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 自动刷新token有效期
|
||||
refreshToken := func() {
|
||||
// 未开启自动刷新
|
||||
if !config.AutoRefresh {
|
||||
return
|
||||
}
|
||||
|
||||
// 刷新次数已达上限
|
||||
if config.MaxRefreshTimes != -1 && token.RefreshCount >= config.MaxRefreshTimes {
|
||||
return
|
||||
}
|
||||
|
||||
// 未达到刷新间隔
|
||||
if gtime.New(token.RefreshAt).Unix()+config.RefreshInterval > now.Unix() {
|
||||
return
|
||||
}
|
||||
|
||||
// 刷新有效期
|
||||
token.ExpireAt = now.Unix() + config.Expires
|
||||
token.RefreshAt = now.Unix()
|
||||
token.RefreshCount += 1
|
||||
|
||||
duration := time.Second * gconv.Duration(config.Expires)
|
||||
|
||||
if err = cache.Instance().Set(ctx, tokenKey, token, duration); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = cache.Instance().Set(ctx, bindKey, tokenKey, duration); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
simple.SafeGo(ctx, func(ctx context.Context) {
|
||||
refreshToken()
|
||||
})
|
||||
|
||||
user = new(model.Identity)
|
||||
user = claims.Identity
|
||||
return
|
||||
}
|
||||
|
||||
// parseToken 解析jwt令牌
|
||||
func parseToken(ctx context.Context, header string) (*Claims, error) {
|
||||
token, err := jwt.ParseWithClaims(header, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(config.SecretKey), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
g.Log().Debugf(ctx, "parseToken err:%+v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return nil, errorLogin
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*Claims)
|
||||
if !ok {
|
||||
return nil, errorLogin
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// GetAuthorization 获取authorization
|
||||
func GetAuthorization(r *ghttp.Request) string {
|
||||
// 默认从请求头获取
|
||||
var authorization = r.Header.Get("Authorization")
|
||||
|
||||
// 如果请求头不存在则从get参数获取
|
||||
if authorization == "" {
|
||||
return r.Get("authorization").String()
|
||||
}
|
||||
|
||||
return gstr.Replace(authorization, "Bearer ", "")
|
||||
}
|
||||
|
||||
// GetAuthKey 认证key
|
||||
func GetAuthKey(token string) string {
|
||||
return gmd5.MustEncryptString(token)
|
||||
}
|
||||
|
||||
// GetTokenKey 令牌缓存key
|
||||
func GetTokenKey(appName, authKey string) string {
|
||||
return fmt.Sprintf("%v:%v:%v", consts.CacheToken, appName, authKey)
|
||||
}
|
||||
|
||||
// GetBindKey 令牌身份绑定key
|
||||
func GetBindKey(appName string, userId int64) string {
|
||||
return fmt.Sprintf("%v:%v:%v", consts.CacheTokenBind, appName, userId)
|
||||
}
|
Reference in New Issue
Block a user