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 ab7893b..33b431b 100644 Binary files a/bff/etc/admin.pb and b/bff/etc/admin.pb differ diff --git a/bff/internal/dialer/nacos.go b/bff/internal/dialer/nacos.go index 53b3eb4..fa3c02e 100644 --- a/bff/internal/dialer/nacos.go +++ b/bff/internal/dialer/nacos.go @@ -1,6 +1,7 @@ package dialer import ( + "bff/internal/response" "context" "fmt" "net" @@ -25,6 +26,7 @@ func Nacos(conf zrpc.RpcClientConf) zrpc.Client { return zrpc.MustNewClient(cliConf, zrpc.WithDialOption(grpc.WithTransportCredentials(insecure.NewCredentials())), + zrpc.WithDialOption(grpc.WithUnaryInterceptor(response.UserClientInterceptor)), zrpc.WithDialOption(grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { inst, err := discovery.Pick(serviceName) if err != nil { diff --git a/bff/internal/response/user.go b/bff/internal/response/user.go new file mode 100644 index 0000000..da213d8 --- /dev/null +++ b/bff/internal/response/user.go @@ -0,0 +1,61 @@ +package response + +import ( + "context" + "net/http" + "net/url" + "strconv" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type ctxKey string + +const ( + CtxUserId ctxKey = "X-User-Id" + CtxUserName ctxKey = "X-User-Name" +) + +type UserInfo struct { + ID int64 + Name string + RawUID string + Valid bool +} + +func GetUserInfo(ctx context.Context) UserInfo { + rawUID, ok := ctx.Value(CtxUserId).(string) + if !ok || rawUID == "" { + return UserInfo{Valid: false} + } + name, _ := ctx.Value(CtxUserName).(string) + uid, err := strconv.ParseInt(rawUID, 10, 64) + if err != nil { + return UserInfo{RawUID: rawUID, Name: name, Valid: false} + } + return UserInfo{ID: uid, Name: name, RawUID: rawUID, Valid: true} +} + +func UserReadMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + uid := r.Header.Get("X-User-Id") + uname := r.Header.Get("X-User-Name") + newCtx := r.Context() + newCtx = context.WithValue(newCtx, CtxUserId, uid) + newCtx = context.WithValue(newCtx, CtxUserName, uname) + next(w, r.WithContext(newCtx)) + } +} + +func UserClientInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + user := GetUserInfo(ctx) + if user.Valid { + md := metadata.New(map[string]string{ + "x-user-id": user.RawUID, + "x-user-name": url.QueryEscape(user.Name), + }) + ctx = metadata.NewOutgoingContext(ctx, md) + } + return invoker(ctx, method, req, reply, cc, opts...) +} diff --git a/deploy/apisix/apisix.yaml b/deploy/apisix/apisix.yaml index 15f1923..2189744 100644 --- a/deploy/apisix/apisix.yaml +++ b/deploy/apisix/apisix.yaml @@ -1,10 +1,21 @@ +plugin_configs: + - id: 1 + plugins: + serverless-pre-function: + phase: access + functions: + - | + local f = assert(loadfile("/usr/local/apisix/auth.lua")) + local mod = f() + return mod.access + routes: - - id: bff - name: bff + - id: bff-no-auth + name: bff-no-auth + priority: 100 uris: - - /admin/* - - /api/* - - /app/* + - /api/v3/login + - /admin/v3/login methods: - GET - POST @@ -14,4 +25,21 @@ routes: type: roundrobin nodes: "bff:10000": 1 -#END + + - id: bff + name: bff + priority: 10 + uris: + - /admin/* + - /api/* + methods: + - GET + - PUT + - POST + - OPTIONS + plugin_config_id: 1 + upstream: + type: roundrobin + nodes: + "bff:10000": 1 +#END \ No newline at end of file diff --git a/deploy/apisix/config.yaml b/deploy/apisix/config.yaml index 5c6a80f..1a4cd73 100644 --- a/deploy/apisix/config.yaml +++ b/deploy/apisix/config.yaml @@ -2,12 +2,37 @@ apisix: node_listen: 9080 enable_ipv6: false +redis: + host: "39.106.171.204" + port: 6379 + password: "lLMLcuPpzSj" + db: 0 + +# HTTP七层插件 plugins: - proxy-rewrite - cors - limit-req + - serverless-pre-function + - serverless-post-function + +# 自定义四层stream插件列表,移除syslog,保留负载均衡、mqtt、限流等 +stream_plugins: + - traffic-split + - limit-conn + - ip-restriction + - mqtt-proxy deployment: role: data_plane role_data_plane: config_provider: yaml + +# http层共享字典(仅http使用) +nginx_config: + lua_package_path: "/usr/local/apisix/?.lua;;" + lua_shared_dict: + prometheus-metrics 10m + prometheus-cache 10m + standalone-config 10m + upstream-healthcheck 10m \ No newline at end of file diff --git a/deploy/apisix/lua/auth.lua b/deploy/apisix/lua/auth.lua new file mode 100644 index 0000000..6cb1e6a --- /dev/null +++ b/deploy/apisix/lua/auth.lua @@ -0,0 +1,185 @@ +local core = require("apisix.core") +local redis = require("resty.redis") + +local _M = {} + +function _M.access(conf, ctx) + -- 确保在请求阶段执行 + if not ngx or not ngx.var then + core.log.warn("Not in request context, skipping auth check") + return + end + + -- ========== 获取请求头 ========== + local headers = ngx.req.get_headers() + local user_key = headers["Authorization"] + local service_code = headers["Authorization-Type"] + local req_auth = headers["Authorization-Auth"] + local refresh = headers["Refresh"] + local request_uri = ngx.var.uri + + -- ========== 获取 Redis 配置 ========== + local redis_config = core.config.local_conf().redis or {} + local redis_host = redis_config.host or "127.0.0.1" + local redis_port = redis_config.port or 6379 + local redis_password = redis_config.password or "" + local redis_db = redis_config.db or 0 + + -- ========== 参数校验 ========== + if not user_key or user_key == "" then + core.log.warn("Missing Authorization header") + return core.response.exit(401, { + code = 10003, + message = "未登录", + data = nil + }) + end + + if not service_code or service_code == "" then + core.log.warn("Missing Authorization-Type header") + return core.response.exit(401, { + code = 10003, + message = "未登录", + data = nil + }) + end + + -- ========== 构建Redis Key ========== + local redis_key = "login:service:" .. service_code .. ":" .. user_key + + -- ========== Redis连接 ========== + local red = redis:new() + red:set_timeouts(1000, 1000, 1000) + + local ok, err = red:connect(redis_host, redis_port) + if not ok then + core.log.error("Redis connect error: ", err) + red:set_keepalive(10000, 100) + return core.response.exit(500, { + code = 10003, + message = "系统错误,请稍后重试", + data = nil + }) + end + + -- Redis认证 + if redis_password and redis_password ~= "" then + local auth_ok, auth_err = red:auth(redis_password) + if not auth_ok then + core.log.error("Redis auth error: ", auth_err) + red:set_keepalive(10000, 100) + return core.response.exit(500, { + code = 10003, + message = "系统错误,请稍后重试", + data = nil + }) + end + end + + -- 选择数据库 + if redis_db and redis_db > 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) }