從權(quán)限控制到shiro框架的應(yīng)用

說(shuō)明:本文很多觀點(diǎn)和內(nèi)容來(lái)自互聯(lián)網(wǎng)以及各種資料穗熬,如果侵犯了您的權(quán)益,請(qǐng)及時(shí)聯(lián)系我,我會(huì)刪除相關(guān)內(nèi)容臼勉。

權(quán)限管理

基本上涉及到用戶參與的系統(tǒng)都要進(jìn)行權(quán)限管理,權(quán)限管理屬于系統(tǒng)安全的范疇餐弱,權(quán)限管理實(shí)現(xiàn)對(duì)用戶訪問(wèn)系統(tǒng)的控制宴霸,按照安全規(guī)則或者安全策略控制用戶可以訪問(wèn)而且只能訪問(wèn)自己被授權(quán)的資源。
權(quán)限管理包括用戶身份認(rèn)證授權(quán)兩部分膏蚓,簡(jiǎn)稱認(rèn)證授權(quán)瓢谢。對(duì)于需要訪問(wèn)控制的資源用戶首先經(jīng)過(guò)身份認(rèn)證,認(rèn)證通過(guò)后用戶具有該資源的訪問(wèn)權(quán)限方可訪問(wèn)驮瞧。

  • 用戶身份認(rèn)證
    身份認(rèn)證氓扛,就是判斷一個(gè)用戶是否為合法用戶的處理過(guò)程。最常用的簡(jiǎn)單身份認(rèn)證方式是核對(duì)用戶輸入的用戶名和口令论笔,來(lái)判斷用戶身份是否正確采郎。對(duì)于采用指紋等系統(tǒng),則出示指紋狂魔;對(duì)于硬件Key等刷卡系統(tǒng)蒜埋,則需要刷卡。
  • 用戶名密碼身份認(rèn)證流程


    用戶名密碼身份認(rèn)證流程.png
  • 關(guān)鍵對(duì)象
    上邊的流程圖中需要理解以下關(guān)鍵對(duì)象:
  1. Subject:主體
    訪問(wèn)系統(tǒng)的用戶毅臊,主體可以是用戶理茎、程序等黑界,進(jìn)行認(rèn)證的都稱為主體;
  2. Principal:身份信息
    是主體(subject)進(jìn)行身份認(rèn)證的標(biāo)識(shí)皂林,標(biāo)識(shí)必須具有唯一性朗鸠,如用戶名、手機(jī)號(hào)础倍、郵箱地址等烛占,一個(gè)主體可以有多個(gè)身份,但是必須有一個(gè)主身份(Primary Principal)沟启。
  3. credential:憑證信息
    只有主體自己知道的安全信息忆家,如密碼、證書(shū)等德迹。
  • 授權(quán)
    概念
    授權(quán)芽卿,即訪問(wèn)控制,控制誰(shuí)能訪問(wèn)哪些資源胳搞。主體進(jìn)行身份認(rèn)證后需要分配權(quán)限方可訪問(wèn)系統(tǒng)的資源卸例,對(duì)于某些資源沒(méi)有權(quán)限是無(wú)法訪問(wèn)的。
    授權(quán)流程:
    下圖中橙色為授權(quán)流程


    授權(quán)流程.png
  • 關(guān)鍵對(duì)象
    授權(quán)可簡(jiǎn)單理解為who對(duì)what(which)進(jìn)行How操作:
    -----Who肌毅,即主體(Subject)筷转,主體需要訪問(wèn)系統(tǒng)中的資源。
    -----What悬而,即資源(Resource)呜舒,如系統(tǒng)菜單、頁(yè)面笨奠、按鈕袭蝗、類方法、系統(tǒng)商品信息等艰躺。資源包括資源類型和資源實(shí)例呻袭,比如商品信息為資源類型,類型為c01的商品為資源實(shí)例腺兴,編號(hào)為001的商品信息也屬于資源實(shí)例左电。
    ------How,權(quán)限/許可(Permission)页响,規(guī)定了主體對(duì)資源的操作許可篓足,權(quán)限離開(kāi)資源沒(méi)有意義,如用戶查詢權(quán)限闰蚕、用戶添加權(quán)限栈拖、某個(gè)類方法的調(diào)用權(quán)限、編號(hào)為001用戶的修改權(quán)限等没陡,通過(guò)權(quán)限可知主體對(duì)哪些資源都有哪些操作許可涩哟。
    權(quán)限分為粗顆粒和細(xì)顆粒索赏,粗顆粒權(quán)限是指對(duì)資源類型的權(quán)限,細(xì)顆粒權(quán)限是對(duì)資源實(shí)例的權(quán)限贴彼。
    主體潜腻、資源、權(quán)限關(guān)系如下圖:


    主體器仗、資源融涣、權(quán)限關(guān)系圖.png

    權(quán)限模型
    對(duì)上節(jié)中的主體、資源精钮、權(quán)限通過(guò)數(shù)據(jù)模型表示威鹿。

  • 主體(賬號(hào)、密碼)
  • 資源(資源名稱轨香、訪問(wèn)地址)
  • 權(quán)限(權(quán)限名稱忽你、資源id)
  • 角色(角色名稱)
  • 角色和權(quán)限關(guān)系(角色id、權(quán)限id)
  • 主體和角色關(guān)系(主體id弹沽、角色id)
    權(quán)限模型.png

    通常企業(yè)開(kāi)發(fā)中將資源和權(quán)限表合并為一張權(quán)限表檀夹,如下:
    資源(資源名稱、訪問(wèn)地址)
    權(quán)限(權(quán)限名稱策橘、資源id)
    合并為:
    權(quán)限(權(quán)限名稱、資源名稱娜亿、資源訪問(wèn)地址)
  • 權(quán)限分配
    對(duì)主體分配權(quán)限丽已,主體只允許在權(quán)限范圍內(nèi)對(duì)資源進(jìn)行操作,比如:對(duì)u01用戶分配商品修改權(quán)限买决,u01用戶只能對(duì)商品進(jìn)行修改沛婴。
    權(quán)限分配的數(shù)據(jù)通常需要持久化,根據(jù)上邊的數(shù)據(jù)模型創(chuàng)建表并將用戶的權(quán)限信息存儲(chǔ)在數(shù)據(jù)庫(kù)中督赤。
  • 權(quán)限控制
    用戶擁有了權(quán)限即可操作權(quán)限范圍內(nèi)的資源嘁灯,系統(tǒng)不知道主體是否具有訪問(wèn)權(quán)限需要對(duì)用戶的訪問(wèn)進(jìn)行控制
  • 基于角色的訪問(wèn)控制
    RBAC基于角色的訪問(wèn)控制(Role-Based Access Control)是以角色為中心進(jìn)行訪問(wèn)控制,比如:主體的角色為總經(jīng)理可以查詢企業(yè)運(yùn)營(yíng)報(bào)表躲舌,查詢員工工資信息等丑婿,訪問(wèn)控制流程如下:


    基于角色的訪問(wèn)控制.png

    上圖中的判斷邏輯代碼可以理解為:

