SpringBoot+Shiro學習(七):Filter過濾器管理

weixin_34194087發表於2018-09-06

先從我們寫的一個自定義Filter來看:

public class RoleOrFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        Subject subject = getSubject(request,response);
        String[] roles = (String[])mappedValue;
        if(roles == null || roles.length ==0){
            return true;
        }
        for(String role:roles){
            if(subject.hasRole(role)){
                return true;
            }
        }
        return false;
    }
}

我們寫的自定義RoleOrFilter表示有多個角色要求時,只需要滿足一個即可通過。
我們順著RoleOrFilter的實現,瞭解他的父類


13126787-7b4008d93b54dcc8.png
image.png

NameableFilter
NameableFilter給Filter起個名字,如果沒有設定預設就是FilterName;還記得之前的如authc嗎?當我們組裝攔截器鏈時會根據這個名字找到相應的攔截器例項;

OncePerRequestFilter
OncePerRequestFilter用於防止多次執行Filter的;也就是說一次請求只會走一次攔截器鏈;另外提供enabled屬性,表示是否開啟該攔截器例項,預設enabled=true表示開啟,如果不想讓某個攔截器工作,可以設定為false即可。

AdviceFilter
AdviceFilter提供了AOP風格的支援,類似於SpringMVC中的Interceptor

1.  boolean preHandle(ServletRequest request, ServletResponse response) throws Exception  
2.  void postHandle(ServletRequest request, ServletResponse response) throws Exception  
3.  void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception;   

preHandler:類似於AOP中的前置增強;在攔截器鏈執行之前執行;如果返回true則繼續攔截器鏈;否則中斷後續的攔截器鏈的執行直接返回;進行預處理(如基於表單的身份驗證、授權)
postHandle:類似於AOP中的後置返回增強;在攔截器鏈執行完成後執行;進行後處理(如記錄執行時間之類的);
afterCompletion:類似於AOP中的後置最終增強;即不管有沒有異常都會執行;可以進行清理資源(如接觸Subject與執行緒的繫結之類的);

PathMatchingFilter

PathMatchingFilter提供了基於Ant風格的請求路徑匹配功能及攔截器引數解析的功能,如“roles[admin,user]”自動根據“,”分割解析到一個路徑引數配置並繫結到相應的路徑:

1.  boolean pathsMatch(String path, ServletRequest request)  
2.  boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception   

pathsMatch:該方法用於path與請求路徑進行匹配的方法;如果匹配返回true
onPreHandle:在preHandle中,當pathsMatch匹配一個路徑後,會呼叫opPreHandler方法並將路徑繫結引數配置傳給mappedValue;然後可以在這個方法中進行一些驗證(如角色授權),如果驗證失敗可以返回false中斷流程;預設返回true;也就是說子類可以只實現onPreHandle即可,無須實現preHandle。如果沒有path與請求路徑匹配,預設是通過的(即preHandle返回true)。

AccessControlFilter

AccessControlFilter提供了訪問控制的基礎功能;比如是否允許訪問/當訪問拒絕時如何處理等:

1.  abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;  
2.  boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;  
3.  abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;   

isAccessAllowed:表示是否允許訪問;mappedValue就是[urls]配置中攔截器引數部分,如果允許訪問返回true,否則false;
onAccessDenied:表示當訪問拒絕時是否已經處理了;如果返回true表示需要繼續處理;如果返回false表示該攔截器例項已經處理了,將直接返回即可。

onPreHandle會自動呼叫這兩個方法決定是否繼續處理:

boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); 
}   

另外AccessControlFilter還提供瞭如下方法用於處理如登入成功後/重定向到上一個請求:

1.  void setLoginUrl(String loginUrl) //身份驗證時使用,預設/login.jsp  
2.  String getLoginUrl()  
3.  Subject getSubject(ServletRequest request, ServletResponse response) //獲取Subject例項  
4.  boolean isLoginRequest(ServletRequest request, ServletResponse response)//當前請求是否是登入請求  
5.  void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //將當前請求儲存起來並重定向到登入頁面  
6.  void saveRequest(ServletRequest request) //將請求儲存起來,如登入成功後再重定向回該請求  
7.  void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登入頁面   

比如基於表單的身份驗證就需要使用這些功能。
到此基本的攔截器就完事了,如果我們想進行訪問訪問的控制就可以繼承AccessControlFilter;如果我們要新增一些通用資料我們可以直接繼承PathMatchingFilter。

AuthorizationFilter
AuthorizationFilter實現了AccessControlFilter的onAccessDenied方法

