优化鉴权

This commit is contained in:
xh
2025-09-19 03:33:02 +08:00
parent 1c1d27681e
commit 5bc7d9dffd
20 changed files with 182 additions and 261 deletions

View File

@@ -113,7 +113,6 @@
</template>
</el-input>
<upload
v-perms="['admin:common:upload:file']"
class="ml-3"
:data="{ cid: cateId }"
:ext="ext"

View File

@@ -16,7 +16,7 @@ func AlbumRoute(rg *gin.RouterGroup) {
handle := albumHandler{}
rg = rg.Group("/common", middleware.TokenAuth())
rg = rg.Group("/common", middleware.LoginAuth())
rg.GET("/album/albumList", handle.albumList)
rg.POST("/album/albumRename", middleware.RecordLog("相册文件重命名"), handle.albumRename)

View File

@@ -11,10 +11,9 @@ import (
func IndexRoute(rg *gin.RouterGroup) {
handle := indexHandler{}
rg = rg.Group("/common", middleware.TokenAuth())
rg.GET("/index/console", handle.console)
rg = rg.Group("/common")
rg.GET("/index/console", middleware.LoginAuth(), handle.console)
rg.GET("/index/config", handle.config)
}
type indexHandler struct{}

View File

@@ -14,7 +14,7 @@ import (
func UploadRoute(rg *gin.RouterGroup) {
handle := uploadHandler{}
rg = rg.Group("/common", middleware.TokenAuth())
rg = rg.Group("/common", middleware.LoginAuth())
rg.POST("/upload/preUploadFile", middleware.RecordLog("文件预上传", middleware.RequestFile), handle.preUploadFile)
rg.POST("/upload/file", middleware.RecordLog("上传文件", middleware.RequestFile), handle.uploadFile)
}

View File

@@ -21,9 +21,10 @@ func DictDataRoute(rg *gin.RouterGroup) {
// authSrv := NewSettingDictDataService(db)
handle := dictDataHandler{}
notAuth := rg.Group("/setting", middleware.LoginAuth())
notAuth.GET("/dict/data/all", handle.All)
rg = rg.Group("/setting", middleware.TokenAuth())
rg.GET("/dict/data/all", handle.All)
rg.GET("/dict/data/detail", handle.Detail)
rg.POST("/dict/data/add", handle.Add)
rg.POST("/dict/data/edit", handle.Edit)

View File

@@ -21,14 +21,15 @@ func DictTypeRoute(rg *gin.RouterGroup) {
// server := NewSettingDictTypeService(db)
handle := dictTypeHandler{}
notAuth := rg.Group("/setting", middleware.LoginAuth())
notAuth.GET("/dict/type/all", handle.All)
rg = rg.Group("/setting", middleware.TokenAuth())
rg.GET("/dict/type/all", handle.All)
rg.GET("/dict/type/list", handle.List)
rg.GET("/dict/type/detail", handle.Detail)
rg.POST("/dict/type/add", handle.Add)
rg.POST("/dict/type/edit", handle.Edit)
rg.POST("/dict/type/del", handle.Del)
auth := rg.Group("/setting", middleware.TokenAuth())
auth.GET("/dict/type/list", handle.List)
auth.GET("/dict/type/detail", handle.Detail)
auth.POST("/dict/type/add", handle.Add)
auth.POST("/dict/type/edit", handle.Edit)
auth.POST("/dict/type/del", handle.Del)
}
type dictTypeHandler struct{}

View File

@@ -20,23 +20,25 @@ import (
func AdminRoute(rg *gin.RouterGroup) {
handle := AdminHandler{}
notAuth := rg.Group("/system", middleware.LoginAuth())
notAuth.GET("/admin/self", handle.Self)
notAuth.POST("/admin/upInfo", middleware.RecordLog("管理员更新"), handle.UpInfo)
rg = rg.Group("/system", middleware.TokenAuth())
auth := rg.Group("/system", middleware.TokenAuth())
rg.GET("/admin/self", handle.Self)
rg.GET("/admin/list", handle.List)
rg.GET("/admin/listAll", handle.ListAll)
rg.GET("/admin/ListByDeptId", handle.ListByDeptId)
rg.GET("/admin/detail", handle.Detail)
rg.POST("/admin/add", middleware.RecordLog("管理员新增"), handle.Add)
rg.POST("/admin/edit", middleware.RecordLog("管理员编辑"), handle.Edit)
rg.POST("/admin/upInfo", middleware.RecordLog("管理员更新"), handle.UpInfo)
rg.POST("/admin/del", middleware.RecordLog("管理员删除"), handle.Del)
rg.POST("/admin/disable", middleware.RecordLog("管理员状态切换"), handle.Disable)
auth.GET("/admin/list", handle.List)
auth.GET("/admin/listAll", handle.ListAll)
auth.GET("/admin/ListByDeptId", handle.ListByDeptId)
auth.GET("/admin/detail", handle.Detail)
auth.POST("/admin/add", middleware.RecordLog("管理员新增"), handle.Add)
auth.POST("/admin/edit", middleware.RecordLog("管理员编辑"), handle.Edit)
rg.GET("/admin/ExportFile", middleware.RecordLog("管理员导出"), handle.ExportFile)
auth.POST("/admin/del", middleware.RecordLog("管理员删除"), handle.Del)
auth.POST("/admin/disable", middleware.RecordLog("管理员状态切换"), handle.Disable)
rg.POST("/admin/ImportFile", handle.ImportFile)
auth.GET("/admin/ExportFile", middleware.RecordLog("管理员导出"), handle.ExportFile)
auth.POST("/admin/ImportFile", handle.ImportFile)
}
@@ -182,7 +184,7 @@ func (ah AdminHandler) Disable(c *gin.Context) {
// @Router /system/admin/ListByDeptId/{deptId} [get]
func (ah AdminHandler) ListByDeptId(c *gin.Context) {
deptIdStr, bool := c.GetQuery("deptId")
if bool == false {
if !bool {
response.FailWithMsg(c, response.Failed, "deptId不能为空")
return
}

View File

@@ -19,10 +19,13 @@ func DeptRoute(rg *gin.RouterGroup) {
// authSrv := system.NewSystemAuthMenuService(db, permSrv)
handle := deptHandler{}
notAuth := rg.Group("/system", middleware.LoginAuth())
// notAuth.GET("/dept/all", handle.All)
notAuth.GET("/dept/list", handle.List)
rg = rg.Group("/system", middleware.TokenAuth())
rg.GET("/dept/all", handle.All)
rg.GET("/dept/list", handle.List)
// rg.GET("/dept/list", handle.List)
rg.GET("/dept/detail", handle.Detail)
rg.POST("/dept/add", handle.Add)
rg.POST("/dept/edit", handle.Edit)

View File

@@ -6,7 +6,6 @@ import (
"x_admin/app/service/commonService"
"x_admin/app/service/systemService"
"x_admin/core/response"
"x_admin/middleware"
"x_admin/util"
"github.com/gin-gonic/gin"
@@ -16,7 +15,7 @@ func LoginRoute(rg *gin.RouterGroup) {
handle := loginHandler{}
rg = rg.Group("/system", middleware.TokenAuth())
rg = rg.Group("/system")
rg.POST("/login", handle.login)
rg.POST("/logout", handle.logout)
}

View File

@@ -14,9 +14,11 @@ import (
func MenuRoute(rg *gin.RouterGroup) {
handle := menuHandler{}
notAuth := rg.Group("/system", middleware.LoginAuth())
notAuth.GET("/menu/route", handle.route)
rg = rg.Group("/system", middleware.TokenAuth())
rg.GET("/menu/route", handle.route)
// rg.GET("/menu/route", handle.route)
rg.GET("/menu/list", handle.List)
rg.GET("/menu/detail", handle.Detail)
rg.POST("/menu/add", handle.Add)

View File

@@ -13,9 +13,11 @@ import (
func PostRoute(rg *gin.RouterGroup) {
handle := postHandler{}
notAuth := rg.Group("/system", middleware.LoginAuth())
notAuth.GET("/post/all", handle.All)
rg = rg.Group("/system", middleware.TokenAuth())
rg.GET("/post/all", handle.All)
// rg.GET("/post/all", handle.All)
rg.GET("/post/list", handle.List)
rg.GET("/post/detail", handle.Detail)
rg.POST("/post/add", handle.Add)

View File

@@ -14,9 +14,11 @@ import (
func RoleRoute(rg *gin.RouterGroup) {
handle := RoleHandler{}
notAuth := rg.Group("/system", middleware.LoginAuth())
notAuth.GET("/role/all", handle.All)
rg = rg.Group("/system", middleware.TokenAuth())
rg.GET("/role/all", handle.All)
// rg.GET("/role/all", handle.All)
rg.GET("/role/list", middleware.RecordLog("角色列表"), handle.List)
rg.GET("/role/detail", middleware.RecordLog("角色详情"), handle.Detail)
rg.POST("/role/add", middleware.RecordLog("角色新增"), handle.Add)

View File

@@ -24,7 +24,7 @@ var captchaConfig = captcha_config.Config{
YOffset: 8,
},
BlockPuzzle: &captcha_config.BlockPuzzleConfig{Offset: 8},
CacheExpireSec: 210 * 60, // 缓存有效时间
CacheExpireSec: 2 * 60, // 缓存有效时间
}
// 服务工厂,主要用户注册 获取 缓存和验证服务

View File

@@ -504,6 +504,9 @@ func (adminSrv systemAuthAdminService) CacheAdminUserByUid(id uint) (err error)
if err != nil {
return
}
// redis排除缓存
admin.Password = ""
str, err := util.ToolsUtil.ObjToJson(&admin)
if err != nil {
return

View File

@@ -83,7 +83,7 @@ func (loginSrv systemLoginService) Login(c *gin.Context, req *systemSchema.Syste
}
}
}()
token := util.ToolsUtil.MakeToken()
token := util.ToolsUtil.MakeUuidV7()
adminIdStr := strconv.FormatUint(uint64(sysAdmin.ID), 10)
// 缓存登录信息

View File

@@ -15,28 +15,28 @@ var AdminConfig = adminConfig{
// #region NotAuth
// 免登录验证
NotLoginUri: []string{
"admin:system:login", // 登录接口
"admin:common:index:config", // 配置接口
// "admin:system:login", // 登录接口
// "admin:common:index:config", // 配置接口
},
// 免权限验证
// 免接口权限验证
NotAuthUri: []string{
"admin:system:logout", // 退出登录
"admin:system:menu:menus", // 系统菜单
"admin:system:menu:route", // 菜单路由
"admin:system:admin:upInfo", // 管理员更新
"admin:system:admin:self", // 管理员信息
"admin:system:role:all", // 所有角色
"admin:system:post:all", // 所有岗位
"admin:system:dept:list", // 所有部门
"admin:setting:dict:type:all", // 所有字典类型
"admin:setting:dict:data:all", // 所有字典数据
// "admin:system:logout", // 退出登录
// "admin:system:menu:menus", // 系统菜单
// "admin:system:menu:route", // 菜单路由
// "admin:system:admin:upInfo", // 管理员更新
// "admin:system:admin:self", // 管理员信息
// "admin:system:role:all", // 所有角色
// "admin:system:post:all", // 所有岗位
// "admin:system:dept:list", // 所有部门
// "admin:setting:dict:type:all", // 所有字典类型
// "admin:setting:dict:data:all", // 所有字典数据
},
// #endregion NotAuth
// 演示模式白名单
ShowWhitelistUri: []string{
"admin:system:login", // 登录接口
"admin:system:logout", // 退出登录
// "admin:system:login", // 登录接口
// "admin:system:logout", // 退出登录
},
// 管理员账号id
@@ -52,13 +52,13 @@ var AdminConfig = adminConfig{
}
type adminConfig struct {
// 管理缓存键
// 管理缓存键"backstage:manage"
BackstageManageKey string
// 角色缓存键
// 角色缓存键"backstage:roles"
BackstageRolesKey string
// 令牌缓存键
// 令牌缓存键"backstage:token:"
BackstageTokenKey string
// 令牌的集合
// 令牌的集合 "backstage:token:set:"
BackstageTokenSet string
// 免登录验证
NotLoginUri []string

View File

@@ -3,7 +3,6 @@ package config
type appConfig struct {
AppName string `mapstructure:"APP_NAME"` // 应用名称
Version string `mapstructure:"VERSION"` // 应用版本
Secret string `mapstructure:"SECRET"` // 应用系统加密字符
Port int `mapstructure:"PORT"` // 应用端口
OssDomain string `mapstructure:"OSS_DOMAIN"` // OSS域名
@@ -14,7 +13,6 @@ type appConfig struct {
var AppConfig = appConfig{
AppName: "x_admin",
Version: "1.0.0",
Secret: "UVTIyzCy",
Port: 8080,
GinMode: "",
OssDomain: "",

View File

@@ -15,129 +15,137 @@ import (
"github.com/gin-gonic/gin"
)
// TokenAuth Token认证中间件
func TokenAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 路由转权限
auths := strings.ReplaceAll(strings.Replace(c.Request.URL.Path, "/api/", "", 1), "/", ":")
// 免登录接口
if util.ToolsUtil.Contains(config.AdminConfig.NotLoginUri, auths) {
c.Next()
return
}
func Auth(c *gin.Context) response.RespType {
// Token是否为空
token := c.Request.Header.Get("token")
if token == "" { // 从url获取token
token = c.Request.URL.Query().Get("token")
}
if token == "" {
response.Fail(c, response.TokenEmpty)
c.Abort()
return
return response.TokenEmpty
}
// Token是否过期
token = config.AdminConfig.BackstageTokenKey + token
existCnt := util.RedisUtil.Exists(token)
tokenKey := config.AdminConfig.BackstageTokenKey + token
existCnt := util.RedisUtil.Exists(tokenKey)
if existCnt < 0 {
response.Fail(c, response.SystemError)
c.Abort()
return
return response.SystemError
} else if existCnt == 0 {
response.Fail(c, response.TokenInvalid)
c.Abort()
return
return response.TokenInvalid
}
// 用户信息缓存
uidStr := util.RedisUtil.Get(token)
uidStr := util.RedisUtil.Get(tokenKey)
var uid uint
if uidStr != "" {
i, err := strconv.ParseUint(uidStr, 10, 32)
if err != nil {
core.Logger.Errorf("TokenAuth Atoi uidStr err: err=[%+v]", err)
response.Fail(c, response.TokenInvalid)
c.Abort()
return
core.Logger.Errorf("uid读取失败: [%+v]", err)
return response.TokenInvalid
}
uid = uint(i)
}
// redis管理员信息不存在时缓存
if !util.RedisUtil.HExists(config.AdminConfig.BackstageManageKey, uidStr) {
err := systemService.AdminService.CacheAdminUserByUid(uid)
err := systemService.AdminService.CacheAdminUserByUid(uid) //缓存管理员
if err != nil {
core.Logger.Errorf("TokenAuth CacheAdminUserByUid err: err=[%+v]", err)
response.Fail(c, response.SystemError)
c.Abort()
return
core.Logger.Errorf("缓存管理员失败: err=[%+v]", err)
return response.SystemError
}
}
// 校验用户被删除
var mapping system_model.SystemAuthAdmin
err := util.ToolsUtil.JsonToObj(util.RedisUtil.HGet(config.AdminConfig.BackstageManageKey, uidStr), &mapping)
var adminUser system_model.SystemAuthAdmin
err := util.ToolsUtil.JsonToObj(util.RedisUtil.HGet(config.AdminConfig.BackstageManageKey, uidStr), &adminUser)
if err != nil {
core.Logger.Errorf("TokenAuth Unmarshal err: err=[%+v]", err)
response.Fail(c, response.SystemError)
c.Abort()
return
return response.SystemError
}
if mapping.IsDelete == 1 {
util.RedisUtil.Del(token)
if adminUser.IsDelete == 1 {
util.RedisUtil.Del(tokenKey)
util.RedisUtil.HDel(config.AdminConfig.BackstageManageKey + uidStr)
response.Fail(c, response.TokenInvalid)
c.Abort()
return
return response.TokenInvalid
}
// 校验用户被禁用
if mapping.IsDisable == 1 {
response.Fail(c, response.LoginDisableError)
c.Abort()
return
if adminUser.IsDisable == 1 {
return response.LoginDisableError
}
// 令牌剩余30分钟自动续签
if util.RedisUtil.TTL(token) < 1800 {
util.RedisUtil.Expire(token, 7200)
if util.RedisUtil.TTL(tokenKey) < 1800 {
util.RedisUtil.Expire(tokenKey, 7200)
}
// 单次请求信息保存
c.Set(config.AdminConfig.ReqAdminIdKey, uid)
c.Set(config.AdminConfig.ReqRoleIdKey, mapping.Role)
c.Set(config.AdminConfig.ReqUsernameKey, mapping.Username)
c.Set(config.AdminConfig.ReqNicknameKey, mapping.Nickname)
// 免权限验证接口
if util.ToolsUtil.Contains(config.AdminConfig.NotAuthUri, auths) || uid == config.AdminConfig.SuperAdminId {
c.Next()
return
}
c.Set(config.AdminConfig.ReqRoleIdKey, adminUser.Role)
c.Set(config.AdminConfig.ReqUsernameKey, adminUser.Username)
c.Set(config.AdminConfig.ReqNicknameKey, adminUser.Nickname)
// 校验角色的权限redis没有就重新查询
roleId := mapping.Role
roleId := adminUser.Role
if !util.RedisUtil.HExists(config.AdminConfig.BackstageRolesKey, roleId) {
i, err := strconv.ParseUint(roleId, 10, 32)
if err != nil {
core.Logger.Errorf("TokenAuth Atoi roleId err: err=[%+v]", err)
response.Fail(c, response.SystemError)
c.Abort()
return
return response.SystemError
}
err = systemService.PermService.CacheRoleMenusByRoleId(uint(i))
if err != nil {
core.Logger.Errorf("TokenAuth CacheRoleMenusByRoleId err: err=[%+v]", err)
response.Fail(c, response.SystemError)
return response.SystemError
}
}
return response.Success
}
// 仅检查token有效性获取用户信息不判断接口权限
func LoginAuth() gin.HandlerFunc {
return func(c *gin.Context) {
resp := Auth(c)
if resp != response.Success {
response.Fail(c, resp)
c.Abort()
return
}
c.Next()
}
}
// TokenAuth 检查token有效性获取用户信息并判断接口权限
func TokenAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 路由转权限
ApiAuth := strings.ReplaceAll(strings.Replace(c.Request.URL.Path, "/api/", "", 1), "/", ":")
// 免登录接口
if util.ToolsUtil.Contains(config.AdminConfig.NotLoginUri, ApiAuth) {
c.Next()
return
}
resp := Auth(c)
if resp != response.Success {
response.Fail(c, resp)
c.Abort()
return
}
// 免权限验证接口
if util.ToolsUtil.Contains(config.AdminConfig.NotAuthUri, ApiAuth) {
c.Next()
return
}
if config.AdminConfig.GetAdminId(c) == config.AdminConfig.SuperAdminId {
c.Next()
return
}
// 验证是否有权限操作
menus := util.RedisUtil.HGet(config.AdminConfig.BackstageRolesKey, roleId)
if !(menus != "" && util.ToolsUtil.Contains(strings.Split(menus, ","), auths)) {
menus := util.RedisUtil.HGet(config.AdminConfig.BackstageRolesKey, config.AdminConfig.GetRoleId(c))
if !(menus != "" && util.ToolsUtil.Contains(strings.Split(menus, ","), ApiAuth)) {
response.Fail(c, response.NoPermission)
c.Abort()
return

View File

@@ -1,88 +0,0 @@
package util
import (
"reflect"
"github.com/fatih/structs"
"github.com/jinzhu/copier"
"github.com/mitchellh/mapstructure"
)
var ConvertUtil = convertUtil{}
// convertUtil 转换工具
type convertUtil struct{}
// StructToMap 结构体转换成map,深度转换
func (c convertUtil) StructToMap(from interface{}) map[string]interface{} {
// var m = map[string]interface{}{}
// mapstructure.Decode(from, &m) //深度转换所有结构体
m := structs.Map(from) // 需要tag:structs深度转换
return m
}
// StructsToMaps 将结构体转换成Map列表
func (c convertUtil) StructsToMaps(from interface{}) (data []map[string]interface{}) {
var objList []interface{}
err := copier.Copy(&objList, from)
if err != nil {
// core.Logger.Errorf("convertUtil.StructsToMaps err: err=[%+v]", err)
return nil
}
for _, v := range objList {
data = append(data, c.StructToMap(v))
}
return data
}
// ShallowStructToMap 将结构体转换成map,浅转换
func (c convertUtil) ShallowStructToMap(from interface{}) map[string]interface{} {
m := make(map[string]interface{})
v := reflect.ValueOf(from)
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i).Interface()
m[field.Name] = value
}
return m
}
// ShallowStructsToMaps 将结构体列表转换成Map列表,浅转换
func (c convertUtil) ShallowStructsToMaps(from interface{}) (data []map[string]interface{}) {
var objList []interface{}
err := copier.Copy(&objList, from)
if err != nil {
// core.Logger.Errorf("convertUtil.StructsToMaps err: err=[%+v]", err)
return nil
}
for _, v := range objList {
data = append(data, c.ShallowStructToMap(v))
}
return data
}
// MapToStruct 将map弱类型转换成结构体
func (c convertUtil) MapToStruct(from interface{}, to interface{}) (err error) {
err = mapstructure.WeakDecode(from, to) // 需要tag:mapstructure
return err
}
// StructToStruct 将结构体from弱类型转换成结构体to
func (c convertUtil) StructToStruct(from interface{}, to interface{}) (err error) {
m := c.StructToMap(from)
err = c.MapToStruct(m, to)
return err
}
func (c convertUtil) Copy(toValue interface{}, fromValue interface{}) interface{} {
if err := copier.Copy(toValue, fromValue); err != nil {
// core.Logger.Errorf("Copy err: err=[%+v]", err)
panic("SystemError")
}
return toValue
}

View File

@@ -10,10 +10,8 @@ import (
"mime/multipart"
"os"
"reflect"
"strconv"
"strings"
"time"
"x_admin/config"
"github.com/google/uuid"
)
@@ -65,14 +63,6 @@ func (tu toolsUtil) GetFileMD5(file *multipart.FileHeader) (string, error) {
return hex.EncodeToString(hash.Sum(nil)), nil
}
// MakeToken 生成唯一Token
func (tu toolsUtil) MakeToken() string {
ms := time.Now().UnixMilli()
token := tu.MakeMd5(tu.MakeUuidV7() + strconv.FormatInt(ms, 10) + tu.RandomString(8))
tokenSecret := token + config.AppConfig.Secret
return tu.MakeMd5(tokenSecret) + tu.RandomString(6)
}
// Contains 判断src是否包含elem元素
func (tu toolsUtil) Contains(src interface{}, elem interface{}) bool {
srcArr := reflect.ValueOf(src)