如何在Java项目中做RBAC权限控制

Posted 广东互动学堂

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在Java项目中做RBAC权限控制相关的知识,希望对你有一定的参考价值。

RBAC

基于角色的权限访问控制(Role-Based Access Control)。

RBAC支持三个著名的安全原则:最小权限原则,责任分离原则和数据抽象原则。

RBAC认为权限授权实际上是Who、What、How的问题。在RBAC模型中,who、what、how构成了访问权限三元组,也就是“Who对What(Which)进行How的操作”。

Who:权限的拥用者或主体(如Principal、User、Group、Role、Actor等等)。

What:权限针对的对象或资源(Resource、Class)。

How:具体的权限(Privilege,正向授权与负向授权)。

Shiro

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。

其基本功能点如下图所示:

如何在Java项目中做RBAC权限控制

Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;

Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Web Support:Web 支持,可以非常容易的集成到 Web 环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;

Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

Shiro 不会去维护用户、维护权限;这些需要我们自己去设计 / 提供;然后通过相应的接口注入给 Shiro 。

如何在Java项目中做RBAC权限控制

从应用程序角度观察如何使用Shiro完成工作

Subject:主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

Realm:域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

如何在Java项目中做RBAC权限控制

从 Shiro 内部来看Shiro 的架构

Subject:主体,可以看到主体可以是任何可以与应用交互的 “用户”;

SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

Realm:可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;

SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);

SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密 / 解密的。

使用Shiro做身份验证

如何在Java项目中做RBAC权限控制

身份认证流程

  1. 首先调用 Subject.login(token) 进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils.setSecurityManager() 设置;

  2. SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;

  3. Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;

  4. Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;

  5. Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回 / 抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。

本文使用SqlLite+Druid做数据库设计。

首先建立SqlLite数据库shiro.db,并建立三张表。(推荐使用Navicat Premium客户端操作SqlLite)

create table users (

id bigint auto_increment,

username varchar(100),

password varchar(100),

password_salt varchar(100),

constraint pk_users primary key(id)

);

create unique index idx_users_username on users(username);

create table user_roles(

id bigint auto_increment,

username varchar(100),

role_name varchar(100),

constraint pk_user_roles primary key(id)

);

create unique index idx_user_roles on user_roles(username, role_name);

create table roles_permissions(

id bigint auto_increment,

role_name varchar(100),

permission varchar(100),

constraint pk_roles_permissions primary key(id)

);

create unique index idx_roles_permissions on roles_permissions(role_name, permission);

编写shiro配置文件

jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm

dataSource=com.alibaba.druid.pool.DruidDataSource

dataSource.driverClassName=org.sqlite.JDBC

dataSource.url=jdbc:sqlite::resource:shiro.db

jdbcRealm.dataSource=$dataSource

jdbcRealm.permissionsLookupEnabled = true

securityManager.realms=$jdbcRealm

数据库中增加记录users("administrator", "123","null"),编写测试代码

//classpath:指定配置文件在src目录下

Factory<org.apache.shiro.mgt.SecurityManager>factory=new IniSecurityManagerFactory("classpath:shiro.ini");

org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();

SecurityUtils.setSecurityManager(securityManager);

Subject subject = SecurityUtils.getSubject();

UsernamePasswordToken token = new UsernamePasswordToken("administrator", "123");

try {

subject.login(token);

} catch (AuthenticationException e) {

e.printStackTrace();

}

if(subject.isAuthenticated()){

System.out.println("身份验证成功!");

}

subject.logout();

ThreadContext.unbindSubject();

使用Shiro做授权管理

授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。

主体

主体,即访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。

资源

在应用中用户可以访问的任何东西,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。

权限

安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如: 访问用户列表页面;查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)。

如上可以看出,权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro 不会去做这件事情,而是由实现人员提供。

Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)。

角色

角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。

隐式角色:

即直接通过角色来验证用户有没有操作权限,如在应用中 CTO、技术总监、开发工程师可以使用打印机,假设某天不允许开发工程师使用打印机,此时需要从应用中删除相应代码;再如在应用中 CTO、技术总监可以查看用户、查看权限;突然有一天不允许技术总监查看用户、查看权限了,需要在相关代码中把技术总监角色从判断逻辑中删除掉;即粒度是以角色为单位进行访问控制的,粒度较粗;如果进行修改可能造成多处代码修改。

显示角色:

在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合;这样假设哪个角色不能访问某个资源,只需要从角色代表的权限集合中移除即可;无须修改多处代码;即粒度是以资源/实例为单位的;粒度较细。

授权流程

  1. 首先调用 Subject.isPermitted*/hasRole*接口,其会委托给 SecurityManager,而 SecurityManager 接着会委托给 Authorizer;

  2. Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),其首先会通过 PermissionResolver 把字符串转换成相应的 Permission 实例;

  3. 在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传入的角色/权限;

  4. Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个 Realm,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted*/hasRole* 会返回 true,否则返回 false 表示授权失败。

基于角色的访问控制(隐式角色)

数据库中增加记录user_roles("administrator", "role1"),编写测试代码

if(subject.hasRole("role1")) {

System.out.println("角色权限验证成功!");

}

基于资源的访问控制(显示角色)

数据库中增加记录roles_permissions("role1", "*:view"),编写测试代码

if(subject.isPermitted("test1:view")) {

System.out.println("资源权限验证成功!");

}

字符串通配符权限

规则:“资源标识符:操作:对象实例 ID” 即对哪个资源的哪个实例可以进行什么操作。其默认支持通配符权限字符串,“:”表示资源/操作/实例的分割;“,”表示操作的分割;“*”表示任意资源/操作/实例。

  • 单个资源单个权限

system:user:update

  • 单个资源多个权限

system:user:update,system:user:delete

  • 单个资源全部权限

system:user:*

  • 所有资源某个权限

*:view

用户拥有所有资源的“view”所有权限。

  • 单个实例单个权限

user:view:1

对资源 user 的 1 实例拥有 view 权限。

  • 单个实例所有权限

user:*:1

  • 所有实例单个权限

auth:*

  • 所有实例所有权限

user:*:*

  • Shiro 对权限字符串缺失部分的处理

如“user:view”等价于“user:view:*”;而“organization”等价于“organization:*”或者“organization:*:*”。可以这么理解,这种方式实现了前缀匹配。

另外如“user:*”可以匹配如“user:delete”、“user:delete”可以匹配如“user:delete:1”、“user:*:1”可以匹配如“user:view:1”、“user”可以匹配“user:view”或“user:view:1”等。即*可以匹配所有,不加*可以进行前缀匹配;但是如“*:view”不能匹配“system:user:view”,需要使用“*:*:view”,即后缀匹配必须指定前缀(多个冒号就需要多个*来匹配)。

以上是关于如何在Java项目中做RBAC权限控制的主要内容,如果未能解决你的问题,请参考以下文章

基于RBAC的权限控制浅析(结合Spring Security)

基于RBAC的权限控制浅析(结合Spring Security)

简单的RBAC用户角色权限控制

权限控制RBAC模型

RBAC模型整合数据权限

ThinkPHP中RBAC权限控制求助