rbac

package module
v1.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 12, 2022 License: Apache-2.0 Imports: 10 Imported by: 0

README

OSCS Status

介绍

一个Go语言基于Casbin认证、JWT授权的 RBAC 基于角色的访问控制完整实现。

特性

  • 支持RefreshToken平滑刷新
  • Token黑名单(待实现)

使用示例

签发授权

import (
    "github.com/lgcgo/rbac"
)

var (
    settings = rbac.Settings{
        TokenSignKey:   []byte("gVoiG1fbXf65osbjfi33MZre"),
        TokenIssuer:    "lgcgo.com",
        PolicyFilePath: "examples/policy.csv",
    }
)

func main(){
    // 实例化
    if r, err :=rbac.New(settings); err != nil {
        panic(err)
    }
    // 签发授权
    if out, err = r.Authorization("uid001", "subAdmin"); err != nil {
        panic(err)
    }
    // 格式化打印
    outJson, _ := json.MarshalIndent(out, "", "   ")
    fmt.Println(string(outJson))
}

打印结果

{
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3QiOiJncmFudCIsImlzciI6InN1YkFkbWluIiwiaXNzIjoibGdjZ28uY29tIiwic3ViIjoidWlkMDAxIiwiZXhwIjoxNjU3NDM3NTk5LCJuYmYiOjE2NTczNTExOTksImlhdCI6MTY1NzM1MTE5OX0.CBzE0bn9mKYqYIDNVgujnHTFUM9uTM54mwRwpzjcDFA",
    "tokenType": "Bearer",
    "expiresIn": 86400,
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3QiOiJyZW5ldyIsImlzciI6InN1YkFkbWluIiwiaXNzIjoibGdjZ28uY29tIiwic3ViIjoidWlkMDAxIiwiZXhwIjoxNjU3NjEwMzk5LCJuYmYiOjE2NTczNTExOTksImlhdCI6MTY1NzM1MTE5OX0.Pozlza3jeWk6Kd2C6ZebiZcD3nZoMRfQdJV9alEzfj0"
}

返回的数据结构,参考了Oauth2授权模式,实际上已经满足了密码模式条件,只是登录认证需要而且应该由应用系统本身实现。

刷新授权

// 实例化
if r, err = rbac.New(settings); err != nil {
    panic(err)
}
refreshToken := "×××.×××.×××"
r.RefreshAuthorization(refreshToken)

平滑的token刷新机制,能有效提升用户体验,这也是为为什么参考Oauth2授权模式的原因;如果你对系统安全有极致的要求,可以在当前步骤中添加使用 refreshToken 的条件。

验证Token

// 实例化
if r, err = rbac.New(settings); err != nil {
    panic(err)
}
accessToken := "×××.×××.×××"
claims, err := r.VerifyToken(accessToken)

该接口通常在系统的中间件中使用,claims中 应该 包含用户唯一ID sub 以及用户角色名称 isr ,可以在该步骤中初始化用户信息(从缓存/数据库中读取用户数据)。

验证请求

// 实例化
if r, err = rbac.New(settings); err != nil {
    panic(err)
}

path := "/user"
method := "GET"
role := claims["isr"].(string)
// 验证请求
r.VerifyRequest(path, method, role)

该接口通常在验证Token后使用,底层调用Casbin进行权限认证,它只对签发角色 isr 负责,即相同的角色对同一个资源有相同的权限。

配置项

项目 必填 说明 示例
TokenSignKey Jwt加密字符串,使用随机的字符串即可 []byte("abc123")
TokenIssuer Jwt的签发者,如lgcgo.com "lgcgo.com"
PolicyFilePath 授权政策文件路径;当使用默认的policy adapter时为必填 "config/policy.csv"
AccessTokenExpireTime accessToken过期时间,默认24小时 24 * time.Hour
RefreshTokenExpireTime refreshToken过期时间,默认是accessToken过期时间的3倍数 24 * time.Hour

