参阅《跟我学shiro》—— 张开涛
授权,也叫控制访问,即在应用中你可以访问哪些资源。授权决定的是你可以做什么。在授权中需要了解几个关键对象:主体,资源,权限,角色。
Shiro提供了三种方式的授权:
(1)编程式:
if(currentUser.hasRole("admin")){
}else{
}
(2)注解式(建议将shiro注解放入controller,因为如果service层使用了spring的事物注解,那么shiro注解将无效):
@RequiresPermissions("account:create")
public void openAccount( Account acct ) {
}
(3)jsp标签:
<shiro:lacksPermission name="users:manage">
No user management for you!
</shiro:lacksPermission>
对于权限,shiro还支持字符串通配符权限控制更加细粒度的权限,规则资源标识符:操作:对象实例ID
,即对哪个资源的哪个实例可以进行什么样的操作,其默认支持通配符权限字符串,"*"表示任意资源|操作|实例。
使用方式如下:
(1)shrio.ini中的配置
[users]
# 用户名 = 密码,角色1,角色2...
user = 123456,admin
[roles]
#角色 = 权限1,权限2
#表示admin角色拥有对所有用户的修改,所有admin的读
admin = user:edit:*,admin:read
(2)进行测试
public static void main(String[] args) {
Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
Object instance = factory.getInstance();
SecurityUtils.setSecurityManager((org.apache.shiro.mgt.SecurityManager) instance);
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("user", "123456");
if (!currentUser.isAuthenticated()) {
currentUser.login(token);
}
if(currentUser.isPermitted("user:edit:1")){
log.info("当前登录拥有: 修改id为1的用户 权限");
}
if(currentUser.isPermitted(new WildcardPermission("admin:read"))){
log.info("当前登录拥有: 读取所有管理原信息 权限");
}
currentUser.logout();
}
Shiro对字符串缺失的处理支持前缀匹配,比如 "user:edit"可以匹配 "user:edit:* ","user:"可以匹配 "user:edit:","user:read:*"等等。
通配符匹配相比于字符串相等匹配更复杂,因此需要花费长时间的比对,但是一般系统的权限不会太多,可以配合缓存提供其性能,如果这样性能还达不到要求我们可以实现位操作算法实现性能更好的权限匹配。另外实例级别的权限验证如果数据量太大也不建议使用,可能造成查询权限及匹配变慢。可以考虑比如在 sql 查询时加上权限字符串之 类的方式在查询时就完成了权限匹配。
Shiro的授权流程如下:
(1)调用 isPermitterd|hasRole 时,会委托给 SecurityManager 进行鉴权,而SecurityManager 会接着委托给 Authorizer进行授权。
(2)Authorizer才是真正的授权者,当调用 isPermitterd|hasRole 时,首先会通过 PermissionResolver 把字符串转化成相应的 Permission 实例。
(3)在进行授权之前,会首先调用相应的 Realm 获取 Subject的所拥有的所有权限用于匹配传入的权限。
(4)Authorizer 会判断传入的权限和用户的权限是否匹配,如果有多个 Realm,会委托给 ModularRealmAuthorizer 进行循环判断(如果有一个匹配则返回true),如果匹配如 isPermitted/hasRole会返回 true,否 则返回 false 表示授权失败。
如果是使用 Realm 进行授权的话,应该继承 AuthorizingRealm,其流程是:
(1)如果是 hasRole()的话,直接获取 AuthorizationInfo.getRoles()与传入的角色比较即可。
(2)首先如果调用如 isPermitted(“user:view”),首先通过 PermissionResolver 将权限字符串转换成相应的 Permission 实例
(3)获取AuthorizationInfo.getPermissions()获取所有的权限字符串并转化成对应的 Permission 实例。然后获取用户的角色,并且通过 RolePermissionResolver 解析角色对应的权限集合(默认没有实现,可以自己提供)。
(4)接着调用 Permission. implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则 false。
使用方式如下:
1. 配置 shiro.ini
[users]
# 用户名 = 密码,角色1,角色2...
user = 123456,admin
[main]
authRealm = com.demo.main.MyAuthRealm
securityManager.realms = $authRealm
2. 进行测试
public class MyAuthRealm extends AuthorizingRealm {
private static final Logger log = LoggerFactory.getLogger(MyAuthRealm.class);
//授权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String useranme = (String) principalCollection.getPrimaryPrincipal();
log.info("去数据库查询用户:{},对应的角色信息或者权限信息", useranme);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRoles(Arrays.asList("admin"));
authorizationInfo.addStringPermission("user:read:1");
return authorizationInfo;
}
//认证方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
Object principal = authenticationToken.getPrincipal();
log.info("用户{}请求授权,去数据源查询对应的用户信息", principal);
return new SimpleAuthenticationInfo(principal, authenticationToken.getCredentials(), getName());
}
}