詳解spring security filter的工作原理
這篇文章介紹filter的工作原理。配置方式為xml。
Filter如何進(jìn)入執(zhí)行邏輯的
初始配置:
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
DelegatingFilterProxy這個(gè)類(lèi)繼承了GenericFilterBean,GenericFilterBean實(shí)現(xiàn)了Filter接口。
這個(gè)配置是一切的開(kāi)始,配置完這個(gè)之后,在啟動(dòng)項(xiàng)目的時(shí)候會(huì)執(zhí)行Filterd的初始化方法:
@Override
public final void init(FilterConfig filterConfig) throws ServletException {
Assert.notNull(filterConfig, "FilterConfig must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
}
this.filterConfig = filterConfig;
// Set bean properties from init parameters.
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
Environment env = this.environment;
if (env == null) {
env = new StandardServletEnvironment();
}
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
String msg = "Failed to set bean properties on filter '" +
filterConfig.getFilterName() + "': " + ex.getMessage();
logger.error(msg, ex);
throw new NestedServletException(msg, ex);
}
}
// Let subclasses do whatever initialization they like.
initFilterBean(); // 這個(gè)方法
if (logger.isDebugEnabled()) {
logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
}
}
在初始化方法中,會(huì)執(zhí)行初始化Filter的方法initFilterBean。這個(gè)方法的實(shí)現(xiàn)在DelegatingFilterProxy中:
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// Fetch Spring root application context and initialize the delegate early,
// if possible. If the root application context will be started after this
// filter proxy, we'll have to resort to lazy initialization.
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac); //這個(gè)方法
}
}
}
}
在這個(gè)初始化方法中又調(diào)用initDelegate方法進(jìn)行初始化:
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
在這個(gè)方法中,先獲取targetBeanName,這個(gè)名字是構(gòu)造方法中賦值的:
public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
this.setTargetBeanName(targetBeanName);
this.webApplicationContext = wac;
if (wac != null) {
this.setEnvironment(wac.getEnvironment());
}
}
這個(gè)名字就是web.xml中配置的名字springSecurityFilterChain:
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter>
springSecurityFilterChain是固定不能改的,如果改了啟動(dòng)時(shí)就會(huì)報(bào)錯(cuò),這是spring 啟動(dòng)時(shí)內(nèi)置的一個(gè)bean,這個(gè)bean實(shí)際是FilterChainProxy。
這樣一個(gè)Filter就初始化話好了,過(guò)濾器chain也初始化好了。
當(dāng)一個(gè)請(qǐng)求進(jìn)來(lái)的時(shí)候,會(huì)進(jìn)入FilterChainProxy執(zhí)行doFilter方法:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
doFilterInternal(request, response, chain);
}
}
先獲取所有的Filter,然后執(zhí)行doFilterInternal方法:
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
// 最終執(zhí)行下面的這些代碼
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
VirtualFilterChain是一個(gè)匿名內(nèi)部類(lèi):
private static class VirtualFilterChain implements FilterChain {
private final FilterChain originalChain;
private final List<Filter> additionalFilters;
private final FirewalledRequest firewalledRequest;
private final int size;
private int currentPosition = 0;
private VirtualFilterChain(FirewalledRequest firewalledRequest,
FilterChain chain, List<Filter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
this.size = additionalFilters.size();
this.firewalledRequest = firewalledRequest;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (currentPosition == size) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " reached end of additional filter chain; proceeding with original chain");
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
originalChain.doFilter(request, response);
}
else {
currentPosition++;
Filter nextFilter = additionalFilters.get(currentPosition - 1);
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " at position " + currentPosition + " of " + size
+ " in additional filter chain; firing Filter: '"
+ nextFilter.getClass().getSimpleName() + "'");
}
nextFilter.doFilter(request, response, this);
}
}
}
filter集合執(zhí)行的邏輯在VirtualFilterChain的doFilter方法中。
filter是如何執(zhí)行的
上面說(shuō)了怎么才能進(jìn)入filter的執(zhí)行邏輯,下面說(shuō)一下filter到底怎么執(zhí)行,為什么一個(gè)
在VirtualFilterChain的doFilter方法可以執(zhí)行所有的filter。
下面寫(xiě)一個(gè)例子,模擬filter的執(zhí)行邏輯。
定義FilterChain接口、Filter接口:
public interface Filter {
void doFilter(String username, int age, FilterChain filterChain);
}
public interface FilterChain {
void doFilter(String username, int age);
}
定義兩個(gè)Filter實(shí)現(xiàn):
public class NameFilter implements Filter {
@Override
public void doFilter(String username, int age, FilterChain filterChain) {
username = username + 1;
System.out.println("username: " + username + " age: " + age);
System.out.println("正在執(zhí)行:NameFilter");
filterChain.doFilter(username, age);
}
}
public class AgeFilter implements Filter {
@Override
public void doFilter(String username, int age, FilterChain filterChain) {
age += 10;
System.out.println("username: " + username + " age: " + age);
System.out.println("正在執(zhí)行:AgeFilter");
filterChain.doFilter(username, age);
}
}
定義一個(gè)FilterChain實(shí)現(xiàn):
public class FilterChainProxy implements FilterChain {
private int position = 0;
private int size = 0;
private List<Filter> filterList = new ArrayList<>();
public void addFilter(Filter filter) {
filterList.add(filter);
size++;
}
@Override
public void doFilter(String username, int age) {
if (size == position) {
System.out.println("過(guò)濾器鏈執(zhí)行結(jié)束");
} else {
Filter filter = filterList.get(position);
position++;
filter.doFilter(username, age, this);
}
}
}
測(cè)試Filter實(shí)現(xiàn):
public class FilterTest {
public static void main(String[] args) {
FilterChainProxy proxy = new FilterChainProxy();
proxy.addFilter(new NameFilter());
proxy.addFilter(new AgeFilter());
proxy.doFilter("張三", 0);
}
}
=======
username: 張三1 age: 0
正在執(zhí)行:NameFilter
username: 張三1 age: 10
正在執(zhí)行:AgeFilter
過(guò)濾器鏈執(zhí)行結(jié)束
在這個(gè)執(zhí)行邏輯中,最重要的是【this】,this就是初始化的好的FilterChain實(shí)例,在這個(gè)測(cè)試實(shí)例中,this就是FilterChainProxy。
執(zhí)行FilterChainProxy的doFilter方法的時(shí)候,傳入了初始參數(shù)username和age,進(jìn)入這個(gè)方法后,根據(jù)position取出相應(yīng)的Filter,初次進(jìn)入position是0,執(zhí)行Filter的doFilter方法,注意,此時(shí)Filter的doFilter方法額外傳入了一個(gè)this參數(shù),這個(gè)參數(shù)就是初始化的好的FilterChain實(shí)例,在Filter中的doFilter的方法中最后又會(huì)執(zhí)行FilterChain的doFilter方法,相當(dāng)于第二次調(diào)用FilterChain實(shí)例的doFilter方法,此時(shí)posotion是1,然后再執(zhí)行Filter的doFilter方法,直到所有的Filter執(zhí)行完,整個(gè)執(zhí)行過(guò)程結(jié)束。
VirtualFilterChain的doFilter方法的執(zhí)行邏輯和這個(gè)測(cè)試實(shí)例中的執(zhí)行邏輯基本一致。
這樣就完成了整個(gè)過(guò)濾器鏈的執(zhí)行。
總結(jié)
以前用Filter的時(shí)候就非常疑惑過(guò)濾器怎么執(zhí)行的,直到今天才算解決了這個(gè)疑惑。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java批量導(dǎo)入Excel數(shù)據(jù)超詳細(xì)實(shí)例
這篇文章主要給大家介紹了關(guān)于java批量導(dǎo)入Excel數(shù)據(jù)的相關(guān)資料,EXCEL導(dǎo)入就是文件導(dǎo)入,操作代碼是一樣的,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下2023-08-08
SpringBoot集成yitter-idgenerator(雪花漂移)分布式Id自增的實(shí)現(xiàn)
本文主要介紹了SpringBoot集成yitter-idgenerator(雪花漂移)分布式Id自增的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01
Java設(shè)計(jì)模式之解釋器模式(Interpreter模式)介紹
這篇文章主要介紹了Java設(shè)計(jì)模式之解釋器模式(Interpreter模式)介紹,Interpreter定義:定義語(yǔ)言的文法,并且建立一個(gè)解釋器來(lái)解釋該語(yǔ)言中的句子,需要的朋友可以參考下2015-03-03
SpringMVC源碼解讀之 HandlerMapping - AbstractDetectingUrlHandlerM
這篇文章主要介紹了SpringMVC源碼解讀之 HandlerMapping - AbstractDetectingUrlHandlerMapping系列初始化的相關(guān)資料,需要的朋友可以參考下2016-02-02
如何只返回實(shí)體類(lèi)中的部分字段問(wèn)題
這篇文章主要介紹了如何只返回實(shí)體類(lèi)中的部分字段問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
Java解決計(jì)算相鄰兩個(gè)數(shù)的最大差值的問(wèn)題
今天給大家?guī)?lái)一道算法題:給定一個(gè)數(shù)組,求如果排序之后,相鄰兩數(shù)的最大差值。要求時(shí)間復(fù)雜度O(N),且要求不能用非基于比較的排序。快來(lái)跟隨小編一起學(xué)習(xí)一下如何解決這一問(wèn)題吧2021-12-12
Springboot整合Thymeleaf引入公共的CSS和JS文件的方法及注意點(diǎn)
有時(shí)候很多css文件是公共的,我們必須要在每個(gè)html文件中引入它們,下面這篇文章主要給大家介紹了關(guān)于Springboot整合Thymeleaf引入公共的CSS和JS文件的方法及注意點(diǎn),需要的朋友可以參考下2024-06-06

