最近在學習 shiro 安全框架後,本身手寫了一個小的管理系統 web 項目,並使用 shiro 做爲安全管理框架。接下來分享一下在這過程當中,遇到的一些問題以及本身的解決思路和方法。html
這個問題不必定全部朋友都會碰到,出現的緣由是個人 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
這是一個容易使人引發誤解的配置,讓人覺得登陸成功後就必定會跳轉到這個頁面。實際上在 shiro 的底層,這是一個驗證成功後默認的跳轉頁面,可是 shiro 底層會記錄你的上次訪問頁面,當你登錄成功後會跳轉到上次訪問被拒絕的頁面。apache
舉個例子:當你打開瀏覽器,訪問一個須要受權的頁面(/** = authc),例如「user/list」頁面,此時會由於沒有受權,而跳轉到配置中的「loginUrl」也就是登陸界面,在你登陸成功後,則會跳轉到「user/list」頁面,而不是「successUrl」。瀏覽器
那 successUrl 何時生效呢?當咱們直接訪問的就是「/login」頁面時,登陸成功後就會跳轉到這個默認的驗證成功的 「successUrl」頁面。tomcat
這個配置的值爲當用戶訪問須要受權的頁面時,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"; }
當咱們給 filterChainDefinitions 添加了權限管理時,沒有權限訪問這個頁面,就會跳入 unauthorizedUrl。須要注意的是,若是咱們用註解的方式添加權限管理,不會走這個頁面,這個頁面只對 filterChainDefinitions 內的配置有效
當咱們訪問這個地址時,就會通過 LogoutFilter ,這個過濾器會將咱們當前用戶退出,源碼以下圖。
同時,這個過濾器會重定向到「/」這個路徑,這就是咱們題目所述問題的根源。
接下來的過程就是:
咱們有兩種解決方案
第一種,將頁面路徑加入 <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"; }
按照標題的方式操做時,出現的現象是,當前 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>
註解模式下,無權訪問的異常類型和在配置文件下的權限配置的不一樣,因此須要咱們用 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
rememberMe 要求 principal 對象是可以序列化的,也就是 實現 Serializable 接口。按照要求我把做爲 principal 的 User 類實現了 Serializable 接口,可是依然失敗,在測試記住我功能的時候,瀏覽器一直沒有得到 Cookie。
出現這個問題的緣由是,個人 User 類下,屬性中還有一個 其餘類的對象,該對象沒有實現 Serializable 接口,因此致使了序列化失敗。解決辦法也很簡單,就是讓它也實現序列化接口。
不必定出現該問題都是這個緣由,只是提醒你們這個點不要忽視了