天天看點

shiro認證授權源碼分析

shiro内置了許多過濾器用來控制認證授權

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

其中認證是否已登入FormAuthenticationFilter

認證是否授權(角色、權限)RolesAuthorizationFilter,PermissionsAuthorizationFilter

(以下以RolesAuthorizationFilter為例)

核心都繼承自AccessControlFilter

shiro認證授權源碼分析
shiro認證授權源碼分析

左邊是認證登入,右邊是認證授權。注意紅框位置FormAuthenticationFilter 和 RolesAuthorizationFilter 都通過了過渡類繼承自AccessControlFilter

簡單介紹下這個類的幾個方法

onPreHandle:判斷認證是否通過,以及後續處理上代碼:

public  boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)  throws Exception {

    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);

}

isAccessAllowed:判斷認證是否通過(FormAuthenticationFilter中是認證是否已登入,RolesAuthorizationFilter是認證是否已授權)

onAccessDenied:認證失敗的後續處理

isLoginRequest:判斷是否是登入請求,登入位址在配置檔案中已配:

<!--未登入狀态下通路authc則進入loginUrl配置的路徑-->

 <property name="loginUrl" value="/login"></property>

 <!--登入成功後跳轉的預設位址-->

 <property name="successUrl" value="/main"></property>

 <!--如果您請求的資源不再您的權限範圍,則跳轉到/400請求位址 -->

 <property name="unauthorizedUrl" value="400"></property>

saveRequest:儲存目前請求

redirectToLogin:重定向到登入頁面

saveRequestAndRedirectToLogin:儲存目前請求并重定向到登入頁面

認證登入流程按照繼承關系:

AccessControlFilter

public  boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)  throws Exception {

        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);

   }

AuthenticatingFilter(判斷是否認證通過,将調用父類的isAccessAllowed)

@Override

protected  boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

    return  super.isAccessAllowed(request, response, mappedValue) ||

           (!isLoginRequest(request, response) && isPermissive(mappedValue));

}

AuthenticationFilter(在這裡真正判斷是否已登入)

protected  boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

   Subject subject = getSubject(request, response);

    return subject.isAuthenticated();

}

登入的話,直接跳轉,如果未登入:

FormAuthenticationFilter 

protected  boolean onAccessDenied(ServletRequest request, ServletResponse response)  throws Exception {

    if (isLoginRequest(request, response)) {

        if (isLoginSubmission(request, response)) {

            if (log.isTraceEnabled()) {

               log.trace("Login submission detected.  Attempting to execute login.");

           }

            return executeLogin(request, response);

       }  else {

            if (log.isTraceEnabled()) {

               log.trace("Login page view.");

           }

            //allow them to see the login page ;)

            return  true;

       }

   }  else {

        if (log.isTraceEnabled()) {

           log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +

                   "Authentication url [" + getLoginUrl() + "]");

       }

       saveRequestAndRedirectToLogin(request, response);

        return  false;

   }

} 首先判斷是否是登入請求,如果是get形式登入頁面跳轉繼續過濾器鍊,如果是post表單送出執行executeLogin方法,

将走realm中的doGetAuthenticationInfo方法。

ps:執行executeLogin方法過程中會生成AuthenticationToken,具體代碼:

protected AuthenticationToken createToken(String username, String password,

                                         ServletRequest request, ServletResponse response) {

    boolean rememberMe = isRememberMe(request);

   String host = getHost(request);

    return createToken(username, password, rememberMe, host);

} 預設傳username,password,是以前台頁面name屬性要一一對應。

登入成功或失敗會走onLoginSuccess或onLoginFailure

如果不是登入請求則儲存目前請求跳轉至登入頁面,如果想封裝ajax請求的shiro處理,可自定義自己的FormAuthenticationFilter

認證授權流程按照繼承關系

AccessControlFilter

public  boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)  throws Exception {

        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);

   } RolesAuthorizationFilter(認證是否有權限,shiro預設是需要所有角色才認證通過的)

public  boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)  throws IOException {

      Subject subject = getSubject(request, response);

      String[] rolesArray = (String[]) mappedValue;

       if (rolesArray ==  null || rolesArray.length == 0) {

           //no roles specified, so nothing to check - allow access.

           return  true;

      }

      Set<String> roles = CollectionUtils.asSet(rolesArray);

       return subject.hasAllRoles(roles);

  }

AuthorizationFilter(認證角色未通過後的處理)

protected  boolean onAccessDenied(ServletRequest request, ServletResponse response)  throws IOException {

      Subject subject = getSubject(request, response);

       // If the subject isn't identified, redirect to login URL

       if (subject.getPrincipal() ==  null) {

          saveRequestAndRedirectToLogin(request, response);

      }  else {

           // If subject is known but not authorized, redirect to the unauthorized URL if there is one

           // If no unauthorized URL is specified, just return an unauthorized HTTP status code

          String unauthorizedUrl = getUnauthorizedUrl();

           //SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit:

           if (StringUtils.hasText(unauthorizedUrl)) {

              WebUtils.issueRedirect(request, response, unauthorizedUrl);

          }  else {

              WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);

          }

      }

       return  false;

  }

如果未登入,儲存目前請求跳轉至登入頁面,否則判斷是否有配置認證權限未通過須要跳轉的請求,如果配置了則跳轉,否則抛出錯誤碼SC_UNAUTHORIZED(401),可在web.xml中捕獲該錯誤碼自己定義要跳轉的頁面。

這是我了解的shiro認證授權政策,其中認證授權前後所要做的處理都可以通過繼承相應類重制方法來達到自定義效果,無需在contrller層寫多餘的業務邏輯代碼,shiro的這些接口和相關配置檔案都可以幫你搞定!

最後需要在shiro配置檔案中配置

01  < bean  id = "shiroFilter"  class = "org.apache.shiro.spring.web.ShiroFilterFactoryBean" >

02          < property  name = "securityManager"  ref = "securityManager" />

03          <!-- 未登入狀态下通路 authc 則進入 loginUrl 配置的路徑 -->

04          < property  name = "loginUrl"  value = "/login" ></ property >

05          <!-- 登入成功後跳轉的預設位址 -->

06          < property  name = "successUrl"  value = "/main" ></ property >

07           <!-- 如果您請求的資源不再您的權限範圍,則跳轉到 / 400 請求位址  -->

08          < property  name = "unauthorizedUrl"  value = "400" ></ property >

09          <property name="filters">

10             <map>

11                 <entry key="authc">

12                     <bean class="com.luming.shiro.MyFormAuthenticationFilter"/>

13                 </entry>

14                 <entry key="roles">

15                     <bean class="com.luming.shiro.MyRolesAuthorizationFilter"/>

16                 </entry>

17             </map>

18         </property>

19  </ bean >

MyFormAuthenticationFilter繼承自FormAuthenticationFilter

MyRolesAuthorizationFilter繼承自RolesAuthorizationFilter

最後基于自身項目的shiro自定義認證授權還須要結合自定義的realm:shiro認證授權

因為JdbcRealm的doGetAuthenticationInfo定義了如何對使用者送出的資料進行認證,doGetAuthorizationInfo定義了授予使用者何種權限,

而FormAuthenticationFilter和RolesAuthorizationFilter主要是負責認證及授權的前後須要做的一些自定義操作,比如IP過濾等

繼續閱讀