在组织内部,通常使用 LDAP 目录服务,为组织提供统一的认证服务。
鉴权比较认证更加的复杂,系统往往需要灵活可配置的权限。
可以通过自定义 Realm 实现以下功能:
下面是 org.apache.shiro.realm.Realm
接口继承体系:
继承 org.apache.shiro.realm.AuthenticatingRealm
抽象类,即表示该 Realm 支持认证。
继承 org.apache.shiro.realm.AuthorizingRealm
抽象类,即表示该 Realm 支持认证和鉴权。
认证需要实现 doGetAuthenticationInfo(AuthenticationToken token):AuthenticationInfo
抽象方法,鉴权需要实现 doGetAuthorizationInfo(PrincipalCollection principals):AuthorizationInfo
抽象方法。
Shiro 提供了基于 LDAP 的 Realm 实现 org.apache.shiro.realm.ldap.DefaultLdapRealm
和基于 JDBC 的 Realm 实现 org.apache.shiro.realm.jdbc.JdbcRealm
。
自定义 Realm 可以通过继承 DefaultLdapRealm 类继承基于 LDAP 认证,通过覆盖基于 LDAP 授权实现基于 JDBC 的鉴权。
RBAC(Role-based Access Control,基于角色的访问控制) 是一种限制已认证用户系统访问的方式。在 RBAC 中,实体(Subject)、角色(Role)和权限(Permissions)之间的 ER 图如下所示:
Subject 与 Role 是多对多的关系,Role 和 Permission 是多对多的关系。
RBAC 的特点如下:
以 BI 系统为例:
小张是一名数据分析师,在 BI 系统中分配了数据分析师角色,数据分析师角色拥有编辑和查看报表的权限,小张可以编辑和查看报表。
小王是一名业务,在 BI 系统中分配了业务角色,业务角色拥有查看报表的权限,小王只可以查看报表不可以编辑报表。
以 Spring Boot 集成 org.apache.shiro:shiro-spring-boot-web-starter 为例,演示如何自定义 Realm。
创建数据库表:
create table if not exists users ( id bigint auto_increment primary key, name varchar(64) not null unique key ); create table if not exists roles ( id bigint auto_increment primary key, name varchar(64) not null unique key ); create table if not exists permissions ( id bigint auto_increment primary key, name varchar(64) not null unique key ); create table if not exists user_roles ( id bigint auto_increment primary key, user_id bigint not null, role_id bigint not null ); create table if not exists role_permissions ( id bigint auto_increment primary key, role_id bigint not null, permission_id bigint not null );
创建 RBACRealm 类:
public class RBACRealm extends DefaultLdapRealm { // ① private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { if (principals == null) { throw new AuthorizationException("PrincipalCollection method argument cannot be null."); } AuthorizationInfo info = null; String username = (String) getAvailablePrincipal(principals); if (username != null && username.trim().length() > 0) { try (Connection conn = this.dataSource.getConnection()) { Set<String> roles = queryRolesByUsername(conn, username); // ② Set<String> permissions = queryPermissionsByRoles(conn, roles); // ③ SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roles); simpleAuthorizationInfo.setStringPermissions(permissions); info = simpleAuthorizationInfo; } catch (IOException ie) { final String message = "There was a IO error while loading sql from classpath"; throw new AuthorizationException(message, ie); } catch (SQLException se) { final String message = "There was a SQL error while authorizing user [" + username + "]"; throw new AuthorizationException(message, se); } } return info; } private Set<String> queryRolesByUser(Connection conn, String username) throws SQLException, IOException { // 省略查询 } private Set<String> queryPermissionsByUser(Connection conn, Set<String> roleNames) throws IOException, SQLException { // 省略查询 } }
① 继承 org.apache.shiro.realm.ldap.DefaultLdapRealm
类,覆盖 doGetAuthorizationInfo(PrincipalCollection principals):AuthorizationInfo
方法;
② 通过 JDBC 查询用户角色;
③ 通过 JDBC 查询用户权限。