Apache Shiro學習筆記(六)FilterChain

魯春利的工作筆記,好記性不如爛筆頭



Apache Shiro學習筆記(七)IniWebEnvironment


Shiro FilterChain

Shiro 對Servlet 容器的FilterChain 進行了代理,即ShiroFilter 在繼續Servlet 容器的Filter鏈的執行之前,通過ProxiedFilterChain 對Servlet 容器的FilterChain 進行了代理;即先走Shiro 自己的Filter 體系,然後纔會委託給Servlet 容器的FilterChain 進行Servlet 容器級別的Filter鏈執行;Shiro的ProxiedFilterChain執行流程:

    1、先執行Shiro自己的Filter鏈;

    2、再執行Servlet容器的Filter鏈(即原始的Filter)。


ProxiedFilterChain 是通過FilterChainResolver 根據配置文件中[urls]部分是否與請求的URL是否匹配解析得到的。

  • FilterChainResolver

package org.apache.shiro.web.filter.mgt;

public interface FilterChainResolver {
    FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);
}

Shiro 內部提供了一個路徑匹配的FilterChainResolver 實現:PathMatchingFilterChainResolver:

    其根據[urls]中配置的url 模式(默認Ant 風格)= 攔截器鏈和請求的url是否匹配來解析得到配置的攔截器鏈的;而PathMatchingFilterChainResolver內部通過FilterChainManager維護着攔截器鏈,比如DefaultFilterChainManager實現維護着url 模式與攔截器鏈的關係。因此我們可以通過FilterChainManager 進行動態動態增加url模式與攔截器鏈的關係。

  • PathMatchingFilterChainResolver

package org.apache.shiro.web.filter.mgt;

public class PathMatchingFilterChainResolver implements FilterChainResolver {
    private FilterChainManager filterChainManager;

    private PatternMatcher pathMatcher;

    public PathMatchingFilterChainResolver() {
        this.pathMatcher = new AntPathMatcher();
        this.filterChainManager = new DefaultFilterChainManager();
    }

    public PathMatchingFilterChainResolver(FilterConfig filterConfig) {
        this.pathMatcher = new AntPathMatcher();
        this.filterChainManager = new DefaultFilterChainManager(filterConfig);
    }
    
    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        String requestURI = getPathWithinApplication(request);

        //the 'chain names' in this implementation are actually path patterns defined by the user.  We just use them
        //as the chain name for the FilterChainManager's requirements
        for (String pathPattern : filterChainManager.getChainNames()) {

            // If the path does match, then pass on to the subclass implementation for specific checks:
            if (pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
                            "Utilizing corresponding filter chain...");
                }
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }

        return null;
    }
}
  • DefaultFilterChainManager

package org.apache.shiro.web.filter.mgt;

public class DefaultFilterChainManager implements FilterChainManager {
    private FilterConfig filterConfig;

    private Map<String, Filter> filters; //pool of filters available for creating chains

    private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain

    public DefaultFilterChainManager() {
        this.filters = new LinkedHashMap<String, Filter>();
        this.filterChains = new LinkedHashMap<String, NamedFilterList>();
        addDefaultFilters(false);
    }

    public DefaultFilterChainManager(FilterConfig filterConfig) {
        this.filters = new LinkedHashMap<String, Filter>();
        this.filterChains = new LinkedHashMap<String, NamedFilterList>();
        setFilterConfig(filterConfig);
        addDefaultFilters(true);
    }
    
    protected void addDefaultFilters(boolean init) {
        for (DefaultFilter defaultFilter : DefaultFilter.values()) {
            addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
        }
    }
}

DefaultFilterChainManager 會默認添加org.apache.shiro.web.filter.mgt.DefaultFilter 中聲明的
攔截器。

  • DefaultFilter

public enum DefaultFilter {

    anon(AnonymousFilter.class),
    authc(FormAuthenticationFilter.class),
    authcBasic(BasicHttpAuthenticationFilter.class),
    logout(LogoutFilter.class),
    noSessionCreation(NoSessionCreationFilter.class),
    perms(PermissionsAuthorizationFilter.class),
    port(PortFilter.class),
    rest(HttpMethodPermissionFilter.class),
    roles(RolesAuthorizationFilter.class),
    ssl(SslFilter.class),
    user(UserFilter.class);
    
    // 代碼略
    
}


如果要註冊自定義攔截器,IniSecurityManagerFactory/WebIniSecurityManagerFactory在啓動時會自動掃描ini 配置文件中的[filters]/[main]部分並註冊這些攔截器到DefaultFilterChainManager;且創建相應的url 模式與其攔截器關係鏈。


如果想自定義FilterChainResolver,可以通過實現WebEnvironment接口完成:

package org.apache.shiro.web.env;

public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
    protected FilterChainResolver createFilterChainResolver() {

        FilterChainResolver resolver = null;

        Ini ini = getIni();

        if (!CollectionUtils.isEmpty(ini)) {
            //only create a resolver if the 'filters' or 'urls' sections are defined:
            Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
            Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
            if (!CollectionUtils.isEmpty(urls) || !CollectionUtils.isEmpty(filters)) {
                //either the urls section or the filters section was defined.  Go ahead and create the resolver:
                IniFilterChainResolverFactory factory = new IniFilterChainResolverFactory(ini, this.objects);
                resolver = factory.getInstance();
            }
        }

        return resolver;
    }
}


  • MyIniWebEnvironment