Policy的储存

默认使用Casbin内置的 file adapter ,在初始化设置Setting中指定PolicyFilePath 即可。

更新policy.csv文件

import (
    "github.com/lgcgo/rbac"
)

var (
    uriPolicys = []rbac.UriPolicy{
        {
            Role:   "u1",
            Path:   "/user",
            Method: "GET",
        },
        {
            Role:   "u1",
            Path:   "/user",
            Method: "PUT",
        },
        {
            Role:   "u1",
            Path:   "/user",
            Method: "DELETE",
        },
        {
            Role:   "u1",
            Path:   "/users",
            Method: "GET",
        },
    }
    rolePolicys = []rbac.RolePolicy{
        {
            ParentRole: "superAdmin",
            Role:       "u1",
        },
    }
    sets = rbac.Settings{
        TokenSignKey:   []byte("gVoiG1fbXf65osbjfi33MZre"),
        TokenIssuer:    "lgcgo.com",
        PolicyFilePath: "examples/policy.csv",
    }
    r   *rbac.Rbac
    err error
)

func main() {
    if r, err = rbac.New(sets); err != nil {
        panic(err)
    }

    if err = r.SavePolicyCsv(uriPolicys, rolePolicys); err != nil {
        panic(err)
    }
}

SavePolicyCsv 仅支持使用默认的policy适配器。请注意每次调用时,都是覆盖重写整个csv文件,也就要求传入完整的 []RuiPolicy[]RolePolicy


可以在这里找到更多的适配器[Casbin适配器](https://casbin.org/docs/zh-CN/adapters)。

使用fs.Fs adapter示例

import (
    casbinfsadapter "github.com/naucon/casbin-fs-adapter"
    "github.com/lgcgo/rbac"
)

var settings = Settings{
    TokenSignKey:   []byte("gVoiG1fbXf65osbjfi33MZre"),
    TokenIssuer:    "lgcgo.com",
    PolicyFilePath: "examples/policy.csv",
}

func main(){
    // 实例化第三方adapter
    fsys := os.DirFS("examples/config/")
    adapter := casbinfsadapter.NewAdapter(fsys, "policy.csv")
    
    // 实例化
    r, err :=rbac.New(settings)
    // 设置适配器
    r.Casbin.SetAdapter(adapter)
    // ...
}

一般情况下不建议使用orm或sql的内置适配器,原因一是效率不如内置的适配器,二是非关系型数据放sql里面怪别扭的。

认证中间件

GoFrame示例

var settings = rbac.Setting{
    // ...
}
func Authentication(r *ghttp.Request) {
    if obj, err :=rbac.New(settings); err != nil {
        panic(err)
    }
    // Header传值 Authorization: Bearer <token>
    if r.Header.Get("Authorization") == "" {
        panic("headers authorization not exists")
    }
    strArr = strings.SplitN(r.Header.Get("Authorization"), " ", 2)
    
    // 支持Bearer方案
    if strArr[0] != "Bearer" {
        panic("authorization scheme not support")
    }
    
    // 获取Token票据
    tokenTicket := strArr[1]
    if claims, err := obj.VerifyToken(tokenTicket); err != nil {
    	panic("token invalid")
    }
    
    // 从声明中获取用户角色
    role := claims["isr"].(string)
    
    // 验证角色的请求权限
    if err = obj.VerifyRequest(path, method, role); err != nil {
        panic("deny the request")
    }

    // 从声明中获取用户唯一ID
    // uid = claims["sub"].(string)
    // 用uid做些什么

    r.Middleware.Next()
}

版权声明

Under the Apache2.0

特别致谢

扩展阅读

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// Rbac
	ErrorTokenSignKeyInvalid           = "token signkey invalid"
	ErrorRefreshTokenExpireTimeInvalid = "token refresh_token expiretime invalid"
	ErrorTokenIssueTypeInvalid         = "token issue type invalid"
	ErrorPolicyFilePathInvalid         = "policy file path invaild"

	// Jwt
	ErrorJwtSigningMethodInvaild = "token signing method invalid"
	ErrorJwtParseInvaild         = "token parse invaid"
	ErrorJwtClaimsInvaild        = "token claim invaid"

	// Casbin
	ErrorCasbinEnforceInvaild = "casbin enforce invaild"
)

