五、授權(quán)

授權(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):

  1. 編程式: Subject#hasRole()
  2. 注解式:通過在執(zhí)行的Java方法上放置相應(yīng)的直接完成,沒有權(quán)限將拋出相應(yīng)的異常,如:@RequiresRoles()
  3. 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í)例。
  • 多層次管理:
  1. 例如:user:query、user.edit
  2. 冒號(hào)是一個(gè)特殊字符,它用來分隔權(quán)限字符串的下一部件:第一部分是權(quán)限被操作的領(lǐng)域(打印機(jī)),第二部分是被執(zhí)行的操作。
  3. 多個(gè)值:每個(gè)部件能夠保護(hù)多個(gè)值。因此,出了授予用戶 user:query 和 user:edit 權(quán)限外,也可以簡單的授予他們一個(gè):user:quert,edit
  4. 還可以用 * 號(hào)代替所有的值,如:user:*,也可以寫:*:query,表示某個(gè)用戶在所有的領(lǐng)域都有query的權(quán)限。
Shiro 的 Permissions
  • 實(shí)例級(jí)訪問控制
  1. 這種情況通常會(huì)使用三個(gè)部件:域、操作、被付諸實(shí)施的實(shí)例。如:user:edit:manager
  2. 也可以使用通配符來定義,如:user:edit:*、user:*:*、user:*:manager
  3. 部分省略通配符:缺少的部位意味著用戶可以訪問所有與之匹配的值,比如:user:edit 等價(jià)于user:edit:*、user 等價(jià)于 user:*:*
  4. 注意:通配符只能從字符串的結(jié)尾處省略部位,也就是說 user:edit 并不等價(jià)于 user:*:edit
授權(quán)流程
  • AuthorizingRealm
  1. 授權(quán)需要繼承 AuthenticatingRealm 類,并實(shí)現(xiàn)其 doGetAuthenticationInfo 方法
  2. AuthorizingRealm 類繼承自 AuthenticatingRealm 類,但沒有實(shí)現(xiàn) AuthenticatingRealm 中的 doGetAuthenticationInfo 方法,所以認(rèn)真和授權(quán)只需要繼承 AuthorizingRealm 就可以了,同時(shí)實(shí)現(xiàn)它的兩個(gè)抽象方法 doGetAuthorizationInfo 和 doGetAuthenticationInfo
  • 流程如下
  1. 首先調(diào)用 Subject.isPermitted/hasRole接口,其會(huì)委托給 SecurityManage,而 SecurityManage 接著會(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 :多Realm匹配流程
  1. 首先檢查相應(yīng)的 Realm是否實(shí)現(xiàn)了Authorizer;
  2. 如果實(shí)現(xiàn)了 Authorizer,那么接著調(diào)用其對(duì)應(yīng)的 isPermitted/hasRole 接口進(jìn)行匹配;
  3. 如果有一個(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;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容