if(主體.hasRole("總經(jīng)理角色id")){
    查詢工資
}

缺點(diǎn):以角色進(jìn)行訪問(wèn)控制粒度較粗,如果上圖中查詢工資所需要的角色變化為總經(jīng)理和部門(mén)經(jīng)理没卸,此時(shí)就需要修改判斷邏輯為“判斷主體的角色是否是總經(jīng)理或部門(mén)經(jīng)理”羹奉,系統(tǒng)可擴(kuò)展性差。
修改代碼如下:

if(主體.hasRole("總經(jīng)理角色id") ||  主體.hasRole("部門(mén)經(jīng)理角色id")){
    查詢工資
}
  • 基于資源的訪問(wèn)控制
    RBAC基于資源的訪問(wèn)控制(Resource-Based Access Control)是以資源為中心進(jìn)行訪問(wèn)控制约计,比如:主體必須具有查詢工資權(quán)限才可以查詢員工工資信息等诀拭,訪問(wèn)控制流程如下:


    基于資源的訪問(wèn)控制.png

    上圖中的判斷邏輯代碼可以理解為:

if(主體.hasPermission("wage:query")){
    查詢工資
}

優(yōu)點(diǎn):系統(tǒng)設(shè)計(jì)時(shí)定義好查詢工資的權(quán)限標(biāo)識(shí),即使查詢工資所需要的角色變化為總經(jīng)理和部門(mén)經(jīng)理也只需要將“查詢工資信息權(quán)限”添加到“部門(mén)經(jīng)理角色”的權(quán)限列表中煤蚌,判斷邏輯不用修改耕挨,系統(tǒng)可擴(kuò)展性強(qiáng)细卧。

權(quán)限管理解決方案

  1. 什么是粗顆粒度和細(xì)顆粒度
    對(duì)資源類型的管理稱為粗顆粒度權(quán)限管理,即只控制到菜單筒占、按鈕酒甸、方法,粗粒度的例子比如:用戶具有用戶管理的權(quán)限赋铝,具有導(dǎo)出訂單明細(xì)的權(quán)限插勤。對(duì)資源實(shí)例的控制稱為細(xì)顆粒度權(quán)限管理,即控制到數(shù)據(jù)級(jí)別的權(quán)限革骨,比如:用戶只允許修改本部門(mén)的員工信息农尖,用戶只允許導(dǎo)出自己創(chuàng)建的訂單明細(xì)。
  2. 如何實(shí)現(xiàn)粗顆粒度和細(xì)顆粒度
    對(duì)于粗顆粒度的權(quán)限管理可以很容易做系統(tǒng)架構(gòu)級(jí)別的功能良哲,即系統(tǒng)功能操作使用統(tǒng)一的粗顆粒度的權(quán)限管理盛卡。
    對(duì)于細(xì)顆粒度的權(quán)限管理不建議做成系統(tǒng)架構(gòu)級(jí)別的功能,因?yàn)閷?duì)數(shù)據(jù)級(jí)別的控制是系統(tǒng)的業(yè)務(wù)需求筑凫,隨著業(yè)務(wù)需求的變更業(yè)務(wù)功能變化的可能性很大滑沧,建議對(duì)數(shù)據(jù)級(jí)別的權(quán)限控制在業(yè)務(wù)層個(gè)性化開(kāi)發(fā),比如:用戶只允許修改自己創(chuàng)建的商品信息可以在service接口添加校驗(yàn)實(shí)現(xiàn)巍实,service接口需要傳入當(dāng)前操作人的標(biāo)識(shí)滓技,與商品信息創(chuàng)建人標(biāo)識(shí)對(duì)比,不一致則不允許修改商品信息棚潦。
  • 基于url攔截
    基于url攔截是企業(yè)中常用的權(quán)限管理方法令漂,實(shí)現(xiàn)思路是:將系統(tǒng)操作的每個(gè)url配置在權(quán)限表中,將權(quán)限對(duì)應(yīng)到角色丸边,將角色分配給用戶叠必,用戶訪問(wèn)系統(tǒng)功能通過(guò)Filter進(jìn)行過(guò)慮,過(guò)慮器獲取到用戶訪問(wèn)的url妹窖,只要訪問(wèn)的url是用戶分配角色中的url則放行繼續(xù)訪問(wèn)纬朝。


    權(quán)限管理解決方案.png

