分享 Shiro 學習過程當中遇到的一些問題

最近在學習 shiro 安全框架後,本身手寫了一個小的管理系統 web 項目,並使用 shiro 做爲安全管理框架。接下來分享一下在這過程當中,遇到的一些問題以及本身的解決思路和方法。html

1、Log out 以後再次登陸,出現 403 forbidden

這個問題不必定全部朋友都會碰到,出現的緣由是個人 webapp 根目錄下沒有 index 頁面(個人 index 頁面放在 /WEB-INF/view/ 下),先看 ShiroFilterFactoryBean的配置代碼。前端

 1 <!--配置 shiro 框架的過濾器-->
 2     <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
 3         <!--注入安全管理器-->
 4         <property name="securityManager" ref="securityManager"/>
 5         <!--默認的認證成功後跳轉的頁面-->
 6         <property name="successUrl" value="/index"/>
 7         <!--認證失敗、登陸訪問的頁面-->
 8         <property name="loginUrl" value="/login"/>
 9         <!--沒有權限訪問時跳轉的頁面-->
10         <property name="unauthorizedUrl" value="/unauthorized"/>
11 
12         <!--注入自定義 filter-->
13         <property name="filters">
14             <map>
15                 <entry key="authc" value-ref="myFormAuthenticationFilter"/>
16             </map>
17         </property>
18 
19         <!--配置過濾器鏈-->
20         <property name="filterChainDefinitions">
21             <value>
22                 <!--靜態資源不須要驗證,放行-->
23                 /lib/** = anon
24                 /static/** = anon
25                 /verifyCode.jsp = anon
26                 /checkVerifyCode = anon
27 
28                 <!--退出登陸-->
29                 /logout = logout
30 
31                 <!--其它全部頁面都須要驗證訪問-->
32                 /** = authc
33             </value>
34         </property>
35     </bean>

以上的配置是沒有問題的,可是當你的 webapp 下沒有 index 頁面(或者沒有配置 web.xml 的 <welcome-file-list>),就會出現標題所描述的問題。web

咱們先來分析一下各項配置的具體含義,只針對幾個容易出現錯誤的配置項。spring

successUrl

這是一個容易使人引發誤解的配置,讓人覺得登陸成功後就必定會跳轉到這個頁面。實際上在 shiro 的底層,這是一個驗證成功後默認的跳轉頁面,可是 shiro 底層會記錄你的上次訪問頁面,當你登錄成功後會跳轉到上次訪問被拒絕的頁面。apache

舉個例子:當你打開瀏覽器,訪問一個須要受權的頁面(/** = authc),例如「user/list」頁面,此時會由於沒有受權,而跳轉到配置中的「loginUrl」也就是登陸界面,在你登陸成功後,則會跳轉到「user/list」頁面,而不是「successUrl」。瀏覽器

那 successUrl 何時生效呢?當咱們直接訪問的就是「/login」頁面時,登陸成功後就會跳轉到這個默認的驗證成功的 「successUrl」頁面。tomcat

loginUrl

這個配置的值爲當用戶訪問須要受權的頁面時,shiro 判斷沒有受權時跳轉的頁面。須要注意的是,在咱們設計登陸頁面時,登陸的表單提交的地址,也要和這個地址同樣。安全

例如當咱們訪問「/login」控制器進入登錄頁面,點擊登陸後,表單提交到的地址也應該是「/login」,不然登陸不成功,繼續跳轉到登錄頁面。個人猜想這種狀況是由於,只有當表單提交的地址和 loginUrl 的地址相同時,請求才會走 FormAuthenticationFilter 過濾器進行登陸驗證。session

當咱們登錄失敗時,會繼續跳轉到 loginUrl 這個頁面。基於這種狀況,咱們能夠在「/login」的控制器上同時得到登錄失敗的異常,這個異常被 shiro 封裝在 request 的屬性中,key 爲 「shiroLoginFailure」。接着能夠根據異常信息,返回錯誤提示給前端顯示。app

@RequestMapping("/login")
    public String login(HttpServletRequest request, Model m) {
        //獲取認證失敗的錯誤信息,在Shiro框架的 FormAuthenticationFilter 過濾器中共享
        // 共享的屬性名稱  shiroLoginFailure , 經過 request 獲取
        // 共享的 shiro 異常的字節碼
        String shiroLogininFailure = String.valueOf(request.getAttribute("shiroLoginFailure"));
        
        if (UnknownAccountException.class.getName().equals(shiroLogininFailure)) {
            m.addAttribute("errorMsg", "帳戶不存在");
        } else if (IncorrectCredentialsException.class.getName().equals(shiroLogininFailure)) {
            m.addAttribute("errorMsg", "密碼錯誤");
        }
        
        System.out.println("異常類型:" + shiroLogininFailure);
        return "login";
    }

unauthorizedUrl

當咱們給 filterChainDefinitions 添加了權限管理時,沒有權限訪問這個頁面,就會跳入 unauthorizedUrl。須要注意的是,若是咱們用註解的方式添加權限管理,不會走這個頁面,這個頁面只對 filterChainDefinitions 內的配置有效

/logout = logout

當咱們訪問這個地址時,就會通過 LogoutFilter ,這個過濾器會將咱們當前用戶退出,源碼以下圖。

同時,這個過濾器會重定向到「/」這個路徑,這就是咱們題目所述問題的根源。

接下來的過程就是:

  1. 咱們登出以後重定向到「/」,「/」符合 「/** = authc」這個配置,須要驗證才能訪問;
  2. 而後咱們進入了 loginUrl ,進行登陸;
  3. 登陸驗證成功後,會跳轉到上次訪問失敗的頁面,也就是「/」地址;
  4. 當咱們訪問根路徑時,tomcat 會默認調用 index.html 等相似的靜態資源,或者根據 web.xml 中配置的 <welcome-file-list> 進行訪問,若這些都沒有資源進行訪問,就會報上述的 403 forbidden 錯誤。

解決方案

咱們有兩種解決方案

第一種,將頁面路徑加入 <welcome-file-list>,如

1 <welcome-file-list>
2   <welcome-file>/WEB-INF/view/index.jsp</welcome-file> 
3 </welcome-file-list>

第二種,咱們修改負責進入 index 頁面的控制器的 RequestMapping,如

@RequestMapping(value = {"/index", ""})
    public String index() {
        return "index";
    }

2、登陸以後,不 logout,手動進入登錄界面再次登陸

按照標題的方式操做時,出現的現象是,當前 subject 的 principal 沒有變動,同時咱們繼續跳轉至登陸界面,能夠說很不符合客戶體驗的需求。

出現這個現象的緣由是:首先,當咱們訪問「/login」時,表單提交的地址也是「/login」,因此很正常咱們繼續停留在了此頁面;另外,每次咱們訪問知足「/** = authc」的頁面時,AuthenticationFilter 會先進行 isAllowedAccess 方法的判斷,咱們登陸事後,這個方法會返回 true,咱們就能夠直接進入頁面,不走驗證流程。

因而咱們能夠新建一個類繼承 FormAuthenticationFilter,並重寫其 isAllowedAccess 方法,在判斷請求時指向登錄頁面,並有表單提交時,若是當前有用戶經過驗證了,將當前用戶 log out,再繼續進行父類的驗證。

subject.logout 會同時清空 session,因此咱們登陸成功後進入的是 successUrl 頁面。完美的用戶體驗
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
    
    /**
     * 重寫 isAccessAllowed 方法,解決重複登陸的問題
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                
                Subject subject = this.getSubject(request, response);
                //判斷當前是否已經登陸
                if (subject.getPrincipal() != null) {
                    System.out.println("log out:" + subject.getPrincipal());
                    //登出用戶
                    subject.logout();
                }
            }
        }
        
        return super.isAccessAllowed(request, response, mappedValue);
    }

}

這尚未完,咱們須要在 ShiroFilterFactoryBean 的配置中,將咱們的 filter 加入進去,替代 authc 進行驗證

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 其餘配置略 -->
    <property name="filters">
        <map>
            <entry key="authc" value-ref="myFormAuthenticationFilter"/>
        </map>
    </property>
</bean>

3、註解模式下的權限配置,沒法進入 unauthorizedUrl

註解模式下,無權訪問的異常類型和在配置文件下的權限配置的不一樣,因此須要咱們用 spring 手動捕捉,並跳轉到須要顯示的異常頁面。須要注意的是,跳轉地址受視圖解析器影響。

<!-- 開啓 spring 的異常攔截捕獲權限異常 -->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="org.apache.shiro.authz.AuthorizationException">unauthorized</prop>
            </props>
        </property>
    </bean>
key 就是異常全限定名,該配置跳轉的地址是:/WEB-INF/view/unauthorized.jsp

4、開啓 rememberMe 後,沒有效果

rememberMe 要求 principal 對象是可以序列化的,也就是 實現 Serializable 接口。按照要求我把做爲 principal 的 User 類實現了 Serializable 接口,可是依然失敗,在測試記住我功能的時候,瀏覽器一直沒有得到 Cookie。

出現這個問題的緣由是,個人 User 類下,屬性中還有一個 其餘類的對象,該對象沒有實現 Serializable 接口,因此致使了序列化失敗。解決辦法也很簡單,就是讓它也實現序列化接口。

不必定出現該問題都是這個緣由,只是提醒你們這個點不要忽視了
相關文章
相關標籤/搜索