【Shiro】3. Shiro授权流程

1,457 阅读5分钟

授权

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

授权的关键对象

授权可简单理解为Who对What / Which进行How操作。

  • Who:主体(Subject),主体需要访问系统中的资源。
  • What / Which:资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型资源实例,比如商品信息为资源类型,类型为Type01的商品为资源实例,编号为001的商品信息也属于资源实例。
  • How:权限(Permission),规定了主体对资源的操作许可。权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。

授权流程

授权一定是基于认证通过后才执行的操作。

20210927094653.png

授权方式(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")) {
            // 具体授权操作
        }

        // 验证Subject是否同时具有admin角色和user角色
        if (subject.hasAllRoles(Arrays.asList("admin", "user"))) {
            // 具体授权操作
        }

        // 验证Subject是否具有以下角色中的一种或者多种
        boolean[] hasRoles = subject.hasRoles(Arrays.asList("admin", "user"));
        for (boolean hasRole : hasRoles) {
            if (hasRole) {
                // 具体授权操作
            }
        }
    }
}

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:*")) {
            // 具体授权操作
        }

        // 判断Subject同时具有创建所有user实例的权限和修改001号user实例的权限
        if (subject.isPermittedAll("user:create:*", "user:update:001")) {
            // 具体授权操作
        }

        // 判断Subject是否具有以下权限中的一种或多种
        boolean[] permissions = subject.isPermitted("user:create:*", "user:update:001");
        for (boolean permission : permissions) {
            if (permission) {
                // 具体授权操作
            }
        }
    }
}