Spring Boot filter鏈

    Filter --Servlet技術支持 -- WEB開發人員通過Filter技術,對web服務器管理的所有web資源:例如Jsp, Servlet, 靜態圖片文件或靜態 html 文件等。通過Filter技術,開發人員可以實現用戶在訪問某個目標資源之前,對訪問的請求和響應進行攔截。

   無論是Filter,MethodInterceptor,ACL都要用到AOP,實際上都是攔截器的概念,其中要用到AbstractSecurityInterceptor總攔截器,AfterInvocationManager後置攔截,authenticationManager驗證管理器,可能還要用上RunAsManager。

1--Filter的創建和銷燬由WEB服務器負責。web應用程序啓動時,web服務器將創建Filter的實例對象,並調用其init方法,完成對象的初始化。filter對象只會創建一次,init方法(完成對象的初始化功能)也只會執行一次

2--用destroy方法銷燬Filter,在Filter的生命週期中僅執行一次。

3-- init(FilterConfig filterConfig)

FilterConfig接口:通過filterConfig對象的方法,就可獲得:
  String getFilterName():得到filter的名稱。
  String getInitParameter(String name): 返回在部署描述中指定名稱的初始化參數的值。如果不存在返回null.
  Enumeration getInitParameterNames():返回過濾器的所有初始化參數的名字的枚舉集合。
  public ServletContext getServletContext():返回Servlet上下文對象的引用。

4--FilterChain  過濾器鏈 ,過濾器鏈中的所有過濾器的doFilter方法都執行完成才能通過。

當第一個Filter的doFilter方法被調用時,web服務器會創建一個代表Filter鏈的FilterChain對象傳遞給該方法。在doFilter方法中,開發人員如果調用了FilterChain對象的doFilter方法,則web服務器會檢查FilterChain對象中是否還有filter,如果有,則調用第2個filter,如果沒有,則調用目標資源。

 

1.ChannelProcessingFilter

2.SecurityContextPersistenceFilter

3.ConcurrentSessionFilter

4.HeaderWriterFilter

5.CsrfFilter

6.LogoutFilter

7.X509AuthenticationFilter

8.AbstractPreAuthenticatedProcessingFilter(Subclasses)

9.CasAuthenticationFilter

10.UsernamePasswordAuthenticationFilter

11.BasicAuthenticationFilter

12.SecurityContextHolderAwareRequestFilter

13.JaasApiIntegrationFilter

14.RememberMeAuthenticationFilter

15.AnonymousAuthenticationFilter

16.SessionManagementFilter

17.ExceptionTranslationFilter

.18.FilterSecurityInterceptor

19.SwitchUserFilter

 

一、配置自定義 Filter 在 Spring Security 過濾器鏈中的位置

WebSecurityConfigurerAdapter  -->  configure(HttpSecurity http)  -->  

HttpSecurity 有三個常用方法來配置:

  • addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter)
    在 beforeFilter 之前添加 filter, 
  • addFilterAfter(Filter filter, Class<? extends Filter> afterFilter)
    在 afterFilter 之後添加 filter
  • addFilterAt(Filter filter, Class<? extends Filter> atFilter)
    在 atFilter 相同位置添加 filter, 此 filter 不覆蓋 filter


作者:Anoyi
鏈接:https://www.jianshu.com/p/deb512b41f99
 

 // 在 UsernamePasswordAuthenticationFilter 前添加 BeforeLoginFilter
        http.addFilterBefore(new BeforeLoginFilter(), UsernamePasswordAuthenticationFilter.class);

        // 在 CsrfFilter 後添加 AfterCsrfFilter
        http.addFilterAfter(new AfterCsrfFilter(), CsrfFilter.class);

二、spring boot 配置Filter過濾器:、

1.通過 @WebFilter 註解來配置

2.通過 @Bean 註解來配置

