记得去年还是前年的时候,就尝试使用过casbin,但是由于那会儿对于访问控制的理解还不到位,导致对于casbin的设计不能有个很好的理解,直到前一阵子自己实现了一个基于Role的访问控制器(基于golang的角色路由访问策略匹配器的实现)后,才逐渐对casbin的设计有了一个入门的认识。
访问控制从ACL、RBAC到ABAC中间的发展就不细说了。就谈谈访问控制中实际可能会遇到的情况。
IP访问限制
ip_model.conf
[request_definition]
r = sub
[policy_definition]
p = sub, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = ipMatch(r.sub, p.sub)
ip_policy.csv
p, 0.0.0.0/0, allow
p, 127.0.0.1/16, allow
accesscontrol/ip.go
package accesscontrol
import (
"github.com/casbin/casbin/v2"
"log"
)
type CIDR struct {
modelFile string
policyFile string
enforcer *casbin.Enforcer
}
func NewCIDR(model, policy string) (*CIDR, error) {
route := &CIDR{}
enforcer, err := casbin.NewEnforcer(model, policy)
if err != nil {
return route, err
}
route.modelFile = model
route.policyFile = policy
route.enforcer = enforcer
return route, nil
}
func (cidr CIDR) Match(ip string) bool {
//log.Println(ip)
enforce, rule, err := cidr.enforcer.EnforceEx(ip)
if err != nil {
log.Println(enforce, rule, err.Error())
return false
}
if enforce {
act := rule[1]
switch act {
case "allow":
return true
case "deny":
return false
}
}
return false
}
简单授权访问
简单授权即不区分用户角色,只要用户成功登录系统,则认为用户已经授权。先说说简单授权访问存在的几种可能性:
1、访问请求如果匹配,则不需要授权,若未匹配,则需要授权。适合只有少量接口允许未授权用户访问的情况。
2、访问请求如果匹配,则需要授权,若未匹配,则不需要授权。适合只有少量接口允许授权用户访问的情况。
针对上面两种不同的情况,只需要反转casbin enforcer的匹配结果即可。
以下是针对简单授权情况,casbin的配置和策略文件的内容。根据官方的设计思路和提供的样例,在mathcer中添加了*作为全部匹配的定义。
basic_alc_model.conf
[request_definition]
r = obj, act
[policy_definition]
p = obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (p.obj == "*" || keyMatch(r.obj, p.obj)) && (p.act == "*" || regexMatch(r.act, p.act))
basic_acl_policy.csv
p, /api/login, POST
p, /api/account/*, GET
角色授权访问
基于角色访问授权,首先需要明确,一个角色对应一个资源和行为,不存在多个角色逻辑(比如:既是admin又是user)对应一个资源和行为的情况。早期看了大量RBAC的原理和设计图说明,讲了一堆用户、角色和资源(权限)的关系,说的最多的就是多个用户对多个角色,多个角色对多个资源(权限),就陷入到了多对多的陷阱里面了,哪怕RBAC存在0~3的四级模型,也和多角色逻辑对应一个资源和行为的情况无关。完全没有人指导和引路去理解这些细节,确实太难了。
角色授权访问,即授权的用户一定具有相应的角色。casbin在配置上比较麻烦的就是,如果多个用户对同一资源都有访问权限,就需要写多条指向相同资源的策略。当然,如果基于casbin的存储接口实现了自定义的策略定义接口,倒不用这么麻烦就是。
因为未授权的用户默认视为游客,如果游客能访问到的资源,那么授权的用户也能进行访问。因此”基于角色授权访问“与”基于简单授权访问“不同的在于,在匹配器执行前,无论用户登录与否,就需要先获取到用户的角色信息,如果访问请求被匹配到,则认为资源是必然可访问的。
当然,在策略定义上还存在以下的情况:
1、全部的角色(除游客外)、资源和行为都应该条分缕析地在策略内定义,因此除游客外的每个带有用户身份的请求,都应该被视为能够被匹配到,若不能被匹配则视为游客,游客有未匹配到的资源的访问权。这种情况存在的问题就是,若存在关键的资源和行为没有被定义或因为疏忽而忘记定义,那么游客也能去访问这个资源,游客的权限可能会被因此放大。
2、游客能访问的,那么授权的用户一定也能访问。因此,策略的重点在于,游客能访问的资源和行为,指定角色能访问的资源和行为,若不匹配,则可以定义未匹配的需要授权去访问,当然也可以不需要授权,这里就可以保留一定的空间,如果是未定义的策略需要用户授权才能访问,那么授权用户的权限可能会被放大,如果是未定义的策略游客能访问,那么游客的权限就被放大,但是一般都采用未匹配的需要授权访问。这种方案也需要在mathcer中添加*作为全部匹配的定义以确保没有通过授权获取角色的游客也能正常访问。
rbac_model.conf
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (p.sub == "*" || g(r.sub, p.sub)) && (p.obj == "*" || keyMatch(r.obj, p.obj)) && (p.act == "*" || regexMatch(r.act, p.act))
rgba_policy.csv
p, *, *, *
p, *, /api/login, POST
p, user, /api/account/*, (GET)|(POST)|(PATCH)|(DELETE)
middleware.go
func AuthorizationMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
rawToken, exists := c.Get("token")
if !exists {
c.AbortWithStatusJSON(http.StatusUnauthorized, app.Error{
Code: app.AuthorizationError,
Err: "unauthorized, middleware error",
})
return
}
token := rawToken.(Token)
// 防止token泄露,判断token ip是否与访问ip一致。
if token.IP != c.ClientIP() {
c.AbortWithStatusJSON(http.StatusUnauthorized, app.Error{
Code: app.AuthorizationError,
Err: "the access address does not match the authorized address",
})
return
}
// 判断哪些角色可以访问哪些路径,如果匹配到则表示角色可以访问这些路径,否则继续默认操作。
method := c.Request.Method
path := c.Request.URL.Path
ac, err := accesscontrol.NewRoleRoute("config/rbac_model.conf", "config/rbac_policy.csv")
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, app.Error{
Code: app.RuntimeError,
Err: err.Error(),
})
return
}
if ac.Match(token.Roles, path, method) {
c.Next()
return
}
// 默认操作。如果token没有被授权,那么不允许用户继续访问。否则允许继续访问。
if !token.Authorized {
c.AbortWithStatusJSON(http.StatusUnauthorized, app.Error{
Code: app.AuthorizationError,
Err: "unauthorized",
})
return
}
c.Next()
}
}
总结
访问控制的本质也就是策略匹配。
casbin可以理解为一个匹配器,根据实际的业务需求,无论是使用这个匹配器做正向匹配,还是反向匹配都可以。
casbin的model具有极高的灵活度,可以在request定义和policy定义中定义实际业务场景所需要匹配的字段,以及扩展即缩减字段(当然也是简单字串,不能使用复杂字串,也需要遵循casbin的命名规则)。例如IP访问控制器中一样,request只定义了sub,而policy中还定义了sub和act,最后由enforcerEx()匹配后判断act是允许还是拒绝。
DISCUSSION
评论
还没有公开评论,欢迎留下想法。