shiro介紹

  • 什么是shiro
    Shiro是apache旗下一個(gè)開(kāi)源框架,它將軟件系統(tǒng)的安全認(rèn)證相關(guān)的功能抽取出來(lái)骄呼,實(shí)現(xiàn)用戶身份認(rèn)證共苛,權(quán)限授權(quán)、加密谒麦、會(huì)話管理等功能俄讹,組成了一個(gè)通用的安全認(rèn)證框架。
  • 為什么要學(xué)shiro
    使用shiro就可以非橙频拢快速的完成認(rèn)證患膛、授權(quán)等功能的開(kāi)發(fā),降低系統(tǒng)成本耻蛇。
    shiro使用廣泛踪蹬,shiro可以運(yùn)行在web應(yīng)用胞此,非web應(yīng)用,集群分布式應(yīng)用中越來(lái)越多的用戶開(kāi)始使用shiro跃捣。
    java領(lǐng)域中spring security(原名Acegi)也是一個(gè)開(kāi)源的權(quán)限管理框架漱牵,但是spring security依賴spring運(yùn)行,而shiro就相對(duì)獨(dú)立疚漆,最主要是因?yàn)閟hiro使用簡(jiǎn)單酣胀、靈活,所以現(xiàn)在越來(lái)越多的用戶選擇shiro娶聘。
  • Shiro架構(gòu)


    shiro框架功能模塊.png

    shiro架構(gòu).png
  1. Subject
    Subject即主體闻镶,外部應(yīng)用與subject進(jìn)行交互,subject記錄了當(dāng)前操作用戶丸升,將用戶的概念理解為當(dāng)前操作的主體铆农,可能是一個(gè)通過(guò)瀏覽器請(qǐng)求的用戶,也可能是一個(gè)運(yùn)行的程序狡耻。 Subject在shiro中是一個(gè)接口墩剖,接口中定義了很多認(rèn)證授相關(guān)的方法,外部程序通過(guò)subject進(jìn)行認(rèn)證授夷狰,而subject是通過(guò)SecurityManager安全管理器進(jìn)行認(rèn)證授權(quán)
  2. SecurityManager
    SecurityManager即安全管理器岭皂,對(duì)全部的subject進(jìn)行安全管理,它是shiro的核心孵淘,負(fù)責(zé)對(duì)所有的subject進(jìn)行安全管理蒲障。通過(guò)SecurityManager可以完成subject的認(rèn)證、授權(quán)等瘫证,實(shí)質(zhì)上SecurityManager是通過(guò)Authenticator進(jìn)行認(rèn)證,通過(guò)Authorizer進(jìn)行授權(quán)庄撮,通過(guò)SessionManager進(jìn)行會(huì)話管理等背捌。
    SecurityManager是一個(gè)接口,繼承了Authenticator, Authorizer, SessionManager這三個(gè)接口洞斯。
  3. Authenticator
    Authenticator即認(rèn)證器毡庆,對(duì)用戶身份進(jìn)行認(rèn)證,Authenticator是一個(gè)接口烙如,shiro提供ModularRealmAuthenticator實(shí)現(xiàn)類么抗,通過(guò)ModularRealmAuthenticator基本上可以滿足大多數(shù)需求,也可以自定義認(rèn)證器亚铁。
  4. Authorizer
    Authorizer即授權(quán)器蝇刀,用戶通過(guò)認(rèn)證器認(rèn)證通過(guò),在訪問(wèn)功能時(shí)需要通過(guò)授權(quán)器判斷用戶是否有此功能的操作權(quán)限徘溢。
  5. realm
    Realm即領(lǐng)域吞琐,相當(dāng)于datasource數(shù)據(jù)源捆探,securityManager進(jìn)行安全認(rèn)證需要通過(guò)Realm獲取用戶權(quán)限數(shù)據(jù),比如:如果用戶身份數(shù)據(jù)在數(shù)據(jù)庫(kù)那么realm就需要從數(shù)據(jù)庫(kù)獲取用戶身份信息站粟。
    注意:不要把realm理解成只是從數(shù)據(jù)源取數(shù)據(jù)黍图,在realm中還有認(rèn)證授權(quán)校驗(yàn)的相關(guān)的代碼。
  6. sessionManager
    sessionManager即會(huì)話管理奴烙,shiro框架定義了一套會(huì)話管理助被,它不依賴web容器的session,所以shiro可以使用在非web應(yīng)用上切诀,也可以將分布式應(yīng)用的會(huì)話集中在一點(diǎn)管理检盼,此特性可使它實(shí)現(xiàn)單點(diǎn)登錄翘单。
  7. SessionDAO
    SessionDAO即會(huì)話dao,是對(duì)session會(huì)話操作的一套接口书在,比如要將session存儲(chǔ)到數(shù)據(jù)庫(kù)竖般,可以通過(guò)jdbc將會(huì)話存儲(chǔ)到數(shù)據(jù)庫(kù)。
  8. CacheManager
    CacheManager即緩存管理彩库,將用戶權(quán)限數(shù)據(jù)存儲(chǔ)在緩存鳞仙,這樣可以提高性能业稼。
  9. Cryptography
    Cryptography即密碼管理,shiro提供了一套加密/解密的組件琴拧,方便開(kāi)發(fā)蚓胸。比如提供常用的散列、加/解密等功能。
  • shiro認(rèn)證


    認(rèn)證流程.png
入門(mén)程序(用戶登陸和退出)

shiro.ini----->通過(guò)Shiro.ini配置文件初始化SecurityManager環(huán)境。

[users]
zhang=123
lisi=123

認(rèn)證代碼

// 用戶登陸唉工、用戶退出
    @Test
    public void testLoginLogout() {
        // 構(gòu)建SecurityManager工廠雹熬,IniSecurityManagerFactory可以從ini文件中初始化SecurityManager環(huán)境
        Factory<SecurityManager> factory = new IniSecurityManagerFactory(
                "classpath:shiro.ini");
        // 通過(guò)工廠創(chuàng)建SecurityManager
        SecurityManager securityManager = factory.getInstance();

        // 將securityManager設(shè)置到運(yùn)行環(huán)境中
        SecurityUtils.setSecurityManager(securityManager);

        // 創(chuàng)建一個(gè)Subject實(shí)例,該實(shí)例認(rèn)證要使用上邊創(chuàng)建的securityManager進(jìn)行
        Subject subject = SecurityUtils.getSubject();

        // 創(chuàng)建token令牌谣膳,記錄用戶認(rèn)證的身份和憑證即賬號(hào)和密碼 
        UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");

        try {
            // 用戶登陸
            subject.login(token);
        } catch (AuthenticationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // 用戶認(rèn)證狀態(tài)
        Boolean isAuthenticated = subject.isAuthenticated();
        System.out.println("用戶認(rèn)證狀態(tài):" + isAuthenticated);

        // 用戶退出
        subject.logout();
        isAuthenticated = subject.isAuthenticated();
        System.out.println("用戶認(rèn)證狀態(tài):" + isAuthenticated);
    }

認(rèn)證執(zhí)行流程
1竿报、 創(chuàng)建token令牌,token中有用戶提交的認(rèn)證信息即賬號(hào)和密碼
2继谚、 執(zhí)行subject.login(token)烈菌,最終由securityManager通過(guò)Authenticator進(jìn)行認(rèn)證
3、 Authenticator的實(shí)現(xiàn)ModularRealmAuthenticator調(diào)用realm從ini配置文件取用戶真實(shí)的賬號(hào)和密碼,這里使用的是IniRealm(shiro自帶)
4芽世、 IniRealm先根據(jù)token中的賬號(hào)去ini中找該賬號(hào)挚赊,如果找不到則給ModularRealmAuthenticator返回null,如果找到則匹配密碼济瓢,匹配密碼成功則認(rèn)證通過(guò)荠割。

  • 常見(jiàn)的異常
  1. UnknownAccountException
    賬號(hào)不存在異常如下:
    org.apache.shiro.authc.UnknownAccountException: No account found for user。葬荷。涨共。。
  2. IncorrectCredentialsException
    當(dāng)輸入密碼錯(cuò)誤會(huì)拋此異常宠漩,如下:
    org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.
  3. 更多如下:
    DisabledAccountException(帳號(hào)被禁用)
    LockedAccountException(帳號(hào)被鎖定)
    ExcessiveAttemptsException(登錄失敗次數(shù)過(guò)多)
    ExpiredCredentialsException(憑證過(guò)期)等
自定義Realm

上邊的程序使用的是Shiro自帶的IniRealm举反,IniRealm從ini配置文件中讀取用戶的信息,大部分情況下需要從系統(tǒng)的數(shù)據(jù)庫(kù)中讀取用戶信息扒吁,所以需要自定義realm火鼻。
shiro提供的realm


realm父子結(jié)構(gòu).png

最基礎(chǔ)的是Realm接口,CachingRealm負(fù)責(zé)緩存處理雕崩,AuthenticationRealm負(fù)責(zé)認(rèn)證魁索,AuthorizingRealm負(fù)責(zé)授權(quán),通常自定義的realm繼承AuthorizingRealm盼铁。

  • 自定義Realm
public class CustomRealm1 extends AuthorizingRealm {

    @Override
    public String getName() {
        return "customRealm1";
    }

    //支持UsernamePasswordToken
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    //認(rèn)證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {

        //從token中 獲取用戶身份信息
        String username = (String) token.getPrincipal();
        //拿username從數(shù)據(jù)庫(kù)中查詢
        //....
        //如果查詢不到則返回null
        if(!username.equals("zhang")){//這里模擬查詢不到
            return null;
        }

        //獲取從數(shù)據(jù)庫(kù)查詢出來(lái)的用戶密碼 
        String password = "123";//這里使用靜態(tài)數(shù)據(jù)模擬粗蔚。。

        //返回認(rèn)證信息由父類AuthenticatingRealm進(jìn)行認(rèn)證
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                username, password, getName());

        return simpleAuthenticationInfo;
    }

    //授權(quán)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        // TODO Auto-generated method stub
        return null;
    }

}

