Shiro整合Web項(xiàng)目及整合后的開(kāi)發(fā)

title: Shiro整合Web項(xiàng)目及整合后的開(kāi)發(fā)
tags: shiro
categories: shiro


將Shiro框架整合到新的web項(xiàng)目中很簡(jiǎn)單,就是在web項(xiàng)目中導(dǎo)入Shiro的相關(guān)jar包以及整合jar包即可完成整合(是不是很簡(jiǎn)單...哈哈就是這么簡(jiǎn)單)。難的就是整合了Shiro框架后的web項(xiàng)目該如何進(jìn)行開(kāi)發(fā),關(guān)于這一點(diǎn),我將在下方通過(guò)一個(gè)demo演示用戶(hù)的登錄與退出及登錄后的權(quán)限管理帶你入門(mén)加入了Shiro框架后的web項(xiàng)目開(kāi)發(fā)。

寫(xiě)在前邊的話:shiro基礎(chǔ)知識(shí)的講解請(qǐng)看前面的兩篇文章。另外我在github上已經(jīng)放了一個(gè)整合了Spring+SpringMVC+Mybatis的web項(xiàng)目(就是關(guān)于商品的增、刪、改、查操作),接下來(lái)我要講解的就是如何在這個(gè)項(xiàng)目中整合進(jìn)我的Shiro框架,整合Shiro框架前的項(xiàng)目源碼請(qǐng)點(diǎn)擊這里前往我的github,并講解了整合了Shiro框架后的web項(xiàng)目該如何進(jìn)行開(kāi)發(fā),整合了Shiro框架后的完整源碼請(qǐng)點(diǎn)擊這里前往我的github。

用于創(chuàng)建表的sql語(yǔ)句見(jiàn)github上src包下的sql包。

1.需求

在一個(gè)整合了Spring+SpringMVC+Mybatis三個(gè)框架的web項(xiàng)目中再整合進(jìn)Shiro框架,實(shí)現(xiàn)基于Shiro的權(quán)限管理機(jī)制。

2.導(dǎo)入jar包

在原先的項(xiàng)目基礎(chǔ)上只需導(dǎo)入三個(gè)jar包即可:1.shiro-spring.jar。2.shiro-web.jar。3.shiro-core.jar。jar包見(jiàn)我github上的源代碼。成功導(dǎo)入jar包,好,下一步,整合完畢。

項(xiàng)目相關(guān)jsp頁(yè)面請(qǐng)?jiān)趃ithub上自行下載,我們這里只進(jìn)行web后端功能的講解。接下來(lái)在原先的項(xiàng)目基礎(chǔ)上通過(guò)增加用戶(hù)登錄和退出的功能對(duì)用戶(hù)進(jìn)行權(quán)限管理來(lái)講解如何使用Shiro 進(jìn)行開(kāi)發(fā)。

3.在web.xml中配置shiro的filter

在web系統(tǒng)中,shiro也是通過(guò)filter進(jìn)行攔截的。filter攔截后將操作權(quán)交給Spring中配置的filterChain(過(guò)濾鏈兒),shiro提供了很多的filter。

在web.xml中配置shiro的filter,加入如下內(nèi)容:

 <!--在這里配置shiro的filter-->
    <!-- shiro過(guò)慮器,DelegatingFilterProxy通過(guò)代理模式將spring容器中的bean和filter關(guān)聯(lián)起來(lái) -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <!-- 設(shè)置true由servlet容器控制filter的生命周期 -->
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
        <!-- 設(shè)置spring容器filter的bean id,如果不設(shè)置則找與filter-name一致的bean-->
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>shiroFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

4.applicationContext-shiro.xml

