四浑彰、認(rèn)證

身份驗(yàn)證

一般需要提供如身份 ID 等一下標(biāo)識信息來表名登錄者的身份,如提供email抹蚀,用戶名/密碼來證明剿牺。
在shiro中,用戶需要提供 principals(身份)和 credentials(證明)給shiro环壤,從而應(yīng)用能夠驗(yàn)證用戶身份晒来。

  • principals:身份,即主體的標(biāo)識屬性郑现,可以是任何屬性湃崩,如用戶名荧降、郵箱等,唯一即可攒读。一個(gè)主體可以有多個(gè)principals朵诫,但只有一個(gè) Primary principals,一般是用戶名/郵箱/手機(jī)號薄扁;
  • credentials 憑證剪返,即只有主體知道的安全值,如密碼等邓梅;
    最常見的 principals 和 credentials 組合就是 用戶名/密碼了脱盲。
基本流程
  1. 獲取當(dāng)前的Subject,調(diào)用SecurityUtils.getSubject();
  2. 測試當(dāng)前用戶是否已經(jīng)被認(rèn)證.即是否已經(jīng)登錄日缨,調(diào)用Subject#isAuthencticated()方法钱反;
  3. 若沒有被認(rèn)證,則把用戶名和密碼封裝為UsernamePasswordToken對象匣距;
  4. 執(zhí)行登錄面哥,調(diào)用Subject#login(AuthenticationToken token)方法,其會自動委托給SecurityManager墨礁;
  5. SecurityManager 負(fù)責(zé)真正的身份驗(yàn)證邏輯幢竹;它會委托給 Authenticator 進(jìn)行身份驗(yàn)證;
  6. 自定義Realm方法恩静,從數(shù)據(jù)庫中獲取對應(yīng)的記錄焕毫,返回給Shiro;
    -a.實(shí)際上需要繼承 org.apache.shiro.realm.AuthenticatingRealm 類驶乾;
    -b.實(shí)現(xiàn) doGetAuthenticationInfo(AuthenticationToken token) 方法.
  7. 由shiro完成對密碼的比對邑飒。
實(shí)現(xiàn)認(rèn)證流程
  1. 將ShiroRealm修改為以下代碼:
public class ShiroRealm extends AuthenticatingRealm {
    //認(rèn)證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        System.out.println("2. " + token.hashCode());
        return null;
    }
}
  1. 實(shí)現(xiàn)請求Controller編碼
@Controller
@RequestMapping("/shiro")
public class ShiroController {

    @RequestMapping("/shiroLogin")
    public String login(@RequestParam("username") String username,@RequestParam("password") String password){
        Subject subject = SecurityUtils.getSubject();

        if (!subject.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken(username,password);
            token.setRememberMe(true);
            try {
                System.out.println("1. " + token.hashCode());
                subject.login(token);
            } catch (AuthenticationException e) {
                e.printStackTrace();
            }
        }

        return "redirect:/list.jsp";
    }
}
  1. login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>login</title>
</head>
<body>
    <h4>login page</h4>

    <form action="shiro/shiroLogin" method="POST">
        username:<input type="text" name="username"/><br><br>
        password:<input type="password" name="password"/><br><br>
        <input type="submit" value="Submit"/>
    </form>

</body>
</html>

4.filterChainDefinitions配置