shiro-realm.ini

[main]
#自定義 realm
customRealm=cn.itcast.shiro.authentication.realm.CustomRealm1
#將realm設(shè)置到securityManager
securityManager.realms=$customRealm

散列算法

散列算法一般用于生成一段文本的摘要信息饶火,散列算法不可逆鹏控,將內(nèi)容可以生成摘要,無(wú)法將摘要轉(zhuǎn)成原始內(nèi)容肤寝。散列算法常用于對(duì)密碼進(jìn)行散列当辐,常用的散列算法有MD5、SHA鲤看。

一般散列算法需要提供一個(gè)salt(鹽)與原始內(nèi)容生成摘要信息缘揪,這樣做的目的是為了安全性,比如:111111的md5值是:96e79218965eb72c92a549dd5a330112义桂,拿著“96e79218965eb72c92a549dd5a330112”去md5破解網(wǎng)站很容易進(jìn)行破解找筝,如果要是對(duì)111111和salt(鹽,一個(gè)隨機(jī)數(shù))進(jìn)行散列慷吊,這樣雖然密碼都是111111加不同的鹽會(huì)生成不同的散列值呻征。
例子

//md5加密,不加鹽
        String password_md5 = new Md5Hash("111111").toString();
        System.out.println("md5加密罢浇,不加鹽="+password_md5);

        //md5加密,加鹽,一次散列
        String password_md5_sale_1 = new Md5Hash("111111", "eteokues", 1).toString();
        System.out.println("password_md5_sale_1="+password_md5_sale_1);
        String password_md5_sale_2 = new Md5Hash("111111", "uiwueylm", 1).toString();
        System.out.println("password_md5_sale_2="+password_md5_sale_2);
        //兩次散列相當(dāng)于md5(md5())

        //使用SimpleHash
        String simpleHash = new SimpleHash("MD5", "111111", "eteokues",1).toString();
        System.out.println(simpleHash);

在realm中使用

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {

        //用戶賬號(hào)
        String username = (String) token.getPrincipal();
        //根據(jù)用戶賬號(hào)從數(shù)據(jù)庫(kù)取出鹽和加密后的值
        //..這里使用靜態(tài)數(shù)據(jù)
        //如果根據(jù)賬號(hào)沒(méi)有找到用戶信息則返回null嚷闭,shiro拋出異吃艿海“賬號(hào)不存在”

        //按照固定規(guī)則加密碼結(jié)果 ,此密碼 要在數(shù)據(jù)庫(kù)存儲(chǔ)胞锰,原始密碼 是111111灾锯,鹽是eteokues
        String password = "cb571f7bd7a6f73ab004a70322b963d5";
        //鹽,隨機(jī)數(shù)嗅榕,此隨機(jī)數(shù)也在數(shù)據(jù)庫(kù)存儲(chǔ)
        String salt = "eteokues";

        //返回認(rèn)證信息
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                username, password, ByteSource.Util.bytes(salt),getName());


        return simpleAuthenticationInfo;
    }

shiro-cryptography.ini

[main]
#定義憑證匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次數(shù)
credentialsMatcher.hashIterations=1

#將憑證匹配器設(shè)置到realm
customRealm=cn.itcast.shiro.authentication.realm.CustomRealm2
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm
shiro授權(quán)
授權(quán)流程.png

Shiro 支持三種方式的授權(quán):

  1. ν編程式:通過(guò)寫(xiě)if/else 授權(quán)代碼塊完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有權(quán)限
} else {
//無(wú)權(quán)限
}
  1. 注解式:通過(guò)在執(zhí)行的Java方法上放置相應(yīng)的注解完成:
@RequiresRoles("admin")
public void hello() {
//有權(quán)限
}
  1. shiro 標(biāo)簽:在JSP/GSP 頁(yè)面通過(guò)相應(yīng)的標(biāo)簽完成:
<shiro:hasRole name="admin">
擁有admin角色才能看到我顺饮!
</shiro:hasRole>

授權(quán)測(cè)試------》創(chuàng)建存放權(quán)限的配置文件shiro-permission.ini,如下:

[users]
#用戶zhang的密碼是123凌那,此用戶具有role1和role2兩個(gè)角色
zhang=123,role1,role2
wang=123,role2

[roles]
#角色role1對(duì)資源user擁有create兼雄、update權(quán)限
role1=user:create,user:update
#角色role2對(duì)資源user擁有create、delete權(quán)限
role2=user:create,user:delete
#角色role3對(duì)資源user擁有create權(quán)限
role3=user:create

在ini文件中用戶帽蝶、角色赦肋、權(quán)限的配置規(guī)則是:“用戶名=密碼,角色1励稳,角色2…” “角色=權(quán)限1佃乘,權(quán)限2…”,首先根據(jù)用戶名找角色驹尼,再根據(jù)角色找權(quán)限趣避,角色是權(quán)限集合。
權(quán)限字符串規(guī)則
權(quán)限字符串的規(guī)則是:“資源標(biāo)識(shí)符:操作:資源實(shí)例標(biāo)識(shí)符”新翎,意思是對(duì)哪個(gè)資源的哪個(gè)實(shí)例具有什么操作程帕,“:”是資源/操作/實(shí)例的分割符,權(quán)限字符串也可以使用*通配符。

例子:
用戶創(chuàng)建權(quán)限:user:create吨述,或user:create:*
用戶修改實(shí)例001的權(quán)限:user:update:001
用戶實(shí)例001的所有權(quán)限:user:*:001

測(cè)試代碼
測(cè)試代碼同認(rèn)證代碼侍筛,注意ini地址改為shiro-permission.ini,主要學(xué)習(xí)下邊授權(quán)的方法敛苇,注意:在用戶認(rèn)證通過(guò)后執(zhí)行下邊的授權(quán)代碼。

