授權(quán),也叫訪問控制,即在應(yīng)用中控制誰訪問哪些資源(如訪問頁面/編輯數(shù)據(jù)等)
- 主體:訪問應(yīng)用的用戶,在 Shiro 中使用 Subject 代表該用戶。用戶只有授權(quán)后才允許訪問相應(yīng)的資源。
- 資源:在應(yīng)用中用戶可以訪問的URL,比如訪問JSP頁面,查看/編輯某些數(shù)據(jù)等等。
- 權(quán)限:安全策略中的原子授權(quán)單位,通過權(quán)限我們可以表示在應(yīng)用中用戶有沒有操作某個(gè)資源的權(quán)利;Shiro 支持粗粒度權(quán)限(如用戶模塊的所有權(quán)限)和細(xì)粒度權(quán)限(操作某個(gè)用戶的權(quán)限,即實(shí)例級(jí)別的)。
- 角色:權(quán)限的集合,一般情況下會(huì)賦予用戶角色而不是權(quán)限。
授權(quán)方式
Shiro支持三種方式的授權(quán):
- 編程式: Subject#hasRole()
- 注解式:通過在執(zhí)行的Java方法上放置相應(yīng)的直接完成,沒有權(quán)限將拋出相應(yīng)的異常,如:@RequiresRoles()
- JSP/GSP標(biāo)簽:<shiro:hasRole name=""></shiro:hasRole>
默認(rèn)攔截器
Shiro 內(nèi)置了很多默認(rèn)的攔截器,比如身份驗(yàn)證、授權(quán)等相關(guān)的,默認(rèn)攔截器可以參考o(jì)rg.apache.shiro.web.filter.mgt.DefaultFilter.class中的枚舉攔截器:
- 身份驗(yàn)證相關(guān)的:
| 默認(rèn)攔截器名 | 攔截器類 | 說明(括號(hào)內(nèi)為默認(rèn)值) |
|---|---|---|
| authc | FormAuthenticationFilter | 基于表單的攔截;如 "/**=authc",如果沒有登錄會(huì)跳轉(zhuǎn)到響應(yīng)的登錄頁面登錄; 主要屬性: usernameParam:表單提交的用戶名參數(shù)名(username); password:表單提交的密碼參數(shù)名(rememberMe); loginUrl:登錄頁面地址(/login.jsp); successUrl:登錄成功后的默認(rèn)重定向地址; failureKeyAttribute:登錄失敗后操作信息存儲(chǔ)key(shiroLoginFailure); |
| authcBasic | BasicHttpAuthenticationFilter | Basic HTTP身份驗(yàn)證攔截器,主要屬性: applicationName: 彈出登錄提示框的信息(application) |
| logout | LogoutFilter | 退出攔截器,主要屬性: redirectUrl:退出成功后重定向的地址(/); |
| user | UserFilter | 用戶攔截器,用戶已經(jīng)身份驗(yàn)證/記住我登錄的都可; |
| anon | AnonymousFilter | 匿名攔截器,即不通過登錄即可訪問;一般用于靜態(tài)資源過濾 |
- 授權(quán)相關(guān)的:
| 默認(rèn)攔截器名 | 攔截器類 | 說明(括號(hào)內(nèi)為默認(rèn)值) |
|---|---|---|
| roles | RolesAuthorizationFilter | 角色授權(quán)攔截器,驗(yàn)證用戶是否擁有該角色; 主要屬性: loginUrl:登錄頁面地址(/login.jsp); unauthorizedUrl:未授權(quán)后重定向的地址; |
| perms | PermissionsAuthorizationFilter | 權(quán)限授權(quán)攔截器,驗(yàn)證用戶是否擁有所有權(quán)限;屬性和roles一樣 |
| port | PortFilter | 端口攔截器,主要屬性: port:可以通過的端口(80) 實(shí)例:"/pay/1=port[80]",如果用戶訪問該請(qǐng)求時(shí)非80, 將自動(dòng)將請(qǐng)求端口修改為80并重定向到該80端口,其他路徑/參數(shù)一致 |
| rest | HttpMethodPermissionFilter | rest風(fēng)格攔截器,自動(dòng)根據(jù)請(qǐng)求方法構(gòu)造權(quán)限字符串 (GET=read,POST=cratea,PUT=update,DELETE=delete,HEAD=read, TRACE=read,OPTIONS=read,MKCOL=create) |
| ssl | SslFilter | SSL攔截器,只有請(qǐng)求協(xié)議是https才能通過;否則自動(dòng)跳轉(zhuǎn)到https端口;其他和port攔截器一樣 |
- 其他:
| 默認(rèn)攔截器名 | 攔截器類 | 說明(括號(hào)內(nèi)為默認(rèn)值) |
|---|---|---|
| noSessionCreation | NoSessionCreationFilter | 不創(chuàng)建會(huì)話攔截器 |
Permissions
- 規(guī)則:
資源標(biāo)識(shí)符:操作:對(duì)象實(shí)例ID即對(duì)哪個(gè)資源的哪個(gè)實(shí)例進(jìn)行什么操作.其默認(rèn)支持通配符權(quán)限字符串,冒號(hào)(:)表示資源/操作/實(shí)例的分隔;逗號(hào)(,)表示操作的分隔,星號(hào)(*)表示任意資源/操作/實(shí)例。 - 多層次管理:
- 例如:user:query、user.edit
- 冒號(hào)是一個(gè)特殊字符,它用來分隔權(quán)限字符串的下一部件:第一部分是權(quán)限被操作的領(lǐng)域(打印機(jī)),第二部分是被執(zhí)行的操作。
- 多個(gè)值:每個(gè)部件能夠保護(hù)多個(gè)值。因此,出了授予用戶 user:query 和 user:edit 權(quán)限外,也可以簡單的授予他們一個(gè):user:quert,edit
- 還可以用 * 號(hào)代替所有的值,如:user:*,也可以寫:*:query,表示某個(gè)用戶在所有的領(lǐng)域都有query的權(quán)限。
Shiro 的 Permissions
- 實(shí)例級(jí)訪問控制
- 這種情況通常會(huì)使用三個(gè)部件:域、操作、被付諸實(shí)施的實(shí)例。如:user:edit:manager
- 也可以使用通配符來定義,如:user:edit:*、user:*:*、user:*:manager
- 部分省略通配符:缺少的部位意味著用戶可以訪問所有與之匹配的值,比如:user:edit 等價(jià)于user:edit:*、user 等價(jià)于 user:*:*
- 注意:通配符只能從字符串的結(jié)尾處省略部位,也就是說 user:edit 并不等價(jià)于 user:*:edit
授權(quán)流程
- AuthorizingRealm
- 授權(quán)需要繼承 AuthenticatingRealm 類,并實(shí)現(xiàn)其 doGetAuthenticationInfo 方法
- AuthorizingRealm 類繼承自 AuthenticatingRealm 類,但沒有實(shí)現(xiàn) AuthenticatingRealm 中的 doGetAuthenticationInfo 方法,所以認(rèn)真和授權(quán)只需要繼承 AuthorizingRealm 就可以了,同時(shí)實(shí)現(xiàn)它的兩個(gè)抽象方法 doGetAuthorizationInfo 和 doGetAuthenticationInfo

