在组织内部,通常使用 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 查询用户权限。