@Test
    public void testPermission() {

        // 從ini文件中創(chuàng)建SecurityManager工廠
        Factory<SecurityManager> factory = new IniSecurityManagerFactory(
                "classpath:shiro-permission.ini");

        // 創(chuàng)建SecurityManager
        SecurityManager securityManager = factory.getInstance();

        // 將securityManager設(shè)置到運(yùn)行環(huán)境
        SecurityUtils.setSecurityManager(securityManager);

        // 創(chuàng)建主體對(duì)象
        Subject subject = SecurityUtils.getSubject();

        // 對(duì)主體對(duì)象進(jìn)行認(rèn)證
        // 用戶登陸
        // 設(shè)置用戶認(rèn)證的身份(principals)和憑證(credentials)
        UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // 用戶認(rèn)證狀態(tài)
        Boolean isAuthenticated = subject.isAuthenticated();

        System.out.println("用戶認(rèn)證狀態(tài):" + isAuthenticated);

        // 用戶授權(quán)檢測(cè) 基于角色授權(quán)
        // 是否有某一個(gè)角色
        System.out.println("用戶是否擁有一個(gè)角色:" + subject.hasRole("role1"));
        // 是否有多個(gè)角色
        System.out.println("用戶是否擁有多個(gè)角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));

//      subject.checkRole("role1");
//      subject.checkRoles(Arrays.asList("role1", "role2"));

        // 授權(quán)檢測(cè)顺呕,失敗則拋出異常
        // subject.checkRole("role22");

        // 基于資源授權(quán)
        System.out.println("是否擁有某一個(gè)權(quán)限:" + subject.isPermitted("user:delete"));
        System.out.println("是否擁有多個(gè)權(quán)限:" + subject.isPermittedAll("user:create:1",    "user:delete"));

        //檢查權(quán)限
        subject.checkPermission("sys:user:delete");
        subject.checkPermissions("user:create:1","user:delete");
    }

基于角色的授權(quán)

// 用戶授權(quán)檢測(cè) 基于角色授權(quán)
// 是否有某一個(gè)角色
System.out.println("用戶是否擁有一個(gè)角色:" + subject.hasRole("role1"));
// 是否有多個(gè)角色
System.out.println("用戶是否擁有多個(gè)角色:" + subject.hasAllRoles(Arrays.asList("role1", "role2")));

對(duì)應(yīng)的check方法:

subject.checkRole("role1");
subject.checkRoles(Arrays.asList("role1", "role2"));

上邊check方法如果授權(quán)失敗則拋出異常:
org.apache.shiro.authz.UnauthorizedException: Subject does not have role […..]
基于資源授權(quán)

// 基于資源授權(quán)
System.out.println("是否擁有某一個(gè)權(quán)限:" + subject.isPermitted("user:delete"));
System.out.println("是否擁有多個(gè)權(quán)限:" + subject.isPermittedAll("user:create:1",    "user:delete"));

對(duì)應(yīng)的check方法:

subject.checkPermission("sys:user:delete");
subject.checkPermissions("user:create:1","user:delete");

上邊check方法如果授權(quán)失敗則拋出異常:
org.apache.shiro.authz.UnauthorizedException: Subject does not have permission [….]

自定義realm

與上邊認(rèn)證自定義realm一樣枫攀,大部分情況是要從數(shù)據(jù)庫(kù)獲取權(quán)限數(shù)據(jù),這里直接實(shí)現(xiàn)基于資源的授權(quán)株茶。
realm代碼
在認(rèn)證章節(jié)寫(xiě)的自定義realm類中完善doGetAuthorizationInfo方法来涨,此方法需要完成:根據(jù)用戶身份信息從數(shù)據(jù)庫(kù)查詢權(quán)限字符串,由shiro進(jìn)行授權(quán)

// 授權(quán)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        // 獲取身份信息
        String username = (String) principals.getPrimaryPrincipal();
        // 根據(jù)身份信息從數(shù)據(jù)庫(kù)中查詢權(quán)限數(shù)據(jù)
        //....這里使用靜態(tài)數(shù)據(jù)模擬
        List<String> permissions = new ArrayList<String>();
        permissions.add("user:create");
        permissions.add("user.delete");

        //將權(quán)限信息封閉為AuthorizationInfo

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for(String permission:permissions){
            simpleAuthorizationInfo.addStringPermission(permission);
        }

        return simpleAuthorizationInfo;
    }

shiro-realm.ini
ini配置文件還使用認(rèn)證階段使用的启盛,不用改變蹦掐。
測(cè)試代碼
同上邊的授權(quán)測(cè)試代碼技羔,注意修改ini地址為shiro-realm.ini。
授權(quán)執(zhí)行流程
1卧抗、 執(zhí)行subject.isPermitted(“user:create”)
2藤滥、 securityManager通過(guò)ModularRealmAuthorizer進(jìn)行授權(quán)
3、 ModularRealmAuthorizer調(diào)用realm獲取權(quán)限信息
4社裆、 ModularRealmAuthorizer再通過(guò)permissionResolver解析權(quán)限字符串拙绊,校驗(yàn)是否匹配

shiro與項(xiàng)目集成開(kāi)發(fā)

  • shiro與spring web項(xiàng)目整合
    shiro與springweb項(xiàng)目整合在“基于url攔截實(shí)現(xiàn)的工程”基礎(chǔ)上整合,基于url攔截實(shí)現(xiàn)的工程的技術(shù)架構(gòu)是springmvc+mybatis泳秀,整合注意兩點(diǎn):shiro與springweb項(xiàng)目整合在“基于url攔截實(shí)現(xiàn)的工程”基礎(chǔ)上整合标沪,基于url攔截實(shí)現(xiàn)的工程的技術(shù)架構(gòu)是springmvc+mybatis,整合注意兩點(diǎn):
  1. shiro與spring整合
  2. 加入shiro對(duì)web應(yīng)用的支持
    取消原springmvc認(rèn)證和授權(quán)攔截器
    去掉springmvc.xml中配置的LoginInterceptor和PermissionInterceptor攔截器嗜傅。
web.xml添加shiro Filter
<!--shiro-->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
      <!-- 該值缺省為false,表示生命周期由SpringApplicationContext管理,設(shè)置為true則表示由ServletContainer管理 -->
      <param-name>targetFilterLifecycle</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

applicationContext-shiro.xml

<!-- Shiro 的Web過(guò)濾器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <!-- 如果沒(méi)有認(rèn)證將要跳轉(zhuǎn)的登陸地址金句,http可訪問(wèn)的url,如果不在表單認(rèn)證過(guò)慮器FormAuthenticationFilter中指定此地址就為身份認(rèn)證地址 -->
        <property name="loginUrl" value="/login.action" />