- 流程如下
- 首先調(diào)用 Subject.isPermitted/hasRole接口,其會(huì)委托給 SecurityManage,而 SecurityManage 接著會(huì)委托給 Authorizer;
- Authorizer 是真正的授權(quán)者,如果調(diào)用如 isPermitted("user:view"),其首先會(huì)通過 PermissionResolver 把字符串轉(zhuǎn)換成相應(yīng)的 Permission 實(shí)例;
- 在進(jìn)行授權(quán)之前,其會(huì)調(diào)用相應(yīng)的 Realm 獲取 Subject 相應(yīng)的角色/權(quán)限用戶匹配傳入的角色/權(quán)限;
- Authorizer 會(huì)判斷 Realm 的角色/權(quán)限是否和傳入的匹配, 如果有多個(gè)Realm,會(huì)委托給 ModularRealmAuthorizer 進(jìn)行循環(huán)判斷,如果匹配如 isPermitted/hasRole 會(huì)返回true,否則返回false表示授權(quán)失??;
- ModularRealmAuthorizer :多Realm匹配流程
- 首先檢查相應(yīng)的 Realm是否實(shí)現(xiàn)了Authorizer;
- 如果實(shí)現(xiàn)了 Authorizer,那么接著調(diào)用其對(duì)應(yīng)的 isPermitted/hasRole 接口進(jìn)行匹配;
- 如果有一個(gè)Realm匹配那么將返回true,否則返回false。
修改 ShiroRealm 類繼承 AuthorizingRealm,并實(shí)現(xiàn)其對(duì)應(yīng)的授權(quán)方法
package org.keyhua.shiro;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import java.util.HashSet;
import java.util.Set;
public class ShiroRealm extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("[FirstRealm] doGetAuthenticationInfo");
//1.把AuthenticationToken轉(zhuǎn)換為UserNamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2.從UserNamePasswordToken中獲取username
String username = upToken.getUsername();
//3.調(diào)用dao層方法,從數(shù)據(jù)庫中查詢username對(duì)應(yīng)的用戶記錄
System.out.println("從數(shù)據(jù)庫中獲取username:" + username + " 所對(duì)應(yīng)的用戶信息。");
//4.若用戶不存在,則可以拋出 UnknownAccountException 異常
if ("unknown".equals(username)) {
throw new UnknownAccountException("用戶不存在");
}
//5.根據(jù)用戶信息的情況,覺得是否需要拋出其他的異常.
if ("monster".equals(username)) {
throw new LockedAccountException("用戶被鎖定");
}
//6.根據(jù)用戶的情況,來構(gòu)造 AuthenticationInfo 對(duì)象并返回
//principal認(rèn)證實(shí)體,可以是username,也可以是數(shù)據(jù)表對(duì)應(yīng)的用戶的實(shí)體類的對(duì)象
Object principal = username;
//credentials:密碼
Object credentials = null;
if ("admin".equals(username)){
credentials = "9aa75c4d70930277f59d117ce19188b0";
} else if ("user".equals(username)) {
credentials = "dd957e81b004227af3e0aa4bde869b25";
}
//realmName:當(dāng)前realm對(duì)象的name.調(diào)用父類的getName()
String realmName = getName();
//鹽值
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
SimpleAuthenticationInfo info = null;
//info = new SimpleAuthenticationInfo(principal, credentials, realmName);
info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
return info;
}
public static void main(String[] args) {
String hashAlgorithmName = "MD5";
Object source = "123456";
Object salt = ByteSource.Util.bytes("user");
int hashIterations = 3;
SimpleHash hash = new SimpleHash(hashAlgorithmName, source, salt, hashIterations);
System.out.println(hash.toString());
}
//授權(quán)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1.從PrincipalCollection 中獲取登錄用戶的信息
Object principal = principals.getPrimaryPrincipal();
//2.利用登錄用戶的信息來獲取當(dāng)前用戶的角色或權(quán)限(可能需要查數(shù)據(jù)庫)
Set<String> roles = new HashSet<>();
roles.add("user");
if ("admin".equals(principal)){
roles.add("admin");
}
//3.創(chuàng)建SimpleAuthorizationInfo ,并設(shè)置其reles屬性.
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//4.返回 SimpleAuthorizationInfo 對(duì)象
return info;
}
}
啟動(dòng)項(xiàng)目測試:當(dāng)使用admin用戶登錄時(shí),所有頁面均可訪問,當(dāng)使用user用戶登錄時(shí),admin.jsp頁面無法訪問。
權(quán)限注解
- @RequiresAuthentication:表示當(dāng)前Subject已經(jīng)通過login進(jìn)行了身份驗(yàn)證;即Subject.isAuthenticated()返回true
- @RequiresUser:表示當(dāng)前 Subject 已經(jīng)身份驗(yàn)證或者通過記住登錄的
- @RequiresGuest:表示當(dāng)前 Subject 沒有身份驗(yàn)證或通過記住我登錄過,即是游客身份
- @RequiresRoles(value={"admin","user"},logical=Logical.AND):表示當(dāng)前Subject需要角色admin和user
- @RequiresPermissions(value={"user:a","user:b"},logical=Logical.OR):表示當(dāng)前Subject需要權(quán)限user:a 或 user:b
如何從數(shù)據(jù)表中初始化資源和權(quán)限
applicationContext.xml修改內(nèi)容:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" value="/list.jsp"/>
<property name="unauthorizedUrl" value="/unauthorized.jsp" />
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
<!--配置哪些頁面需要受保護(hù),以及訪問這些頁面需要的權(quán)限
a. anon 可以被匿名訪問
b. authc 必須認(rèn)證(登錄)后才可以訪問的頁面
c. logout 登出b
d. roles 角色過濾器
-->
<!--
<property name="filterChainDefinitions">
<value>
/login.jsp = anon
/shiro/shiroLogin = anon
/shiro/logout = logout
/user.jsp = roles[user]
/admin.jsp = roles[admin]
/** = authc
</value>
</property>
-->
</bean>
<!-- 配置一個(gè)bean,該bean實(shí)際上是一個(gè)Map。通過實(shí)例工廠方法的方式 -->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildfilterChainDefinitionMap"/>
<bean id="filterChainDefinitionMapBuilder" class="org.keyhua.shiro.FilterChainDefinitionMapBuilder"></bean>
FilterChainDefinitionMapBuilder:
public class FilterChainDefinitionMapBuilder {
public LinkedHashMap<String,String> buildfilterChainDefinitionMap(){
LinkedHashMap<String,String> map = new LinkedHashMap<>();
//可以從數(shù)據(jù)表中初始化資源和權(quán)限
map.put("/login.jsp", "anon");
map.put("/shiro/shiroLogin", "anon");
map.put("/shiro/logout", "logout");
map.put("/**", "authc");
return map;
}
}