本文大體記錄了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存在 |