From 7228d3fedc327a103d708ac88b354ab1df310849 Mon Sep 17 00:00:00 2001 From: gjs Date: Mon, 10 Aug 2026 15:46:49 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF=E4=BC=A0?= =?UTF-8?q?=E5=88=B0service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/internal/dao/admin.go | 24 ++- admin/internal/dao/login.go | 24 +-- admin/internal/dao/servers.go | 1 - admin/internal/logic/admineditlogic.go | 10 +- admin/internal/logic/adminloginlogic.go | 51 ++++--- admin/internal/logic/adminstatuslogic.go | 11 +- admin/internal/logic/ownpasswordlogic.go | 41 ++++- admin/internal/logic/passwordlogic.go | 11 +- bff/bff.go | 2 + bff/etc/admin.pb | Bin 14709 -> 14703 bytes bff/internal/dialer/nacos.go | 2 + bff/internal/response/user.go | 61 ++++++++ deploy/apisix/apisix.yaml | 40 ++++- deploy/apisix/config.yaml | 25 +++ deploy/apisix/lua/auth.lua | 185 +++++++++++++++++++++++ deploy/docker-compose.yml | 11 +- pkg/utils/config.go | 2 +- pkg/utils/loginInfo.go | 67 ++++++++ pkg/utils/status.go | 11 +- 19 files changed, 505 insertions(+), 74 deletions(-) create mode 100644 bff/internal/response/user.go create mode 100644 deploy/apisix/lua/auth.lua create mode 100644 pkg/utils/loginInfo.go diff --git a/admin/internal/dao/admin.go b/admin/internal/dao/admin.go index 43a1f29..cba84c9 100644 --- a/admin/internal/dao/admin.go +++ b/admin/internal/dao/admin.go @@ -18,28 +18,24 @@ type Admin struct { FailNumber int `gorm:"column:fail_number;type:tinyint(2);default:0;comment:连续登录失败次数" json:"fail_number"` Reason string `gorm:"column:reason;type:varchar(255);comment:原因" json:"reason"` AdminId int `gorm:"column:admin_id;type:int(11);default:0;comment:操作人;NOT NULL" json:"admin_id"` - CreateTime utils.CustomTime `gorm:"column:create_time;type:datetime;default:NULL;comment:添加时间" json:"create_time"` - UpdateTime utils.CustomTime `gorm:"column:update_time;type:datetime;default:NULL;comment:更新时间" json:"update_time"` } // AdminPassword 修改管理员密码 type AdminPassword struct { - Id int `json:"id"` - Password string `json:"password"` - Salt string `json:"salt"` - AdminId int `json:"admin_id"` - AdminName string `gorm:"column:admin_name;type:varchar(100);comment:操作人姓名" json:"admin_name"` - UpdateTime utils.CustomTime `gorm:"column:update_time;type:datetime;default:NULL;comment:更新时间" json:"update_time"` + Id int `json:"id"` + Password string `json:"password"` + Salt string `json:"salt"` + AdminId int `json:"admin_id"` + AdminName string `gorm:"column:admin_name;type:varchar(100);comment:操作人姓名" json:"admin_name"` } // AdminStatus 修改管理员状态 type AdminStatus struct { - Id int `json:"id"` - Status uint8 `json:"status"` - Reason string `json:"reason"` - AdminId int `json:"admin_id"` - AdminName string `gorm:"column:admin_name;type:varchar(100);comment:操作人姓名" json:"admin_name"` - UpdateTime utils.CustomTime `gorm:"column:update_time;type:datetime;default:NULL;comment:更新时间" json:"update_time"` + Id int `json:"id"` + Status uint8 `json:"status"` + Reason string `json:"reason"` + AdminId int `json:"admin_id"` + AdminName string `gorm:"column:admin_name;type:varchar(100);comment:操作人姓名" json:"admin_name"` } // AdminInfo 管理员详情 diff --git a/admin/internal/dao/login.go b/admin/internal/dao/login.go index de67e26..b7689b5 100644 --- a/admin/internal/dao/login.go +++ b/admin/internal/dao/login.go @@ -1,22 +1,26 @@ package dao import ( - "github.com/golang-jwt/jwt/v4" "pkg.local/utils" ) type JwtInfo struct { - Id int64 `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT" json:"id"` - Avatar string `gorm:"column:avatar;type:varchar(255);comment:头像" json:"avatar"` - Name string `gorm:"column:name;type:varchar(30);comment:真实姓名;NOT NULL" json:"name"` - Phone string `gorm:"column:phone;type:char(12);comment:电话" json:"phone"` - Email string `gorm:"column:email;type:varchar(255);comment:邮箱" json:"email"` - Roles string `gorm:"column:roles;type:varchar(255);comment:角色" json:"roles"` - LastTime utils.CustomTime `gorm:"column:last_time;type:datetime;comment:最后一次登录时间" json:"last_time"` - Time int `json:"time"` - jwt.RegisteredClaims + Id int64 `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT" json:"id"` + Avatar string `gorm:"column:avatar;type:varchar(255);comment:头像" json:"avatar"` + Name string `gorm:"column:name;type:varchar(30);comment:真实姓名;NOT NULL" json:"name"` + Phone string `gorm:"column:phone;type:char(12);comment:电话" json:"phone"` + Email string `gorm:"column:email;type:varchar(255);comment:邮箱" json:"email"` + LastTime utils.CustomTime `gorm:"column:last_time;type:datetime;comment:最后一次登录时间" json:"last_time"` + Time int `json:"time"` + Roles []JwtInfoServices `json:"roles"` } type Token struct { Token string `json:"token"` } + +type JwtInfoServices struct { + Id int `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT" json:"id"` + Name string `gorm:"column:name;type:varchar(255);comment:项目描述;NOT NULL" json:"name"` + Items map[string]string `json:"items"` +} diff --git a/admin/internal/dao/servers.go b/admin/internal/dao/servers.go index 2f887fe..73145ff 100644 --- a/admin/internal/dao/servers.go +++ b/admin/internal/dao/servers.go @@ -33,5 +33,4 @@ type ServicesStatus struct { type ServicesNameItems struct { Id int `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT" json:"id"` Name string `gorm:"column:name;type:varchar(255);comment:项目描述;NOT NULL" json:"name"` - Logo string `gorm:"column:logo;type:varchar(255);default:'';comment:logo;NOT NULL" json:"logo"` } diff --git a/admin/internal/logic/admineditlogic.go b/admin/internal/logic/admineditlogic.go index 2ffe51f..2b06a64 100644 --- a/admin/internal/logic/admineditlogic.go +++ b/admin/internal/logic/admineditlogic.go @@ -36,7 +36,10 @@ func (l *AdminEditLogic) AdminEdit(in *admin.AdminEditRequest) (*admin.Response, if fail := l.checkParams(in, &v); fail != nil { return fail, nil } - + adminInfo := utils.GetUserFromCtx(l.ctx) + if adminInfo.ID < utils.NumberOne { + return l.fail(utils.ErrorNoLoginInfo), nil + } encryPhone, cErr := utils.EncryptPhone(in.Phone) if cErr != nil { l.Logger.Error(cErr) @@ -78,8 +81,9 @@ func (l *AdminEditLogic) AdminEdit(in *admin.AdminEditRequest) (*admin.Response, return l.fail(utils.ErrorExistUser), nil } } - //adminId, _ := c.Get(utils.USERID) - //adminName, _ := c.Get(utils.USERNAME) + + info.AdminId = int(adminInfo.ID) + info.AdminName = adminInfo.Name var roles []string if len(in.Roles) > utils.NumberZero { for _, role := range in.Roles { diff --git a/admin/internal/logic/adminloginlogic.go b/admin/internal/logic/adminloginlogic.go index 2e15d1f..e51a600 100644 --- a/admin/internal/logic/adminloginlogic.go +++ b/admin/internal/logic/adminloginlogic.go @@ -7,9 +7,13 @@ import ( "admin/internal/svc" "admin/validator" "context" + "strconv" + "time" + jsoniter "github.com/json-iterator/go" "github.com/zeromicro/go-zero/core/logx" "pkg.local/modelbase" + "pkg.local/redis" "pkg.local/utils" ) @@ -29,12 +33,13 @@ func NewAdminLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminL } func (l *AdminLoginLogic) AdminLogin(in *admin.AdminLoginRequest) (*admin.Response, error) { + l.Logger.Error("u:", in.Username, "p:", in.Password, "|") var v validator.LoginValidator if fail := l.checkParams(in, &v); fail != nil { return fail, nil } - phone, dErr := utils.DecryptPhone(in.Username) + phone, dErr := utils.EncryptPhone(in.Username) if dErr != nil { l.Logger.Error(dErr) return l.fail(utils.Fail), nil @@ -59,12 +64,12 @@ func (l *AdminLoginLogic) AdminLogin(in *admin.AdminLoginRequest) (*admin.Respon } data := dao.JwtInfo{ - Id: info.Id, - Name: info.Name, - Email: info.Email, - Phone: info.Phone, - Avatar: info.Avatar, - Roles: info.Roles, + Id: info.Id, + Name: info.Name, + Email: info.Email, + Phone: info.Phone, + Avatar: info.Avatar, + //Roles: info.Roles, LastTime: utils.Now(), } retData := dao.Token{ @@ -77,16 +82,24 @@ func (l *AdminLoginLogic) AdminLogin(in *admin.AdminLoginRequest) (*admin.Respon } func (l *AdminLoginLogic) setLogin(token string, data dao.JwtInfo) { - //duration := utils.GetConfigInt("base.login_out_time") - //if data.Time > utils.NumberZero { - // duration = int(time.Minute * time.Duration(int64(data.Time))) - //} - //redisKey := utils.GetLoginKey(utils.LoginUser, token) - //userStr, _ := jsoniter.Marshal(data) - //redis.New().Set(redisKey, string(userStr), duration).Result() - //oldAuth, oldErr := utils.Redis.HGet(utils.GetRedisKey(utils.LoginUserKey), strconv.Itoa(data.Id)).Result() - //if oldErr == nil { - // utils.Redis.Del(utils.GetRedisKey(utils.LoginUser + oldAuth)).Result() - //} - //utils.Redis.HSet(utils.GetRedisKey(utils.LoginUserKey), strconv.Itoa(data.Id), token).Result() + expireMin := utils.GetConfigInt("base.login_out_time") + var expire time.Duration + // 默认使用配置时长 + expire = time.Duration(expireMin) * time.Minute + // 如果用户自定义了过期时间则覆盖 + if data.Time > utils.NumberZero { + expire = time.Duration(data.Time) * time.Minute + } + + redisKey := utils.GetLoginKey(utils.LoginTypeAdmin, token) + l.Logger.Error(redisKey) + userStr, _ := jsoniter.Marshal(data) + ctx := context.Background() + redis.Client.Set(ctx, redisKey, userStr, expire).Result() + redisKeysKey := utils.GetLoginKeysKey(utils.LoginTypeAdmin) + oldAuth, oldErr := redis.Client.HGet(ctx, redisKeysKey, strconv.Itoa(int(data.Id))).Result() + if oldErr == nil { + redis.Client.Del(ctx, utils.GetLoginKey(utils.LoginTypeAdmin, oldAuth)).Result() + } + redis.Client.HSet(ctx, redisKeysKey, strconv.Itoa(int(data.Id)), token).Result() } diff --git a/admin/internal/logic/adminstatuslogic.go b/admin/internal/logic/adminstatuslogic.go index 1a4d54f..deb5e21 100644 --- a/admin/internal/logic/adminstatuslogic.go +++ b/admin/internal/logic/adminstatuslogic.go @@ -35,6 +35,10 @@ func (l *AdminStatusLogic) AdminStatus(in *admin.StatusRequest) (*admin.Response if fail := l.checkParams(in, &v); fail != nil { return fail, nil } + adminInfo := utils.GetUserFromCtx(l.ctx) + if adminInfo.ID < utils.NumberOne { + return l.fail(utils.ErrorNoLoginInfo), nil + } var info dao.AdminStatus w := modelbase.Params{Eq: map[string]string{"id": strconv.Itoa(int(in.Id))}} modelObj := model.AdminModel{}.Init() @@ -50,11 +54,8 @@ func (l *AdminStatusLogic) AdminStatus(in *admin.StatusRequest) (*admin.Response info.Status = uint8(in.Status) info.Reason = in.Reason - info.UpdateTime = utils.Now() - //adminId, _ := c.Get(utils.USERID) - //adminName, _ := c.Get(utils.USERNAME) - //info.AdminId = adminId.(int) - //info.AdminName = adminName.(string) + info.AdminId = int(adminInfo.ID) + info.AdminName = adminInfo.Name _, editErr := modelObj.Edit(w, info) if editErr != nil { l.Logger.Error(editErr) diff --git a/admin/internal/logic/ownpasswordlogic.go b/admin/internal/logic/ownpasswordlogic.go index 9e22bf8..57c4278 100644 --- a/admin/internal/logic/ownpasswordlogic.go +++ b/admin/internal/logic/ownpasswordlogic.go @@ -1,18 +1,26 @@ package logic import ( + "admin/internal/dao" + "admin/internal/model" + "admin/validator" "context" + "strconv" "admin/admin" "admin/internal/svc" "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/metadata" + "pkg.local/modelbase" + "pkg.local/utils" ) type OwnPasswordLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger + BaseLogic } func NewOwnPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OwnPasswordLogic { @@ -24,7 +32,36 @@ func NewOwnPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OwnPa } func (l *OwnPasswordLogic) OwnPassword(in *admin.OwnPasswordRequest) (*admin.Response, error) { - // todo: add your logic here and delete this line + var v validator.AdminOwnPasswordValidator + if fail := l.checkParams(in, &v); fail != nil { + return fail, nil + } + var info dao.AdminPassword + md, exist := metadata.FromIncomingContext(l.ctx) + l.Logger.Info("收到完整metadata", exist, md) + adminInfo := utils.GetUserFromCtx(l.ctx) + if adminInfo.ID < utils.NumberOne { + return l.fail(utils.ErrorNoLoginInfo), nil + } + l.Logger.Info("解析出用户信息", adminInfo.ID, adminInfo.Name) + w := modelbase.Params{Eq: map[string]string{"id": strconv.Itoa(int(adminInfo.ID))}} + modelObj := model.AdminModel{}.Init() + err := modelObj.GetOne(w, &info) + if err != nil { + l.Logger.Error(err) + return l.fail(utils.Fail), nil + } + if info.Id < utils.NumberOne { + return l.fail(utils.ErrorNotFund), nil + } - return &admin.Response{}, nil + pwd := utils.GetSaltPassword(info.Salt, in.Password) + pwd, _ = utils.EncryptPassword(pwd) + info.Password = pwd + _, editErr := modelObj.Edit(w, info) + if editErr != nil { + l.Logger.Error(err) + return l.fail(utils.Fail), nil + } + return l.ok(utils.NumberOne), nil } diff --git a/admin/internal/logic/passwordlogic.go b/admin/internal/logic/passwordlogic.go index e080c26..cce2468 100644 --- a/admin/internal/logic/passwordlogic.go +++ b/admin/internal/logic/passwordlogic.go @@ -35,6 +35,10 @@ func (l *PasswordLogic) Password(in *admin.PasswordRequest) (*admin.Response, er if fail := l.checkParams(in, &v); fail != nil { return fail, nil } + adminInfo := utils.GetUserFromCtx(l.ctx) + if adminInfo.ID < utils.NumberOne { + return l.fail(utils.ErrorNoLoginInfo), nil + } var info dao.AdminPassword w := modelbase.Params{Eq: map[string]string{"id": strconv.Itoa(int(in.Id))}} modelObj := model.AdminModel{}.Init() @@ -50,11 +54,8 @@ func (l *PasswordLogic) Password(in *admin.PasswordRequest) (*admin.Response, er pwd := utils.GetSaltPassword(info.Salt, in.Password) pwd, _ = utils.EncryptPassword(pwd) info.Password = pwd - info.UpdateTime = utils.Now() - //adminId, _ := c.Get(utils.USERID) - //adminName, _ := c.Get(utils.USERNAME) - //info.AdminId = adminId.(int) - //info.AdminName = adminName.(string) + info.AdminId = int(adminInfo.ID) + info.AdminName = adminInfo.Name _, editErr := modelObj.Edit(w, info) if editErr != nil { l.Logger.Error(err) diff --git a/bff/bff.go b/bff/bff.go index 710ad62..d542d8f 100644 --- a/bff/bff.go +++ b/bff/bff.go @@ -50,6 +50,8 @@ func main() { gw := gateway.MustNewServer(c.GatewayConf, gateway.WithDialer(dialer.Nacos), + // 权鉴 + gateway.WithMiddleware(response.UserReadMiddleware), gateway.WithMiddleware(response.Wrap), ) defer gw.Stop() diff --git a/bff/etc/admin.pb b/bff/etc/admin.pb index ab7893b98c5dfd538fe60a0634985dd426c60e96..33b431b4a8be33ca1b375e74a54e791bec4fa5d0 100644 GIT binary patch delta 91 zcmexb^uB1rV`Ijy%} 0 then + red:select(redis_db) + end + + -- ========== 获取用户信息 ========== + local res, err = red:get(redis_key) + red:set_keepalive(10000, 100) + + if err then + core.log.error("Redis get error: ", err) + return core.response.exit(500, { + code = 10003, + message = "系统错误,请稍后重试", + data = nil + }) + end + + if not res or res == ngx.null then + core.log.warn("User not found in redis, key: ", redis_key) + return core.response.exit(401, { + code = 10003, + message = "未登录或登录已过期", + data = nil + }) + end + + -- ========== 解析用户信息 ========== + local json, decode_err = core.json.decode(res) + if not json then + core.log.error("JSON decode error: ", decode_err, " data: ", res) + return core.response.exit(500, { + code = 10003, + message = "数据解析错误", + data = nil + }) + end + + -- ========== Admin路径权限校验 ========== + if service_code == "admin1" then + if not req_auth or req_auth == "" then + core.log.warn("Missing Authorization-Auth header for admin") + return core.response.exit(403, { + code = 10008, + message = "没有权限访问该资源", + data = nil + }) + end + + local roles = json.roles or {} + local expect_path = roles[req_auth] + + if not expect_path or expect_path == "" then + core.log.error("Permission not found for auth: ", req_auth, + " user_id: ", json.id, " roles: ", core.json.encode(roles)) + return core.response.exit(403, { + code = 10008, + message = "没有权限访问该资源", + data = nil + }) + end + + local full_expect_uri = "/" .. expect_path + + if request_uri ~= full_expect_uri then + local is_prefix_match = false + if expect_path:sub(-1) == "*" then + local base_path = expect_path:sub(1, -2) + if request_uri:find(base_path, 1, true) == 1 then + is_prefix_match = true + end + end + + if not is_prefix_match then + core.log.error("URI mismatch - request: ", request_uri, + " expected: ", full_expect_uri, + " auth: ", req_auth, + " user_id: ", json.id) + return core.response.exit(403, { + code = 10008, + message = "没有权限访问该资源", + data = nil + }) + end + end + end + + -- ========== 向下游透传用户信息 ========== + if json.id then + ngx.req.set_header("X-User-Id", tostring(json.id)) + end + + if json.name then + ngx.req.set_header("X-User-Name", json.name) + end + + if json.username then + ngx.req.set_header("X-Username", json.username) + end + ngx.req.set_header("X-Refresh", refresh ) + + ngx.req.set_header("X-Service-Code", service_code) + + return +end + +return _M \ No newline at end of file diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index cc1fa08..7cc430b 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -1,5 +1,4 @@ services: - rnacos: image: qingpan/rnacos:stable restart: unless-stopped @@ -17,11 +16,15 @@ services: volumes: - ./apisix/config.yaml:/usr/local/apisix/conf/config.yaml:ro - ./apisix/apisix.yaml:/usr/local/apisix/conf/apisix.yaml:ro + # 单文件挂载到 /usr/local/apisix/auth.lua + - ./apisix/lua/auth.lua:/usr/local/apisix/auth.lua:ro ports: - - "9080:9080" # 业务入口 - - "9443:9443" # HTTPS + - "9080:9080" + - "9443:9443" + networks: + - default networks: default: external: true - name: services-network + name: services-network \ No newline at end of file diff --git a/pkg/utils/config.go b/pkg/utils/config.go index 8f1c757..52fac1d 100644 --- a/pkg/utils/config.go +++ b/pkg/utils/config.go @@ -34,7 +34,7 @@ func GetConfigString(key string) string { } func GetConfigInt(key string) int { data := make(map[string]int) - data["base.login_out_time"] = 86400 + data["base.login_out_time"] = 60 * 24 * 30 //分 return data[key] } func GetConfigInt64(key string) int64 { return runViper.GetInt64(key) } diff --git a/pkg/utils/loginInfo.go b/pkg/utils/loginInfo.go new file mode 100644 index 0000000..b99153f --- /dev/null +++ b/pkg/utils/loginInfo.go @@ -0,0 +1,67 @@ +package utils + +import ( + "context" + "net/url" + "strconv" + + "google.golang.org/grpc/metadata" +) + +type UserInfo struct { + ID int64 + Name string + RawUID string + Refresh string + Valid bool +} + +// GetUserFromCtx 从rpc metadata获取用户 +func GetUserFromCtx(ctx context.Context) UserInfo { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return UserInfo{Valid: false} + } + + uidList := md.Get("x-user-id") + nameList := md.Get("x-user-name") + refreshList := md.Get("x-refresh") + + rawUID := "" + encodeName := "" + refresh := "" + if len(uidList) > 0 { + rawUID = uidList[0] + } + if len(nameList) > 0 { + encodeName = nameList[0] + } + if len(refreshList) > 0 { + refresh = refreshList[0] + } + + if rawUID == "" { + return UserInfo{Valid: false} + } + + uid, err := strconv.ParseInt(rawUID, 10, 64) + if err != nil { + return UserInfo{ + RawUID: rawUID, + Name: encodeName, + Refresh: refresh, + Valid: false, + } + } + + // 解码中文 + userName, _ := url.QueryUnescape(encodeName) + + return UserInfo{ + ID: uid, + Name: userName, + RawUID: rawUID, + Refresh: refresh, + Valid: true, + } +} diff --git a/pkg/utils/status.go b/pkg/utils/status.go index 84c7744..4b735f9 100644 --- a/pkg/utils/status.go +++ b/pkg/utils/status.go @@ -45,8 +45,10 @@ const ( const ( // LoginUser 所有的登录 加上服务名:id // %s 为,admin,user ,... - LoginUser = "login:service:%s:" - LoginUserKey = "login:service:%s:keys" + Login = "login:service:%s:" + LoginKey = "login:service:%s:keys" + LoginTypeAdmin = "admin" + LoginTypeUser = "user" CodeKey = "code:" ) @@ -62,6 +64,7 @@ var ( ErrorPwdError = Status{Code: 10002, Msg: "密码错误"} ErrorNoLogin = Status{Code: 10003, Msg: "没有登录"} ErrorAuthority = Status{Code: 10008, Msg: "没有权限"} + ErrorNoLoginInfo = Status{Code: 10003, Msg: "没有获取到登录信息"} // 基础类 ErrorExist = NewError(998, "已存在") @@ -101,9 +104,9 @@ func SetError(error Error, msg string) Error { return NewError(error.GetCode(), msg) } func GetLoginKey(Type, key string) string { - return fmt.Sprintf(LoginUser, Type) + key + return fmt.Sprintf(Login, Type) + key } func GetLoginKeysKey(Type string) string { - return fmt.Sprintf(LoginUserKey, Type) + return fmt.Sprintf(LoginKey, Type) }