本文大体记录了eudore-website认证鉴权体系的实现,实现了acl、rbac、pbac鉴权和ak、token、bearer认证,完整细节请查看源码。git
在线demo,用户密码均为guest。github
eudore-website使用ak、token、bearer认证三种综合认证,原理经过web请求中间件使用请求信息得到用户信息,保存到请求上下文中而后供后续使用。golang
bearer认证原理是利用jwt非对称签名防止数据篡改。web
最初就初始化jwt解析对象,而后处理请求Authorization Header,解析出jwt的数据,从中提取到userid和username信息,而后设置请求上下文的参数中。ajax
源码sql
func(ctx eudore.Context) { data, err := jwtParse.ParseBearer(ctx.GetHeader(eudore.HeaderAuthorization)) if err == nil { ctx.SetParam("UID", eudore.GetString(data["userid"])) ctx.SetParam("UNAME", eudore.GetString(data["name"])) return } ... }
而后客户端请求添加Authorization Header.数据库
例如基于mithriljs封装ajax添加Header:json
if(typeof m.request !== 'undefined') { var oldrequest = m.request m.request = function(args) { // add header if(!("headers" in args)) { args["headers"] = {} } if(Base.lang != "") { args["headers"]["Accept-Language"] = Base.lang } if(Base.bearer != "") { args["headers"]["Authorization"] = Base.bearer } if(requestid!="") { args["headers"]["X-Parent-Id"] = requestid } return oldrequest(args) } }
具备一个全局遍历Base保存用户相关信息,例如Base.bearer,在使用m.request方法时,就自动给参数添加bearer信息。api
curl直接-H指定header便可。安全
token认证原理使用token加载到对应的用户信息
token供api使用。
建立数据表tb_auth_access_token,里面保存token对应的用户信息。
CREATE TABLE tb_auth_access_token( "userid" INTEGER PRIMARY KEY, "token" VARCHAR(32), "expires" TIMESTAMP, "createtime" TIMESTAMP DEFAULT (now()) );
从请求中提取到token参数,而后数据库查询tb_auth_access_token表,找到用户信息,设置到请求上下文中。
stmtQueryAccessToken, err := db.Prepare("SELECT userid,(SELECT name FROM tb_auth_user_info WHERE id = userid) FROM tb_auth_access_token WHERE token=$1 and expires > now()") func(ctx eudore.Context) { ... token := ctx.GetQuery("token") if token != "" { var userid string var username string err := stmtQueryAccessToken.QueryRow(token).Scan(&userid, &username) if err == nil { ctx.SetParam("UID", userid) ctx.SetParam("UNAME", username) return } ctx.Error(err) } ... }
ak认证的原理例如非对称加密实现,用户有效校验
ak和token同样用于api使用,可是ak更加复杂和安全。
accesskey代表是那个ak,accesssecrect是签名使用的私钥,而后客户端和服务端使用accesssecrect签名一个数据获得签名结果signature,若是signature相同就是表示accesssecrect相同,那么用户使用的ak就是有效的。
ak认证建立tb_auth_access_key表,保存ak和用户信息和token表相识。
CREATE TABLE tb_auth_access_key( "userid" INTEGER PRIMARY KEY, "accesskey" VARCHAR(32), "accesssecrect" VARCHAR(32), "expires" TIMESTAMP, "createtime" TIMESTAMP DEFAULT (now()) );
ak认证先提取到accesskey、signature、expires三个参数,用于ak认证使用,accesskey对应ak记录、signature是ak签名结果、expires是签名过时时间。
先检查下有效时间是否有效,且有效时间不大于60分钟。
而后数据库查询一下accesskey对应的accesssecrect和用户数据。
再计算一下签名结果,若是结果和signature同样那么就是经过,而后设置用户数据。
当前签名格式是accesskey-expires,过于简单,可是也能够用。
stmtQueryAccessKey, err := db.Prepare("SELECT userid,(SELECT name FROM tb_auth_user_info WHERE id = userid),accesssecrect FROM tb_auth_access_key WHERE accesskey=$1 and expires > $2") func(ctx eudore.Context) { ... key, signature, expires := ctx.GetQuery("accesskey"), ctx.GetQuery("signature"), ctx.GetQuery("expires") if key != "" && signature != "" && expires != "" { tunix, err := strconv.ParseInt(expires, 10, 64) if err != nil { ctx.Error(err) return } ttime := time.Unix(tunix, 0) if ttime.After(time.Now().Add(60 * time.Minute)) { ctx.Errorf("accesskey expires is to long, max 60 min") return } // var userid, username, scerect string err = stmtQueryAccessKey.QueryRow(key, ttime).Scan(&userid, &username, &scerect) if err != nil { ctx.Error(err) return } h := hmac.New(sha1.New, []byte(scerect)) fmt.Fprintf(h, "%s-%s", key, expires) if signature != base64.StdEncoding.EncodeToString(h.Sum(nil)) { ctx.Errorf("signature is invalid") return } ctx.SetParam("UID", userid) ctx.SetParam("UNAME", username) } }
eudore-website使用acl、rbac、pbac三种复合鉴权设计,按顺序依次处理,某个对象能够处理就返回结果。
eudore定义了一个ram接口,ram接口会传递用户id、用户行为信息给ram鉴权使用,而后ram对象返回处理结果和是否处理。
// RamHandler 定义Ram处理接口 type RamHandler interface { RamHandle(int, string, eudore.Context) (bool, bool) // return1 验证结果 return2 是否验证 }
RamHttp对象会处理http相关内容,获取到用户id和行为传递给多Ram对象依次处理,而后根据Ram结果处理,通常RAM对象若是处理了请求会设置ram参数为处理者,例如acl处理的请求,得到ram参数的值就是acl。
ram须要两个参数用户id和action,用户id由认证体系提供的UID得到,action参数由路过提供的静态值
例如路由指定的action参数为Get
app.GetFunc("/* action=Get", func(ctx eudore.Context){})
或者控制器指定的路由参数,例如website控制器使用的action参数,由包名称、控制器名称、控制器方法组成。
// GetRouteParam 方法添加路由参数信息。 func (ctl *ControllerWebsite) GetRouteParam(pkg, name, method string) string { pos := strings.LastIndexByte(pkg, '/') + 1 if pos != 0 { pkg = pkg[pos:] } if strings.HasSuffix(name, "Controller") { name = name[:len(name)-len("Controller")] } return fmt.Sprintf("action=%s:%s:%s", pkg, name, method)
例如一条路由注册日志
github.com/eudore/website/handlers/auth.PolicyController.GetIdById就是处理函数,对应的包是auth、控制器名称是Policy(移除应用控制器的Controller后缀)、控制器方法是GetIdById,组合的action为 auth:Policy:GetIdById
{"time":"2019-10-02 18:57:23","level":"INFO","message":"RegisterHandler: GET /api/v1/auth/policy/id/:id prefix=/api/v1/auth action=auth:Policy:GetIdById [github.com/eudore/website/handlers/auth.PolicyController.GetIdById]"}
website使用pgsql数据库,而后创建user_info、user_permisson、user_role、user_policy表,记录用户基本信息和绑定的权限、角(jue)色、策略信息,对应是是acl、rbac、pbac三种鉴权数据。
数据库惟一约束未添加
-- 用户信息表 CREATE SEQUENCE seq_auth_user_info_id INCREMENT by 1 MINVALUE 1 START 1; CREATE TABLE tb_auth_user_info( "id" INTEGER PRIMARY KEY DEFAULT nextval('seq_auth_user_info_id'), "name" VARCHAR(32) NOT NULL, "status" INTEGER DEFAULT 0, "level" INTEGER DEFAULT 0, "mail" VARCHAR(48), "tel" VARCHAR(16), "icon" INTEGER DEFAULT 0, "loginip" INTEGER DEFAULT 0, "logintime" TIMESTAMP, "sigintime" TIMESTAMP DEFAULT (now()) ); -- 用户绑定权限列表 CREATE TABLE tb_auth_user_permission( "userid" INTEGER, "permissionid" INTEGER, "effect" bool, "time" TIMESTAMP DEFAULT (now()), PRIMARY KEY("userid", "permissionid") ); COMMENT ON TABLE "public"."tb_auth_user_permission" IS 'ACL用户绑定权限列表'; COMMENT ON COLUMN "tb_auth_user_permission"."userid" IS '用户id'; COMMENT ON COLUMN "tb_auth_user_permission"."permissionid" IS '权限id'; -- 用户绑定角色关系 CREATE TABLE tb_auth_user_role( "userid" INTEGER, "roleid" INTEGER, "time" TIMESTAMP DEFAULT (now()), PRIMARY KEY("userid", "roleid") ); COMMENT ON TABLE "public"."tb_auth_user_role" IS 'RBAC用户绑定角色关系'; COMMENT ON COLUMN "tb_auth_user_role"."userid" IS '用户id'; COMMENT ON COLUMN "tb_auth_user_role"."roleid" IS '角色id'; -- 用户绑定策略 CREATE TABLE tb_auth_user_policy( "userid" INTEGER, "policyid" INTEGER, "index" INTEGER DEFAULT 0, "time" TIMESTAMP DEFAULT (now()), PRIMARY KEY("userid", "policyid") ); COMMENT ON TABLE "public"."tb_auth_user_policy" IS 'PBAC用户绑定策略'; COMMENT ON COLUMN "tb_auth_user_policy"."userid" IS 'User ID'; COMMENT ON COLUMN "tb_auth_user_policy"."policyid" IS 'Polic ID'; COMMENT ON COLUMN "tb_auth_user_policy"."index" IS '策略优先级';
而后建立权限、角色、策略相关的表,建立permission、role、policy三种权限对象的信息,和role绑定的权限信息。
-- 资源权限列表 CREATE SEQUENCE seq_auth_permission_id INCREMENT by 1 MINVALUE 1 START 1; CREATE TABLE tb_auth_permission( "id" INTEGER PRIMARY KEY DEFAULT nextval('seq_auth_permission_id'), "name" VARCHAR(64) NOT NULL, "description" VARCHAR(512), "time" TIMESTAMP DEFAULT (now()) ); COMMENT ON TABLE "public"."tb_auth_permission" IS '资源权限列表'; COMMENT ON COLUMN "tb_auth_permission"."id" IS '权限id'; COMMENT ON COLUMN "tb_auth_permission"."name" IS '权限行为'; -- 角色信息表 CREATE SEQUENCE seq_auth_role_id INCREMENT by 1 MINVALUE 1 START 1; CREATE TABLE tb_auth_role( "id" INTEGER PRIMARY KEY DEFAULT nextval('seq_auth_role_id'), "name" VARCHAR(32), "description" VARCHAR(64), "time" TIMESTAMP DEFAULT (now()) ); COMMENT ON TABLE "public"."tb_auth_role" IS 'RBAC角色信息表'; COMMENT ON COLUMN "tb_auth_role"."id" IS '角色id'; COMMENT ON COLUMN "tb_auth_role"."name" IS '角色名称'; -- 角色绑定权限 CREATE TABLE tb_auth_role_permission( "roleid" INTEGER, "permissionid" INTEGER, "time" TIMESTAMP DEFAULT (now()), PRIMARY KEY("roleid", "permissionid") ); COMMENT ON TABLE "public"."tb_auth_role_permission" IS 'RBAC角色绑定权限'; COMMENT ON COLUMN "tb_auth_role_permission"."roleid" IS '角色id'; COMMENT ON COLUMN "tb_auth_role_permission"."permissionid" IS '权限id'; -- PBAC策略信息表 CREATE SEQUENCE seq_auth_policy_id INCREMENT by 1 MINVALUE 1 START 1; CREATE TABLE tb_auth_policy( "id" INTEGER PRIMARY KEY DEFAULT nextval('seq_auth_policy_id'), "name" VARCHAR(64), "description" VARCHAR(512), "policy" VARCHAR(4096), "time" TIMESTAMP DEFAULT (now()) ); COMMENT ON TABLE "public"."tb_auth_policy" IS 'PBAC策略信息表'; COMMENT ON COLUMN "tb_auth_policy"."id" IS 'Polic ID'; COMMENT ON COLUMN "tb_auth_policy"."policy" IS '策略内容';
acl(access control list)访问控制列表,记录用户对某个权限是容许和拒绝。
Permissions记录权限对应的id,对应tb_auth_permission表。
AllowBinds和DenyBinds记录用户绑定的信息(为什么不定义成map[int]map[int]bool忘记了),对应tb_auth_user_permission表。
RamHandle先使用权限行为转换成权限id,而后map查找用户id和权限id对应的结果,若是查找到就返回结果。
type Acl struct { AllowBinds map[int]map[int]struct{} DenyBinds map[int]map[int]struct{} Permissions map[string]int } // RamHandle 方法实现ram.RamHandler接口,匹配一个请求。 func (acl *Acl) RamHandle(id int, perm string, ctx eudore.Context) (bool, bool) { permid, ok := acl.Permissions[perm] // 存在这个权限 if ok { // 绑定Allow _, ok = acl.AllowBinds[id][permid] if ok { ctx.SetParam(eudore.ParamRAM, "acl") return true, true } _, ok = acl.DenyBinds[id][permid] if ok { ctx.SetParam(eudore.ParamRAM, "acl") return false, true } } return false, false }
基于角色的权限访问控制(Role-Based Access Control),判断一个用户的角色是否拥有对应的权限,因为用户绑定角色、角色绑定权限,因此只须要遍历用户的所有角色的所有权限判断便可。
三表对应关系:
RoleBinds => tb_auth_user_role
PermissionBinds => tb_auth_role_permission
Permissions => tb_auth_permission
RamHandle方法先转换权限成id,而后遍历用户id对应的所有角色,再遍历角色对应的所有权限id,检查用户是否拥有这个角色,若是某个角色拥有这个权限id,那么就是用户绑定的拥有这个权限(优化:未使用二分,匹配性能可提高4倍)。
type ( // Rbac 定义rbac对象。 Rbac struct { RoleBinds map[int][]int PermissionBinds map[int][]int Permissions map[string]int } ) // RamHandle 方法实现ram.RamHandler接口。 func (r *Rbac) RamHandle(id int, name string, ctx eudore.Context) (bool, bool) { permid, ok := r.Permissions[name] if !ok { return false, false } // 遍历角色 for _, roles := range r.RoleBinds[id] { // 遍历权限 for _, perm := range r.PermissionBinds[roles] { // 匹配权限 if perm == permid { ctx.SetParam(eudore.ParamRAM, "rbac") return true, true } } } return false, false }
PBAC基于策略的权限控制,一个用户有多个策略,依次判断策略匹配结果,pbac也是eudore-website主要使用的鉴权方式。
type ( // Pbac 定义PBAC鉴权对象。 Pbac struct { PolicyBinds map[int][]int `json:"-" key:"-"` Policys map[int]*Policy `json:"-" key:"-"` } ) // RamHandle 方法实现ram.RamHandler接口,匹配一个请求。 func (p *Pbac) RamHandle(id int, action string, ctx eudore.Context) (bool, bool) { // 得到资源resource resource := getResource(ctx) bs, ok := p.PolicyBinds[id] if ok { // 遍历所有策略 for _, b := range bs { // 检查策略id是否存在 ps, ok := p.Policys[b] if !ok { continue } // 匹配策略描述 for _, s := range ps.Statement { if s.MatchAction(action) && s.MatchResource(resource) && s.MatchCondition(ctx) { ctx.SetParam(eudore.ParamRAM, "pbac") return s.Effect, true } } } } return false, false } // getResource 函数未更新 func getResource(ctx eudore.Context) string { path := ctx.Path() prefix := ctx.GetParam("prefix") if prefix != "" { path = path[len(prefix):] } ctx.SetParam("resource", path) return path }
eudore pbac的策略对象会绑定多个描述对象,每一个描述对象具备鉴权结果(effect)、行为、资源和多项条件。
例如一个策略:
定义了一个描述对象,若是行为是auth和status任意对象的的Get方法就会经过,同时限制了请求时间是2021年前和http请求方法是GET方法(browser限制ua未实现)。
{ "version": "1", "description": "所有文档只读权限", "statement": [ { "effect": true, "action": [ "auth:*:Get*", "status:*:Get*" ], "resource": [ "*" ], "conditions": { "time": { "befor": "2020-12-31" }, "method": [ "GET" ], "browser": [ "Chrome/60+", "Chromium/0-90", "Firefox" ] } } ] }
go定义的Policy对象,其中Conditions做为接口,容许扩展多种条件限制,当前容许or、and、sourceip、time、method这些条件。
type ( // Policy 定义一个策略。 Policy struct { Description string `json:"description"` Version string `json:"version"` Statement []Statement `json:"statement"` } // Statement 定义一条策略内容。 Statement struct { Effect bool Action []string Resource []string Conditions *Conditions `json:"conditions,omitempty"` } // Conditions 定义PBAC使用的条件对象。 Conditions struct { Conditions []Condition } // Condition 定义策略条件 Condition interface { Name() string Match(ctx eudore.Context) bool } ConditionOr struct { Conditions []Condition } ConditionAnd struct { Conditions []Condition } ConditionSourceIp struct { SourceIp []*net.IPNet } ConditionTime struct { Befor time.Time `json:"befor"` After time.Time `json:"after"` } ConditionMethod struct { Methods []string } )
PBAC存在问题:
RegisterCondition函数忘记写了
获取resource对象未更新
browser限制ua未实现
website须要对ram数据与数据库同步,没有直接使用eudore-RAM,而是进行了简单封装。
website-ram从新实现了eudore.RamHttp对象,同时额外添加用户访问本身资源经过,若是路由参数中具备username和userid就是访问属于用户本身的资源。
init系列函数是初始化7张权限表数据到ram对象
import eram "github.com/eudore/eudore/middleware/ram" type Ram struct { Acl *eram.Acl Rbac *eram.Rbac Pbac *eram.Pbac } func NewRam(app *eudore.App) *Ram { db, ok := app.Config.Get("keys.db").(*sql.DB) if !ok { panic("init middleware check config 'keys.db' not find database.") } ram := &Ram{ Acl: eram.NewAcl(), Rbac: eram.NewRbac(), Pbac: eram.NewPbac(), } errs := eudore.NewErrors() // 初始化: 权限、策略 // TODO: 数据修改并发问题 errs.HandleError(ram.InitPermissionInfo(db)) errs.HandleError(ram.InitPolicyInfo(db)) // 初始化: 用户绑定权限、用户绑定教师、角色绑定权限、用户绑定策略 errs.HandleError(ram.InitUserBindPermission(db)) errs.HandleError(ram.InitUserBindRole(db)) errs.HandleError(ram.InitRoleBindPermission(db)) errs.HandleError(ram.InitUserBindPolicy(db)) if errs.GetError() != nil { panic(errs.GetError()) } // 传递ram对象 app.Set("keys.ram", ram) return ram } func (ram *Ram) NewRamFunc() eudore.HandlerFunc { handler := eram.NewRamAny( ram.Acl, ram.Rbac, ram.Pbac, eram.DenyHander, ).RamHandle return func(ctx eudore.Context) { // 若是请求用户资源是用户自己的直接经过,UID、UNAME由用户信息中间件加载,userid、username由路由参数加载。 if ctx.GetParam("userid") == ctx.GetParam("UID") && ctx.GetParam("userid") != "" { return } if ctx.GetParam("username") == ctx.GetParam("UNAME") && ctx.GetParam("username") != "" { return } // 执行ram鉴权逻辑 action := ctx.GetParam("action") if len(action) > 0 && !eram.HandleDefaultRam(eudore.GetInt(ctx.GetParam("UID")), action, ctx, handler) { ctx.WriteHeader(403) ctx.Render(map[string]interface{}{ eudore.ParamRAM: ctx.GetParam("ram"), eudore.ParamAction: action, }) ctx.End() } } }
InitPermissionInfo方法就从数据库查询权限信息,而后赋值给ACL和RBAC对象。
其余init函数相似。
func (ram *Ram) InitPermissionInfo(db *sql.DB) error { rows, err := db.Query("SELECT id,name FROM tb_auth_permission") if err != nil { return err } defer rows.Close() var Permissions = make(map[string]int) var id int var name string for rows.Next() { err = rows.Scan(&id, &name) if err != nil { return err } Permissions[name] = id } // 共享权限信息 ram.Acl.Permissions = Permissions ram.Rbac.Permissions = Permissions return nil }
例如策略控制器赋值策略的管理,实现策略CURD和RAM信息同步,其余User、Permission、Role三个控制器行为类型。
type PolicyController func NewPolicyController(db *sql.DB, ram *middleware.Ram) *PolicyController func (ctl *PolicyController) DeleteIdById() (err error) func (ctl *PolicyController) DeleteNameByName() (err error) func (ctl *PolicyController) GetCount() interface{} func (ctl *PolicyController) GetIdById() (interface{}, error) func (ctl *PolicyController) GetIndex() (interface{}, error) func (ctl *PolicyController) GetList() (interface{}, error) func (ctl *PolicyController) GetNameByName() (interface{}, error) func (ctl *PolicyController) GetSearchByKey() (interface{}, error) func (ctl *PolicyController) GetUserIdById() (interface{}, error) func (ctl *PolicyController) GetUserNameByName() (interface{}, error) func (ctl *PolicyController) PostIdById() (err error) func (ctl *PolicyController) PostNameByName() (err error) func (ctl *PolicyController) PutNew() (err error) func (ctl *PolicyController) Release() error
若是请求方法是POST、PUT、DELETE就是对策略信息有所修改,就调用RAM从新初始化策略数据,实现鉴权信息同步。
type PolicyController struct { controller.ControllerWebsite Ram *middleware.Ram } // Release 方法用于刷新ram策略信息。 func (ctl *PolicyController) Release() error { // 若是修改策略信息成功,则刷新ram策略信息。 if ctl.Response().Status() == 200 && (ctl.Method() == "POST" || ctl.Method() == "PUT" || ctl.Method() == "DELETE") { ctl.Ram.InitPolicyInfo(ctl.DB) } return nil }
type UserController struct { controller.ControllerWebsite Ram *middleware.Ram } // Release 方法刷新用户绑定ram资源信息。 func (ctl *UserController) Release() error { // 若是修改策略信息成功,则刷新ram策略信息。 if ctl.Response().Status() == 200 && ctl.GetParam("bind") != "" { switch ctl.GetParam("bind") { case "permission": ctl.Ram.InitUserBindPermission(ctl.DB) case "role": ctl.Ram.InitUserBindRole(ctl.DB) case "policy": ctl.Ram.InitUserBindPolicy(ctl.DB) } } return nil } // GetRouteParam 方法额外添加bind路由参数信息,用于Release刷新ram。 func (ctl *UserController) GetRouteParam(pkg, name, method string) string { params := ctl.ControllerWebsite.GetRouteParam(pkg, name, method) // 添加bind参数 if strings.HasPrefix(method, "PutBind") || strings.HasPrefix(method, "DeleteBind") { method = strings.TrimPrefix(method, "PutBind") method = strings.TrimPrefix(method, "DeleteBind") switch method[0:4] { case "Perm": method = "permission" case "Poli": method = "policy" case "Role": method = "role" } params += fmt.Sprintf(" bind=%s", method) } return params }
/* 用户策略 */ // GetPolicyNameByName 方法根据策略id得到绑定的用户。 func (ctl *UserController) GetPolicyIdById() (interface{}, error) { return ctl.QueryRows("SELECT * FROM tb_auth_user_policy AS u JOIN tb_auth_policy AS p ON u.policyid = p.id WHERE userid=$1", ctl.GetParam("id")) } // GetPolicyNameByName 方法根据策略name得到绑定的用户。 func (ctl *UserController) GetPolicyNameByName() (interface{}, error) { return ctl.QueryRows("SELECT * FROM tb_auth_user_policy AS u JOIN tb_auth_policy AS p ON u.policyid = p.id WHERE userid=(SELECT id FROM tb_auth_user_info WHERE name=$1)", ctl.GetParam("name")) } // PutBindPolicyById 方法给用户批量绑定多条策略。 // // body: [{"id":4},{"id":6}] func (ctl *UserController) PutBindPolicyById() error { err := ctl.ExecBodyWithJSON(fmt.Sprintf("INSERT INTO tb_auth_user_policy(userid,policyid) VALUES(%d,$1);", ctl.GetParamInt("id")), "id") return err } // PutBindPolicyByUidById 方法给指定用户绑定指定权限。 func (ctl *UserController) PutBindPolicyByUidById() (err error) { _, err = ctl.Exec("INSERT INTO tb_auth_user_policy(userid,policyid) VALUES($1,$2)", ctl.GetParam("uid"), ctl.GetParam("id")) return } // DeleteBindPolicyById 方法给用户批量删除多条策略。 // // body: [{"id":4},{"id":6}] func (ctl *UserController) DeleteBindPolicyById() error { err := ctl.ExecBodyWithJSON(fmt.Sprintf("DELETE FROM tb_auth_user_policy WHERE userid=%s AND policyid=$1", ctl.GetParamInt("id")), "id") return err } // DeleteBindPolicyByUidById 方法给指定用户删除指定权限。 func (ctl *UserController) DeleteBindPolicyByUidById() (err error) { _, err = ctl.Exec("DELETE FROM tb_auth_user_policy WHERE userid=$1 AND policyid=$2", ctl.GetParam("uid"), ctl.GetParam("id")) return }
访问日志记录了请求信息,能够清晰的看到权限相关的行为。
{"time":"2019-10-02 19:27:14","level":"INFO","fields":{"time":"1.066777ms","route":"/auth/","method":"GET","path":"/auth/","proto":"HTTP/1.1","status":200,"remote":"59.63.178.92","host":"47.52.173.119:8082","size":582,"x-request-id":"294459f1b000000"}} {"time":"2019-10-02 19:27:14","level":"INFO","fields":{"ram":"ram-pbac","remote":"59.63.178.92","proto":"HTTP/1.1","action":"auth:Permission:GetCount","resource":"/permission/count","x-request-id":"294459f67c00000","method":"GET","status":200,"route":"/api/v1/auth/permission/count","size":36,"x-parent-id":"294459f1b000000","path":"/api/v1/auth/permission/count","host":"47.52.173.119:8082","time":"1.337828ms"}} {"time":"2019-10-02 19:27:14","level":"INFO","fields":{"remote":"59.63.178.92","proto":"HTTP/1.1","time":"1.695997ms","x-parent-id":"294459f1b000000","method":"GET","action":"auth:Permission:GetIndex","ram":"ram-pbac","x-request-id":"294459f68000000","host":"47.52.173.119:8082","status":200,"route":"/api/v1/auth/permission/index","path":"/api/v1/auth/permission/index","size":225,"resource":"/permission/index"}} {"time":"2019-10-02 19:27:14","level":"INFO","fields":{"method":"GET","path":"/api/v1/auth/user/icon/name/root","action":"auth:User:GetIconNameByName","route":"/api/v1/auth/user/icon/name/:name","x-request-id":"294459f76c00000","remote":"59.63.178.92","proto":"HTTP/1.1","host":"47.52.173.119:8082","status":200,"time":"1.029588ms","size":12164,"ram":"ram-acl"}}
例如第二条格式化结果:
{ "time": "2019-10-02 19:27:14", "level": "INFO", "fields": { "ram": "ram-pbac", "remote": "59.63.178.92", "proto": "HTTP/1.1", "action": "auth:Permission:GetCount", "resource": "/permission/count", "x-request-id": "294459f67c00000", "method": "GET", "status": 200, "route": "/api/v1/auth/permission/count", "size": 36, "x-parent-id": "294459f1b000000", "path": "/api/v1/auth/permission/count", "host": "47.52.173.119:8082", "time": "1.337828ms" } }
其中部分参数含义:
参数 | 值 | 含义 |
---|---|---|
path | /api/v1/auth/permission/count | http请求路径 |
route | /api/v1/auth/permission/count | 路由匹配规则 |
action | auth:Permission:GetCount | 处理行为 |
ram | ram-pbac | ram执行者,若是status非403执行结果为经过 |
resoure | /permission/count | 资源值,仅pbac存在 |