eudore-website 認證鑑權體系實踐

本文大致記錄了eudore-website認證鑑權體系的實現,實現了acl、rbac、pbac鑑權和ak、token、bearer認證,完整細節請查看源碼

在線demo,用戶密碼均爲guest。

認證設計

eudore-website使用ak、token、bearer認證三種綜合認證,原理通過web請求中間件使用請求信息獲得用戶信息,保存到請求上下文中然後供後續使用。

bearer認證

bearer認證原理是利用jwt非對稱簽名防止數據篡改。

最初就初始化jwt解析對象,然後處理請求Authorization Header,解析出jwt的數據,從中提取到userid和username信息,然後設置請求上下文的參數中。

源碼

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:

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信息。

curl直接-H指定header即可。

token認證

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認證的原理例如非對稱加密實現,用戶有效校驗

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 RAM 鑑權設計

eudore-website使用acl、rbac、pbac三種複合鑑權設計,按順序依次處理,某個對象可以處理就返回結果。

RAM

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

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
}

RBAC

基於角色的權限訪問控制(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基於策略的權限控制,一個用戶有多個策略,依次判斷策略匹配結果,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 封裝

website需要對ram數據與數據庫同步,沒有直接使用eudore-RAM,而是進行了簡單封裝。

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()
        }
    }
}

Init

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
}

Controller

例如策略控制器賦值策略的管理,實現策略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
}

用戶綁定策略CURD

/*
用戶策略
*/

// 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存在
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章