@Bean
public FilterRegistrationBean testFilterRegistration() {
		FilterRegistrationBean registration = new FilterRegistrationBean(new TestFilter());
		registration.addUrlPatterns("/webapi/*"); 
//		registration.addInitParameter("paramName", "paramValue"); 
//		registration.setName("testFilter");	
	registration.setOrder(1);
		return registration;	
} 
	@Bean
	public FilterRegistrationBean authFilterRegistration() {
		FilterRegistrationBean registration = new FilterRegistrationBean(new AuthFilter());		
        registration.addUrlPatterns("/webapi/*"); 
//		registration.addInitParameter("paramName", "paramValue"); 
//		registration.setName("authFilter");		
        registration.setOrder(2);	
	    return registration;
	}

 

三、攔截器

1 -- 資源管理攔截器AbstractSecurityInterceptor  --  總攔截器

訪問資源(即授權管理),訪問url時,會通過AbstractSecurityInterceptor攔截器攔截,其中會調用FilterInvocationSecurityMetadataSource的方法來獲取被攔截url所需的全部權限,在調用授權管理器AccessDecisionManager,這個授權管理器會通過spring的全局緩存SecurityContextHolder獲取用戶的權限信息,還會獲取被攔截的url和被攔截url所需的全部權限,然後根據所配的策略(有:一票決定,一票否定,少數服從多數等),如果權限足夠,則返回,權限不夠則報錯並調用權限不足頁面。

2 -- 登陸驗證攔截器AuthenticationProcessingFilter

3 -- AfterInvocationManager後置攔截

 

處於繼承樹頂端的AbstractSecurityInterceptor有三個實現類:

  • FilterSecurityInterceptor,負責處理FilterInvocation,實現對URL資源的攔截。

  • MethodSecurityInterceptor,負責處理MethodInvocation,實現對方法調用的攔截。

  • AspectJSecurityInterceptor,負責處理JoinPoint,主要也是用於對方法調用的攔截。

1) FilterSecurityInterceptor 繼承了 AbstractSecurityInterceptor, 實現了  Filter 接口

-- AuthenticationManager:認證管理器,實現用戶認證的入口

-- AccessDecisionManager:訪問決策器,決定某個用戶具有的角色,是否有足夠的權限去訪問某個資源

-- FilterInvocationSecurityMetadataSource:資源源數據定義,即定義某一資源可以被哪些角色訪問

這三個類都是FilterSecurityInterceptor的屬性,FilterSecurityInterceptor的主要職責是處理http資源的安全性。

ConfigAttribute接口 --  爲了限制用戶訪問被保護資源,Spring Security提供了一套元數據,用於定義被保護資源的訪問權限,這套元數據主要體現爲ConfigAttribute(一個接口,只有一個方法--getAttribute()  )和Collection<ConfigAttribute>。每個ConfigAttribute中只包含一個字符串,而Collection<ConfigAttribute>包含多個ConfigAttribute。對於系統來說,每個被保護資源都將對應一組Collection<ConfigAttribute>,這個Collection<ConfigAttribute>中包含的多個ConfigAttribute就是訪問該資源所需的權限。

FilterInvocation類--
將doFilter傳進來的request,response和FilterChain對象保存起來,供FilterSecurityInterceptor的處理代碼調用。
因爲FilterInvocation類替代了這些參數在FilterSecurityInterceptor類中各處遊動,這樣通過該類屏蔽了web filter過濾器環境。

Spring Security處理時序圖:  --  最重要還是beforeInvocation(),finallyInvocation(),afterInvocation().

beforeInvocation()方法  ---  主要包括用戶的驗證與授權,實現了對訪問受保護對象的權限校驗,內部用到了SecurityMetadataSource、AccessDecisionManager和AuthenticationManager;
finallyInvocation()方法  --  設置用戶的SecurityContext信息。用於實現受保護對象請求完畢後的一些清理工作,主要是如果在beforeInvocation()中改變了SecurityContext,則在finallyInvocation()中需要將其恢復爲原來的SecurityContext,該方法的調用應當包含在子類請求受保護資源時的finally語句塊中;
afterInvocation()方法實現了對返回結果的處理,在注入了AfterInvocationManager的情況下默認會調用其decide()方法。

 

  RequestMatcher

