授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
授权的关键对象
授权可简单理解为Who对What / Which进行How操作。
- Who:主体(Subject),主体需要访问系统中的资源。
- What / Which:资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为Type01的商品为资源实例,编号为001的商品信息也属于资源实例。
- How:权限(Permission),规定了主体对资源的操作许可。权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。
授权流程
授权一定是基于认证通过后才执行的操作。
授权方式(RBAC)
RBAC主要包含两种授权模式:
-
基于角色的访问控制(Role-Based Access Control):以角色为中心进行访问控制。
伪代码可表示为:
if (subject.hasRole("admin")) { // 执行操作 }
-
基于资源的访问控制(Resource-Based Access Control):以资源为中心进行访问控制。
伪代码可表示为:
if (subject.isPermitted("user:find:*")) { // 执行操作 }
isPermission
传入参数是一个权限字符串,其格式为 "资源类型 : 操作 : 资源实例" 。" user : find : * " 表示的是Subject对所有User实例具有查询的权限(操作类型)。
" user : * : 001 " 表示的是Subject对ID为001的User实例具有所有的权限(操作实例)。
Shiro权限字符串
1. 组成规则
在Shiro中使用权限字符串必须按照Shiro指定的规则。
权限字符串组合规则为:"资源类型标识符 : 操作 : 资源实例标识符"
-
资源类型标识符: 一般会按模块,对系统划分资源。比如user模块,product模块,order模块等,对应的资源类型标识符就是:user,product,order。
-
操作: 一般为增删改查(create,delete,update,find),还有 * 标识统配。
-
资源实例标识符: 如果Subject控制的是资源类型,那么资源实例标识符就是 "*" ;如果Subject控制的是资源实例,那么就需要在资源实例标识符就是该资源的唯一标识(ID等)。
2. 示例
" * : * : * " 表示Subject对所有类型的所有实例有所有操作权限,相当于超级管理员。
" user : create : * " 表示Subject对user类型的所有实例有创建的权限,可以简写为:" user : create "。
" user : update : 001 " 表示Subject对ID为001的user实例有修改的权限。
" user : * : 001 " 表示Subject对ID为001的user实例有所有权限。
Shiro授权方式
Shiro中对于后台授权提供了三种实现方式:
- 编程式
- 注解式
- 标签式(已淘汰,只能在JSP,Thymeleaf等模板引擎中使用)
1. 编程式
Subject subject = SecurityUtils.getSubject();
if (subject.hasRole("admin")) {
// 有权限
} else {
// 无权限
}
2. 注解式
@RequiresRoles("admin")
public void find() {
// 有权限
}
Shiro授权源码
SimpleAccountRealm
类就是Shiro默认完成认证和授权的底层操作,其中 doGetAuthenticationInfo
方法处理认证 ,doGetAuthorizationInfo
方法处理授权。
public class SimpleAccountRealm extends AuthorizingRealm {
...
// 认证处理
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
SimpleAccount account = getUser(upToken.getUsername());
if (account != null) {
if (account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}
if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}
}
return account;
}
// 授权处理
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = getUsername(principals);
USERS_LOCK.readLock().lock();
try {
return this.users.get(username);
} finally {
USERS_LOCK.readLock().unlock();
}
}
}
Shiro授权Demo
授权必须在认证的基础上,因此本Demo基于前文中MD5加密认证的Demo实现授权。
1. 基于角色访问控制
在定义Realm中的授权操作中给Subject添加角色
public class UserMD5Realm extends AuthorizingRealm {
// 授权操作
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取身份信息(用户名)
String username = (String) principals.getPrimaryPrincipal();
// 根据身份信息从数据库中该用户的角色信息装载进入SimpleAuthorizationInfo
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
// 返回权限信息对象
return simpleAuthorizationInfo;
}
// 认证操作
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
...
return null;
}
}
授权流程
public static void main(String[] args) {
// 认证操作
...
// 如果认证成功,可以进行授权操作
if (subject.isAuthenticated()) {
// 验证Subject是否具有admin角色
if (subject.hasRole("admin")) {
// 具体授权操作
System.out.println("具有 admin 角色");
}
// 验证Subject是否同时具有admin角色和user角色
if (subject.hasAllRoles(Arrays.asList("admin", "user"))) {
// 具体授权操作
System.out.println("同时具有 admin 和 user 角色");
}
// 验证Subject是否具有以下角色中的一种或者多种
List<String> roles = Arrays.asList("admin", "user");
boolean[] hasRoles = subject.hasRoles(roles);
for (int i = 0; i < roles.size(); i++) {
if (hasRoles[i]) {
// 具体授权操作
System.out.println("具有 " + roles.get(i) + " 角色");
}
}
}
}
2. 基于权限访问控制
在定义Realm中的授权操作中给Subject添加授权信息
public class UserMD5Realm extends AuthorizingRealm {
// 授权操作
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取身份信息(用户名)
String username = (String) principals.getPrimaryPrincipal();
// 根据身份信息从数据库中该用户的权限信息装载进入SimpleAuthorizationInfo
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermission("user:create:001");
simpleAuthorizationInfo.addStringPermission("user:update:*");
// 返回权限信息对象
return simpleAuthorizationInfo;
}
// 认证操作
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
...
return null;
}
}
授权流程
public static void main(String[] args) {
// 认证操作
...
// 如果认证成功,可以进行授权操作
if (subject.isAuthenticated()) {
// 判断Subject是否有创建所有user实例的权限
if (subject.isPermitted("user:create:*")) {
// 具体授权操作
System.out.println("具有 user:create:* 权限");
}
// 判断Subject同时具有创建所有user实例的权限和修改001号user实例的权限
if (subject.isPermittedAll("user:create:*", "user:update:001")) {
// 具体授权操作
System.out.println("同时具有 user:create:* 和 user:update:001 权限");
}
// 判断Subject是否具有以下权限中的一种或多种
String[] permissions = {"user:create:*", "user:update:001"};
boolean[] hasPermissions = subject.isPermitted(permissions);
for (int i = 0; i < permissions.length; i++) {
if (hasPermissions[i]) {
// 具体授权操作
System.out.println("具有 " + permissions[i] + " 权限");
}
}
}
}
原文地址:https://juejin.cn/post/7012824575293521933