角色
角色代表了操作集合,可以理解為權(quán)限的集合,一般情況下我們會(huì)賦予用戶角色而不是權(quán)限,即這樣用戶可以擁有一組權(quán)限,賦予權(quán)限時(shí)比較方便。典型的如:項(xiàng)目經(jīng)理、技術(shù)總監(jiān)、CTO、開發(fā)工程師等都是角色,不同的角色擁有一組不同的權(quán)限。
隱式角色:
即直接通過角色來驗(yàn)證用戶有沒有操作權(quán)限,如在應(yīng)用中CTO、技術(shù)總監(jiān)、開發(fā)工程師可以使用打印機(jī),假設(shè)某天不允許開發(fā)工程師使用打印機(jī),此時(shí)需要從應(yīng)用中刪除相應(yīng)代碼;再如在應(yīng)用中CTO、技術(shù)總監(jiān)可以查看用戶、查看權(quán)限;突然有一天不允許技術(shù)總監(jiān)查看用戶、查看權(quán)限了,需要在相關(guān)代碼中把技術(shù)總監(jiān)角色從判斷邏輯中刪除掉;即粒度是以角色為單位進(jìn)行訪問控制的,粒度較粗;如果進(jìn)行修改可能造成多處代碼修改。
顯示角色:
在程序中通過權(quán)限控制誰能訪問某個(gè)資源,角色聚合一組權(quán)限集合;這樣假設(shè)哪個(gè)角色不能訪問某個(gè)資源,只需要從角色代表的權(quán)限集合中移除即可;無須修改多處代碼;即粒度是以資源/實(shí)例為單位的;粒度較細(xì)。
授權(quán)
一、基于角色的訪問控制(隱式角色)
- 在ini配置文件配置用戶擁有的角色
shiro-role.ini
[users]
admin=123,role1,role2
root=123,role1
規(guī)則即:“用戶名=密碼,角色1,角色2”,如果需要在應(yīng)用中判斷用戶是否有相應(yīng)角色,就需要在相應(yīng)的Realm中返回角色信息,也就是說Shiro不負(fù)責(zé)維護(hù)用戶-角色信息,需要應(yīng)用提供,Shiro只是提供相應(yīng)的接口方便驗(yàn)證
- 測試
- 我們可以將身份驗(yàn)證的代碼抽出來,方便后續(xù)驗(yàn)證
BaseTest.java
public abstract class BaseTest {
@After
public void tearDown() throws Exception{
ThreadContext.unbindSubject(); //退出時(shí)請(qǐng)解除綁定Subject到線程,否則對(duì)下次測試造成影響
}
protected void login(String configFile, String username, String password) {
//1.獲取SecurityManager工廠,此處使用ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFile);
//2.得到SecurityManager實(shí)例,并綁定給SecurityUtils
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3.得到Subject及創(chuàng)建用戶名/密碼身份驗(yàn)證token
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
}
public Subject subject() {
return SecurityUtils.getSubject();
}
}
- 測試代碼
@Test
public void testHasRole() {
login("classpath:shiro-role.ini", "admin", "123");
//判斷擁有角色:role1
Assert.assertTrue(subject().hasRole("role1"));
//判斷擁有角色:role1,role2
Assert.assertTrue(subject().hasAllRoles(Arrays.asList("role1", "role2")));
//判斷擁有角色:role1 and role2 and !role3`
boolean[] result = subject().hasRoles(Arrays.asList("role1", "role2", "role3"));
System.out.println(result[0]);
System.out.println(result[1]);
System.out.println(result[2]);
Assert.assertEquals(true, result[0]);
Assert.assertEquals(true, result[1]);
Assert.assertEquals(false, result[2]);
}