public class MyIniWebEnvironment extends IniWebEnvironment {
    @Override
    protected FilterChainResolver createFilterChainResolver() {
        //在此處擴展自己的FilterChainResolver
        return super.createFilterChainResolver();
    }
}

如果覆蓋了IniWebEnvironment 默認的FilterChainResolver,需要自己來解析請求與FilterChain 之間的關係。如果想動態實現url-攔截器的註冊,就可以通過實現此處的FilterChainResolver來完成,比如:

//1、創建FilterChainResolver
    PathMatchingFilterChainResolver filterChainResolver = new PathMatchingFilterChainResolver();
//2、創建FilterChainManager
    DefaultFilterChainManager filterChainManager = new DefaultFilterChainManager();
//3、註冊Filter
    for(DefaultFilter filter : DefaultFilter.values()) {
        filterChainManager.addFilter(filter.name(), (Filter) ClassUtils.newInstance(filter.getFilterClass()));
    }
//4、註冊URL-Filter的映射關係
    filterChainManager.addToChain("/login.jsp", "authc");
    filterChainManager.addToChain("/unauthorized.jsp", "anon");
    filterChainManager.addToChain("/**", "authc");
    filterChainManager.addToChain("/**", "roles", "admin");
//5、設置Filter的屬性
    FormAuthenticationFilter authcFilter = (FormAuthenticationFilter)filterChainManager.getFilter("authc");
    authcFilter.setLoginUrl("/login.jsp");
    RolesAuthorizationFilter rolesFilter = (RolesAuthorizationFilter)filterChainManager.getFilter("roles");
    rolesFilter.setUnauthorizedUrl("/unauthorized.jsp");
    filterChainResolver.setFilterChainManager(filterChainManager);
    return filterChainResolver;

此處自己去實現註冊filter,及url 模式與filter 之間的映射關係。可以通過定製FilterChainResolver或FilterChainManager來完成諸如動態URL匹配的實現。



然後再web.xml中進行如下配置Environment:

<context-param>
    <param-name>shiroEnvironmentClass</param-name>
    <param-value>com.github.zhangkaitao.shiro.chapter8.web.env.MyIniWebEnvironment</param-value>
</context-param>


自定義攔截器

package com.invicme.apps.shiro.filter;

import java.io.IOException;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.apache.shiro.web.util.WebUtils;

/**
 * 
 * @author lucl
 * 自定義表單攔截器
 *
 */
public class FormLoginFilter extends PathMatchingFilter {
    private String loginUrl = "/login.jsp";
    private String successUrl = "/";

    @Override
    protected boolean onPreHandle(ServletRequest request, 
            ServletResponse response, Object mappedValue) throws Exception {
        if (SecurityUtils.getSubject().isAuthenticated()) {
            return true;// 已經登錄過
        }
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (isLoginRequest(req)) {
            if ("post".equalsIgnoreCase(req.getMethod())) {// form表單提交
                boolean loginSuccess = login(req); // 登錄
                if (loginSuccess) {
                    return redirectToSuccessUrl(req, resp);
                }
            }
            return true;// 繼續過濾器鏈
        } else {// 保存當前地址並重定向到登錄界面
            saveRequestAndRedirectToLogin(req, resp);
            return false;
        }
    }

    private boolean redirectToSuccessUrl(HttpServletRequest req,
            HttpServletResponse resp) throws IOException {
        WebUtils.redirectToSavedRequest(req, resp, successUrl);
        return false;
    }

    private void saveRequestAndRedirectToLogin(HttpServletRequest req,
            HttpServletResponse resp) throws IOException {
        WebUtils.saveRequest(req);
        WebUtils.issueRedirect(req, resp, loginUrl);
    }

    private boolean login(HttpServletRequest req) {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        try {
            SecurityUtils.getSubject().login(
                    new UsernamePasswordToken(username, password));
        } catch (Exception e) {
            req.setAttribute("shiroLoginFailure", e.getClass());
            return false;
        }
        return true;
    }

    private boolean isLoginRequest(HttpServletRequest req) {
        return pathsMatch(loginUrl, WebUtils.getPathWithinApplication(req));
    }
}

onPreHandle主要流程:

wKioL1enQlnC_4iHAAN1YxzeNnY002.jpg

shiro.ini配置

[filters]
formLogin=com.invicme.apps.shiro.filter.FormLoginFilter
[urls]
/test.jsp=formLogin
/login.jsp=formLogin


默認攔截器

Shiro 內置了很多默認的攔截器,比如身份驗證、授權等相關的。默認攔截器可以參考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚舉攔截器:

wKioL1enRL3AZJFGAAc4_ipMBjE296.jpg

wKioL1enRjuiv-XUAAG46lTojvI302.jpg

wKioL1enRj-Atm_QAAdf-HyXhF0902.jpg


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章