匹配HttpServletRequest的簡單策略接口RequestMatcher,其下定義了matches方法,如果返回是true表示提供的請求與提供的匹配規則匹配,如果返回的是false則不匹配。

RequestMatcher其實現類:

  • AntPathRequestMatcher:重點
  • MvcRequestMatcher:重點
  • RegexRequestMatcher: 根據正則模式進行匹配
  • AnyRequestMatcher
  •  

匹配URL地址  --  AntPathRequestMatcher -- 接口RequestMatcher的實現類

我們默認使用的URL匹配器就是這個AntPathRequestMatcher,它來自於http://ant.apache.org/項目,是一種簡單易懂的路徑匹配策略。

它爲我們提供了三種通配符。

  • 通配符:?

    示例:/admin/g?t.jsp

    匹配任意一個字符,/admin/g?t.jsp可以匹配/admin/get.jsp和/admin/got.jsp或是/admin/gxt.do。不能匹配/admin/xxx.jsp。

  • 通配符:*

    示例:/admin/*.jsp

    匹配任意多個字符,但不能跨越目錄。/*/index.jsp可以匹配/admin/index.jsp和/user/index.jsp,但是不能匹配/index.jsp和/user/test/index.jsp。

  • 通配符:**

    示例:/**/index.jsp

    可以匹配任意多個字符,可以跨越目錄,可以匹配/index.jsp,/admin/index.jsp,/user/admin/index.jsp和/a/b/c/d/index.jsp

匹配URL地址  --  RegexRequestMatcher

   如果默認的AntPathRequestMatcher無法滿足需求,還可以選擇使用更強大的RegexRequestMatcher,它支持使用正則表達式對URL地址進行匹配。

 

ServletRequest由Servlet容器來管理,當客戶請求到來時,容器創建一個ServletRequest對象,封裝請求數據,同時創建一個ServletResponse對象,封裝響應數據。這兩個對象將被容器作爲service()方法的參數傳遞給Servlet,Servlet利用ServletRequest對象獲取客戶端發來的請求數據,利用ServletResponse對象發送響應數據。

 

FilterInvocationSecurityMetadataSource --> 
MyInvocationSecurityMetadataSource --> 資源源數據定義,即某一資源可以被哪些角色訪問
package com.dongnao.james.interceptor;

import com.dongnao.james.model.Sys.SysPermission;
import com.dongnao.james.model.Sys.SysRole;
import com.dongnao.james.utils.SQLRepository.SysPermissionRepository;
import com.dongnao.james.utils.SQLRepository.SysRoleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

//資源源數據定義,即某一資源可以被哪些角色訪問
@Service
public class MyInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    private SysPermissionRepository permissionRepository;
    @Autowired
    private SysRoleRepository roleRepository;

    //ConfigAttribute--Spring Security提供了一套元數據,用於定義被保護資源的訪問權限
    private HashMap<String, Collection<ConfigAttribute>> map = null;

    /**
     * 加載權限表中所有權限
     * ConfigAttribute -- SecurityConfig實現了該接口
     */
    public void loadResourceDefine() {
        map = new HashMap<>();
        Collection<ConfigAttribute> array;
        ConfigAttribute cfg;
        List<SysRole> roles = roleRepository.findAll();
        for (SysRole role : roles){
            List<SysPermission> permissions = role.getPermissions();
            array = new ArrayList<>();
            cfg = new SecurityConfig(role.getRoleName());
            array.add(cfg);
            for (SysPermission permission : permissions) {
                //用uri作爲map的key,用ConfigAttribute的集合作爲 value,
                map.put(permission.getUri(), array);
            }
        }
    }

    //此方法是爲了判定用戶請求的url 是否在權限表中,如果在權限表中,則返回給 MyAccessDecisionManager中decide方法,用來判定用戶是否有此權限。如果不在權限表中則放行。
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        if (map == null)
            loadResourceDefine();
        //object 中包含用戶請求的request 信息
        HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
        //匹配HttpServletRequest的簡單策略接口RequestMatcher的實現類AntPathRequestMatcher
        //默認使用的URL匹配器就是這個AntPathRequestMatcher
        AntPathRequestMatcher matcher;
        String resUri;
        for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
            resUri = iter.next();
            matcher = new AntPathRequestMatcher(resUri);
            if (matcher.matches(request)) {
                return map.get(resUri);
            }
        }
        return null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        /**
         * 如果爲真則說明支持當前格式類型,纔會到上面的 getAttributes 方法中
         */
        return true;
    }

}
AccessDecisionManager --> 
MyAccessDecisionManager --> 自定義決策管理器 -- 判斷用戶是否有訪問資源權限
package com.dongnao.james.interceptor;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Iterator;