//訪問拒絕才會進來此方法
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        Subject subject = getSubject(request, response);
        if (subject.getPrincipal() == null) {
            // 如果未登入,儲存當前頁面,重定向到登入頁面
            saveRequestAndRedirectToLogin(request, response);
        } else {
            //匿名訪問地址
            String unauthorizedUrl = getUnauthorizedUrl();
            if (StringUtils.hasText(unauthorizedUrl)) {
                //如果匿名訪問地址存在,則跳轉去匿名訪問地址
                WebUtils.issueRedirect(request, response, unauthorizedUrl);
            } else {
                //不存在則返回404
                WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
        }
        return false;
    }

ShiroFilterFactoryBean
當我們寫好了自定義Filter後,如何在Shiro中使用它呢?在config類中注入ShiroFilter的bean

     /**
     * ShiroFilter主要配置
      * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter (SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //自定義攔截器
        Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
        filtersMap.put("roleOrFilter", roleOrFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //注意過濾器配置順序 不能顛倒
        //配置退出 過濾器,其中的具體的退出程式碼Shiro已經替我們實現了,登出後跳轉配置的loginUrl
        filterChainDefinitionMap.put("/logout", "logout");
        // 配置不會被攔截的連結 順序判斷
        //filterChainDefinitionMap.put("/hello", "anon");
        filterChainDefinitionMap.put("/ajaxLogin", "anon");
        filterChainDefinitionMap.put("/testRole", "anon");
        filterChainDefinitionMap.put("/**", "roleOrFilter[admin,admin1]");
        //自動跳去登入的地址
        shiroFilterFactoryBean.setLoginUrl("/login");
        //上面提到的匿名地址
        //shiroFilterFactoryBean.setUnauthorizedUrl();
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

這樣我們在訪問對應的請求時,就會先呼叫shiro的filter。

預設攔截器名 攔截器類 說明(括號裡的表示預設值)
身份驗證相關的
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter 基於表單的攔截器;如“/**=authc”,如果沒有登入會跳到相應的登入頁面登入;主要屬性:usernameParam:表單提交的使用者名稱引數名( username); passwordParam:表單提交的密碼引數名(password); rememberMeParam:表單提交的密碼引數名(rememberMe); loginUrl:登入頁面地址(/login.jsp);successUrl:登入成功後的預設重定向地址; failureKeyAttribute:登入失敗後錯誤資訊儲存key(shiroLoginFailure);
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter Basic HTTP身份驗證攔截器,主要屬性: applicationName:彈出登入框顯示的資訊(application);
logout org.apache.shiro.web.filter.authc.LogoutFilter 退出攔截器,主要屬性:redirectUrl:退出成功後重定向的地址(/)
user org.apache.shiro.web.filter.authc.UserFilter 使用者攔截器,使用者已經身份驗證/記住我登入的都可;
anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名攔截器,即不需要登入即可訪問;一般用於靜態資源過濾;示例“/static/**=anon”
授權相關的
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 角色授權攔截器,驗證使用者是否擁有所有角色;主要屬性: loginUrl:登入頁面地址(/login.jsp);unauthorizedUrl:未授權後重定向的地址;示例“/admin/**=roles[admin]”
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter 許可權授權攔截器,驗證使用者是否擁有所有許可權;屬性和roles一樣;示例“/user/**=perms["user:create"]”
port org.apache.shiro.web.filter.authz.PortFilter 埠攔截器,主要屬性:port(80):可以通過的埠;示例“/test= port[80]”,如果使用者訪問該頁面是非80,將自動將請求埠改為80並重定向到該80埠,其他路徑/引數等都一樣
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter rest風格攔截器,自動根據請求方法構建許可權字串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)構建許可權字串;示例“/users=rest[user]”,會自動拼出“user:read,user:create,user:update,user:delete”許可權字串進行許可權匹配(所有都得匹配,isPermittedAll);
ssl org.apache.shiro.web.filter.authz.SslFilter SSL攔截器,只有請求協議是https才能通過;否則自動跳轉會https埠(443);其他和port攔截器一樣;

自定義filter異常

此外,我在查詢資料的時候,看到一個問題Shiro 自定義 filter 匹配異常,問題主要說在使用自定義filter定義的時候,使用@Bean模式注入自定義filter之後,會導致自定義filter的呼叫在shiroFilter的前面,具體解決方案可以點開原文看。
我在我本地測試後發現沒有問題,可能是新版本已經修復了這個bug。當前版本1.3.2

相關文章