Functions

This section is empty.

Types

type Casbin

type Casbin struct {
	PolicyFilePath string
	Domain         string
	Enforcer       *casbin.Enforcer
	Adapter        persist.Adapter
}

func NewCasbin

func NewCasbin(policyFilePath string) *Casbin

func (*Casbin) Demo added in v1.1.1

func (c *Casbin) Demo()

func (*Casbin) Init

func (c *Casbin) Init() error

func (*Casbin) SaveAllPolicyCsv added in v1.1.1

func (c *Casbin) SaveAllPolicyCsv(ups []UriPolicy, rps []RolePolicy) error

更新Policy.csv文件

Example
var (
	uriPolicys = []UriPolicy{
		{
			Domain: "manager",
			Role:   "admin1",
			Path:   "/user",
			Method: "POST",
		},
		{
			Domain: "manager",
			Role:   "admin1",
			Path:   "/user",
			Method: "GET",
		},
		{
			Domain: "manager",
			Role:   "admin1",
			Path:   "/user",
			Method: "PUT",
		},
		{
			Domain: "manager",
			Role:   "admin1",
			Path:   "/user",
			Method: "DELETE",
		},
		{
			Domain: "manager",
			Role:   "admin1",
			Path:   "/users",
			Method: "GET",
		},
		{
			Domain: "www",
			Role:   "admin1",
			Path:   "/article",
			Method: "GET",
		},
		{
			Domain: "www",
			Role:   "userGroup1",
			Path:   "/article",
			Method: "GET",
		},
	}
	rolePolicys = []RolePolicy{
		{
			Role:   "admin1",
			Domain: "manager",
		},
		{
			ParentRole: "admin1",
			Role:       "admin2",
			Domain:     "manager",
		},
		{
			ParentRole: "admin1",
			Role:       "userGroup1",
			Domain:     "www",
		},
	}
	sets = Settings{
		TokenSignKey:   []byte("gVoiG1fbXf65osbjfi33MZre"),
		TokenIssuer:    "lgcgo.com",
		PolicyFilePath: "examples/policy.csv",
	}
	r   *Rbac
	err error
)

if r, err = New(sets); err != nil {
	panic(err)
}

if err = r.Casbin.SaveAllPolicyCsv(uriPolicys, rolePolicys); err != nil {
	panic(err)
}
Output:

func (*Casbin) SetAdapter added in v1.1.1

func (c *Casbin) SetAdapter(a persist.Adapter)

设置适配器

func (*Casbin) SetDomain added in v1.1.1

func (c *Casbin) SetDomain(domain string)

设置域

func (*Casbin) VerifyUriPolicy

func (c *Casbin) VerifyUriPolicy(p *UriPolicy) error

检测Policy

type Claims

type Claims struct {
	IssueType string `json:"ist"` // 签发类型, grant=授予,renew=刷新
	IssueRole string `json:"isr"` // 签发角色, 签发的角色名称(允许多角色)
	pkg.RegisteredClaims
}

声明格式 RegisteredClaims 包含了JWT给出的7个官方字段 - iss (issuer):发布者,通常填域名即可 - sub (subject):主题, - iat (Issued At):生成签名的时间 - exp (expiration time):签名过期时间 - aud (audience):观众,相当于接受者 - nbf (Not Before):生效时间 - jti (JWT ID):编号

type IPolicy

type IPolicy interface {
	FormatLine() string // 格式化行字符串
}

授权政策接口