Shiro提供了hasRole/hasRole用于判斷用戶是否擁有某個(gè)角色/某些權(quán)限;但是沒有提供如hashAnyRole用于判斷是否有某些權(quán)限中的某一個(gè)。
Shiro提供的checkRole/checkRoles和hasRole/hasAllRoles不同的地方是它在判斷為假的情況下會(huì)拋出UnauthorizedException異常。
@Test(expected = UnauthorizedException.class)
public void testCheckRole() {
login("classpath:shiro-role.ini", "admin", "123");
//斷言擁有角色:role1
subject().checkRole("role1");
//斷言擁有角色:role1 and role3 失敗拋出異常
subject().checkRoles("role1", "role3");
}
二、基于資源的訪問控制(顯示角色)
1、在ini配置文件配置用戶擁有的角色及角色-權(quán)限關(guān)系
shiro-permission.ini
[users]
admin=123,role1,role2
root=123,role1
[roles]
role1=user:create,user:update
role2=user:create,user:delete
規(guī)則:“用戶名=密碼,角色1,角色2” “角色=權(quán)限1,權(quán)限2”
2、測試代碼
@Test
public void testIsPermitted() {
login("classpath:shiro-permission.ini", "admin", "123");
//判斷擁有權(quán)限:user:create
Assert.assertTrue(subject().isPermitted("user:create"));
//判斷擁有權(quán)限:user:update and user:delete
Assert.assertTrue(subject().isPermittedAll("user:update", "user:delete"));
//判斷沒有權(quán)限:user:view
Assert.assertFalse(subject().isPermitted("user:view"));
}
Shiro提供了isPermitted和isPermittedAll用于判斷用戶是否擁有某個(gè)權(quán)限或所有權(quán)限,也沒有提供如isPermittedAny用于判斷擁有某一個(gè)權(quán)限的接口
授權(quán)流程

流程如下:
1、首先調(diào)用Subject.isPermitted/hasRole接口,其會(huì)委托給SecurityManager,而SecurityManager接著會(huì)委托給Authorizer;
2、Authorizer是真正的授權(quán)者,如果我們調(diào)用如isPermitted(“user:view”),其首先會(huì)通過PermissionResolver把字符串轉(zhuǎn)換成相應(yīng)的Permission實(shí)例;
3、在進(jìn)行授權(quán)之前,其會(huì)調(diào)用相應(yīng)的Realm獲取Subject相應(yīng)的角色/權(quán)限用于匹配傳入的角色/權(quán)限;
4、Authorizer會(huì)判斷Realm的角色/權(quán)限是否和傳入的匹配,如果有多個(gè)Realm,會(huì)委托給ModularRealmAuthorizer進(jìn)行循環(huán)判斷,如果匹配如isPermitted/hasRole會(huì)返回true,否則返回false表示授權(quán)失敗。
ModularRealmAuthorizer進(jìn)行多Realm匹配流程:
1、首先檢查相應(yīng)的Realm是否實(shí)現(xiàn)了實(shí)現(xiàn)了Authorizer;
2、如果實(shí)現(xiàn)了Authorizer,那么接著調(diào)用其相應(yīng)的isPermitted/hasRole接口進(jìn)行匹配;
3、如果有一個(gè)Realm匹配那么將返回true,否則返回false。
如果Realm進(jìn)行授權(quán)的話,應(yīng)該繼承AuthorizingRealm,其流程是:
1.1、如果調(diào)用hasRole*,則直接獲取AuthorizationInfo.getRoles()與傳入的角色比較即可;
1.2、首先如果調(diào)用如isPermitted(“user:view”),首先通過PermissionResolver將權(quán)限字符串轉(zhuǎn)換成相應(yīng)的Permission實(shí)例,默認(rèn)使用WildcardPermissionResolver,即轉(zhuǎn)換為通配符的WildcardPermission;
2、通過AuthorizationInfo.getObjectPermissions()得到Permission實(shí)例集合;通過AuthorizationInfo. getStringPermissions()得到字符串集合并通過PermissionResolver解析為Permission實(shí)例;然后獲取用戶的角色,并通過RolePermissionResolver解析角色對(duì)應(yīng)的權(quán)限集合(默認(rèn)沒有實(shí)現(xiàn),可以自己提供);
3、接著調(diào)用Permission. implies(Permission p)逐個(gè)與傳入的權(quán)限比較,如果有匹配的則返回true,否則false。
Authorizer、PermissionResolver及RolePermissionResolver
Authorizer的職責(zé)是進(jìn)行授權(quán)(訪問控制),是Shiro API中授權(quán)核心的入口點(diǎn),其提供了相應(yīng)的角色/權(quán)限判斷接口,具體請(qǐng)參考其Javadoc。SecurityManager繼承了Authorizer接口,且提供了ModularRealmAuthorizer用于多Realm時(shí)的授權(quán)匹配。PermissionResolver用于解析權(quán)限字符串到Permission實(shí)例,而RolePermissionResolver用于根據(jù)角色解析相應(yīng)的權(quán)限集合。