文章目录
- 1 jcasbin
- 1.1 前言
- 1.2 工作原理
- 1.2.1 PERM模型
- 1.2.2 Model语法
- 1.2.2.1 Request定义
- 1.2.2.2 Policy定义
- 1.2.2.3 Policy effect定义
- 1.2.2.4 角色定义
- 1.2.2.5 匹配器
- 1.2.2.6 完整model.conf
- 1.2.3 policy.csv
- 1.3 准备
- 1.3.1 mavan依赖
- 1.3.2 配置文件
- 1.2.3 读取权限信息进行初始化
- 1.4 使用
- 1.4.1 权限控制
- 1.4.2 添加删除权限
1 jcasbin
1.1 前言
作为一名后台开发人员,权限这个名词应该算是特别熟悉的了。就算是java
里的类也有 public
、private
等权限
之分。之前项目里一直使用shiro
作为权限管理的框架。说实话,shiro
的确挺强大的,但是它也有很多不好的地方。shiro
默认的登录地址还是login.jsp
,前后端分离模式使用shiro
还要重写好多类;手机端存储用户信息、保持登录状态等等,对shiro
来说也是一个难题。
在分布式项目里,比如电商项目,其实不太需要明确的权限划分,说白了,认为没必要做太麻烦的权限管理,一切从简。何况shiro
对于springCloud
等各种分布式框架来说,简直就是灾难
。每个子系统里都要写点shiro
的东西,慢慢的,越来越恶心。zuul网关
就在这里大显身手了,控制用户的登录,鉴定用户的权限等等。zuul网关控制用户登录,鉴权以后再详说。
然后最近发现了另一个权限框架jcasbin
github地址:https://github.com/casbin/jcasbin
点击此处了解spring security权限控制使用操作
1.2 工作原理
1.2.1 PERM模型
在 Casbin
中, 访问控制模型被抽象为基于 PERM (Policy, Effect, Request, Matcher)
的一个文件,描述了资源与用户之间的关系。
PERM :
Policy
:策略,定义权限的规则, p = {sub, obj, act, eft},策略一般存储到数据库,因为会有很多。Effect
:影响,它决定我们是否可以放行。Request
:访问请求, r = {sub, obj, act}。它实际上定义了我们应该提供访问控制匹配功能的参数名称和顺序。
sub, obj, act, eft含义如下:sub
:subject
,访问实体;obj
:object
,访问的资源;act
:action
,访问方法;eft
:effect
,策略结果,一般为空,默认指定allow
,还可以定义为deny
Matcher
:匹配规则,判断 Request 是否满足 Policy。
1.2.2 Model语法
Model
是Casbin
的具体访问模型,其主要以文件的形式出现,该文件常常以.conf
最为后缀。
Model CONF
至少应包含四个部分: [request_definition], [policy_definition], [policy_effect], [matchers]
如果 model
使用 RBAC
, 还需要添加[role_definition]
部分。
Model CONF
文件可以包含注释。注释以 #
开头, #
会注释该行剩余部分。
1.2.2.1 Request定义
[request_definition]
r = sub, obj, act
[request_definition]
部分用于request
的定义,它明确了 e.Enforce(…)
函数中参数的含义。sub, obj, act
表示经典三元组: 访问实体 (Subject),访问资源 (Object) 和访问方法 (Action)
。
1.2.2.2 Policy定义
[policy_definition]
p = sub, obj, act
[policy_definition]
部分是对policy
的定义,policy
部分的每一行称之为一个策略规则, 每条策略规则通常以形如p, p2的policy type开头。
1.2.2.3 Policy effect定义
[policy_effect]
e = some(where (p.eft == allow))
[policy_definition]
部分是对policy
的定义。上面的策略效果表示如果有任何匹配的策略规则 允许,最终效果是允许 (aka allow-override
)
p.eft
是策略的效果,它可以 允许 或 否定。 它是可选的,默认值是 允许。 因为我们没有在上面指定它,所以它使用默认值。
在这个表达式中,some
是一个关键字,表示策略中是否存在符合后续条件的策略项。where
是一个关键字,用于指定条件。eft 属性
表示策略的效果,通常用于表示允许访问。
1.2.2.4 角色定义
[role_definition]
g = _, _
这个部分定义了角色的结构
g = _, _
表示角色使用通配符 _
表示,表示不关心角色的具体值
1.2.2.5 匹配器
[matchers]
是策略匹配程序的定义。匹配程序是表达式。它定义了如何根据请求评估策略规则。
[matchers]
m = g(r.sub,p.sub) && r.obj == p.obj && r.act == p.act
上述匹配器是最简单的,这意味着请求中的主题、对象和行动应该与政策规则中的匹配。
g(r.sub, p.sub)
:表示通过匹配器函数g
来匹配主体属性r.sub
和策略项的主体属性 p.sub。这里使用的是通配符 “_”,表示不关心具体的角色值。r.obj == p.obj
:表示匹配对象属性,要求请求的对象属性r.obj
和策略项的对象属性p.obj
相等。r.act == p.act
:表示匹配动作属性,要求请求的动作属性r.act
和策略项的动作属性p.act
相等。
注意:g()
函数和==
运算符在匹配器中有不同的作用。
g(r.sub, p.sub)
:g()
函数是一个特殊的函数,用于在匹配器中进行角色匹配。在这里,g()
函数将匹配请求中的主体属性(r.sub
)和策略项中的主体属性(p.sub
)。这里的g()
函数可以根据需求进行定制,例如使用角色继承关系等。通常情况下,使用通配符_
来表示不关心角色的具体值。r.obj == p.obj和r.act == p.act
:==
是相等比较运算符,用于比较请求中的对象属性(r.obj)和策略项中的对象属性(p.obj),以及请求中的动作属性(r.act)和策略项中的动作属性(p.act)。它们要求两边的操作数在值上完全相等才会返回true。g()
函数用于角色匹配,而==
运算符用于属性值的比较。这两者在匹配器中的作用是不同的
1.2.2.6 完整model.conf
# Request定义
[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 = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
1.2.3 policy.csv
policy.csv文件就相当于一张权限表。
p, zhangsan, data1, read
1.3 准备
1.3.1 mavan依赖
<dependency>
<groupId>org.casbin</groupId>
<artifactId>jcasbin</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.casbin</groupId>
<artifactId>jdbc-adapter</artifactId>
<version>2.0.0</version>
</dependency>
1.3.2 配置文件
jcasbin
把用户的角色、权限信息(访问路径)放置在配置文件里,然后通过输入流读取配置文件。主要有两个配置文件:model.conf
和 policy.csv。
其实也可以读取数据库的角色权限配置。所以我们可以把关于数据库的信息提取出来,可以进行动态设置。
@Configuration
@ConfigurationProperties(prefix = "org.jcasbin")
public class EnforcerConfigProperties {
private String url;
private String driverClassName;
private String username;
private String password;
private String modelPath;
}
这样我们就可以在application.properties
里进行相关配置了。model.conf
是固定的文件。policy.csv的内容是可以从数据库读取的。
org.jcasbin.url=jdbc:mysql://localhost:3306/casbin?useSSL=false
org.jcasbin.driver-class-name=com.mysql.cj.jdbc.Driver
org.jcasbin.username=root
org.jcasbin.password=root
# 绝对路径
org.jcasbin.model-path=D:/Work/Project/IDEAWorkSpace/idea2023/swagger-api-demo/conf/authz_model.conf
# 绝对路径
org.jcasbin.policy-path=D:/Work/Project/IDEAWorkSpace/idea2023/swagger-api-demo/conf/policy.csv
1.2.3 读取权限信息进行初始化
我们要对 Enforcer
这个类初始化,加载配置文件里的信息。所以我们写一个类实现InitializingBean
,在容器加载的时候就初始化这个类,方便后续的使用。
@Component
public class EnforcerFactory implements InitializingBean {
private static Enforcer enforcer;
@Autowired
private EnforcerConfigProperties enforcerConfigProperties;
private static EnforcerConfigProperties config;
@Override
public void afterPropertiesSet() throws Exception {
config = enforcerConfigProperties;
//从数据库读取策略
//JDBCAdapter jdbcAdapter = new JDBCAdapter(config.getDriverClassName(),config.getUrl(),config.getUsername(),config.getPassword());
//enforcer = new Enforcer(config.getModelPath(), jdbcAdapter);
//从配置文件读取策略
enforcer = new Enforcer(config.getModelPath(), config.getPolicyPath());
enforcer.loadPolicy();//Load the policy from DB.
}
/**
* 添加权限
* @param policy
* @return
*/
public static boolean addPolicy(Policy policy){
boolean addPolicy = enforcer.addPolicy(policy.getSub(),policy.getObj(),policy.getAct());
enforcer.savePolicy();
return addPolicy;
}
/**
* 删除权限
* @param policy
* @return
*/
public static boolean removePolicy(Policy policy){
boolean removePolicy = enforcer.removePolicy(policy.getSub(),policy.getObj(),policy.getAct());
enforcer.savePolicy();
return removePolicy;
}
public static Enforcer getEnforcer(){
return enforcer;
}
}
在这个类里,我们注入写好的配置类,然后转为静态的,在afterPropertiesSet
方法里实例化Enforcer并加载policy(策略,角色权限/url对应关系)。
同时又写了两个方法,用来添加和删除policy,Policy
是自定的一个类,对官方使用的集合/数组进行了封装。
public class Policy {
/**想要访问资源的用户 或者角色*/
private String sub;
/**将要访问的资源,可以使用 * 作为通配符,例如/user/* */
private String obj;
/**用户对资源执行的操作。HTTP方法,GET、POST、PUT、DELETE等,可以使用 * 作为通配符*/
private String act;
}
1.4 使用
1.4.1 权限控制
jcasbin
的权限控制非常简单,自定义一个过滤器,if判断就可以搞定,没错,就这么简单。
@WebFilter(urlPatterns = "/*" , filterName = "JCasbinAuthzFilter")
@Order(Ordered.HIGHEST_PRECEDENCE)//执行顺序,最高级别最先执行,int从小到大
public class JCasbinAuthzFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(JCasbinAuthzFilter.class);
private static Enforcer enforcer;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String user = request.getParameter("username");
String path = request.getRequestURI();
String method = request.getMethod();
enforcer = EnforcerFactory.getEnforcer();
if (path.contains("anon")) {
chain.doFilter(request, response);
}else if (enforcer.enforce(user, path, method)) {
chain.doFilter(request, response);
} else {
log.info("无权访问");
Map<String, Object> result = new HashMap<String, Object>();
result.put("code", "1001");
result.put("msg", "用户权限不足");
result.put("data",null);
response.setCharacterEncoding("UTF-8");
response.setContentType(application/json);
response.getWriter().write(JSONObject.toJSONString(result,SerializerFeature.WriteMapNullValue));
}
}
@Override
public void destroy() {
}
}
主要是用 enforcer.enforce(user, path, method)
这个方法对用户、访问资源、方式进行匹配。这里的逻辑可以根据自己的业务来实现。在这个过滤器之前还可以添加一个判断用户是否登录的过滤器。
1.4.2 添加删除权限
对于权限的操作,直接调用上面写好的 EnforcerFactory
里对应的方法即可。并且,可以达到同步的效果。就是不用重启服务器或者其他任何操作,添加或删除用户权限后,用户对应的访问就会收到影响。
@RestController
public class PerController {
@PutMapping("/anon/role/per")
public String addPer(){
EnforcerFactory.addPolicy(new Policy("alice", "/user/list", "*"));
return "OK";
}
@DeleteMapping("/anon/role/per")
public String deletePer(){
EnforcerFactory.removePolicy(new Policy("alice", "/user/list", "*"));
return "OK";
}
}