package gtoken import ( "context" "errors" "fmt" "github.com/gogf/gf/v2/crypto/gaes" "github.com/gogf/gf/v2/crypto/gmd5" "github.com/gogf/gf/v2/encoding/gbase64" "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/gogf/gf/v2/util/grand" "net/http" "strings" ) var GFToken *GfToken // GfToken gtoken结构体 type GfToken struct { // GoFrame server name ServerName string // 缓存模式 1 gcache 2 gredis 默认1 CacheMode int8 // 缓存key CacheKey string // 超时时间 默认10天(毫秒) Timeout int // 缓存刷新时间 默认为超时时间的一半(毫秒) MaxRefresh int // Token分隔符 TokenDelimiter string // Token加密key EncryptKey []byte // 认证失败中文提示 AuthFailMsg string // 是否支持多端登录,默认false MultiLogin bool // 是否是全局认证,兼容历史版本,已废弃 GlobalMiddleware bool // 中间件类型 1 GroupMiddleware 2 BindMiddleware 3 GlobalMiddleware MiddlewareType uint // 登录路径 LoginPath string // 登录验证方法 return userKey 用户标识 如果userKey为空,结束执行 LoginBeforeFunc func(r *ghttp.Request) (string, interface{}) // 登录返回方法 LoginAfterFunc func(r *ghttp.Request, respData Resp) // 登出地址 LogoutPath string // 登出验证方法 return true 继续执行,否则结束执行 LogoutBeforeFunc func(r *ghttp.Request) bool // 登出返回方法 LogoutAfterFunc func(r *ghttp.Request, respData Resp) // 拦截地址 AuthPaths g.SliceStr // 拦截排除地址 AuthExcludePaths g.SliceStr // 认证验证方法 return true 继续执行,否则结束执行 AuthBeforeFunc func(r *ghttp.Request) bool // 认证返回方法 AuthAfterFunc func(r *ghttp.Request, respData Resp) } // Login 登录 func (m *GfToken) Login(r *ghttp.Request) { userKey, data := m.LoginBeforeFunc(r) if userKey == "" { g.Log().Error(r.Context(), msgLog(MsgErrUserKeyEmpty)) return } if m.MultiLogin { // 支持多端重复登录,返回相同token userCacheResp := m.getToken(r.Context(), userKey) if userCacheResp.Success() { respToken := m.EncryptToken(r.Context(), userKey, userCacheResp.GetString(KeyUuid)) m.LoginAfterFunc(r, respToken) return } } // 生成token respToken := m.genToken(r.Context(), userKey, data) m.LoginAfterFunc(r, respToken) } // Logout 登出 func (m *GfToken) Logout(r *ghttp.Request) { if !m.LogoutBeforeFunc(r) { return } // 获取请求token respData := m.getRequestToken(r) if respData.Success() { // 删除token m.RemoveToken(r.Context(), respData.DataString()) } m.LogoutAfterFunc(r, respData) } // AuthMiddleware 认证拦截 func (m *GfToken) authMiddleware(r *ghttp.Request) { urlPath := r.URL.Path if !m.AuthPath(r.Context(), urlPath) { // 如果不需要认证,继续 r.Middleware.Next() return } // 不需要认证,直接下一步 if !m.AuthBeforeFunc(r) { r.Middleware.Next() return } // 获取请求token tokenResp := m.getRequestToken(r) if tokenResp.Success() { // 验证token tokenResp = m.validToken(r.Context(), tokenResp.DataString()) } m.AuthAfterFunc(r, tokenResp) } // GetTokenData 通过token获取对象 func (m *GfToken) GetTokenData(r *ghttp.Request) Resp { respData := m.getRequestToken(r) if respData.Success() { // 验证token respData = m.validToken(r.Context(), respData.DataString()) } return respData } // AuthPath 判断路径是否需要进行认证拦截 // return true 需要认证 func (m *GfToken) AuthPath(ctx context.Context, urlPath string) bool { // 去除后斜杠 if strings.HasSuffix(urlPath, "/") { urlPath = gstr.SubStr(urlPath, 0, len(urlPath)-1) } // 分组拦截,登录接口不拦截 if m.MiddlewareType == MiddlewareTypeGroup { if (m.LoginPath != "" && gstr.HasSuffix(urlPath, m.LoginPath)) || (m.LogoutPath != "" && gstr.HasSuffix(urlPath, m.LogoutPath)) { return false } } // 全局处理,认证路径拦截处理 if m.MiddlewareType == MiddlewareTypeGlobal { var authFlag bool for _, authPath := range m.AuthPaths { tmpPath := authPath if strings.HasSuffix(tmpPath, "/*") { tmpPath = gstr.SubStr(tmpPath, 0, len(tmpPath)-2) } if gstr.HasPrefix(urlPath, tmpPath) { authFlag = true break } } if !authFlag { // 拦截路径不匹配 return false } } // 排除路径处理,到这里nextFlag为true for _, excludePath := range m.AuthExcludePaths { tmpPath := excludePath // 前缀匹配 if strings.HasSuffix(tmpPath, "/*") { tmpPath = gstr.SubStr(tmpPath, 0, len(tmpPath)-2) if gstr.HasPrefix(urlPath, tmpPath) { // 前缀匹配不拦截 return false } } else { // 全路径匹配 if strings.HasSuffix(tmpPath, "/") { tmpPath = gstr.SubStr(tmpPath, 0, len(tmpPath)-1) } if urlPath == tmpPath { // 全路径匹配不拦截 return false } } } return true } // getRequestToken 返回请求Token func (m *GfToken) getRequestToken(r *ghttp.Request) Resp { authHeader := r.Header.Get("Authorization") if authHeader != "" { parts := strings.SplitN(authHeader, " ", 2) if !(len(parts) == 2 && parts[0] == "Bearer") { g.Log().Warning(r.Context(), msgLog(MsgErrAuthHeader, authHeader)) return Unauthorized(fmt.Sprintf(MsgErrAuthHeader, authHeader), "") } else if parts[1] == "" { g.Log().Warning(r.Context(), msgLog(MsgErrAuthHeader, authHeader)) return Unauthorized(fmt.Sprintf(MsgErrAuthHeader, authHeader), "") } return Succ(parts[1]) } authHeader = r.Get(KeyToken).String() if authHeader == "" { return Unauthorized(MsgErrTokenEmpty, "") } return Succ(authHeader) } // genToken 生成Token func (m *GfToken) genToken(ctx context.Context, userKey string, data interface{}) Resp { token := m.EncryptToken(ctx, userKey, "") if !token.Success() { return token } cacheKey := m.CacheKey + userKey userCache := g.Map{ KeyUserKey: userKey, KeyUuid: token.GetString(KeyUuid), KeyData: data, KeyCreateTime: gtime.Now().TimestampMilli(), KeyRefreshTime: gtime.Now().TimestampMilli() + gconv.Int64(m.MaxRefresh), } cacheResp := m.setCache(ctx, cacheKey, userCache) if !cacheResp.Success() { return cacheResp } return token } // validToken 验证Token func (m *GfToken) validToken(ctx context.Context, token string) Resp { if token == "" { return Unauthorized(MsgErrTokenEmpty, "") } decryptToken := m.DecryptToken(ctx, token) if !decryptToken.Success() { return decryptToken } userKey := decryptToken.GetString(KeyUserKey) uuid := decryptToken.GetString(KeyUuid) userCacheResp := m.getToken(ctx, userKey) if !userCacheResp.Success() { return userCacheResp } if uuid != userCacheResp.GetString(KeyUuid) { g.Log().Debug(ctx, msgLog(MsgErrAuthUuid)+", decryptToken:"+decryptToken.Json()+" cacheValue:"+gconv.String(userCacheResp.Data)) return Unauthorized(MsgErrAuthUuid, "") } return userCacheResp } // getToken 通过userKey获取Token func (m *GfToken) getToken(ctx context.Context, userKey string) Resp { cacheKey := m.CacheKey + userKey userCacheResp := m.getCache(ctx, cacheKey) if !userCacheResp.Success() { return userCacheResp } userCache := gconv.Map(userCacheResp.Data) nowTime := gtime.Now().TimestampMilli() refreshTime := userCache[KeyRefreshTime] // 需要进行缓存超时时间刷新 if gconv.Int64(refreshTime) == 0 || nowTime > gconv.Int64(refreshTime) { userCache[KeyCreateTime] = gtime.Now().TimestampMilli() userCache[KeyRefreshTime] = gtime.Now().TimestampMilli() + gconv.Int64(m.MaxRefresh) return m.setCache(ctx, cacheKey, userCache) } return Succ(userCache) } // RemoveToken 删除Token func (m *GfToken) RemoveToken(ctx context.Context, token string) Resp { decryptToken := m.DecryptToken(ctx, token) if !decryptToken.Success() { return decryptToken } cacheKey := m.CacheKey + decryptToken.GetString(KeyUserKey) return m.removeCache(ctx, cacheKey) } // EncryptToken token加密方法 func (m *GfToken) EncryptToken(ctx context.Context, userKey string, uuid string) Resp { if userKey == "" { return Fail(MsgErrUserKeyEmpty) } if uuid == "" { // 重新生成uuid newUuid, err := gmd5.Encrypt(grand.Letters(10)) if err != nil { g.Log().Error(ctx, msgLog(MsgErrAuthUuid), err) return Error(MsgErrAuthUuid) } uuid = newUuid } tokenStr := userKey + m.TokenDelimiter + uuid token, err := gaes.Encrypt([]byte(tokenStr), m.EncryptKey) if err != nil { g.Log().Error(ctx, msgLog(MsgErrTokenEncrypt), tokenStr, err) return Error(MsgErrTokenEncrypt) } return Succ(g.Map{ KeyUserKey: userKey, KeyUuid: uuid, KeyToken: gbase64.EncodeToString(token), }) } // DecryptToken token解密方法 func (m *GfToken) DecryptToken(ctx context.Context, token string) Resp { if token == "" { return Fail(MsgErrTokenEmpty) } token64, err := gbase64.Decode([]byte(token)) if err != nil { g.Log().Error(ctx, msgLog(MsgErrTokenDecode), token, err) return Error(MsgErrTokenDecode) } decryptToken, err2 := gaes.Decrypt(token64, m.EncryptKey) if err2 != nil { g.Log().Error(ctx, msgLog(MsgErrTokenEncrypt), token, err2) return Error(MsgErrTokenEncrypt) } tokenArray := gstr.Split(string(decryptToken), m.TokenDelimiter) if len(tokenArray) < 2 { g.Log().Error(ctx, msgLog(MsgErrTokenLen), token) return Error(MsgErrTokenLen) } return Succ(g.Map{ KeyUserKey: tokenArray[0], KeyUuid: tokenArray[1], }) } // InitConfig 初始化配置信息 func (m *GfToken) InitConfig() bool { if m.CacheMode == 0 { m.CacheMode = CacheModeCache } if m.CacheKey == "" { m.CacheKey = DefaultCacheKey } if m.Timeout == 0 { m.Timeout = DefaultTimeout } if m.MaxRefresh == 0 { m.MaxRefresh = m.Timeout / 2 } if m.TokenDelimiter == "" { m.TokenDelimiter = DefaultTokenDelimiter } if len(m.EncryptKey) == 0 { m.EncryptKey = []byte(DefaultEncryptKey) } if m.AuthFailMsg == "" { m.AuthFailMsg = DefaultAuthFailMsg } // 设置中间件模式,未设置说明历史版本,通过GlobalMiddleware兼容 if m.MiddlewareType == 0 { if m.GlobalMiddleware { m.MiddlewareType = MiddlewareTypeGlobal } else { m.MiddlewareType = MiddlewareTypeBind } } if m.LoginAfterFunc == nil { m.LoginAfterFunc = func(r *ghttp.Request, respData Resp) { if !respData.Success() { r.Response.WriteJson(respData) } else { r.Response.WriteJson(Succ(g.Map{ KeyToken: respData.GetString(KeyToken), })) } } } if m.LogoutBeforeFunc == nil { m.LogoutBeforeFunc = func(r *ghttp.Request) bool { return true } } if m.LogoutAfterFunc == nil { m.LogoutAfterFunc = func(r *ghttp.Request, respData Resp) { if respData.Success() { r.Response.WriteJson(Succ(MsgLogoutSucc)) } else { r.Response.WriteJson(respData) } } } if m.AuthBeforeFunc == nil { m.AuthBeforeFunc = func(r *ghttp.Request) bool { // 静态页面不拦截 if r.IsFileRequest() { return false } return true } } if m.AuthAfterFunc == nil { m.AuthAfterFunc = func(r *ghttp.Request, respData Resp) { if respData.Success() { r.Middleware.Next() } else { var params map[string]interface{} if r.Method == http.MethodGet { params = r.GetMap() } else if r.Method == http.MethodPost { params = r.GetMap() } else { r.Response.Writeln(MsgErrReqMethod) return } no := gconv.String(gtime.TimestampMilli()) g.Log().Warning(r.Context(), fmt.Sprintf("[AUTH_%s][url:%s][params:%s][data:%s]", no, r.URL.Path, params, respData.Json())) respData.Msg = m.AuthFailMsg r.Response.WriteJson(respData) r.ExitAll() } } } return true } // Start 启动 func (m *GfToken) Start() error { if !m.InitConfig() { return errors.New(MsgErrInitFail) } ctx := context.Background() g.Log().Info(ctx, msgLog("[params:"+m.String()+"]start... ")) s := g.Server(m.ServerName) // 缓存模式 if m.CacheMode > CacheModeFile { g.Log().Error(ctx, msgLog(MsgErrNotSet, "CacheMode")) return errors.New(fmt.Sprintf(MsgErrNotSet, "CacheMode")) } // 初始化文件缓存 if m.CacheMode == 3 { m.initFileCache(ctx) } // 认证拦截器 if m.AuthPaths == nil { g.Log().Error(ctx, msgLog(MsgErrNotSet, "AuthPaths")) return errors.New(fmt.Sprintf(MsgErrNotSet, "AuthPaths")) } // 是否是全局拦截 if m.MiddlewareType == MiddlewareTypeGlobal { s.BindMiddlewareDefault(m.authMiddleware) } else { for _, authPath := range m.AuthPaths { tmpPath := authPath if !strings.HasSuffix(authPath, "/*") { tmpPath += "/*" } s.BindMiddleware(tmpPath, m.authMiddleware) } } // 登录 if m.LoginPath == "" { g.Log().Error(ctx, msgLog(MsgErrNotSet, "LoginPath")) return errors.New(fmt.Sprintf(MsgErrNotSet, "LoginPath")) } if m.LoginBeforeFunc == nil { g.Log().Error(ctx, msgLog(MsgErrNotSet, "LoginBeforeFunc")) return errors.New(fmt.Sprintf(MsgErrNotSet, "LoginBeforeFunc")) } s.BindHandler(m.LoginPath, m.Login) // 登出 if m.LogoutPath == "" { g.Log().Error(ctx, msgLog(MsgErrNotSet, "LogoutPath")) return errors.New(fmt.Sprintf(MsgErrNotSet, "LogoutPath")) } s.BindHandler(m.LogoutPath, m.Logout) return nil } // Stop 结束 func (m *GfToken) Stop(ctx context.Context) error { g.Log().Info(ctx, "[GToken]stop. ") return nil } // String token解密方法 func (m *GfToken) String() string { return gconv.String(g.Map{ // 缓存模式 1 gcache 2 gredis 默认1 "CacheMode": m.CacheMode, "CacheKey": m.CacheKey, "Timeout": m.Timeout, "TokenDelimiter": m.TokenDelimiter, "AuthFailMsg": m.AuthFailMsg, "MultiLogin": m.MultiLogin, "MiddlewareType": m.MiddlewareType, "LoginPath": m.LoginPath, "LogoutPath": m.LogoutPath, "AuthPaths": gconv.String(m.AuthPaths), "AuthExcludePaths": gconv.String(m.AuthExcludePaths), }) } // ValidToken 验证Token func (m *GfToken) ValidToken(ctx context.Context, token string) Resp { if token == "" { return Unauthorized(MsgErrTokenEmpty, "") } decryptToken := m.DecryptToken(ctx, token) if !decryptToken.Success() { return decryptToken } userKey := decryptToken.GetString(KeyUserKey) uuid := decryptToken.GetString(KeyUuid) userCacheResp := m.getToken(ctx, userKey) if !userCacheResp.Success() { return userCacheResp } if uuid != userCacheResp.GetString(KeyUuid) { g.Log().Debug(ctx, msgLog(MsgErrAuthUuid)+", decryptToken:"+decryptToken.Json()+" cacheValue:"+gconv.String(userCacheResp.Data)) return Unauthorized(MsgErrAuthUuid, "") } return userCacheResp }