Spring Security
DelegatingFilterProxy
初始化
帶入問題
- Spring Security 的過濾器鏈的入口在哪
- 非Spring Boot 項目 (簡稱 Spring)式與Spring Boot 項目(簡稱Spring Boot)
DelegatingFilterProxy
的創建方式;
DelegatingFilterProxy
DelegatingFilterProxy
是整個Spring Security 過濾器鏈的入口,攔截所有的請求;最後交給 FilterChainProxy
處理
Spring DelegatingFilterProxy
的初始化
過濾器創建需要提到 @HandlesTypes
註解和 ServletContainerInitializer
接口;Servlet 規範中指出容器啓動時通過jar中META-INF/services/javax.servlet.ServletContainerInitializer文件中的規定的並實現ServletContainerInitializer
接口的類,通過反射機制實例化這些類並調用onStart(Set<Class> cls,ServletContext sc)方法;通常這些類會被@HandlesTypes
修飾,在實例化這些類的同時查找@HandlesTypes
參數規定的類,將其作爲參數傳到onStart方法中。這裏只是瞭解下@HandlesTypes
和 ServletContainerInitializer
的用法,需要了解更多的可以參考 Servlet 4.0規範 官方文檔
創建一個ServletContainerInitializer 實現類如下;
@HandlesTypes(BootAppInitializer.class)
public class BootAppServletContainerInitializer implements ServletContainerInitializer {
private Logger logger = LoggerFactory.getLogger(BootAppServletContainerInitializer.class);
@Override
public void onStartup(Set<Class<?>> cls, ServletContext ctx) throws ServletException {
logger.info("BootAppInit-->onStart()");
// 沒有找到BootAppInitializer相關的類,接口時該參數爲null
if (cls==null ){
return;
}
List<BootAppInitializer> initializers = new LinkedList<>();
for (Class cs:cls){
// 不是接口或抽象類
if (!cs.isInterface()&&!Modifier.isAbstract(cs.getModifiers())){
try {
initializers.add((BootAppInitializer)cs.newInstance());
}catch (Throwable ex){
logger.error(ex.getMessage());
}
}
}
if(initializers.isEmpty()){
logger.warn("No BootAppInitializer");
}
// 調用onStart方法
for (BootAppInitializer initializer:initializers){
initializer.onStart(ctx);
}
}
}
在Spring中WebApplicationInitializer
爲ServletContainerInitializer
的實現類,並在SpringServletContainerInitializer
上使用了@HandlesTypes
註解,指定收集WebApplicationInitializer
的子類,其中AbstractSecurityWebApplicationInitializer
爲其子類;SpringServletContainerInitializer:onStart()方法核心代碼如下
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
/* 判斷是否爲接口或抽象類 不是則收集,在集成Security 時要自己寫一個非抽象類實現
* AbstractSecurityWebApplicationInitializer抽象類,最後收集的就是自己寫的非抽象的實現類
**/
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
.........
// 循環調用收集到 {WebApplicationInitializer} 子類的onStart()方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
具體過濾器鏈入口的配置與實例化的核心代碼如下,以下代碼刪除了部分代碼保留了創建springSecurityFilterChain的核心代碼,主要看中文註釋部分即可,入口在onStart方法
public abstract class AbstractSecurityWebApplicationInitializer
implements WebApplicationInitializer {
......
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
public final void onStartup(ServletContext servletContext) throws ServletException {
......
beforeSpringSecurityFilterChain(servletContext);
// 調用insertSpringSecurityFilterChain()創建
insertSpringSecurityFilterChain(servletContext);
afterSpringSecurityFilterChain(servletContext);
}
// 實例化Filter
private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = DEFAULT_FILTER_NAME;
// 實例化
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
filterName);
String contextAttribute = getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
// 配置FIlter的信息,
private final void registerFilter(ServletContext servletContext,
boolean insertBeforeOtherFilters, String filterName, Filter filter) {
// 添加到 ServletContext,(讓Filter生效)
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
throw new IllegalStateException(
"Duplicate Filter registration for '" + filterName
+ "'. Check to ensure the Filter is only configured once.");
}
registration.setAsyncSupported(isAsyncSecuritySupported());
/* dispatcherTypes getSecurityDispatcherTypes(){EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR,
DispatcherType.ASYNC)}
*/
EnumSet<DispatcherType> dispatcherTypes = getSecurityDispatcherTypes();
// 攔截的url (/*)
registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters,
"/*");
}
}
在onStart() 中 調用insertSpringSecurityFilterChain方法實例化 Filter ,之後調用registerFilter方法 配置Filter並添加到ServletContext中。ServletContext
初始化完成後,ContextLoaderListener
登場該類實現了ServletContextListener
和繼承了ContextLoader
並覆蓋了contextInitialized()
方法,此方法會在ServletContext
初始完成後執行,具體方法如下,這個方法的具體方法initWebApplicationContext()
實現在ContextLoader
中,這個方法完成了XmlWebApplicationContext
的初始化(bean收集等)。到這裏FilterChainProxy
已配置完成。
@Override
public void contextInitialized(ServletContextEvent event) {
// 初始化XmlWebApplicationContext 並放入ServletContext 中
initWebApplicationContext(event.getServletContext());
}
這個方法執行完後成後便是各種Filter
,HttpServlet
,ServletRequestListener
等的初始化方法的調用,到這裏DelegatingFilterProxy
的init()方法也會執行,該方法會調用initFilterBean()
方法,最終DelegatingFilterProxy
中的成員Filter (變量名:delegate) 被賦值,(從XmlWebApplicationContext
取出)
public class DelegatingFilterProxy extends GenericFilterBean {
@Nullable
private volatile Filter delegate;
....
@Override
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);
}
}
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
@Nullable
protected WebApplicationContext findWebApplicationContext() {
if (this.webApplicationContext != null) {
// The user has injected a context at construction time -> use it...
if (this.webApplicationContext instanceof ConfigurableApplicationContext) {
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) this.webApplicationContext;
if (!cac.isActive()) {
// The context has not yet been refreshed -> do so before returning it...
cac.refresh();
}
}
return this.webApplicationContext;
}
String attrName = getContextAttribute();
if (attrName != null) {
return WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
}
else {
return WebApplicationContextUtils.findWebApplicationContext(getServletContext());
}
}
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;
}
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
}
從上述的類圖和DelegatingFilterProxy
的部分代碼可以看出,執行最終邏輯的Filter爲FilterChainProxy
,過濾器鏈已經初始化完成。接下來分析下Spring Boot 的DelegatingFilterProxy
初始化過程
Spring Boot DelegatingFilterProxy
初始化
在 Spring Boot 中 DelegatingFilterProxy
初始化在SecurityFilterAutoConfiguration
類中完成初始化,在這之前先看一個接口ServletContextInitializer
關於接口的官方描述如下。
/**
* Interface used to configure a Servlet 3.0+ context programmatically.
* Unlike WebApplicationInitializer,
* classes that implement this interface (and do not implement WebApplicationInitializer) will not be detected by SpringServletContainerInitializer and hence will not be automatically bootstrapped by the Servlet container.
*/
@FunctionalInterface
public interface ServletContextInitializer {
/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initialization.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;
}
官方註釋大概意思就是該接口用於編程的方式配置ServletContext
,如果有類實現了該接口而沒有實現WebApplicationInitializer
的話就不會被SpringServletContainerInitializer
檢測到,所以Servlet容器啓動時,不會自動調用該實現類的onStart()方法。
我們看一下DelegatingFilterProxyRegistrationBean
類的繼承關係,該類間接實現了 ServletContextInitializer
,該類主要用來初始化配置DelegatingFilterProxy
。通過源碼分析一下DelegatingFilterProxyRegistrationBean
的onStart()方法什麼時候被調用,這裏只做簡單分析,並不會將每一行的代碼的意義都做分析。這裏先埋個坑,日後再做這個系列的文章。
Spring Boot 啓動流程的入口爲SpringApplication類的run方法,該方法如下(大部分已被省略…)。我們只重點關注流程,不做各個方法功能的具體分析,在這個方法中我們關注的重點是refreshContext(context)
這個方法的調用,首先該方法在通過調用createApplicationContext()
以反射的方式創建並返回 AnnotationConfigServletWebServerApplicationContext
的實例,之後便是調用 refreshContext(context)
將 context
作爲參數傳入,通過如下源碼可以看出在 SpringAbblication
這個類中的 refreshContext(context)
方法最後會調用 AbstractApplicationContext
的refresh()
方法。
public class SpringApplication {
......
public ConfigurableApplicationContext run(String... args) {
.......
ConfigurableApplicationContext context = null;
try {
.......
// 通過反射的方式創建應用上下文,返回的是 AnnotationConfigServletWebServerApplicationContext 實例
context = createApplicationContext();
refreshContext(context);
......
}
catch (Throwable ex) {
.......
}
.......
return context;
}
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
.......
}
AnnotationConfigServletWebServerApplicationContext
的繼承關係如下圖所示,在下圖中重點部分已經用紅框標出,接下來我們圍繞紅框標出的重點類的源碼進行分析。
通過上述的分析我們進入了 AbstractApplicationContext
的 refresh()
方法中,該方法如下(大部分被省略…),和之前的一樣我們只關注 onRefresh()
方法其他的跳過,該方法實際調用子類ServletWebServerApplicationContext
的 onRefresh()
。
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
......
try {
// Initialize other special beans in specific context subclasses.
onRefresh();
......
}
catch (BeansException ex) {
.......
}
finally {
.......
}
}
}
}
ServletWebServerApplicationContext
的部分源碼如下,該方法中調用鏈 onRefresh()->createWebServer()->getWebServerFactory()
,這裏使用的是Spring Boot默認的內嵌tomcat 所以 getWebServerFactory()
返回的的實例爲 TomcatServletWebServerFactory
。
public class ServletWebServerApplicationContext extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 默認Tomcat 返回的實例爲 TomcatServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
return this::selfInitialize;
}
private void selfInitialize(ServletContext servletContext) throws ServletException {
prepareWebApplicationContext(servletContext);
registerApplicationScope(servletContext);
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
servletContext);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
}
通過ServletWebServerFactory
的 getWebServer(ServletContextInitializer... initializers)
方法獲取一個 WebServer
實例,該方法傳入當前對象的 getSelfInitializer()
方法的返回值 ServletContextInitializer
;與其對應的方法體爲 selfInitialize(ServletContext servletContext)
,當某個方法中調用了當前實例ServletContextInitializer
的onStart(ServletContext servletContext)
相當於執行了selfInitialize(ServletContext servletContext)
這個方法。(這裏需要去理解下@FunctionalInterface這個註解的作用及用法);我們繼續跟蹤進入TomcatServletWebServerFactory
的 getWebServer(ServletContextInitializer... initializers)
方法。
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
......
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
configureContext(context, initializersToUse);
......
}
protected void configureContext(Context context,
ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
......
context.addServletContainerInitializer(starter, NO_CLASSES);
......
}
}
在getWebServer
方法中創建Tomcat實例並做了一些配置,該方法中的主要調用鏈 getWebServer()->prepareContext(tomcat.getHost(), initializers)->configureContext(context, initializersToUse)
,跟蹤進入configureContext(context, initializersToUse)
方法,其中以ServletContextInitializer[] initializers
作爲構造參數創建了TomcatStarter
實例,並將其添加到Tomcat的生命週期中,在Tomcat 啓動時會被調用它的 onStart(Set<Class<?>> classes,ServletContext context)
方法,具體的調用在StandardContext
的 5139 行左右,我們跟蹤進入 TomcatStarter
中
class TomcatStarter implements ServletContainerInitializer {
......
TomcatStarter(ServletContextInitializer[] initializers) {
this.initializers = initializers;
}
@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext)
throws ServletException {
try {
for (ServletContextInitializer initializer : this.initializers) {
initializer.onStartup(servletContext);
}
}
catch (Exception ex) {
this.startUpException = ex;
// Prevent Tomcat from logging and re-throwing when we know we can
// deal with it in the main thread, but log for information here.
if (logger.isErrorEnabled()) {
logger.error("Error starting Tomcat context. Exception: "
+ ex.getClass().getName() + ". Message: " + ex.getMessage());
}
}
}
public Exception getStartUpException() {
return this.startUpException;
}
}
TomcatStarter
類中的實現並不複雜,只是循環執行 ServletContextInitializer
的onStart
方法,其中一個就是ServletWebServerApplicationContext
中的 selfInitialize
方法,在該方法中就是對ServletContextInitializer
的子類進行onStart方法的調用,我們關注的DelegatingFilterProxyRegistrationBean
就是其中一個子類,到這裏我們回到之前的方法調用鏈getWebServer()->prepareContext(tomcat.getHost(), initializers)->configureContext(context, initializersToUse)
,分析到這步就是configureContext(context, initializersToUse)
執行完成,回到TomcatServletWebServerFactory
類的getWebServer()
方法中,之後便是調用getTomcatWebServer(tomcat)
方法獲取一個WebServer
實例,我們繼續跟蹤。(爲了方便再把上面的源碼在弄一份下來)
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
......
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
......
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
configureContext(context, initializersToUse);
......
}
protected void configureContext(Context context,
ServletContextInitializer[] initializers) {
TomcatStarter starter = new TomcatStarter(initializers);
......
context.addServletContainerInitializer(starter, NO_CLASSES);
......
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}
}
從上述源碼中可以發現getTomcatWebServer(Tomcat tomcat)
只是一個簡單用 new
關鍵字實例化一個TomcatWebServer
實例,跟蹤進入 TomcatWebServer
類
public class TomcatWebServer implements WebServer {
......
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
}
在TomcatWebServer
的構造函數中執行了initialize()
啓動 Tomcat
。分析到這裏Tomcat
啓動,TomcatStarter
的onStart
方法會被調用,最終DelegatingFilterProxyRegistrationBean
的 onStart
也會被調用。接下來我們分析當DelegatingFilterProxyRegistrationBean
的onStart調用之後 DelegatingFilterProxy
在哪裏被實例化。
根據上圖紅框圈出幾個重要的類,onStart
方法被定義在 RegistrationBean
中,onStart被調用的時候調用定義在DynamicRegistrationBean
中的的 register(String description, ServletContext servletContext)
方法,在register
中調用addRegistration()->getFilter()
(部分方法參數省略)。getFilter定義在DelegatingFilterProxyRegistrationBean
類中
public class DelegatingFilterProxyRegistrationBean
extends AbstractFilterRegistrationBean<DelegatingFilterProxy>
implements ApplicationContextAware {
......
@Override
public DelegatingFilterProxy getFilter() {
return new DelegatingFilterProxy(this.targetBeanName,
getWebApplicationContext()) {
@Override
protected void initFilterBean() throws ServletException {
// Don't initialize filter bean on init()
}
};
}
......
}
getFilter()
方法直接new
創建並返回DelegatingFilterProxy
之後就是各種配置這裏就不在贅述,分析到這裏DelegatingFilterProxy
已經被實例並配置到 ServletContext
中。
總結
- 本篇講述了
DelegatingFilterProxy
作用。 - 從兩個角度(Spring 和Spring Boot)分析了
DelegatingFilterProxy
的初始化過程。 - 在Spring Boot 中還從源碼的角度細緻的進行跟蹤,簡單瞭解Spring Boot的啓動流程。
微信公衆號[UitBG]
GitHub 博客地址