<!-- 沒(méi)有權(quán)限跳轉(zhuǎn)的地址 -->
        <property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- shiro攔截器配置 -->
        <property name="filters">
            <map>
                <entry key="authc" value-ref="formAuthenticationFilter" />
            </map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                <!-- 必須通過(guò)身份認(rèn)證方可訪問(wèn)磺陡,身份認(rèn) 證的url必須和過(guò)慮器中指定的loginUrl一致 -->
                /loginsubmit.action = authc
                <!-- 退出攔截趴梢,請(qǐng)求logout.action執(zhí)行退出操作 -->
                /logout.action = logout
                <!-- 無(wú)權(quán)訪問(wèn)頁(yè)面 -->
                /refuse.jsp = anon
                <!-- roles[XX]表示有XX角色才可訪問(wèn) -->
                /item/list.action = roles[item],authc
                /js/** anon
                /images/** anon
                /styles/** anon
                <!-- user表示身份認(rèn)證通過(guò)或通過(guò)記住我認(rèn)證通過(guò)的可以訪問(wèn) -->
                /** = user
                <!-- /**放在最下邊,如果一個(gè)url有多個(gè)過(guò)慮器則多個(gè)過(guò)慮器中間用逗號(hào)分隔币他,如:/** = user,roles[admin] -->

            </value>
        </property>
    </bean>


    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />

    </bean>

    <!-- 自定義 realm -->
    <bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">
    </bean>
    <!-- 基于Form表單的身份驗(yàn)證過(guò)濾器坞靶,不配置將也會(huì)注冊(cè)此過(guò)慮器,表單中的用戶賬號(hào)蝴悉、密碼及l(fā)oginurl將采用默認(rèn)值彰阴,建議配置 -->
    <bean id="formAuthenticationFilter"
        class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
        <!-- 表單中賬號(hào)的input名稱 -->
        <property name="usernameParam" value="usercode" />
        <!-- 表單中密碼的input名稱 -->
        <property name="passwordParam" value="password" />
        <!-- <property name="rememberMeParam" value="rememberMe"/> -->
        <!-- loginurl:用戶登陸地址,此地址是可以http訪問(wèn)的url地址 -->
        <property name="loginUrl" value="/loginsubmit.action" />
    </bean>

使用shiro注解授權(quán)

在springmvc.xml中配置shiro注解支持拍冠,可在controller方法中使用shiro注解配置權(quán)限:

<!-- 開(kāi)啟aop尿这,對(duì)類代理 -->
    <aop:config proxy-target-class="true"></aop:config>
    <!-- 開(kāi)啟shiro注解支持 -->
    <bean
        class="
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>

修改Controller代碼,在方法上添加授權(quán)注解庆杜,如下:

// 查詢列表
    @RequestMapping("/queryItem")
    @RequiresPermissions("item:query")
    public ModelAndView queryItem() throws Exception {

上邊代碼@RequiresPermissions("item:query")表示必須擁有“item:query”權(quán)限方可執(zhí)行射众。

  • 自定義realm
    此realm先不從數(shù)據(jù)庫(kù)查詢權(quán)限數(shù)據(jù),當(dāng)前需要先將shiro整合完成晃财,在上邊章節(jié)定義的realm基礎(chǔ)上修改叨橱。
public class CustomRealm1 extends AuthorizingRealm {

    @Autowired
    private SysService sysService;

    @Override
    public String getName() {
        return "customRealm";
    }

    // 支持什么類型的token
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    // 認(rèn)證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {

        // 從token中 獲取用戶身份信息
        String username = (String) token.getPrincipal();
        // 拿username從數(shù)據(jù)庫(kù)中查詢
        // ....
        // 如果查詢不到則返回null
        if (!username.equals("zhang")) {// 這里模擬查詢不到
            return null;
        }

        // 獲取從數(shù)據(jù)庫(kù)查詢出來(lái)的用戶密碼
        String password = "123";// 這里使用靜態(tài)數(shù)據(jù)模擬。断盛。

        // 根據(jù)用戶id從數(shù)據(jù)庫(kù)取出菜單
        //...先用靜態(tài)數(shù)據(jù)
        List<SysPermission> menus = new ArrayList<SysPermission>();;

        SysPermission sysPermission_1 = new SysPermission();
        sysPermission_1.setName("商品管理");
        sysPermission_1.setUrl("/item/queryItem.action");
        SysPermission sysPermission_2 = new SysPermission();
        sysPermission_2.setName("用戶管理");
        sysPermission_2.setUrl("/user/query.action");

        menus.add(sysPermission_1);
        menus.add(sysPermission_2);

        // 構(gòu)建用戶身體份信息
        ActiveUser activeUser = new ActiveUser();
        activeUser.setUserid(username);
        activeUser.setUsername(username);
        activeUser.setUsercode(username);
        activeUser.setMenus(menus);

        // 返回認(rèn)證信息由父類AuthenticatingRealm進(jìn)行認(rèn)證
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                activeUser, password, getName());

        return simpleAuthenticationInfo;
    }

    // 授權(quán)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        // 獲取身份信息
        ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
        //用戶id
        String userid = activeUser.getUserid();
        // 根據(jù)用戶id從數(shù)據(jù)庫(kù)中查詢權(quán)限數(shù)據(jù)
        // ....這里使用靜態(tài)數(shù)據(jù)模擬
        List<String> permissions = new ArrayList<String>();
        permissions.add("item:query");
        permissions.add("item:update");

        // 將權(quán)限信息封閉為AuthorizationInfo

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for (String permission : permissions) {
            simpleAuthorizationInfo.addStringPermission(permission);
        }

        return simpleAuthorizationInfo;
    }

}
登錄
//用戶登陸頁(yè)面
    @RequestMapping("/login")
    public String login()throws Exception{
        return "login";
    }
    // 用戶登陸提交
    @RequestMapping("/loginsubmit")
    public String loginsubmit(Model model, HttpServletRequest request)
            throws Exception {

        // shiro在認(rèn)證過(guò)程中出現(xiàn)錯(cuò)誤后將異常類路徑通過(guò)request返回
        String exceptionClassName = (String) request
                .getAttribute("shiroLoginFailure");
        if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
            throw new CustomException("賬號(hào)不存在");
        } else if (IncorrectCredentialsException.class.getName().equals(
                exceptionClassName)) {
            throw new CustomException("用戶名/密碼錯(cuò)誤");
        } else{
            throw new Exception();//最終在異常處理器生成未知錯(cuò)誤
        }
    }

首頁(yè)
由于session由shiro管理罗洗,需要修改首頁(yè)的controller方法:

//系統(tǒng)首頁(yè)
    @RequestMapping("/first")
    public String first(Model model)throws Exception{

        //主體
        Subject subject = SecurityUtils.getSubject();
        //身份
        ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
        model.addAttribute("activeUser", activeUser);
        return "/first";
    }

退出
由于使用shiro的sessionManager,不用開(kāi)發(fā)退出功能钢猛,使用shiro的logout攔截器即可伙菜。

<!-- 退出攔截,請(qǐng)求logout.action執(zhí)行退出操作 -->
/logout.action = logout

無(wú)權(quán)限r(nóng)efuse.jsp
當(dāng)用戶無(wú)操作權(quán)限命迈,shiro將跳轉(zhuǎn)到refuse.jsp頁(yè)面贩绕。
參考:applicationContext-shiro.xml

  • realm連接數(shù)據(jù)庫(kù)
  1. 添加憑證匹配器
  2. 添加憑證匹配器實(shí)現(xiàn)md5加密校驗(yàn)火的。
    修改applicationContext-shiro.xml:
<!-- 憑證匹配器 -->
    <bean id="credentialsMatcher"
        class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5" />
        <property name="hashIterations" value="1" />
    </bean>

<!-- 自定義 realm -->
    <bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">
        <property name="credentialsMatcher" ref="credentialsMatcher" />
    </bean>

realm代碼
修改realm代碼從數(shù)據(jù)庫(kù)中查詢用戶身份信息和權(quán)限信息,將sysService注入realm丧叽。

public class CustomRealm1 extends AuthorizingRealm {

    @Autowired
    private SysService sysService;

    @Override
    public String getName() {
        return "customRealm";
    }

    // 支持什么類型的token
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        // 從token中獲取用戶身份
        String usercode = (String) token.getPrincipal();

        SysUser sysUser = null;
        try {
            sysUser = sysService.findSysuserByUsercode(usercode);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // 如果賬號(hào)不存在
        if (sysUser == null) {
            throw new UnknownAccountException("賬號(hào)找不到");
        }

        // 根據(jù)用戶id取出菜單
        List<SysPermission> menus = null;
        try {
            menus = sysService.findMenuList(sysUser.getId());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        // 用戶密碼
        String password = sysUser.getPassword();
        //鹽
        String salt = sysUser.getSalt();

        // 構(gòu)建用戶身體份信息
        ActiveUser activeUser = new ActiveUser();
        activeUser.setUserid(sysUser.getId());
        activeUser.setUsername(sysUser.getUsername());
        activeUser.setUsercode(sysUser.getUsercode());
        activeUser.setMenus(menus);

        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                activeUser, password, ByteSource.Util.bytes(salt),getName());

        return simpleAuthenticationInfo;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {

        //身份信息
        ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
        //用戶id
        String userid = activeUser.getUserid();
        //獲取用戶權(quán)限
        List<SysPermission> permissions = null;
        try {
            permissions = sysService.findSysPermissionList(userid);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //構(gòu)建shiro授權(quán)信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for(SysPermission sysPermission:permissions){
            simpleAuthorizationInfo.addStringPermission(sysPermission.getPercode());
        }

        return simpleAuthorizationInfo;

    }

}
緩存

shiro每個(gè)授權(quán)都會(huì)通過(guò)realm獲取權(quán)限信息卫玖,為了提高訪問(wèn)速度需要添加緩存,第一次從realm中讀取權(quán)限數(shù)據(jù)踊淳,之后不再讀取,這里Shiro和Ehcache整合陕靠。

  • 添加Ehcache的依賴
    在applicationContext-shiro.xml中配置緩存管理器迂尝。
<!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
        <property name="sessionManager" ref="sessionManager" />
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

<!-- 緩存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    </bean>
  • session管理
    在applicationContext-shiro.xml中配置sessionManager:
<!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
        <property name="sessionManager" ref="sessionManager" />
    </bean>
<!-- 會(huì)話管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- session的失效時(shí)長(zhǎng),單位毫秒 -->
        <property name="globalSessionTimeout" value="600000"/>
        <!-- 刪除失效的session -->
        <property name="deleteInvalidSessions" value="true"/>
    </bean>
  • 驗(yàn)證碼
    需要在驗(yàn)證賬號(hào)和名稱之前校驗(yàn)驗(yàn)證碼-------》自定義FormAuthenticationFilter
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
    protected boolean onAccessDenied(ServletRequest request,
            ServletResponse response, Object mappedValue) throws Exception {

        // 校驗(yàn)驗(yàn)證碼
        // 從session獲取正確的驗(yàn)證碼
        HttpSession session = ((HttpServletRequest)request).getSession();
        //頁(yè)面輸入的驗(yàn)證碼
        String randomcode = request.getParameter("randomcode");
        //從session中取出驗(yàn)證碼
        String validateCode = (String) session.getAttribute("validateCode");
        if (!randomcode.equals(validateCode)) {
            // randomCodeError表示驗(yàn)證碼錯(cuò)誤 
            request.setAttribute("shiroLoginFailure", "randomCodeError");
            //拒絕訪問(wèn)剪芥,不再校驗(yàn)賬號(hào)和密碼 
            return true;
        }

        return super.onAccessDenied(request, response, mappedValue);
    }
}
  • 修改FormAuthenticationFilter配置
    修改applicationContext-shiro.xml中對(duì)FormAuthenticationFilter的配置垄开。
<bean id="formAuthenticationFilter"
        class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">

改為

<bean id="formAuthenticationFilter"
        class="cn.itcast.ssm.shiro.MyFormAuthenticationFilter">
  • 登陸頁(yè)面
    添加驗(yàn)證碼:
<TR>
    <TD>驗(yàn)證碼:</TD>
    <TD><input id="randomcode" name="randomcode" size="8" /> 
    <img id="randomcode_img" src="${baseurl}validatecode.jsp" alt="" width="56" height="20" align='absMiddle' /> 
    <a href=javascript:randomcode_refresh()>刷新</a></TD>
</TR>
  • 配置validatecode.jsp匿名訪問(wèn)
    修改applicationContext-shiro.xml:


    validatecode.jsp.png
  • 記住我
    用戶登陸選擇“自動(dòng)登陸”本次登陸成功會(huì)向cookie寫(xiě)身份信息,下次登陸從cookie中取出身份信息實(shí)現(xiàn)自動(dòng)登陸税肪。注意:用戶身份實(shí)現(xiàn)java.io.Serializable接口,向cookie記錄身份信息需要用戶身份信息對(duì)象實(shí)現(xiàn)序列化接口溉躲。
    配置
<!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
        <property name="sessionManager" ref="sessionManager" />
        <property name="cacheManager" ref="cacheManager"/>
        <!-- 記住我 -->
        <property name="rememberMeManager" ref="rememberMeManager"/>
    </bean>

<!-- rememberMeManager管理器 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cookie" ref="rememberMeCookie" />
    </bean>
    <!-- 記住我cookie -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="rememberMe" />
        <!-- 記住我cookie生效時(shí)間30天 -->
        <property name="maxAge" value="2592000" />
    </bean>

修改formAuthenticationFitler添加頁(yè)面中“記住我checkbox”的input名稱:

<bean id="formAuthenticationFilter"
        class="cn.itcast.ssm.shiro.MyFormAuthenticationFilter">
        <!-- 表單中賬號(hào)的input名稱 -->
        <property name="usernameParam" value="usercode" />
        <!-- 表單中密碼的input名稱 -->
        <property name="passwordParam" value="password" />
        <property name="rememberMeParam" value="rememberMe"/>
        <!-- loginurl:用戶登陸地址,此地址是可以http訪問(wèn)的url地址 -->
        <property name="loginUrl" value="/loginsubmit.action" />
    </bean>
  • 登陸頁(yè)面
    在login.jsp中添加“記住我”checkbox益兄。
<TR>
    <TD></TD>
    <TD>
    <input type="checkbox" name="rememberMe" />自動(dòng)登陸
    </TD>
</TR>

附錄:
shiro過(guò)慮器

anon
org.apache.shiro.web.filter.authc.AnonymousFilter
authc
org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port
org.apache.shiro.web.filter.authz.PortFilter
rest
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl
org.apache.shiro.web.filter.authz.SslFilter
user
org.apache.shiro.web.filter.authc.UserFilter
logout
org.apache.shiro.web.filter.authc.LogoutFilter

anon :例子 /admins/=anon 沒(méi)有參數(shù)锻梳,表示可以匿名使用。
authc :例如 /admins/user/
=authc 表示需要認(rèn)證(登錄)才能使用净捅,沒(méi)有參數(shù)
roles :例子 /admins/user/=roles[admin] ,參數(shù)可以寫(xiě)多個(gè)疑枯,多個(gè)時(shí)必須加上引號(hào),并且參數(shù)之間用逗號(hào)分割蛔六,當(dāng)有多個(gè)參數(shù)時(shí)荆永,如admins/user/=roles["admin,guest"] ,每個(gè)參數(shù)通過(guò)才算通過(guò),相當(dāng)于 hasAllRoles() 方法国章。
perms :例子 /admins/user/=perms[user:add:] 參數(shù)可以寫(xiě)多個(gè)具钥,多個(gè)時(shí)必須加上引號(hào),并且參數(shù)之間用逗號(hào)分割液兽,例如 /admins/user/=perms["user:add:,user:modify:"] 骂删,當(dāng)有多個(gè)參數(shù)時(shí)必須每個(gè)參數(shù)都通過(guò)才通過(guò),想當(dāng)于isPermitedAll()方法抵碟。
rest :例子 /admins/user/
=rest[user] ,根據(jù)請(qǐng)求的方法桃漾,相當(dāng)于 /admins/user/=perms[user:method] ,其中method為post,get拟逮,delete等撬统。
por t:例子 /admins/user/
=port[8081] ,當(dāng)請(qǐng)求的url的端口不是8081是跳轉(zhuǎn)到 schemal://serverName:8081?queryString ,其中schmal是協(xié)議http或https等,serverName是你訪問(wèn)的host,8081是url配置里port的端口敦迄,queryString
是你訪問(wèn)的url里的恋追?后面的參數(shù)凭迹。
authcBasic :例如 /admins/user/
=authcBasic 沒(méi)有參數(shù)表示httpBasic認(rèn)證
ssl: 例子 /admins/user/
=ssl 沒(méi)有參數(shù),表示安全的url請(qǐng)求苦囱,協(xié)議為https
user :例如 /admins/user/
*=user 沒(méi)有參數(shù)表示必須存在用戶嗅绸,當(dāng)?shù)侨氩僮鲿r(shí)不做檢查
注:
anon,authcBasic撕彤,auchc鱼鸠,user是認(rèn)證過(guò)濾器,
perms羹铅,roles蚀狰,ssl,rest职员,port是授權(quán)過(guò)濾器

  • shiro的jsp標(biāo)簽
    Jsp頁(yè)面添加:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>

標(biāo)簽名稱 標(biāo)簽條件(均是顯示標(biāo)簽內(nèi)容)
<shiro:authenticated> 登錄之后
<shiro:notAuthenticated> 不在登錄狀態(tài)時(shí)
<shiro:guest> 用戶在沒(méi)有RememberMe時(shí)
<shiro:user> 用戶在RememberMe時(shí)
<shiro:hasAnyRoles name="abc,123" > 在有abc或者123角色時(shí)
<shiro:hasRole name="abc"> 擁有角色abc
<shiro:lacksRole name="abc"> 沒(méi)有角色abc
<shiro:hasPermission name="abc"> 擁有權(quán)限資源abc
<shiro:lacksPermission name="abc"> 沒(méi)有abc權(quán)限資源
<shiro:principal> 顯示用戶身份名稱
<shiro:principal property="username"/> 顯示用戶身份中的屬性值

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末麻蹋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子焊切,更是在濱河造成了極大的恐慌扮授,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件专肪,死亡現(xiàn)場(chǎng)離奇詭異刹勃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)牵祟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)深夯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人诺苹,你說(shuō)我怎么就攤上這事咕晋。” “怎么了收奔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵掌呜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我坪哄,道長(zhǎng)质蕉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任翩肌,我火速辦了婚禮模暗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘念祭。我一直安慰自己兑宇,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布粱坤。 她就那樣靜靜地躺著隶糕,像睡著了一般瓷产。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上枚驻,一...
    開(kāi)封第一講書(shū)人閱讀 52,262評(píng)論 1 308
  • 那天濒旦,我揣著相機(jī)與錄音,去河邊找鬼再登。 笑死尔邓,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锉矢。 我是一名探鬼主播铃拇,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沈撞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起雕什,我...
    開(kāi)封第一講書(shū)人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缠俺,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后贷岸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體壹士,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年偿警,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躏救。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡螟蒸,死狀恐怖盒使,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情七嫌,我是刑警寧澤少办,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站诵原,受9級(jí)特大地震影響英妓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绍赛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一蔓纠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吗蚌,春花似錦腿倚、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)潦刃。三九已至,卻和暖如春懈叹,著一層夾襖步出監(jiān)牢的瞬間乖杠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工澄成, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胧洒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓墨状,卻偏偏與公主長(zhǎng)得像卫漫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肾砂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 簡(jiǎn)介 Apache Shiro是一個(gè)強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗(yàn)證列赎、授權(quán)、密碼學(xué)和會(huì)話管理镐确。使用Shi...
    Austin_Brant閱讀 1,184評(píng)論 1 10
  • 一.Shiro簡(jiǎn)介 Shiro框架是和spring security框架作用差不多的一個(gè)安全認(rèn)證授權(quán)框架包吝,但它更加...
    興厚閱讀 5,534評(píng)論 0 14
  • 一:基礎(chǔ)概念 什么是權(quán)限管理 權(quán)限管理包括用戶身份認(rèn)證和授權(quán)兩部分,簡(jiǎn)稱認(rèn)證授權(quán)源葫。對(duì)于需要訪問(wèn)控制的資源用戶首先經(jīng)...
    QGUOFENG閱讀 560評(píng)論 0 0
  • 1.簡(jiǎn)介 Apache Shiro是Java的一個(gè)安全框架诗越。功能強(qiáng)大,使用簡(jiǎn)單的Java安全框架息堂,它為開(kāi)發(fā)人員提供...
    H_Man閱讀 3,171評(píng)論 4 48
  • 曾經(jīng)在教室里打打鬧鬧嚷狞,無(wú)憂無(wú)就,雖然有寫(xiě)不完的作業(yè)荣堰,但是也不是很在意床未。從沒(méi)想過(guò)以后的以后,走著走著我們就長(zhǎng)大了...
    靜如微塵閱讀 1,167評(píng)論 0 0