<property name="filterChainDefinitions">
            <value>
                /login.jsp = anon
                /shiro/shiroLogin = anon

                /** = authc
            </value>
        </property>
  1. 運(yùn)行項(xiàng)目
    在登錄頁面隨意輸入用戶名和密碼,登錄失敗级乐,兩次打印的token#hashCode()相等疙咸,后臺出現(xiàn)"org.apache.shiro.authc.UnknownAccountException"異常,這是因?yàn)闆]有具體實(shí)現(xiàn)認(rèn)證過程风科,接下來將完成認(rèn)證的Realm撒轮。
實(shí)現(xiàn)認(rèn)證Realm

例子:

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.把AuthenticationToken轉(zhuǎn)換為UserNamePasswordToken
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        //2.從UserNamePasswordToken中獲取username
        String username = upToken.getUsername();
        //3.調(diào)用dao層方法,從數(shù)據(jù)庫中查詢username對應(yīng)的用戶記錄
        System.out.println("從數(shù)據(jù)庫中獲取username:" + username + " 所對應(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 對象并返回
        //principal認(rèn)證實(shí)體顶瞳,可以是username,也可以是數(shù)據(jù)表對應(yīng)的用戶的實(shí)體類的對象
        Object principal = username;
        //credentials:密碼
        Object credentials = "123456";
        //realmName:當(dāng)前realm對象的name.調(diào)用父類的getName()
        String realmName = getName();
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);

        return info;
    }

啟動項(xiàng)目:

  • 在登錄頁面輸入"unknown"作為用戶名時(shí),拋出用戶不存在異常慨菱;
  • 輸入"monster"作為用戶名時(shí)焰络,拋出用戶被鎖定異常;
  • 輸入其他用戶名+密碼非"123456"時(shí)符喝,認(rèn)證不通過闪彼;
  • 輸入其他用戶名+密碼"123456"時(shí),認(rèn)證通過协饲,重定向到list.jsp頁面备蚓;
密碼比對

通過 AuthenticatingRealm 的 credentialsMatcher 屬性來進(jìn)行的密碼的比對!

  1. 如何把一個(gè)字符串加密為MD5
    public static void main(String[] args) {
        String hashAlgorithmName = "MD5";
        Object source = "123456";
        Object salt = null;
        int hashIterations = 3;
        SimpleHash hash = new SimpleHash(hashAlgorithmName, source, salt, hashIterations);
        System.out.println(hash.toString());
    }
  1. 替換當(dāng)前 Realm 的 credentialsMatcher 屬性囱稽,直接使用 HashedCredentialsMatcher 對象,并設(shè)置加密算法即可二跋。
    <bean id="shiroRealm" class="org.keyhua.shiro.ShiroRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!--加密算法-->
                <property name="hashAlgorithmName" value="MD5"></property>
                <!--加密次數(shù)-->
                <property name="hashIterations" value="3"></property>
            </bean>
        </property>
    </bean>
  1. MD5鹽值加密
  • 認(rèn)證時(shí)返回 SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 構(gòu)造器
  • 使用 ByteSource.Util.bytes() 來計(jì)算鹽值
  • 鹽值需要唯一:一般使用隨機(jī)字符串或用戶id
  • 使用 new SimpleHash(hashAlgorithmName, source, salt, hashIterations) 來計(jì)算鹽值加密后的密碼
    修改認(rèn)證實(shí)現(xiàn):
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1.把AuthenticationToken轉(zhuǎn)換為UserNamePasswordToken
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        //2.從UserNamePasswordToken中獲取username
        String username = upToken.getUsername();
        //3.調(diào)用dao層方法战惊,從數(shù)據(jù)庫中查詢username對應(yīng)的用戶記錄
        System.out.println("從數(shù)據(jù)庫中獲取username:" + username + " 所對應(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 對象并返回
        //principal認(rèn)證實(shí)體谚鄙,可以是username各拷,也可以是數(shù)據(jù)表對應(yīng)的用戶的實(shí)體類的對象
        Object principal = username;
        //credentials:密碼
        Object credentials = null;
        if ("admin".equals(username)){
            credentials = "9aa75c4d70930277f59d117ce19188b0";
        } else if ("user".equals(username)) {
            credentials = "dd957e81b004227af3e0aa4bde869b25";
        }
        //realmName:當(dāng)前realm對象的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;
    }

啟動項(xiàng)目,用戶名 user/admin闷营,密碼123456烤黍,認(rèn)證通過。

多Realm驗(yàn)證

再自定義一個(gè)relam:

public class SecondRealm extends AuthenticatingRealm {

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("[SecondRealm]  doGetAuthenticationInfo");

        //1.把AuthenticationToken轉(zhuǎn)換為UserNamePasswordToken
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        //2.從UserNamePasswordToken中獲取username
        String username = upToken.getUsername();
        //3.調(diào)用dao層方法傻盟,從數(shù)據(jù)庫中查詢username對應(yīng)的用戶記錄
        System.out.println("從數(shù)據(jù)庫中獲取username:" + username + " 所對應(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 對象并返回
        //principal認(rèn)證實(shí)體,可以是username诽表,也可以是數(shù)據(jù)表對應(yīng)的用戶的實(shí)體類的對象
        Object principal = username;
        //credentials:密碼
        Object credentials = null;
        if ("admin".equals(username)){
            credentials = "28078bcf86c16b80329aa523afb74da57ffb8a11";
        } else if ("user".equals(username)) {
            credentials = "393c16607f34db540e1ec19ab2829044e98efaca";
        }
        //realmName:當(dāng)前realm對象的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;
    }
}

修改applicationContext.xml配置文件:

<!-- 1.配置SecurityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager" />
        <property name="authenticator" ref="authenticator" />
        <property name="realms">
            <list>
                <ref bean="shiroRealm" />
                <ref bean="secondRealm"/>
            </list>
        </property>
    </bean>

    <!--2.配置CacheManager
        2.1.需要引入ehcache的jar及配置文件
    -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
    </bean>

    <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
    </bean>

    <!--3.配置realm
        3.1 直接實(shí)現(xiàn)了Realm接口的bean
    -->
    <bean id="shiroRealm" class="org.keyhua.shiro.ShiroRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!--加密算法-->
                <property name="hashAlgorithmName" value="MD5"></property>
                <!--加密次數(shù)-->
                <property name="hashIterations" value="3"></property>
            </bean>
        </property>
    </bean>

    <bean id="secondRealm" class="org.keyhua.shiro.SecondRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!--加密算法-->
                <property name="hashAlgorithmName" value="SHA1"></property>
                <!--加密次數(shù)-->
                <property name="hashIterations" value="3"></property>
            </bean>
        </property>
    </bean>

多個(gè)Realm進(jìn)行驗(yàn)證時(shí)唉锌,驗(yàn)證規(guī)則通過 AuthenticationStrategy 接口指定
AuthenticationStrategy接口的默認(rèn)實(shí)現(xiàn):
-FirstSuccessfulStrategy:只要有一個(gè)Realm驗(yàn)證成功即可,只返回第一個(gè)Realm身份驗(yàn)證成功的認(rèn)證信息竿奏,其他的忽略袄简;
-AtLeastOneSuccessfulStrategy:只要有一個(gè)Realm驗(yàn)證成功即可,和 FirstSuccessfulStrategy 不同议双,將返回所有Realm身份驗(yàn)證成功的認(rèn)證信息痘番;
-AllSuccessfulStrategy:所有Realm驗(yàn)證成功才算成功,且返回所有Realm身份驗(yàn)證成功的認(rèn)證信息,如果有一個(gè)失敗就失敗了汞舱;

  • ModularRealmAuthenticator 默認(rèn)是 AtLeastOneSuccessfulStrategy 策略

認(rèn)證策略的設(shè)置:

<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <!-- 設(shè)置認(rèn)證策略 -->
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
        </property>
        <property name="realms">
            <list>
                <ref bean="shiroRealm" />
                <ref bean="secondRealm"/>
            </list>
        </property>
    </bean>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伍纫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子昂芜,更是在濱河造成了極大的恐慌莹规,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泌神,死亡現(xiàn)場離奇詭異良漱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)欢际,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進(jìn)店門母市,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人损趋,你說我怎么就攤上這事患久。” “怎么了浑槽?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵蒋失,是天一觀的道長。 經(jīng)常有香客問我桐玻,道長篙挽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任镊靴,我火速辦了婚禮铣卡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘邑闲。我一直安慰自己算行,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布苫耸。 她就那樣靜靜地躺著州邢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪褪子。 梳的紋絲不亂的頭發(fā)上量淌,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天,我揣著相機(jī)與錄音嫌褪,去河邊找鬼呀枢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛笼痛,可吹牛的內(nèi)容都是我干的裙秋。 我是一名探鬼主播琅拌,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼摘刑!你這毒婦竟也來了进宝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤枷恕,失蹤者是張志新(化名)和其女友劉穎党晋,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徐块,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡未玻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了胡控。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扳剿。...
    茶點(diǎn)故事閱讀 38,673評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖昼激,靈堂內(nèi)的尸體忽然破棺而出舞终,到底是詐尸還是另有隱情,我是刑警寧澤癣猾,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站余爆,受9級特大地震影響纷宇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛾方,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一像捶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桩砰,春花似錦拓春、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至煮纵,卻和暖如春懂鸵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背行疏。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工匆光, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酿联。 一個(gè)月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓终息,卻偏偏與公主長得像夺巩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子周崭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評論 2 349

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