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);
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銷燬
}
}