在src包下的config包下創(chuàng)建applicationContext-shiro.xml,在applicationContext-shiro.xml中配置web.xml中fitler對(duì)應(yīng)spring容器中的bean以及SecurityManeger和自定義Realm的配置。內(nèi)容如下:


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">


    <!--web.xml中shiro的filter對(duì)應(yīng)的bean-->
    <!-- Shiro 的Web過(guò)濾器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <!-- loginUrl認(rèn)證提交地址,如果沒(méi)有認(rèn)證將會(huì)請(qǐng)求此地址進(jìn)行認(rèn)證,請(qǐng)求此地址將由formAuthenticationFilter進(jìn)行表單認(rèn)證 -->
        <property name="loginUrl" value="/login.action" />
        <!--認(rèn)證成功統(tǒng)一跳轉(zhuǎn)到first.actio,建議不配置,不配置的話shiro認(rèn)證成功會(huì)自動(dòng)到上一個(gè)請(qǐng)求路徑-->
       <property name="successUrl" value="/first.action"/>
        <property name="unauthorizedUrl" value="/refuse.jsp" />
        <!-- 過(guò)慮器鏈定義,從上向下順序執(zhí)行,一般將/**放在最下邊 -->
        <property name="filterChainDefinitions">
            <value>
                <!--對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn)-->
                /images/**=anon
                /js/**=anon
                /style/**=anon

                <!--/**=anon 表示所有的url都可以匿名訪問(wèn),anon是shiro中一個(gè)過(guò)濾器的簡(jiǎn)寫(xiě),關(guān)于shiro中的過(guò)濾器介紹見(jiàn)-->
                /**=anon
            </value>
        </property>
    </bean>

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

    <!--自定義realm-->
    <bean id="customRealm" class="shiro.CustomRealm">
    </bean>
</beans>

在applicationContext-shiro.xml的配置文件中,我們對(duì)系統(tǒng)的任何資源進(jìn)行攔截,即通過(guò)/**=anon設(shè)置系統(tǒng)的任何資源都可以進(jìn)行匿名訪問(wèn)。運(yùn)行程序,在瀏覽器中輸入http://localhost:8080/Shiro/即可訪問(wèn)系統(tǒng),發(fā)現(xiàn)沒(méi)有任何攔截即可以正常訪問(wèn)系統(tǒng),因?yàn)閟hiro的過(guò)濾器沒(méi)有對(duì)系統(tǒng)任何資源進(jìn)行攔截,若想進(jìn)行攔截,可以在上述配置文件中的<value></value>標(biāo)簽之間加入相應(yīng)的攔截語(yǔ)句。下面就通過(guò)增加用戶(hù)的登錄實(shí)現(xiàn)通過(guò)Shiro的filter進(jìn)行認(rèn)證攔截的功能。即當(dāng)訪問(wèn)被shiro攔截的系統(tǒng)資源時(shí),系統(tǒng)會(huì)自動(dòng)跳轉(zhuǎn)到登錄頁(yè)面提醒用戶(hù)需要經(jīng)過(guò)用戶(hù)登錄認(rèn)證后才能正常訪問(wèn)。

5.用Shiro實(shí)現(xiàn)登錄認(rèn)證

5.1原理

用戶(hù)登錄是在一個(gè)表單進(jìn)行的,所以這里我們需要通過(guò)shiro的一個(gè)表單過(guò)濾器(FormAuthenticationFilter)進(jìn)行實(shí)現(xiàn),原理如下:

用戶(hù)沒(méi)有認(rèn)證時(shí),請(qǐng)求loginurl進(jìn)行認(rèn)證,輸入用戶(hù)名和密碼點(diǎn)擊登錄時(shí)將用戶(hù)身份和用戶(hù)密碼提交數(shù)據(jù)到loginurl,然后FormAuthenticationFilter進(jìn)行攔截取出request中的username和password(FormAuthenticationFilter源碼中將username和password兩個(gè)參數(shù)名稱(chēng)寫(xiě)死了,而我們今后是可以將這兩個(gè)參數(shù)名稱(chēng)寫(xiě)在配置文件中的),然后FormAuthenticationFilter會(huì)調(diào)用realm傳入一個(gè)token(將username和password傳入到token中),realm認(rèn)證時(shí)根據(jù)username在數(shù)據(jù)庫(kù)中查詢(xún)用戶(hù)信息(將在數(shù)據(jù)庫(kù)中查詢(xún)到的信息保存在在Activeuser.java對(duì)象中,包括 userid、usercode、username、menus),然后返回一個(gè)authenticationInfo。如果查詢(xún)不到,realm就返回null,同時(shí)FormAuthenticationFilter會(huì)向request域中填充一個(gè)參數(shù)(記錄了異常信息)。

5.2登錄的代碼實(shí)現(xiàn)

可想而知該代碼在控制器Controller中實(shí)現(xiàn),創(chuàng)建一個(gè)LoginController.java,代碼如下:

@Controller
public class LoginController
{
    @RequestMapping("/login")
    public String login(HttpServletRequest request) throws Exception
    {
        //如果登錄失敗從request中獲取認(rèn)證異常信息,shiroLoginFailure就是shiro異常類(lèi)的全限定名
        String exceptionClassName= (String) request.getAttribute("shiroLoginFailure");

        //根據(jù)shiro返回的異常類(lèi)路徑判斷,拋出指定異常信息
        if(exceptionClassName!=null){
            if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
                //最終會(huì)拋給異常處理器
                throw new CustomException("賬號(hào)不存在");
            } else if (IncorrectCredentialsException.class.getName().equals(
                    exceptionClassName)) {
                throw new CustomException("用戶(hù)名/密碼錯(cuò)誤");
            } else if("randomCodeError".equals(exceptionClassName)){
                throw new CustomException("驗(yàn)證碼錯(cuò)誤");
            } else{
                throw new Exception();//最終在異常處理器生成未知錯(cuò)誤
            }
        }
}

5.3配置認(rèn)證攔截過(guò)濾器

在applicationContext.xml的<bean>標(biāo)簽中加入如下標(biāo)簽配置:

 <!-- loginUrl認(rèn)證提交地址,如果沒(méi)有認(rèn)證將會(huì)請(qǐng)求此地址進(jìn)行認(rèn)證,請(qǐng)求此地址將由formAuthenticationFilter進(jìn)行表單認(rèn)證 -->
        <property name="loginUrl" value="/login.action" />

并在<value>標(biāo)簽之間加入相應(yīng)的攔截語(yǔ)句:

                <!-- -/**=authc 表示所有的url都必須認(rèn)證通過(guò)才可以訪問(wèn)- -->
                /** = authc


                <!--/**=anon 表示所有的url都可以匿名訪問(wèn)-->
               可以匿名訪問(wèn)的頁(yè)面我們以后再配置

運(yùn)行服務(wù)器,訪問(wèn)系統(tǒng)首頁(yè)發(fā)現(xiàn)系統(tǒng)會(huì)對(duì)我們?cè)L問(wèn)的資源進(jìn)行攔截并退回到登錄頁(yè)面,但是這里會(huì)有個(gè)問(wèn)題發(fā)現(xiàn)登錄頁(yè)面的靜態(tài)資源也被攔截了,所以我們應(yīng)在<value>標(biāo)簽之間加入對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn)的設(shè)置:

 <!--對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn)-->
                /images/**=anon
                /js/**=anon
                /style/**=anon

                <!--請(qǐng)求這個(gè)地址就自動(dòng)退出-->
                /logout.action=logout

                <!--商品查詢(xún)需要商品查詢(xún)權(quán)限-->
                /items/queryItems.action=perms[item:query]

                /items/editItems.action=perms[item:edit]


                <!-- -/**=authc 表示所有的url都必須認(rèn)證通過(guò)才可以訪問(wèn)- -->
                /** = authc

                <!--/**=anon 表示所有的url都可以匿名訪問(wèn)-->
                可以匿名訪問(wèn)的頁(yè)面我們以后再配置

然后運(yùn)行程序,訪問(wèn)系統(tǒng)資源時(shí)系統(tǒng)發(fā)現(xiàn)用戶(hù)信息沒(méi)有得到認(rèn)證所以會(huì)退回到登錄頁(yè)面讓你進(jìn)行登錄,你只有輸入了密碼為111111后才能成功完成登錄,因?yàn)槲覀冊(cè)谧远xCustomRealm.java文件中只是模擬從數(shù)據(jù)庫(kù)中查到的數(shù)據(jù)(我們?cè)O(shè)置查到的密碼為111111)。登錄成功后便可進(jìn)行系統(tǒng)的訪問(wèn)了,但是登錄成功后只能訪問(wèn)系統(tǒng)的首頁(yè),因?yàn)槲覀冞€沒(méi)有對(duì)該用戶(hù)進(jìn)行權(quán)限分配指定該用戶(hù)可以對(duì)系統(tǒng)的哪些資源進(jìn)行操作了,所以這里當(dāng)然只能訪問(wèn)系統(tǒng)首頁(yè)。當(dāng)然運(yùn)行程序之前你得完成自定義CustomRealm的代碼,我們采用前篇文章的自定義CustomRealm的內(nèi)容,如下:

public class CustomRealm extends AuthorizingRealm
{

    //注入service
    @Autowired
    private SysService sysService;

    //設(shè)置realm的名稱(chēng)
    @Override
    public void setName(String name) {
        super.setName("customRealm");
    }

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

        //token是用戶(hù)輸入的
        //第一步:叢token中取出身份信息
        String userCode= (String) token.getPrincipal();

        //第二步:根據(jù)用戶(hù)輸入的userCode叢數(shù)據(jù)庫(kù)查詢(xún)


        //模擬叢數(shù)據(jù)庫(kù)查詢(xún)到的密碼
        String password="111111";

      

        //如果查不到返回null,

        //如果查詢(xún)到,返回認(rèn)證信息AuthenticationInfo

        ///將activeUser設(shè)置到simpleAuthenticationInfo
        SimpleAuthenticationInfo simpleAuthenticationInfo=new
                SimpleAuthenticationInfo(userCode,password,this.getName());


        return simpleAuthenticationInfo;
    }

    //用于授權(quán)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)  {
        return null;
    }
}

這樣便完成用戶(hù)的認(rèn)證功能,接下來(lái)是退出功能。

6.退出

退出功能就是當(dāng)用戶(hù)點(diǎn)擊退出按鈕時(shí)清楚保存在session中信息,這個(gè)功能不用我們實(shí)現(xiàn),交給Shiro的LogoutFilter過(guò)濾器即可實(shí)現(xiàn):當(dāng)我們?cè)L問(wèn)一個(gè)退出的url時(shí),由LogoutFilter攔截住,然后清楚session。

6.1配置退出過(guò)濾器

在applicationContext-shiro.xml的<value>標(biāo)簽中加入如下內(nèi)容:

<!--請(qǐng)求這個(gè)地址就自動(dòng)退出-->
/logout.action=logout

即完成清楚session即退出系統(tǒng)的功能。

7.實(shí)現(xiàn)用戶(hù)成功登錄后將認(rèn)證信息顯示在頁(yè)面上

需求:1.認(rèn)證后用戶(hù)菜單在首頁(yè)顯示。2.認(rèn)證后用戶(hù)的信息(例如用戶(hù)名)在頁(yè)頭顯示。

7.1修改自定義Realm設(shè)置完整的認(rèn)證信息

先前我們通過(guò)realm在數(shù)據(jù)庫(kù)中通過(guò)用戶(hù)名查詢(xún)到的用戶(hù)信息只有密碼,而現(xiàn)在我們需要查詢(xún)到的數(shù)據(jù)包括用戶(hù)可以操作的用戶(hù)菜單、usercode用戶(hù)id、username用戶(hù)名等。

我們先將這些信息用靜態(tài)代碼實(shí)現(xiàn)(即仍然沒(méi)有涉及到數(shù)據(jù)庫(kù)的查詢(xún)):

    //模擬叢數(shù)據(jù)庫(kù)查詢(xún)到的密碼
        String password="111111";

       //activeUser就是用戶(hù)的身份信息
        ActiveUser activeUser=new ActiveUser();
        activeUser.setUserid("zhangsan");
        activeUser.setUsercode("zhangsan");
        activeUser.setUsername("張三");

        //根據(jù)用戶(hù)id取出菜單
        //通過(guò)service取出菜單
        List<SysPermission> menus=null;
        try {
            menus=sysService.findMenuListByUserId("zhangsan");

        } catch (Exception e) {
            e.printStackTrace();
        }

        //將用戶(hù)菜單設(shè)置到activeUser
        activeUser.setMenus(menus);


        //如果查不到返回null,

        //如果查詢(xún)到,返回認(rèn)證信息AuthenticationInfo

        ///將activeUser設(shè)置到simpleAuthenticationInfo
        SimpleAuthenticationInfo simpleAuthenticationInfo=new
                SimpleAuthenticationInfo(activeUser,password,this.getName());

然后修改first.action(在控制器FirstAction.java中實(shí)現(xiàn)該方法,當(dāng)訪問(wèn)系統(tǒng)主頁(yè)index.jsp時(shí)該index.jsp頁(yè)面中設(shè)置將頁(yè)面進(jìn)行跳轉(zhuǎn)到first.action)將認(rèn)證信息在頁(yè)面中進(jìn)行顯示:

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

        //從shiro的session中取出activeUser
        Subject subject= SecurityUtils.getSubject();
        //取出身份信息
        ActiveUser activeUser= (ActiveUser) subject.getPrincipal();
        //通過(guò)model傳到頁(yè)面
        model.addAttribute("activeUser",activeUser);
        
        return "/first";
    }
    
    //歡迎頁(yè)面
    @RequestMapping("/welcome")
    public String welcome(Model model)throws Exception{
        
        return "/welcome";
        
    }
}   

運(yùn)行程序,登錄該系統(tǒng)后發(fā)現(xiàn)出現(xiàn)商品管理的菜單,我們?nèi)匀粵](méi)有對(duì)商品進(jìn)行操作的權(quán)限,所以接下來(lái)要講解通過(guò)Shiro如何對(duì)用戶(hù)進(jìn)行授權(quán)操作。仍然在自定義Realm中模擬從數(shù)據(jù)庫(kù)查詢(xún)到的用戶(hù)權(quán)限。

8.授權(quán)過(guò)濾器的測(cè)試

在Shiro中使用PermissionsAuthorizationFilter對(duì)用戶(hù)進(jìn)行授權(quán),首先在applicationContext-shiro.xml中進(jìn)行配置,加入如下內(nèi)容:

<!--商品查詢(xún)需要商品查詢(xún)權(quán)限-->
/items/queryItems.action=perms[item:query]
<!--商品修改需要商品修改權(quán)限-->
/items/editItems.action=perms[item:edit]

通過(guò)上述配置,用戶(hù)在認(rèn)證通過(guò)后請(qǐng)求/items/queryItems.action的資源時(shí)會(huì)被PermissionsAuthorizationFilter攔截,發(fā)現(xiàn)需要“item:query”權(quán)限,然后PermissionsAuthorizationFilter調(diào)用realm中的doGetAuthorizationInfo獲取數(shù)據(jù)庫(kù)中正確的權(quán)限,對(duì)二者進(jìn)行對(duì)比,如果“item:query”在realm返回的權(quán)限列表中,授權(quán)通過(guò)。如果不通過(guò),則授權(quán)失敗,跳轉(zhuǎn)到refuse.jsp頁(yè)面。所以我們還需要在applicationContext-shiro.xml進(jìn)行授權(quán)失敗后跳轉(zhuǎn)到的頁(yè)面配置:

   <property name="unauthorizedUrl" value="/refuse.jsp" />

在自定義Realm中的授權(quán)方法中加入如下內(nèi)容模擬從數(shù)據(jù)庫(kù)中查到的用戶(hù)權(quán)限,內(nèi)容如下:

  //用于授權(quán)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        //從principals獲取主身份信息
        //將getPrimaryPrincipal方法返回值轉(zhuǎn)為真實(shí)身份類(lèi)型(在上邊的goGetAuthenticationInfo認(rèn)證通過(guò)填充到SimpleAuthenticationInfo)
        ActiveUser activeUser= (ActiveUser) principals.getPrimaryPrincipal();

        //根據(jù)身份信息獲取權(quán)限信息,
        //模擬從數(shù)據(jù)庫(kù)中獲取到的動(dòng)態(tài)權(quán)限數(shù)據(jù)
        List<String> permissions=new ArrayList<>();
        permissions.add("user:create");//模擬user的創(chuàng)建權(quán)限
        permissions.add("item:query");//模擬查詢(xún)權(quán)限
        permissions.add("item:add");//模擬商品的添加權(quán)限
        permissions.add("item:edit");//模擬修改權(quán)限

        //查到權(quán)限數(shù)據(jù),返回授權(quán)信息(包括上邊的permissions)
        SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();

        //將上邊查詢(xún)到授權(quán)信息填充到simpleAuthorizationInfo對(duì)象中
        simpleAuthorizationInfo.addStringPermissions(permissions);

        return simpleAuthorizationInfo;
    }

在該自定義Realm中設(shè)置從數(shù)據(jù)庫(kù)中查詢(xún)到的用戶(hù)權(quán)限有創(chuàng)建用戶(hù)、查詢(xún)商品、添加商品、編輯商品等權(quán)限,所以我們?cè)谶\(yùn)行程序后訪問(wèn)服務(wù)器后就會(huì)得到這些權(quán)限。

9.問(wèn)題總結(jié)

1、在applicationContext-shiro.xml中配置過(guò)慮器鏈接,需要將全部的url和權(quán)限對(duì)應(yīng)起來(lái)進(jìn)行配置,比較發(fā)麻不方便使用。

2、每次授權(quán)都需要調(diào)用realm查詢(xún)數(shù)據(jù)庫(kù),對(duì)于系統(tǒng)性能有很大影響,可以通過(guò)shiro緩存來(lái)解決。

10.Shiro的過(guò)濾器

過(guò)濾器簡(jiǎn)稱(chēng) 對(duì)應(yīng)的java類(lèi)
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,anon后面沒(méi)有參數(shù),表示該路徑下的資源可以匿名使用。

authc:例如/admins/user/**=authc,authc后面沒(méi)有參數(shù),表示該路徑下的資源需要認(rèn)證(登錄)才能使用,F(xiàn)ormAuthenticationFilter是表單認(rèn)證,沒(méi)有參數(shù)。

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()方法。

user:例如/admins/user/**=user,user后面沒(méi)有參數(shù),表示必須存在用戶(hù), 身份認(rèn)證通過(guò)或通過(guò)記住我認(rèn)證通過(guò)的可以訪問(wèn),當(dāng)?shù)侨氩僮鲿r(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()方法。

rest:例如/admins/user/**=rest[user],根據(jù)請(qǐng)求的方法,相當(dāng)于/admins/user/**=perms[user:method],其中method為post,get,delete等。

上述涉及到的過(guò)濾器中:anon,authcBasic,auchc,user是認(rèn)證過(guò)濾器,perms,roles,ssl,rest,port是授權(quán)過(guò)濾器。

上面我們自定義的Realm進(jìn)行認(rèn)證和授權(quán)時(shí)都是通過(guò)將用戶(hù)輸入的信息和我們自己給的數(shù)據(jù)進(jìn)行比對(duì),而沒(méi)有從數(shù)據(jù)庫(kù)中查詢(xún)到相關(guān)信息。所以接下來(lái)要講通過(guò)Realm從數(shù)據(jù)庫(kù)中查詢(xún)認(rèn)證數(shù)據(jù)和權(quán)限數(shù)據(jù)的開(kāi)發(fā)重新實(shí)現(xiàn)上述的登錄和授權(quán)功能。

11.通過(guò)查詢(xún)數(shù)據(jù)庫(kù)完成認(rèn)證

11.1需求

修改realm的doGetAuthenticationInfo()方法,從數(shù)據(jù)庫(kù)查詢(xún)用戶(hù)信息,realm返回的用戶(hù)信息中包括(數(shù)據(jù)庫(kù)庫(kù)中經(jīng)過(guò)md5加密后的串和salt),實(shí)現(xiàn)讓shiro進(jìn)行散列串的校驗(yàn)。

11.2修改doGetAuthenticationInfo從數(shù)據(jù)庫(kù)查詢(xún)用戶(hù)信息

修改自定義CustomRealm代碼,由于要向數(shù)據(jù)庫(kù)中查詢(xún)數(shù)據(jù),所以需要在CustomRealm中注入SysService對(duì)象。修改后的代碼如下:

public class CustomRealm extends AuthorizingRealm
{

    //注入service
    @Autowired
    private SysService sysService;

    //設(shè)置realm的名稱(chēng)
    @Override
    public void setName(String name) {
        super.setName("customRealm");
    }

    //realm的認(rèn)證方法,從數(shù)據(jù)庫(kù)查詢(xún)用戶(hù)信息
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //token是用戶(hù)輸入的
        //第一步:叢token中取出身份信息
        String userCode= (String) token.getPrincipal();

        //第二步:根據(jù)用戶(hù)輸入的userCode叢數(shù)據(jù)庫(kù)查詢(xún)
        SysUser sysUser =null;
        try {
            sysUser=sysService.findSysUserByUserCode(userCode);
        } catch (Exception e) {
            e.printStackTrace();
        }


        //判斷是否從數(shù)據(jù)庫(kù)中查詢(xún)到用戶(hù)信息
        if (sysService==null)
        {
            return null;
        }

        //從數(shù)據(jù)庫(kù)查詢(xún)到的密碼
        String password=sysUser.getPassword();

        //鹽salt
        String salt=sysUser.getSalt();
        System.out.println(salt);

       //activeUser就是用戶(hù)的身份信息
        ActiveUser activeUser=new ActiveUser();
        activeUser.setUserid(sysUser.getId());
        activeUser.setUsercode(sysUser.getUsercode());
        activeUser.setUsername(sysUser.getUsername());

        //根據(jù)用戶(hù)id取出菜單
        //通過(guò)service取出菜單
        List<SysPermission> menus=null;
        try {
            menus=sysService.findMenuListByUserId(sysUser.getId());

        } catch (Exception e) {
            e.printStackTrace();
        }

        //將用戶(hù)菜單設(shè)置到activeUser
        activeUser.setMenus(menus);


        //如果查不到返回null,

        //如果查詢(xún)到,返回認(rèn)證信息AuthenticationInfo

        ///將activeUser設(shè)置到simpleAuthenticationInfo
        SimpleAuthenticationInfo simpleAuthenticationInfo=new
                SimpleAuthenticationInfo(activeUser,password, ByteSource.Util.bytes(salt),this.getName());


        return simpleAuthenticationInfo;
    }
}

就是將之前的靜態(tài)數(shù)據(jù)換成從數(shù)據(jù)庫(kù)中查詢(xún)到的動(dòng)態(tài)數(shù)據(jù)。

11.3設(shè)置憑證匹配器

數(shù)據(jù)庫(kù)中存儲(chǔ)到的md5的散列值,在realm中需要設(shè)置數(shù)據(jù)庫(kù)中的散列值它使用散列算法及散列次數(shù),讓shiro進(jìn)行散列對(duì)比時(shí)和原始數(shù)據(jù)庫(kù)中的散列值使用的算法一致。在applicationContext-shiro.xml中配置如下內(nèi)容:

    <!-- 憑證匹配器 -->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5" />
        <property name="hashIterations" value="1" />
    </bean>

并將憑證匹配器設(shè)置到我們自定義realm的配置中,在自定義realm的標(biāo)簽中加入如下標(biāo)簽:

  <!--自定義realm-->
    <bean id="customRealm" class="shiro.CustomRealm">
        <!--將憑證匹配器設(shè)置到realm中,realm按照憑證匹配器要求進(jìn)行散列-->
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>

這樣我們便通過(guò)realm將用戶(hù)輸入的信息和從數(shù)據(jù)庫(kù)中查到的數(shù)據(jù)進(jìn)行對(duì)比從而完成了認(rèn)證。

12.通過(guò)查詢(xún)數(shù)據(jù)庫(kù)完成授權(quán)

12.1需求

修改realm的doGetAuthorizationInfo()方法從數(shù)據(jù)庫(kù)查詢(xún)權(quán)限信息。授權(quán)的方式上面介紹過(guò)三種,正式開(kāi)發(fā)中我們使用注解式授權(quán)方法和jsp標(biāo)簽授權(quán)方法。

12.2修改doGetAuthorizationInfo從數(shù)據(jù)庫(kù)查詢(xún)權(quán)限

修改自定義Realm中的doGetAuthorizationInfo授權(quán)方法,修改后的代碼如下:

 //用于授權(quán)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        //從principals獲取主身份信息
        //將getPrimaryPrincipal方法返回值轉(zhuǎn)為真實(shí)身份類(lèi)型(在上邊的goGetAuthenticationInfo認(rèn)證通過(guò)填充到SimpleAuthenticationInfo)
        ActiveUser activeUser= (ActiveUser) principals.getPrimaryPrincipal();

        //根據(jù)身份信息獲取權(quán)限信息,
        //從數(shù)據(jù)庫(kù)中獲取到的動(dòng)態(tài)權(quán)限數(shù)據(jù)
        List<SysPermission> permissionList=null;
        try {
            permissionList=sysService.findPermissionListByUserId(activeUser.getUserid());
        } catch (Exception e) {
            e.printStackTrace();
        }

        List<String> permissions=new ArrayList<>();

        if (permissionList!=null)
        {
            for (SysPermission sysPermission:permissionList)
            {
                //將數(shù)據(jù)庫(kù)中的權(quán)限標(biāo)簽符放入集合
                permissions.add(sysPermission.getPercode());
            }
        }

        //查到權(quán)限數(shù)據(jù),返回授權(quán)信息(包括上邊的permissions)
        SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();

        //將上邊查詢(xún)到授權(quán)信息填充到simpleAuthorizationInfo對(duì)象中
        simpleAuthorizationInfo.addStringPermissions(permissions);

        return simpleAuthorizationInfo;
    }

我們之前給用戶(hù)授權(quán)是在applicationContext-shiro.xml中的<value>標(biāo)簽中采用:

 /items/queryItems.action=perms[item:query]
 <!--商品修改需要商品修改權(quán)限-->
 /items/editItems.action=perms[item:edit]

的方式給用戶(hù)訪問(wèn)的資源進(jìn)行授權(quán),所以接下來(lái)我們講解注解授權(quán),將上述進(jìn)行授權(quán)的內(nèi)容注釋掉,注解授權(quán)的步驟如下。

12.3開(kāi)啟controller類(lèi)aop支持

對(duì)系統(tǒng)中類(lèi)的方法給用戶(hù)授權(quán),建議在controller層進(jìn)行方法授權(quán),在springmvc.xml中配置:

 <!-- 開(kāi)啟aop,對(duì)類(lèi)代理 -->
    <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>

12.4在controller方法中添加注解

給商品查詢(xún)方法添加查詢(xún)商品權(quán)限:

@RequestMapping("/queryItems")
@RequiresPermissions("item:query")
public ModelAndView queryItems() throws Exception {
...
}

給商品修改方法添加商品更新權(quán)限:

@RequestMapping(value = "/editItems",method = RequestMethod.GET)
@RequiresPermissions("item:update")//執(zhí)行此方法需要item:update權(quán)限
public String editItems(Model model, Integer id) throws Exception
{
...
}

給商品修改頁(yè)面的提交方法添加商品更新權(quán)限:

@RequestMapping("/editItemSubmit")
@RequiresPermissions("item:update")//執(zhí)行此方法需要item:update權(quán)限
public String editItemSubmit(Model model,Integer id,
                                 @Validated(value = {ValidGroup1.class}) @ModelAttribute(value = "itemsCustom") ItemsCustom itemsCustom,
                                 BindingResult bindingResult,
                                 //上傳圖片
                                 MultipartFile pictureFile
                                 ) throws Exception
{
...
}

另一種方式在jsp標(biāo)簽授權(quán)。

12.5jsp標(biāo)簽授權(quán)

在itemsList.jsp頁(yè)面最上方添加如下標(biāo)簽:

<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>

然后修改itemsList.jsp頁(yè)面部分內(nèi)容:

<td>
<!--有item:update權(quán)限才現(xiàn)實(shí)修改鏈接,沒(méi)有權(quán)限則不顯示修改鏈接-->
<shiro:hasPermission name="item:update">
    <a href="${pageContext.request.contextPath }/items/editItems.action?id=${item.id}">修改</a>
</shiro:hasPermission>
</td>

相關(guān)jsp標(biāo)簽授權(quán)的解釋如下表:

標(biāo)簽名稱(chēng) 標(biāo)簽條件(均是顯示標(biāo)簽內(nèi)容)
<shiro:authenticated> 登錄之后
<shiro:notAuthenticated> 不在登錄狀態(tài)時(shí)
<shiro:guest> 用戶(hù)在沒(méi)有RememberMe時(shí)
<shiro:user> 用戶(hù)在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> 顯示用戶(hù)身份名稱(chēng)
<shiro:principal property="username"/> 顯示用戶(hù)身份中的屬性值

12.6授權(quán)測(cè)試

當(dāng)調(diào)用controller的一個(gè)方法(比如ItemsController的queryItems()方法),由于該方法加了@RequiresPermissions("item:query") ,shiro調(diào)用realm獲取數(shù)據(jù)庫(kù)中的權(quán)限信息,看"item:query"是否在權(quán)限數(shù)據(jù)中存在,如果不存在就拒絕訪問(wèn),如果存在就授權(quán)通過(guò)。

當(dāng)展示一個(gè)jsp頁(yè)面時(shí),頁(yè)面中如果遇到<shiro:hasPermission name="item:update">,shiro調(diào)用realm獲取數(shù)據(jù)庫(kù)中的權(quán)限信息,看item:update是否在權(quán)限數(shù)據(jù)中存在,如果不存在就拒絕訪問(wèn),如果存在就授權(quán)通過(guò)。

問(wèn)題:只要遇到注解或jsp標(biāo)簽的授權(quán),都會(huì)調(diào)用realm方法查詢(xún)數(shù)據(jù)庫(kù),需要使用緩存解決此問(wèn)題。

13.Shiro緩存

需求:針對(duì)上邊授權(quán)頻繁查詢(xún)數(shù)據(jù)庫(kù),需要使用shiro緩存。

13.1緩存流程

shiro中提供了對(duì)認(rèn)證信息和授權(quán)信息的緩存。shiro默認(rèn)是關(guān)閉認(rèn)證信息緩存的,對(duì)于授權(quán)信息的緩存shiro默認(rèn)開(kāi)啟的。主要研究授權(quán)信息緩存,因?yàn)槭跈?quán)的數(shù)據(jù)量大。

當(dāng)用戶(hù)認(rèn)證通過(guò)時(shí),該用戶(hù)第一次授權(quán):調(diào)用realm查詢(xún)數(shù)據(jù)庫(kù)查詢(xún)?cè)撚脩?hù)的授權(quán)信息然后給該用戶(hù)授權(quán)。該用戶(hù)第二次授權(quán)時(shí):不調(diào)用realm查詢(xún)數(shù)據(jù)庫(kù),直接從緩存中取出授權(quán)信息(權(quán)限標(biāo)識(shí)符)然后給該用戶(hù)授權(quán)。

13.2使用ehcache緩存

13.2.1添加jar包

包括ehcache-core.jar和整合包shiro-ehcache.jar。

13.2.2配置cacheManager

在application-shiro.xml中加入ehcache的緩存管理器配置,如下:

 <!-- 緩存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
    </bean>

然后將緩存管理器注入到securityManager安全管理器中,在安全管理器中加入如下內(nèi)容:

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

        <!--注入緩存管理器-->
        <property name="cacheManager" ref="cacheManager"/>
     </bean>

然后需要進(jìn)行shiro-ecache的配置,跟我們?cè)贛ybatis中整合ehcache的內(nèi)容一樣,在config包下創(chuàng)建一個(gè)shiro-ehcache.xml文件,內(nèi)容如下:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!--diskStore:緩存數(shù)據(jù)持久化的目錄 地址  -->
    <diskStore path="/Users/codingboy/develop/ehcache" />
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

13.2.3緩存清空

需求:如果用戶(hù)正常退出,緩存自動(dòng)清空;如果用戶(hù)非正常退出,緩存自動(dòng)清空。如果修改了用戶(hù)的權(quán)限,而用戶(hù)不退出系統(tǒng),修改的權(quán)限無(wú)法立即生效,需要手動(dòng)進(jìn)行編程實(shí)現(xiàn):

在權(quán)限修改后調(diào)用realm的clearCache方法清除緩存,下邊的代碼正常開(kāi)發(fā)時(shí)要放在service中調(diào)用。這里我們只是進(jìn)行一下測(cè)試。

在realm中添加如下方法:

//清除緩存
    public void clearCached() {
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principals);
    }

然后便可以進(jìn)行測(cè)試類(lèi)的編寫(xiě)了,在controller包下創(chuàng)建一個(gè)ClearShiroCache.java,代碼如下:

@Controller
public class ClearShiroCache
{
    @Autowired
    private CustomRealm customRealm;

    @RequestMapping("/clearShiroCache")
    public String clearShiroCache()
    {

        //清除緩存,將來(lái)開(kāi)發(fā)要在service調(diào)用
        customRealm.clearCached();
        return "success";
    }
}

然后進(jìn)行測(cè)試,在服務(wù)器中輸入http://localhost:8080/Shiro,進(jìn)行登錄,訪問(wèn)系統(tǒng)首頁(yè)。此時(shí)再輸入http://localhost:8080/Shiro/clearShiroCache即可清除該用戶(hù)的權(quán)限。這里我們只進(jìn)行測(cè)試,以后是在service中進(jìn)行。

14.會(huì)話管理器sessionManager

和shiro整合后,使用shiro的sessionManager對(duì)會(huì)話session進(jìn)行管理,此外shiro還提供sessionDao操作會(huì)話數(shù)據(jù)。

配置sessionManager,在application-shiro.xml中加入會(huì)話管理器的配置內(nèi)容:

   <!-- 會(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>

然后將該管理器注入到安全管理器中:

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

        <!--注入緩存管理器-->
        <property name="cacheManager" ref="cacheManager"/>

        <!--注入會(huì)話管理器-->
        <property name="sessionManager" ref="sessionManager" />
    </bean>

15.實(shí)現(xiàn)驗(yàn)證碼

15.1思路

shiro使用FormAuthenticationFilter進(jìn)行表單認(rèn)證,驗(yàn)證校驗(yàn)的功能應(yīng)該加在FormAuthenticationFilter中,在認(rèn)證之前進(jìn)行驗(yàn)證碼校驗(yàn),而shiro為我們提供的FormAuthenticationFilter中沒(méi)有對(duì)驗(yàn)證碼進(jìn)行認(rèn)證。所以我們需要寫(xiě)FormAuthenticationFilter的子類(lèi),繼承FormAuthenticationFilter,改寫(xiě)它的認(rèn)證方法,在認(rèn)證之前進(jìn)行驗(yàn)證碼校驗(yàn)。

15.2自定義FormAuthenticationFilter

在src包下的shiro包下創(chuàng)建一個(gè)CustomFormAuthenticationFilter.java,內(nèi)容如下:

public class CustomFromAuthenticationFilter extends FormAuthenticationFilter
{
    @Override
    protected boolean onAccessDenied(ServletRequest request,
                                     ServletResponse response) throws Exception {

        //在這里進(jìn)行驗(yàn)證碼的校驗(yàn)
        HttpServletRequest httpServletRequest= (HttpServletRequest) request;
        HttpSession session=httpServletRequest.getSession();
        //取出session中的正確驗(yàn)證碼
        String validateCode= (String) session.getAttribute("validateCode");

        //取出頁(yè)面的驗(yàn)證碼
        String randomcode=httpServletRequest.getParameter("randomcode");
        if (randomcode!=null&&validateCode!=null&&!randomcode.equals(validateCode))
        {
            //如果校驗(yàn)失敗,將驗(yàn)證碼錯(cuò)誤的失敗信息,通過(guò)shiroLoginFailure設(shè)置到request中
            httpServletRequest.setAttribute("shiroLoginFailure","randomCodeError");

            //拒絕訪問(wèn),不再校驗(yàn)賬號(hào)和密碼
            return true;

        }

        return super.onAccessDenied(request, response);
    }
}

15.3配置自定義FormAuthenticationFilter

在shiro中加入配置信息:

 <!--自定義form認(rèn)證過(guò)濾器-->
    <bean id="formAuthenticationFilter"
          class="shiro.CustomFromAuthenticationFilter">
        <!-- 表單中賬號(hào)的input名稱(chēng) -->
        <property name="usernameParam" value="username" />
        <!-- 表單中密碼的input名稱(chēng) -->
        <property name="passwordParam" value="password" />
        <!--記住我input的名稱(chēng)-->
        <property name="rememberMeParam" value="rememberMe"/>
    </bean>

然后將它注入到Shiro的過(guò)濾器中,在<bean id="shiroFilter">中加入自定義filter的配置:

<!--自定義filter-->
<property name="filters">
    <map>
        !-- 將自定義的FormAuthenticationFilter注入shiroFiler中 -->
        <entry key="authc" value-ref="formAuthenticationFilter" />
    </map>
</property>

然后在login.action中對(duì)驗(yàn)證錯(cuò)誤進(jìn)行解析:

else if("randomCodeError".equals(exceptionClassName)){
                throw new CustomException("驗(yàn)證碼錯(cuò)誤");
} 

在登錄頁(yè)面中添加驗(yàn)證碼:

<TR>
    <TD>密 碼:</TD>
    <TD><input type="password" id="pwd" name="password" style="WIDTH: 130px" />
    </TD>
</TR>
<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>

在shiro的過(guò)濾器filter中配置匿名訪問(wèn)驗(yàn)證碼的圖片資源:

            <value>
                <!--對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn)-->
                /images/**=anon
                /js/**=anon
                /style/**=anon

                <!--驗(yàn)證碼-->
                /validatecode.jsp=anon

                <!--請(qǐng)求這個(gè)地址就自動(dòng)退出-->
                /logout.action=logout

                <!--商品查詢(xún)需要商品查詢(xún)權(quán)限,取消url攔截配置,采用注解授權(quán)-->
                <!--/items/queryItems.action=perms[item:query]-->
                <!--&lt;!&ndash;商品修改需要商品修改權(quán)限&ndash;&gt;-->
                <!--/items/editItems.action=perms[item:edit]-->
                <!-- -/**=authc 表示所有的url都必須認(rèn)證通過(guò)才可以訪問(wèn)- -->
                /** = authc
                <!--/**=anon 表示所有的url都可以匿名訪問(wèn)-->

            </value>

16.實(shí)現(xiàn)"記住我"功能

用戶(hù)登陸選擇“自動(dòng)登陸”本次登陸成功會(huì)向cookie寫(xiě)身份信息,下次登陸從cookie中取出身份信息實(shí)現(xiàn)自動(dòng)登陸。

這里涉及到session的序列化與反序列化,所以涉及到的pojo類(lèi)都應(yīng)該實(shí)現(xiàn)java.io.Serializable接口。首先讓ActiveUser.java實(shí)現(xiàn)java.io.Serializable接口,然后讓SysPermission.java實(shí)現(xiàn)java.io.Serializable接口。

16.1配置rememberMeManager

在application-shiro.xml中加入記住我的管理器,內(nèi)容如下:

<!-- 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">
        <!--rememberMe時(shí)cookie的名字-->
        <constructor-arg value="rememberMe" />
        <!-- 記住我cookie生效時(shí)間30天 -->
        <property name="maxAge" value="2592000" />
    </bean>

并注入到securityManager中:

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

        <!--注入緩存管理器-->
        <property name="cacheManager" ref="cacheManager"/>

        <!--注入會(huì)話管理器-->
        <property name="sessionManager" ref="sessionManager" />

        <!-- 記住我 -->
        <property name="rememberMeManager" ref="rememberMeManager"/>
    </bean>

然后修改登錄頁(yè)面,加入記住我的按鈕,然后在application-shiro.xml的我們自定義form認(rèn)證過(guò)濾器的配置中加入rememberMe的input名稱(chēng)配置:

 <!--自定義form認(rèn)證過(guò)濾器-->
    <bean id="formAuthenticationFilter"
          class="shiro.CustomFromAuthenticationFilter">
        <!-- 表單中賬號(hào)的input名稱(chēng) -->
        <property name="usernameParam" value="username" />
        <!-- 表單中密碼的input名稱(chēng) -->
        <property name="passwordParam" value="password" />
        <!--記住我input的名稱(chēng)-->
        <property name="rememberMeParam" value="rememberMe"/>
    </bean>

然后便可以進(jìn)行測(cè)試,在登錄頁(yè)面輸入登錄信息后點(diǎn)擊下次自動(dòng)登錄,登錄成功后我們查看瀏覽器的cookie緩存會(huì)發(fā)現(xiàn)多了一個(gè)名叫rememberMe的cookie信息。然而此時(shí)我們?nèi)敉顺龅卿?,退回到登錄?yè)面,按理說(shuō)此時(shí)瀏覽器已經(jīng)存在該cookie了所以此時(shí)若我們直接訪問(wèn)系統(tǒng)主頁(yè)是可以直接訪問(wèn)的,然而測(cè)試結(jié)果仍然不行,因?yàn)樵撜?qǐng)求被/**=authc攔截了,所以我們要使用UserFilter,將記住我即可訪問(wèn)的地址配置讓UserFilter攔截。

16.2使用UserFilter

在application-shiro.xml的<value>中加入U(xiǎn)serFilter攔截的資源配置:

                <value>
                <!--對(duì)靜態(tài)資源設(shè)置匿名訪問(wèn)-->
                /images/**=anon
                /js/**=anon
                /style/**=anon

                <!--驗(yàn)證碼-->
                /validatecode.jsp=anon

                <!--請(qǐng)求這個(gè)地址就自動(dòng)退出-->
                /logout.action=logout

                <!--商品查詢(xún)需要商品查詢(xún)權(quán)限,取消url攔截配置,采用注解授權(quán)-->
                <!--/items/queryItems.action=perms[item:query]-->
                <!--&lt;!&ndash;商品修改需要商品修改權(quán)限&ndash;&gt;-->
                <!--/items/editItems.action=perms[item:edit]-->
                <!--配置記住我或認(rèn)證通過(guò)可以訪問(wèn)的資源url-->
                /index.jsp=user
                /first.action=user
                /welcome.jsp=user
                <!-- -/**=authc 表示所有的url都必須認(rèn)證通過(guò)才可以訪問(wèn)- -->
                /** = authc
                <!--/**=anon 表示所有的url都可以匿名訪問(wèn)-->

            </value>

此時(shí)登錄時(shí)點(diǎn)擊記住我,成功登錄后關(guān)掉瀏覽器,再次輸入系統(tǒng)的主頁(yè)地址即可直接訪問(wèn)系統(tǒng)的這三個(gè)資源:/index.jsp、/first.action、welcome.jsp。到此,我們便簡(jiǎn)單的入門(mén)了整合了Shiro框架的SSM的web項(xiàng)目該如何進(jìn)行開(kāi)發(fā)。

2018.3.19更

歡迎加入我的Java交流1群:659957958。群里目前已有1800人,每天都非?;钴S,但為了篩選掉那些不懷好意的朋友進(jìn)來(lái)搞破壞,所以目前入群方式已改成了付費(fèi)方式,你只需要支付9塊錢(qián),即可獲取到群文件中的所有干貨以及群里面各位前輩們的疑惑解答;為了鼓勵(lì)良好風(fēng)氣的發(fā)展,讓每個(gè)新人提出的問(wèn)題都得到解決,所以我將得到的入群收費(fèi)收入都以紅包的形式發(fā)放到那些主動(dòng)給新手們解決疑惑的朋友手中。在這里,我們除了談技術(shù),還談生活、談理想;在這里,我們?yōu)槟愕膶W(xué)習(xí)方向指明方向,為你以后的求職道路提供指路明燈;在這里,我們把所有好用的干貨都與你分享。還在等什么,快加入我們吧!

2018.4.21更:如果群1已滿(mǎn)或者無(wú)法加入,請(qǐng)加Java學(xué)習(xí)交流2群:305335626 。群2作為群1的附屬群,除了日常的技術(shù)交流、資料分享、學(xué)習(xí)方向指明外,還會(huì)在每年互聯(lián)網(wǎng)的秋春招時(shí)節(jié)在群內(nèi)發(fā)布大量的互聯(lián)網(wǎng)內(nèi)推方式,話不多說(shuō),快上車(chē)吧!

17.聯(lián)系

If you have some questions after you see this article,you can tell your doubts in the comments area or you can find some info by clicking these links.

?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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