//自定義決策管理器 -- 判斷用戶是否有訪問資源權限
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {

    // decide 方法是判定是否擁有權限的決策方法,authentication 權限信息集合,參數object 包含客戶端發起的請求的requset信息
    //configAttributes 爲MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法返回的結果,訪問該資源所需的權限
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        if(null == configAttributes || configAttributes.size() <= 0){
            return;
        }
        ConfigAttribute c;
        String needRole;
        for(Iterator<ConfigAttribute> iter = configAttributes.iterator();iter.hasNext();){
            c = iter.next();
            needRole = c.getAttribute();
            System.out.println("acc 需要的角色:"+needRole);
            for(GrantedAuthority ga : authentication.getAuthorities()){
                if(needRole.trim().equals(ga.getAuthority())){
                    return;
                }else if(ga.getAuthority().equals("ROLE_ANONYMOUS")){
                   // 用戶沒有登陸!
                    return ;
                }
            }
        }

        throw new AccessDeniedException("You don't have right");
    }


    @Override
    public boolean supports(Class<?> aClass) {
        /**
         * 表示當前AccessDecisionManager是否支持對應的受保護對象類型
         */
        return true;
    }


    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        /**
         * 表示當前AccessDecisionManager是否支持對應的ConfigAttribute
         */
        return true;
    }

}
自定義FilterSecurityInterceptor --> 
MyFilterSecurityInterceptor --> Http資源安全過濾
package com.dongnao.james.interceptor;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;

import javax.servlet.*;
import java.io.IOException;

//自定義FilterSecurityInterceptor
//Spring Security 是通過這個過濾器來實現 Http資源安全過濾的。
@Service
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { //資源管理攔截器AbstractSecurityInterceptor

    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    @Autowired
    public void setAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
        super.setAccessDecisionManager(myAccessDecisionManager);
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //FilterInvocation類--將doFilter傳進來的request,response和FilterChain對象保存起來,供FilterSecurityInterceptor的處理代碼調用。
        FilterInvocation filterInvocation = new FilterInvocation(servletRequest, servletResponse, filterChain);
        invoke(filterInvocation);
    }

    public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
        /**
         * filterInvocation裏面有一個被攔截的url
         * beforeInvocation()方法實現了對訪問受保護對象的權限校驗,主要包括用戶的認證與授權。
         * 裏面調用MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取filterInvocation對應的所有權限
         * 再調用MyAccessDecisionManager的decide方法來校驗用戶的權限是否足夠訪問
         * InterceptorStatusToken -- 攔截器狀態令牌
         * afterInvocation()方法實現了對返回結果的處理,在注入了AfterInvocationManager的情況下默認會調用其decide()方法。
         */
        InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
        try {
            //執行下一個攔截器
            filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //完成Filter對象的初始化功能
    }

    @Override
    public void destroy() {
        //Filter銷燬
    }

}

 

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