type IssueClaims

type IssueClaims struct {
	Type     string   // 签发类型,这里 grant=授权, renew=刷新
	Role     string   // 签发角色,相同角色具备相同的权限
	Subject  string   // 签发主题,一般用使用用户的唯一标识
	Audience []string // 签发授众,例如指定的浏览器、应用标识等
}

签发字段

type Jwt

type Jwt struct {
	// contains filtered or unexported fields
}

func NewJwt

func NewJwt(signKey []byte, issuer string) *Jwt

func (*Jwt) IssueToken

func (j *Jwt) IssueToken(iClaims *IssueClaims, expireTime time.Duration) (string, error)

签发Token

func (*Jwt) ParseToken

func (j *Jwt) ParseToken(ticket string) (map[string]interface{}, error)

解析Token

type Rbac

type Rbac struct {
	Jwt    *Jwt
	Casbin *Casbin
	// contains filtered or unexported fields
}

func New

func New(sets Settings) (*Rbac, error)

func (*Rbac) Authorization

func (r *Rbac) Authorization(subject, role string) (*Token, error)

签发授权(oauth2密码模式)

Example
var (
	sets = Settings{
		TokenSignKey:   []byte("gVoiG1fbXf65osbjfi33MZre"),
		TokenIssuer:    "lgcgo.com",
		PolicyFilePath: "examples/policy.csv",
	}
	r   *Rbac
	out *Token
	err error
)

if r, err = New(sets); err != nil {
	panic(err)
}
if out, err = r.Authorization("uid001", "subAdmin"); err != nil {
	panic(err)
}
outJson, err := json.MarshalIndent(out, "", "	")

fmt.Println(string(outJson))
Output:

{
	"AccessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3QiOiJncmFudCIsImlzciI6InN1YkFkbWluIiwiaXNzIjoibGdjZ28uY29tIiwic3ViIjoidWlkMDAxIiwiZXhwIjoxNjU3NDM1MzM0LCJuYmYiOjE2NTczNDg5MzQsImlhdCI6MTY1NzM0ODkzNH0.KfU0WgfT33v_5-HqqCryPCRC512dV2CTQ_uXCh5dJMM",
	"TokenType": "Bearer",
	"ExpiresIn": 86400,
	"RefreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3QiOiJyZW5ldyIsImlzciI6InN1YkFkbWluIiwiaXNzIjoibGdjZ28uY29tIiwic3ViIjoidWlkMDAxIiwiZXhwIjoxNjU3NjA4MTM0LCJuYmYiOjE2NTczNDg5MzQsImlhdCI6MTY1NzM0ODkzNH0.cd2-AplZwnu4CbhAZvSwRdWYESWurHTZlbXMSDta4wA"
}

func (*Rbac) RefreshAuthorization

func (r *Rbac) RefreshAuthorization(ticket string) (*Token, error)

刷新授权

Example
var (
	sets = Settings{
		TokenSignKey:   []byte("gVoiG1fbXf65osbjfi33MZre"),
		TokenIssuer:    "lgcgo.com",
		PolicyFilePath: "examples/policy.csv",
	}
	r     *Rbac
	token *Token
	out   *Token
	err   error
)

if r, err = New(sets); err != nil {
	panic(err)
}
if token, err = r.Authorization("uid001", "subAdmin"); err != nil {
	panic(err)
}
if out, err = r.RefreshAuthorization(token.RefreshToken); err != nil {
	panic(err)
}
outJson, err := json.MarshalIndent(out, "", "	")

fmt.Println(string(outJson))
Output:

{
	"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3QiOiJncmFudCIsImlzciI6InN1YkFkbWluIiwiaXNzIjoibGdjZ28uY29tIiwic3ViIjoidWlkMDAxIiwiZXhwIjoxNjU3NTk2NzM2LCJuYmYiOjE2NTc1MTAzMzYsImlhdCI6MTY1NzUxMDMzNn0.jtcnM1Gvcs3XQFl7xdDU7-qnnL90RyhfljAqKE_DmsA",
	"tokenType": "Bearer",
	"expiresIn": 86400,
	"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3QiOiJyZW5ldyIsImlzciI6InN1YkFkbWluIiwiaXNzIjoibGdjZ28uY29tIiwic3ViIjoidWlkMDAxIiwiZXhwIjoxNjU3NzY5NTM2LCJuYmYiOjE2NTc1MTAzMzYsImlhdCI6MTY1NzUxMDMzNn0.xFpO99WrCxmrrvi6rlDy2DdAFdT6-DkdMWaA-QXIQsU"
}

func (*Rbac) VerifyRequest

func (r *Rbac) VerifyRequest(path, method, role string) error

验证角色请求

Example
var (
	sets = Settings{
		TokenSignKey:   []byte("gVoiG1fbXf65osbjfi33MZre"),
		TokenIssuer:    "lgcgo.com",
		PolicyFilePath: "examples/policy.csv",
	}
	r *Rbac
	// out   map[string]interface{}
	err error
)
if r, err = New(sets); err != nil {
	panic(err)
}
r.Casbin.SetDomain("www")
err = r.VerifyRequest("/article", "GET", "role::admin1")
if err != nil {
	fmt.Println(err.Error())
}
fmt.Println()
Output:

func (*Rbac) VerifyToken

func (r *Rbac) VerifyToken(ticket string) (map[string]interface{}, error)

验证Token

Example
var (
	sets = Settings{
		TokenSignKey:   []byte("gVoiG1fbXf65osbjfi33MZre"),
		TokenIssuer:    "lgcgo.com",
		PolicyFilePath: "examples/policy.csv",
	}
	r     *Rbac
	token *Token
	out   map[string]interface{}
	err   error
)

if r, err = New(sets); err != nil {
	panic(err)
}
if token, err = r.Authorization("uid001", "subAdmin"); err != nil {
	panic(err)
}
if out, err = r.VerifyToken(token.AccessToken); err != nil {
	panic(err)
}
outJson, err := json.MarshalIndent(out, "", "	")

fmt.Println(string(outJson))
Output:

{
	"exp": 1657596869,
	"iat": 1657510469,
	"isr": "subAdmin",
	"iss": "lgcgo.com",
	"ist": "grant",
	"nbf": 1657510469,
	"sub": "uid001"
}

type RolePolicy

type RolePolicy struct {
	ParentRole string // 父级角色名称
	Role       string // 角色名称
	Domain     string // 域
}

角色关系政策

func (*RolePolicy) FormatLine

func (r *RolePolicy) FormatLine() string

角色关系政策,实现格式化行字符串

type Settings

type Settings struct {
	DefaultDomain          string
	PolicyFilePath         string        // 可选项,授权政策文件路径;当使用默认的adapter时为必填
	TokenSignKey           []byte        // 必填项,Jwt加密字符串,使用随机的字符串即可
	TokenIssuer            string        // 选填项,Jwt的签发者,如lgcgo.com
	AccessTokenExpireTime  time.Duration // 可选项,access_token过期时间,默认24小时
	RefreshTokenExpireTime time.Duration // 可选项,refresh_token过期时间,默认是access_token过期时间的3倍数
}

设置项

type Token

type Token struct {
	AccessToken  string `json:"accessToken"`
	TokenType    string `json:"tokenType"`
	ExpiresIn    uint   `json:"expiresIn"`
	RefreshToken string `json:"refreshToken"`
}

授权返回结构

type UriPolicy

type UriPolicy struct {
	Role   string // 用户角色
	Domain string // 域
	Path   string // 资源路径
	Method string // 请求方法
}

资源访问政策

func (*UriPolicy) FormatLine

func (u *UriPolicy) FormatLine() string

资源访问政策,实现格式化行字符串

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL