diff --git a/.github/workflows/sync.yml b/.github/workflows/deploy.yml similarity index 100% rename from .github/workflows/sync.yml rename to .github/workflows/deploy.yml diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 03139f1..40a4982 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -8,6 +8,7 @@ export default defineConfig({ ['meta', { name: 'description', content: '读尽天下源码,心中自然无码——源码猎人' }], ['link', { rel: 'icon', type: 'image/png', href: 'https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/favicon-32x32.png' }] ], + ignoreDeadLinks: true, themeConfig: { search: { provider: 'local' diff --git a/docs/Spring/clazz/Spring-SystemPropertyUtils.md b/docs/Spring/clazz/Spring-SystemPropertyUtils.md index 5db5f07..7291fec 100644 --- a/docs/Spring/clazz/Spring-SystemPropertyUtils.md +++ b/docs/Spring/clazz/Spring-SystemPropertyUtils.md @@ -42,7 +42,7 @@ private static final PropertyPlaceholderHelper nonStrictHelper = - 解析属性 -![SystemPropertyUtils-resolvePlaceholders.png](/images/spring/SystemPropertyUtils-resolvePlaceholders.png) +![SystemPropertyUtils-resolvePlaceholders.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/SystemPropertyUtils-resolvePlaceholders.png) 时序图因为有递归所以看着有点长, 其核心方法最后会指向 PlaceholderResolver diff --git a/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md b/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md index 22d121d..52bc9d3 100644 --- a/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md +++ b/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md @@ -3,7 +3,7 @@ - 类全路径: `org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory` - 类图 - ![EmbeddedValueResolutionSupport](/images/spring/DateTimeFormatAnnotationFormatterFactory.png) + ![EmbeddedValueResolutionSupport](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/DateTimeFormatAnnotationFormatterFactory.png) ```java public class DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport diff --git a/docs/Spring/mvc/Spring-MVC-HandlerMapping.md b/docs/Spring/mvc/Spring-MVC-HandlerMapping.md index df07c90..b70dac4 100644 --- a/docs/Spring/mvc/Spring-MVC-HandlerMapping.md +++ b/docs/Spring/mvc/Spring-MVC-HandlerMapping.md @@ -65,7 +65,7 @@ public final HandlerExecutionChain getHandler(HttpServletRequest request) throws 存在的实现方法 - ![image-20200915135933146](images/image-20200915135933146.png) + ![image-20200915135933146](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/image-20200915135933146.png) - 先看`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal`方法是怎么一回事. diff --git a/docs/SpringBoot/Spring-Boot-Run.md b/docs/SpringBoot/Spring-Boot-Run.md index 1f08c26..a989fa1 100644 --- a/docs/SpringBoot/Spring-Boot-Run.md +++ b/docs/SpringBoot/Spring-Boot-Run.md @@ -141,11 +141,11 @@ private List createSpringFactoriesInstances(Class type, Class[] par - `SpringFactoriesLoader.loadFactoryNames(type, classLoader)` 是 spring 提供的方法,主要目的是读取`spring.factories`文件 - 读取需要创建的内容 -![image-20200318080601725](../../images/SpringBoot/image-20200318080601725.png) +![image-20200318080601725](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318080601725.png) - 创建完成 - ![image-20200318080901881](../../images/SpringBoot/image-20200318080901881.png) + ![image-20200318080901881](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318080901881.png) - `AnnotationAwareOrderComparator.sort(instances)`排序 @@ -153,21 +153,21 @@ private List createSpringFactoriesInstances(Class type, Class[] par `SharedMetadataReaderFactoryContextInitializer` - ![image-20200318081112670](../../images/SpringBoot/image-20200318081112670.png) + ![image-20200318081112670](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081112670.png) - 同样的再找一个`DelegatingApplicationContextInitializer` - ![image-20200318081322781](../../images/SpringBoot/image-20200318081322781.png) + ![image-20200318081322781](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081322781.png) - 下图中的所有类都有 Order 数值返回 排序前: -![image-20200318081352639](../../images/SpringBoot/image-20200318081352639.png) +![image-20200318081352639](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081352639.png) 排序后: -![image-20200318081458019](../../images/SpringBoot/image-20200318081458019.png) +![image-20200318081458019](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081458019.png) ### listeners.starting() @@ -360,7 +360,7 @@ private List createSpringFactoriesInstances(Class type, Class[] par ### exceptionReporters -![image-20200318085243888](../../images/SpringBoot/image-20200318085243888.png) +![image-20200318085243888](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318085243888.png) ### prepareContext @@ -439,9 +439,9 @@ private List createSpringFactoriesInstances(Class type, Class[] par context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); ``` -![image-20200318090128983](../../images/SpringBoot/image-20200318090128983.png) +![image-20200318090128983](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318090128983.png) -![image-20200318090312626](../../images/SpringBoot/image-20200318090312626.png) +![image-20200318090312626](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318090312626.png) ### applyInitializers @@ -466,7 +466,7 @@ private List createSpringFactoriesInstances(Class type, Class[] par - 数据结果 -![image-20200318090935285](../../images/SpringBoot/image-20200318090935285.png) +![image-20200318090935285](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318090935285.png) - 子类的具体实现不展开了 @@ -488,7 +488,7 @@ private List createSpringFactoriesInstances(Class type, Class[] par - `primarySources` 就是我们的项目启动类,在`SpringApplication`的构造器中有`this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources))` -![image-20200318091558233](../../images/SpringBoot/image-20200318091558233.png) +![image-20200318091558233](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318091558233.png) ### load @@ -552,7 +552,7 @@ private int load(Object source) { - 通过前文我们已经知道 `source`就是一个 class - ![image-20200318092027020](../../images/SpringBoot/image-20200318092027020.png) + ![image-20200318092027020](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318092027020.png) ```java private int load(Class source) { diff --git a/docs/SpringBoot/SpringBoot-ConditionalOnBean.md b/docs/SpringBoot/SpringBoot-ConditionalOnBean.md index 5a0e06a..f816a15 100644 --- a/docs/SpringBoot/SpringBoot-ConditionalOnBean.md +++ b/docs/SpringBoot/SpringBoot-ConditionalOnBean.md @@ -97,7 +97,7 @@ public enum SearchStrategy { - 类图 - ![image-20200824085726621](../../images/SpringBoot/image-20200824085726621.png) + ![image-20200824085726621](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200824085726621.png) 在看这部分源码之前需要先了解 `Conditional`和`Condition`的源码 @@ -421,7 +421,7 @@ for (String type : spec.getTypes()) { - 在忽略 bean 找到之后做一个类型移除的操作. -![image-20200825140750035](../../images/SpringBoot/image-20200825140750035.png) +![image-20200825140750035](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825140750035.png) ### 返回值 @@ -469,7 +469,7 @@ public static ConditionOutcome noMatch(ConditionMessage message) { return ConditionOutcome.match(matchMessage); ``` -![image-20200825141506531](../../images/SpringBoot/image-20200825141506531.png) +![image-20200825141506531](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825141506531.png) - 到此结果封装完毕.回到方法`org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches(org.springframework.context.annotation.ConditionContext, org.springframework.core.type.AnnotatedTypeMetadata)` 继续进行 - 再往后就继续执行 spring 的 bean 初始化咯 @@ -492,7 +492,7 @@ public static ConditionOutcome noMatch(ConditionMessage message) { - 根据类的注解信息我们可以找到有`ResourceBundleCondition` - ![image-20200825092343271](../../images/SpringBoot/image-20200825092343271.png) + ![image-20200825092343271](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825092343271.png) - 获取类名或者方法名的结果是`MessageSourceAutoConfiguration`全路径 @@ -592,8 +592,8 @@ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition - 此时我们可以和前文的源码分析连接起来有一个完整的认识了 - ![image-20200825142332485](../../images/SpringBoot/image-20200825142332485.png) + ![image-20200825142332485](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825142332485.png) - 最后来看整体类图 - ![image-20200825142418115](../../images/SpringBoot/image-20200825142418115.png) + ![image-20200825142418115](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825142418115.png) diff --git a/docs/SpringBoot/SpringBoot-ConfigurationProperties.md b/docs/SpringBoot/SpringBoot-ConfigurationProperties.md index 124043f..8d3ba5e 100644 --- a/docs/SpringBoot/SpringBoot-ConfigurationProperties.md +++ b/docs/SpringBoot/SpringBoot-ConfigurationProperties.md @@ -33,7 +33,7 @@ public @interface ConfigurationPropertiesScan {} ## ConfigurationPropertiesScanRegistrar -![image-20200323094446756](../../images/SpringBoot/image-20200323094446756.png) +![image-20200323094446756](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323094446756.png) - debug 没有抓到后续补充 @@ -137,11 +137,11 @@ public @interface EnableConfigurationProperties { - 先看输入参数 **metadata** -![image-20200323134135926](../../images/SpringBoot/image-20200323134135926.png) +![image-20200323134135926](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323134135926.png) - getTypes 结果 -![image-20200323134325955](../../images/SpringBoot/image-20200323134325955.png) +![image-20200323134325955](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323134325955.png) - 源码开始,先找出刚才的对象`org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration` @@ -192,7 +192,7 @@ public @interface EnableConfigurationProperties { ## ConfigurationPropertiesBindingPostProcessor -![image-20200323095626953](../../images/SpringBoot/image-20200323095626953.png) +![image-20200323095626953](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323095626953.png) ### postProcessBeforeInitialization @@ -301,15 +301,15 @@ public @interface EnableConfigurationProperties { - `annotation` -![image-20200323104711545](../../images/SpringBoot/image-20200323104711545.png) +![image-20200323104711545](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323104711545.png) - `bindType` -![image-20200323104815305](../../images/SpringBoot/image-20200323104815305.png) +![image-20200323104815305](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323104815305.png) - 返回对象 -![image-20200323105053757](../../images/SpringBoot/image-20200323105053757.png) +![image-20200323105053757](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323105053757.png) - 此时数据还没有进去 @@ -319,7 +319,7 @@ public @interface EnableConfigurationProperties { 直接看结果 -![image-20200323105155998](../../images/SpringBoot/image-20200323105155998.png) +![image-20200323105155998](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323105155998.png) - 上述配置和我在配置文件中写的配置一致 @@ -361,7 +361,7 @@ BindResult bind(ConfigurationPropertiesBean propertiesBean) { } ``` -![image-20200323105830138](../../images/SpringBoot/image-20200323105830138.png) +![image-20200323105830138](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323105830138.png) ##### findProperty @@ -427,11 +427,11 @@ BindResult bind(ConfigurationPropertiesBean propertiesBean) { ``` -![image-20200323115408877](../../images/SpringBoot/image-20200323115408877.png) +![image-20200323115408877](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323115408877.png) -![image-20200323115701118](../../images/SpringBoot/image-20200323115701118.png) +![image-20200323115701118](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323115701118.png) -![image-20200323115711826](../../images/SpringBoot/image-20200323115711826.png) +![image-20200323115711826](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323115711826.png) ##### getBindHandler @@ -464,7 +464,7 @@ private BindHandler getBindHandler(Bindable target, ConfigurationProperti - 最终获取得到的处理器 -![image-20200323110603959](../../images/SpringBoot/image-20200323110603959.png) +![image-20200323110603959](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323110603959.png) - 最后的 bind @@ -498,7 +498,7 @@ private BindHandler getBindHandler(Bindable target, ConfigurationProperti ``` -![image-20200323112945449](../../images/SpringBoot/image-20200323112945449.png) +![image-20200323112945449](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323112945449.png) 配置信息到此绑定成功,关于如何处理集合相关的配置请各位读者自行学习 diff --git a/docs/SpringBoot/SpringBoot-LogSystem.md b/docs/SpringBoot/SpringBoot-LogSystem.md index 8430b12..9da37e9 100644 --- a/docs/SpringBoot/SpringBoot-LogSystem.md +++ b/docs/SpringBoot/SpringBoot-LogSystem.md @@ -19,7 +19,7 @@ - `org.springframework.boot.logging.java.JavaLoggingSystem` - ![image-20200323144523848](../../images/SpringBoot/image-20200323144523848.png) + ![image-20200323144523848](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323144523848.png) ```java static { @@ -125,7 +125,7 @@ private static LoggingSystem get(ClassLoader classLoader, String loggingSystemCl ``` -![image-20200323151409473](../../images/SpringBoot/image-20200323151409473.png) +![image-20200323151409473](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323151409473.png) - 默认日志: `org.springframework.boot.logging.logback.LogbackLoggingSystem` @@ -133,7 +133,7 @@ private static LoggingSystem get(ClassLoader classLoader, String loggingSystemCl - 初始化之前 - ![image-20200323154205484](../../images/SpringBoot/image-20200323154205484.png) + ![image-20200323154205484](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323154205484.png) - 链路 @@ -344,9 +344,9 @@ private static LoggingSystem get(ClassLoader classLoader, String loggingSystemCl - 添加配置文件 - ![image-20200323161442058](../../images/SpringBoot/image-20200323161442058.png) + ![image-20200323161442058](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323161442058.png) - ![image-20200323161522570](../../images/SpringBoot/image-20200323161522570.png) + ![image-20200323161522570](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323161522570.png) - 此时配置文件地址出现了 diff --git a/docs/SpringBoot/SpringBoot-application-load.md b/docs/SpringBoot/SpringBoot-application-load.md index e8641ad..06c60b4 100644 --- a/docs/SpringBoot/SpringBoot-application-load.md +++ b/docs/SpringBoot/SpringBoot-application-load.md @@ -9,17 +9,17 @@ 2. 全局搜索 yml - ![image-20200319083048849](../../images/SpringBoot/image-20200319083048849.png) + ![image-20200319083048849](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319083048849.png) 3. 换成`properties`搜索 - ![image-20200319083140225](../../images/SpringBoot/image-20200319083140225.png) + ![image-20200319083140225](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319083140225.png) 4. 我们以`yml`为例打上断点开始源码追踪 看到调用堆栈 -![image-20200319083345067](../../images/SpringBoot/image-20200319083345067.png) +![image-20200319083345067](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319083345067.png) - 一步一步回上去看如何调用具体方法的 @@ -29,9 +29,9 @@ ### 调用过程 -![image-20200319082131146](../../images/SpringBoot/image-20200319082131146.png) +![image-20200319082131146](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319082131146.png) -![image-20200319082544653](../../images/SpringBoot/image-20200319082544653.png) +![image-20200319082544653](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319082544653.png) `org.springframework.boot.context.config.ConfigFileApplicationListener#addPropertySources` @@ -68,13 +68,13 @@ protected void addPropertySources(ConfigurableEnvironment environment, ResourceL - 搜索目标: `org.springframework.boot.env.PropertySourceLoader` - ![image-20200319084141748](../../images/SpringBoot/image-20200319084141748.png) + ![image-20200319084141748](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319084141748.png) -![image-20200319084151997](../../images/SpringBoot/image-20200319084151997.png) +![image-20200319084151997](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319084151997.png) 观察发现里面有一个`YamlPropertySourceLoader`和我们之前找 yml 字符串的时候找到的类是一样的。说明搜索方式没有什么问题。 -![image-20200319084357652](../../images/SpringBoot/image-20200319084357652.png) +![image-20200319084357652](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319084357652.png) 初始化完成,后续进行解析了 @@ -110,7 +110,7 @@ protected void addPropertySources(ConfigurableEnvironment environment, ResourceL ### initializeProfiles - 初始化`private Deque profiles;` 属性 -- ![image-20200319084902957](../../images/SpringBoot/image-20200319084902957.png) +- ![image-20200319084902957](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319084902957.png) ### load @@ -135,7 +135,7 @@ private void load(Profile profile, DocumentFilterFactory filterFactory, Document - 资源路径可能性 -![image-20200319085446640](../../images/SpringBoot/image-20200319085446640.png) +![image-20200319085446640](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319085446640.png) 该方法采用循环每个路径下面都去尝试一遍 @@ -190,7 +190,7 @@ private void load(Profile profile, DocumentFilterFactory filterFactory, Document ``` -![image-20200319090446231](../../images/SpringBoot/image-20200319090446231.png) +![image-20200319090446231](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319090446231.png) - `PropertiesPropertySourceLoader`解析同理不在次展开描述了 diff --git a/docs/SpringBoot/SpringBoot-自动装配.md b/docs/SpringBoot/SpringBoot-自动装配.md index 049ab30..48754bd 100644 --- a/docs/SpringBoot/SpringBoot-自动装配.md +++ b/docs/SpringBoot/SpringBoot-自动装配.md @@ -53,7 +53,7 @@ public @interface EnableAutoConfiguration { - 类图 -![image-20200320150642022](../../images/SpringBoot/image-20200320150642022.png) +![image-20200320150642022](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320150642022.png) ## getAutoConfigurationMetadata() @@ -107,7 +107,7 @@ public @interface EnableAutoConfiguration { ``` - ![image-20200320160423991](../../images/SpringBoot/image-20200320160423991.png) + ![image-20200320160423991](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320160423991.png) - `protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";` @@ -131,11 +131,11 @@ public @interface EnableAutoConfiguration { org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ ``` -![image-20200320162835665](../../images/SpringBoot/image-20200320162835665.png) +![image-20200320162835665](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320162835665.png) 同样找一下 redis -![image-20200320163001728](../../images/SpringBoot/image-20200320163001728.png) +![image-20200320163001728](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320163001728.png) - 仔细看`org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration`类 @@ -213,13 +213,13 @@ public class RedisProperties { - `org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process` - ![image-20200320163806852](../../images/SpringBoot/image-20200320163806852.png) + ![image-20200320163806852](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320163806852.png) 再此之前我们看过了`getAutoConfigurationMetadata()`的相关操作 关注 `AnnotationMetadata annotationMetadata` 存储了一些什么 -![image-20200320164145286](../../images/SpringBoot/image-20200320164145286.png) +![image-20200320164145286](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320164145286.png) 这里简单理解 @@ -271,7 +271,7 @@ public class RedisProperties { ``` -![image-20200320171138431](../../images/SpringBoot/image-20200320171138431.png) +![image-20200320171138431](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320171138431.png) ### getCandidateConfigurations @@ -289,7 +289,7 @@ public class RedisProperties { ``` -![image-20200320171734270](../../images/SpringBoot/image-20200320171734270.png) +![image-20200320171734270](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320171734270.png) - 第一个是我自己写的一个测试用 @@ -341,7 +341,7 @@ public class RedisProperties { ``` -![image-20200323080611527](../../images/SpringBoot/image-20200323080611527.png) +![image-20200323080611527](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323080611527.png) - 修改启动类 @@ -350,7 +350,7 @@ public class RedisProperties { ``` - ![image-20200323081009823](../../images/SpringBoot/image-20200323081009823.png) + ![image-20200323081009823](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323081009823.png) ### checkExcludedClasses @@ -418,7 +418,7 @@ public class RedisProperties { - `getAutoConfigurationImportFilters()` 从`spring.factories` 获取 `AutoConfigurationImportFilter`的接口 -![image-20200323081903145](../../images/SpringBoot/image-20200323081903145.png) +![image-20200323081903145](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323081903145.png) - 循环内执行`Aware`系列接口 @@ -426,7 +426,7 @@ public class RedisProperties { - `filter.match(candidates, autoConfigurationMetadata)` 比较判断哪些是需要自动注入的类 -![image-20200323082553595](../../images/SpringBoot/image-20200323082553595.png) +![image-20200323082553595](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323082553595.png) ### fireAutoConfigurationImportEvents @@ -448,11 +448,11 @@ public class RedisProperties { ``` -![image-20200323083149737](../../images/SpringBoot/image-20200323083149737.png) +![image-20200323083149737](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323083149737.png) - `AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);` -![image-20200323083247061](../../images/SpringBoot/image-20200323083247061.png) +![image-20200323083247061](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323083247061.png) - `org.springframework.boot.autoconfigure.AutoConfigurationImportListener#onAutoConfigurationImportEvent` 在执行自动配置时触发 , 实现类只有 **`ConditionEvaluationReportAutoConfigurationImportListener`** @@ -470,7 +470,7 @@ public class RedisProperties { ``` -![image-20200323083656670](../../images/SpringBoot/image-20200323083656670.png) +![image-20200323083656670](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323083656670.png) - 初始化完 @@ -478,7 +478,7 @@ public class RedisProperties { - `org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process` -![image-20200323084922159](../../images/SpringBoot/image-20200323084922159.png) +![image-20200323084922159](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323084922159.png) - 后续的一些行为相对简单,直接放个源码了. diff --git a/docs/SpringCloud/spring-cloud-gateway-source-note.md b/docs/SpringCloud/spring-cloud-gateway-source-note.md index ab1a789..e1697ac 100644 --- a/docs/SpringCloud/spring-cloud-gateway-source-note.md +++ b/docs/SpringCloud/spring-cloud-gateway-source-note.md @@ -102,13 +102,13 @@ public class GatewayClassPathWarningAutoConfiguration { > > Route 是由 AsyncPredicate 和 GatewayFilter 组成的。而 AsyncPredicate 由 RoutePredicateFactory 生成,GatewayF 创建 ilter 由 GatewayFilterFactory -![RouteLocator](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteLocator.png) +![RouteLocator](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteLocator.png) > RoutePredicateHandlerMapping 通过 RouteLocator 得到的 `Flux` ,遍历执行`Route.getPredicate().apply(ServerWebExchange)` 返回`true`说明命中了路由规则,将命中的 Route 存到 ServerWebExchange 中,然后执行 FilteringWebHandler 。 > > FilteringWebHandler 的逻辑就是执行 GlobalFilter + GatewayFilter -![Route](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/Route.png) +![Route](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/Route.png) ### 源码 @@ -633,7 +633,7 @@ public @interface ConditionalOnEnabledPredicate { 因为 @ConditionalOnEnabledGlobalFilter 上标注了 @Conditional,所以在 [ConfigurationClassPostProcessor](https://github.com/haitaoss/spring-framework/blob/source-v5.3.10/note/spring-source-note.md#conditional) 解析配置类时,会执行 `OnEnabledGlobalFilter#matches(ConditionContext,AnnotatedTypeMetadata)` 结果是`true`才会将 bean 注册到 BeanFactory 中 -![OnEnabledComponent](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/OnEnabledComponent.png) +![OnEnabledComponent](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/OnEnabledComponent.png) ```java /** @@ -1121,7 +1121,7 @@ public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator 看 PredicateDefinition、FilterDefinition 的构造器,就能明白属性文件为啥可以写 `Weight=group1,8` -![image-20230428141218057](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/image-20230428141218057-1682662351478.png) +![image-20230428141218057](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/image-20230428141218057-1682662351478.png) ## InMemoryRouteDefinitionRepository @@ -1129,7 +1129,7 @@ InMemoryRouteDefinitionRepository 是由 [GatewayAutoConfiguration](#GatewayAuto RouteDefinitionRepository 的职责是通过缓存的方式记录 RouteDefinition,而不是通过属性 映射成 RouteDefinition。而 [AbstractGatewayControllerEndpoint](#AbstractGatewayControllerEndpoint) 会依赖 RouteDefinitionWriter 的实例,用来缓存通过接口方式注册的 RouteDefinition。 -![RouteDefinitionRepository](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteDefinitionRepository.png) +![RouteDefinitionRepository](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteDefinitionRepository.png) ```java public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository { @@ -1186,7 +1186,7 @@ GatewayControllerEndpoint 和 GatewayLegacyControllerEndpoint 是由 [GatewayAut 刷新 RouteDefinition 是会发布 RefreshRoutesEvent 事件,该事件会有 [CachingRouteLocator](#RouteLocator) 处理 -![AbstractGatewayControllerEndpoint](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/AbstractGatewayControllerEndpoint.png) +![AbstractGatewayControllerEndpoint](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/AbstractGatewayControllerEndpoint.png) ## RouteRefreshListener diff --git a/docs/SpringSecurity/SpringSecurity流程补充.md b/docs/SpringSecurity/SpringSecurity流程补充.md index ff4b34c..e69de29 100644 --- a/docs/SpringSecurity/SpringSecurity流程补充.md +++ b/docs/SpringSecurity/SpringSecurity流程补充.md @@ -1,1291 +0,0 @@ -# SpringSecurity 流程补充 - -注意: - -1. 基于 spring-boot-dependencies:2.7.7 -2. 首先需要了解 [springboot2.7 升级](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes) - `Changes to Auto-configuration` 以后使用 `autoconfigure` 进行自动注入 -3. 代码地址 [io.github.poo0054](https://github.com/poo0054/security-study/blob/master/starter/src/main/java/io/github/poo0054/security/StarterApplication.java) - -## 启动 - -我们每次添加 `spring-boot-starter-security`,启动的时候会有一条类似的日志: - -```txt -Using generated springSecurity password: 1db8eb87-e2ee-4c72-88e7-9b85268c4430 - -This generated password is for development use only. Your springSecurity configuration must be updated before running your -application in production. -``` - -找到 `UserDetailsServiceAutoConfiguration#InMemoryUserDetailsManager` 类,它是 springboot 自动装配的。 - -下面这些都是 springboot 自动装配类: - -```java -org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration -org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration -org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration -org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration -org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration -org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration -org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration -.......... -org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration -``` - -## SecurityAutoConfiguration - -```java - -/** - * {@code EnableAutoConfiguration} for Spring Security. - * - * @author Dave Syer - * @author Andy Wilkinson - * @author Madhura Bhave - * @since 1.0.0 - */ -@AutoConfiguration -@ConditionalOnClass(DefaultAuthenticationEventPublisher.class) -@EnableConfigurationProperties(SecurityProperties.class) -@Import({SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class}) -public class SecurityAutoConfiguration { - - @Bean - @ConditionalOnMissingBean(AuthenticationEventPublisher.class) - public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) { - return new DefaultAuthenticationEventPublisher(publisher); - } - -} -``` - -### @EnableConfigurationProperties(SecurityProperties.class) - -这个是 springSecurity 的核心配置类`SecurityProperties`,里面能配置 -`filter`: 过滤,`user` : 用户信息 - -### @Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class }) - -这里导入了 2 个类 `SpringBootWebSecurityConfiguration`和`SecurityDataConfiguration`,`SecurityDataConfiguration`是 Spring -springSecurity 与 Spring 数据的集成,暂时不做讲解,重点是`SpringBootWebSecurityConfiguration` - -#### SpringBootWebSecurityConfiguration - -这个类就是一个 `Configuration` 类,条件必须为 `@ConditionalOnWebApplication(type = Type.SERVLET)` 才会注入 - -##### SecurityFilterChainConfiguration - -其中第一个子类`SecurityFilterChainConfiguration`添加了`@ConditionalOnDefaultWebSecurity`,这个类有个注解 -`@Conditional(DefaultWebSecurityCondition.class)`,而`DefaultWebSecurityCondition`类继承了`AllNestedConditions` - -所以下面代码就是判断该类是否生效,如果不存在`SecurityFilterChain`和`WebSecurityConfigurerAdapter` -的 bean,就生效。创建默认的`SecurityFilterChain` - -```java -/** - * {@link Condition} for - * {@link ConditionalOnDefaultWebSecurity @ConditionalOnDefaultWebSecurity}. - * - * @author Phillip Webb - */ -class DefaultWebSecurityCondition extends AllNestedConditions { - - DefaultWebSecurityCondition() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @ConditionalOnClass({SecurityFilterChain.class, HttpSecurity.class}) - static class Classes { - - } - - @ConditionalOnMissingBean({ - org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.class, - SecurityFilterChain.class}) - @SuppressWarnings("deprecation") - static class Beans { - - } - -} -``` - -`SecurityFilterChain`就是整个 springsecurity 的流程了,有俩个方法,一个是`boolean matches(HttpServletRequest request);` -,是否匹配这次请求,匹配成功就获取当前所有`Filter`进行处理 - -`SecurityFilterChain`类会放在最下面单独讲解 - -##### ErrorPageSecurityFilterConfiguration - -这是第二个子类,主要就是通过`FilterRegistrationBean`注入了一个`ErrorPageSecurityFilter`。 用于拦截错误调度,以确保对错误页面的授权访问。 - -```java - /** -* Configures the {@link ErrorPageSecurityFilter} -*/ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass(WebInvocationPrivilegeEvaluator.class) -@ConditionalOnBean(WebInvocationPrivilegeEvaluator.class) -static class ErrorPageSecurityFilterConfiguration { - - @Bean - FilterRegistrationBean errorPageSecurityFilter(ApplicationContext context) { - FilterRegistrationBean registration = new FilterRegistrationBean<>( - new ErrorPageSecurityFilter(context)); - registration.setDispatcherTypes(DispatcherType.ERROR); - return registration; - } - -} -``` - -##### WebSecurityEnablerConfiguration - -这个类主要就是添加了`@EnableWebSecurity`注解,这个注解也很重要,后面跟`SecurityFilterChain`一起讲解 - -### DefaultAuthenticationEventPublisher - -在类中还存在`SecurityAutoConfiguration`bean,这个是属于 spring 的发布订阅。改装一下,就是 springSecurity 的成功和失败事件,可以订阅失败后的一些处理,如日志打印等 - -```java -/** - * @author Luke Taylor - * @since 3.0 - */ -public interface AuthenticationEventPublisher { - - void publishAuthenticationSuccess(Authentication authentication); - - void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication); - -} -``` - -## UserDetailsServiceAutoConfiguration - -注入条件 - -```java - -@ConditionalOnBean(ObjectPostProcessor.class) -@ConditionalOnMissingBean( - value = {AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, - AuthenticationManagerResolver.class}, - type = {"org.springframework.security.oauth2.jwt.JwtDecoder", - "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector", - "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", - "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository"}) -``` - -### InMemoryUserDetailsManager - -UserDetailsManager 的非持久化实现,支持内存映射。 -主要用于测试和演示目的,其中不需要完整的持久系统 - -## SecurityFilterAutoConfiguration - -SpringSecurity 的过滤器 - -自动配置。与 SpringBootWebSecurityConfiguration 分开配置,以确保在存在用户提供的 WebSecurityConfiguration 时,过滤器的顺序仍然被配置。 - -### DelegatingFilterProxyRegistrationBean - -这个类是继承了`AbstractFilterRegistrationBean`,`FilterRegistrationBean`也是继承的`AbstractFilterRegistrationBean` -。但是`DelegatingFilterProxyRegistrationBean`是使用的一个`targetBeanName` -找到 bean。进行注入。其中`private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;` -也就是`springSecurityFilterChain` - -其中有俩个属性, Order 和 DispatcherTypes - -- Order 默认为-100 - -- `DispatcherType`就是`DispatcherType`类 - - `private Set dispatcherTypes = new HashSet<>( -Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));` - -注意: 这里需要了解一下`DelegatingFilterProxyRegistrationBean`以及 spring 如何整合 filter 和 mvc 的。 springSecurity 核心就是 filter - -![img.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7_0.png) - -`DelegatingFilterProxyRegistrationBean`和`FilterRegistrationBean`都是继承的`RegistrationBean`,而`RegistrationBean` -又是`ServletContextInitializer`的实现类。其中`void onStartup(ServletContext servletContext)`方法是关键。 在`javax.servlet` -中,存在这样一个类 - -```java -public interface ServletContainerInitializer { - - /** - * Receives notification during startup of a web application of the classes - * within the web application that matched the criteria defined via the - * {@link javax.servlet.annotation.HandlesTypes} annotation. - * - * @param c The (possibly null) set of classes that met the specified - * criteria - * @param ctx The ServletContext of the web application in which the - * classes were discovered - * - * @throws ServletException If an error occurs - */ - void onStartup(Set> c, ServletContext ctx) throws ServletException; -``` - -springboot 中的`TomcatStarter`继承了这个类,而这个类又是 spring 启动的核心`AbstractApplicationContext#refresh()` -中的`onRefresh();`方法。 - -找到实现类`ServletWebServerApplicationContext`的`onRefresh()`方法。里面有`createWebServer();`方法。 -在里面有创建`webServer`的方法。`this.webServer = factory.getWebServer(getSelfInitializer());`这个就是创建`tomcat`的工厂。 -`TomcatServletWebServerFactory#getWebServer(ServletContextInitializer... initializers)` -。里面就是创建 tomcat,并启动`TomcatWebServer#initialize()`(这就是 springboot 不用 tamcat 的原因) - -而把 filter 注入 servlet 中的是`TomcatServletWebServerFactory#prepareContext(Host host, ServletContextInitializer[] initializers)` -中的`TomcatServletWebServerFactory#configureContext(context, initializersToUse)` -方法,在里面创建了一个`TomcatStarter starter = new TomcatStarter(initializers);`。而`TomcatStarter` -继承了`ServletContainerInitializer`类。调用`ServletContainerInitializer#onStartup(ServletContext servletContext)` -时候会进入到`RegistrationBean`中。 -然后`AbstractFilterRegistrationBean#addRegistration`里面添加 filter -`return servletContext.addFilter(getOrDeduceName(filter), filter);`这样每次请求 servlet,tomcat 就会先使用 filter 过滤器进行拦截 - -简单来说就是`TomcatStarter`继承了`ServletContainerInitializer`。tomcat 会调用`onStartup` -方法,在这个方法里面会调用`ServletContextInitializer#onStartup`。在这个里面有`filter`和其余需要整合`ServletContext`的方法 - -比如`springSecurityFilterChain`使用的是`DelegatingFilterProxyRegistrationBean`,需要使用 bean 去获取`getFilter` -。而`ErrorPageFilter`使用的是`FilterRegistrationBean`。直接就可以注入 - -## @EnableWebSecurity - -这个就是 springSecurity 的核心注解 -需要注意,`@EnableWebMvcSecurity`已经弃用,请使用`@EnableWebSecurity` - -```java - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Documented -@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, - HttpSecurityConfiguration.class}) -@EnableGlobalAuthentication -@Configuration -public @interface EnableWebSecurity { - - /** - * Controls debugging support for Spring Security. Default is false. - * @return if true, enables debug support with Spring Security - */ - boolean debug() default false; - -} -``` - -### WebSecurityConfiguration - -首先先看类注释 (以后都默认翻译成简体中文 .ali 翻译): - -使用 WebSecurity 创建执行 Spring 安全 web 安全的 FilterChainProxy。然后导出必要的 bean。 -可以通过实现 WebSecurityConfigurer WebSecurityConfigurer 并将其公开为 Configuration 或公开 WebSecurityCustomizer bean 来进行自定义。 -使用 EnableWebSecurity 时会导入该配置。 - -#### setFilterChainProxySecurityConfigurer - -逐行解释: - -> this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor)); - -`ObjectPostProcessor`也就是`AutowireBeanFactoryObjectPostProcessor`。在`AuthenticationConfiguration` -类上`@Import(ObjectPostProcessorConfiguration.class)`. - -`AutowireBeanFactoryObjectPostProcessor`类里面创建`webSecurity` -`AutowireBeanFactoryObjectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));` -使用`AutowireCapableBeanFactory`创建出`WebSecurity` -`AutowireBeanFactoryObjectPostProcessor`把`SmartInitializingSingleton`和`DisposableBean`拿出来,使用自己的`destroy()` -和`afterSingletonsInstantiated()`执行 - -> List> webSecurityConfigurers = new -> AutowiredWebSecurityConfigurersIgnoreParents(beanFactory).getWebSecurityConfigurers(); - -`AutowiredWebSecurityConfigurersIgnoreParents`也就是获取所有的`WebSecurityConfigurerAdapter` - -这里有几个类需要了解`SecurityConfigurer`和`SecurityBuilder` - -先了解一下结构 -![img_1.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7_1.png) - -![img_2.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7-_2.png) - -使用`WebSecurity` -聚合了`private final LinkedHashMap>, List>> configurers = new LinkedHashMap<>();` -也就是`WebSecurityConfigurerAdapter`(当然还有别的,这里主要讲`WebSecurityConfigurerAdapter`) - -`WebSecurityConfigurerAdapter`也可以认为就是`SecurityConfigurer` - -`WebSecurity`也就是`SecurityBuilder` - -然后在`SecurityBuilder`的实现类`AbstractConfiguredSecurityBuilder`的`doBuild()`方法进行很多别的操作。 - -```java -protected final O doBuild() throws Exception { - synchronized (this.configurers) { - this.buildState = BuildState.INITIALIZING; - beforeInit(); - init(); - this.buildState = BuildState.CONFIGURING; - beforeConfigure(); - configure(); - this.buildState = BuildState.BUILDING; - O result = performBuild(); - this.buildState = BuildState.BUILT; - return result; - } -} -``` - -回到原来地方,返回的`webSecurityConfigurers`,里面的 - -```java -for (SecurityConfigurer webSecurityConfigurer : webSecurityConfigurers) { - this.webSecurity.apply(webSecurityConfigurer); -} -``` - -然后就到了`AbstractConfiguredSecurityBuilder#apply`方法,里面调用了`add(configurer);` 也就是把`SecurityConfigurer` -放入了`AbstractConfiguredSecurityBuilder#configurers`的一个 map 中,这样就使用`SecurityBuilder`聚合了`SecurityConfigurer`。 -在构建的时候可以做一些事情 - -也就是说使用`WebSecurity`聚合了`SecurityConfigurer`(包括`WebSecurityConfigurerAdapter`) - -> this.securityFilterChains = securityFilterChains; - -获取所有的`securityFilterChains` - -> this.webSecurityCustomizers = webSecurityCustomizers; - -获取所有`webSecurityCustomizers` - -#### public Filter springSecurityFilterChain() - -这个里面最关键的也就是这个了。 - -```java -for (SecurityFilterChain securityFilterChain : this.securityFilterChains) { - this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain); - for (Filter filter : securityFilterChain.getFilters()) { - if (filter instanceof FilterSecurityInterceptor) { - this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter); - break; - } - } -} -for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) { - customizer.customize(this.webSecurity); -} -return this.webSecurity.build(); -``` - -首先使用根据获取到的`securityFilterChains`set 入`WebSecurity#securityFilterChainBuilders`的 List 属性 - -这里有个需要注意的地方,如果你继承了`WebSecurityConfigurerAdapter`。`this.securityFilterChains` 就是一个空的。 - -而且会由`WebSecurityConfigurerAdapter#getHttp()`进行创建`WebSecurity` -。这就跟 spring 的 order 有关了。 `@Order(SecurityProperties.BASIC_AUTH_ORDER)` - -其中`SpringBootWebSecurityConfiguration#SecurityFilterChainConfiguration`有一个注解`@ConditionalOnDefaultWebSecurity` - -```java - -@Configuration(proxyBeanMethods = false) -@ConditionalOnDefaultWebSecurity -static class SecurityFilterChainConfiguration { - - @Bean - @Order(SecurityProperties.BASIC_AUTH_ORDER) - SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { - http.authorizeRequests().anyRequest().authenticated(); - http.formLogin(); - http.httpBasic(); - return http.build(); - } - -} -``` - -这里会创建`SecurityFilterChain`。 还会有一个`HttpSecurity`的注入 - -继续回到上面, - -```java -if (filter instanceof FilterSecurityInterceptor) { - this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter); - break; -} -``` - -`FilterSecurityInterceptor`也在这里进行处理,也就是`SecurityMetadataSource`元数据 - -然后自定义的`WebSecurityCustomizer`也在这里。可以自行改变`webSecurity` - -```java -for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) { - customizer.customize(this.webSecurity); -} -``` - -接下来就是构建了,来到 `AbstractConfiguredSecurityBuilder#doBuild()` - -```java -protected final O doBuild() throws Exception { - synchronized (this.configurers) { - this.buildState = BuildState.INITIALIZING; - beforeInit(); - init(); - this.buildState = BuildState.CONFIGURING; - beforeConfigure(); - configure(); - this.buildState = BuildState.BUILDING; - O result = performBuild(); - this.buildState = BuildState.BUILT; - return result; - } -} -``` - -> init(); -> 如果继承了`WebSecurityConfigurerAdapter`,就会在这里创建`HttpSecurity` - -注意: 这里个`buildState`,用来控制当前状态的 - -> beforeConfigure(); - -在当前是没有什么处理 - -> configure(); - -这行代码就是我们每次继承`WebSecurityConfigurerAdapter`的处理了 - -> O result = performBuild(); - -然后就到了`WebSecurity#performBuild()`。 - -1. 首先排除忽略的`RequestMatcher` -2. 添加入`securityFilterChain` 和`requestMatcherPrivilegeEvaluatorsEntries` -3. 创建出`FilterChainProxy`bean 的名称为`springSecurityFilterChain` (重点) - -剩下的都是一些创建一些 bean 了。 - -`SecurityExpressionHandler`: 默认为`DefaultWebSecurityExpressionHandler`类 (Facade 将 springSecurity 评估安全表达式的要求与基础表达式对象的实现隔离) - -`WebInvocationPrivilegeEvaluator`: 为 `WebSecurity#performBuild()`中创建的 `requestMatcherPrivilegeEvaluatorsEntries` -使用`RequestMatcherDelegatingWebInvocationPrivilegeEvaluator`包装。(允许用户确定他们是否具有给定 web URI 的特权。) - -这俩个类都是很重要的。 一个是解析器,一个是判断 uri 是否合格的类。 后面单独讲 - -### HttpSecurityConfiguration - -接下来到了`HttpSecurityConfiguration` - -根据上面`WebSecurityConfiguration`,可以得出。`WebSecurityConfiguration`创建`WebSecurity`,`WebSecurity` -创建了`FilterChainProxy`的 bean。 - -`HttpSecurityConfiguration`创建`HttpSecurity`。 而在`SecurityFilterChainConfiguration`类中,使用`HttpSecurity` -创建了`SecurityFilterChain`。这也就是我们使用了`WebSecurityConfigurerAdapter`。为什么会存在`SecurityFilterChain` -类的原因。是`SecurityFilterChainConfiguration#defaultSecurityFilterChain`创建了一个`SecurityFilterChain`。 - -得出结论,`FilterChainProxy`持有`SecurityFilterChain`。而`DelegatingFilterProxyRegistrationBean`又持有`FilterChainProxy` - -`DelegatingFilterProxyRegistrationBean`->`FilterChainProxy`->`SecurityFilterChain` - -其实到了这一步。后面的就是我们自己编写的代码了比如 - -```java -@Bean -public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // 配置认证 - http.formLogin(); - - // 设置 URL 的授权问题 - // 多个条件取交集 - http.authorizeRequests() - // 匹配 / 控制器 permitAll() 不需要被认证就可以访问 - .antMatchers("/login").permitAll() - .antMatchers("/error").permitAll() - .antMatchers("/fail").permitAll() - // anyRequest() 所有请求 authenticated() 必须被认证 - .anyRequest().authenticated(); - // .accessDecisionManager(accessDecisionManager()); - // 关闭 csrf - http.csrf().disable(); - return http.build(); -} -``` - -或者继承 WebSecurityConfigurerAdapter 的类了。 - -这里我们用 springSecurity 默认的`SpringBootWebSecurityConfiguration`来举例 - -```java -@Bean -@Order(SecurityProperties.BASIC_AUTH_ORDER) -SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { - http.authorizeRequests().anyRequest().authenticated(); - http.formLogin(); - http.httpBasic(); - return http.build(); -} -``` - -首先获取`HttpSecurity http`(这里的`HttpSecurity`是从`HttpSecurityConfiguration` -里面创建的,如果是自定义的`SecurityFilterChain`就是从自己写的里面来的) - -> 我们来到`HttpSecurityConfiguration#httpSecurity()` -> 先创建一个默认的密码管理器, - -接下来进入`authenticationBuilder.parentAuthenticationManager(authenticationManager());` -,这里就是`AuthenticationConfiguration` -里面的处理。这个类后面和 springaop 加载我们写的注释单独在`@EnableGlobalAuthentication`注解类说 - -接着创建` HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());`, - -> .csrf(withDefaults()) -> 启用 CSRF 保护,创建`CsrfConfigurer`类,并添加入添加入`AbstractConfiguredSecurityBuilder#configurers` - -> .addFilter(new WebAsyncManagerIntegrationFilter()) - -这个有一个很有意思的类`FilterOrderRegistration`。 前面问的根据 filter 是如何包装顺序的。就在这个类里面 - -```java -FilterOrderRegistration() { - Step order = new Step(INITIAL_ORDER, ORDER_STEP); - put(DisableEncodeUrlFilter.class, order.next()); - put(ForceEagerSessionCreationFilter.class, order.next()); - put(ChannelProcessingFilter.class, order.next()); - order.next(); // gh-8105 - put(WebAsyncManagerIntegrationFilter.class, order.next()); - put(SecurityContextHolderFilter.class, order.next()); - put(SecurityContextPersistenceFilter.class, order.next()); - put(HeaderWriterFilter.class, order.next()); - put(CorsFilter.class, order.next()); - put(CsrfFilter.class, order.next()); - put(LogoutFilter.class, order.next()); -} -``` - -springSecurity 事先使用这个类把预加载的类全部排序好,然后每次 add 一个新的 filter 就会使用这个里面的序号。如果我们有自定义的类,也要提前加载到里面去,不然就会 - -```java -throw new IllegalArgumentException("The Filter class "+filter.getClass().getName() - +" does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead."); -``` - -`WebAsyncManagerIntegrationFilter`: 与处理异步 web 请求相关的实用程序方法 - -> .exceptionHandling(withDefaults()) - -这个是异常的处理 - -提示: 这个里面每次添加一个类,如果在`HttpSecurity`中调用`getOrApply` -。比如这个代码调用的是`exceptionHandlingCustomizer.customize(getOrApply(new ExceptionHandlingConfigurer<>()));`。 -打开`ExceptionHandlingConfigurer`类,发现是一个`HttpSecurityBuilder`, 这样只需要看`configure`方法大概就能明白这个是一个什么类。 -这个就是在 filter 中添加了一个`ExceptionTranslationFilter`filter. 主要就是`SecurityConfigurer` -的俩个方法。先调用`init(B builder)`,然后`configure(B builder)` - -后面都是一样,就跳过了 - -> applyDefaultConfigurers(http); - -这里的这一句,就是从 "META-INF/spring.factories" 中加载并实例化给定类型的工厂实现 -`SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader)` -然后调用`http.apply(configurer);` 添加到`configurers`里面 - -接下来回到`SecurityFilterChainConfiguration`类 - -> http.authorizeRequests().anyRequest().authenticated(); - -首先添加了`http.authorizeRequests()` -然后调用 `return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)).getRegistry();`。 -先把`ExpressionUrlAuthorizationConfigurer`放入 config 中,返回一个调用了`getRegistry()`。 -也就是`ExpressionInterceptUrlRegistry`类。 - -后面调用的`.anyRequest()`,也就是`AbstractRequestMatcherRegistry#anyRequest()`。先了解一下结构图 - -![img_3.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7-_3.png) - -完整调用链就是`AbstractRequestMatcherRegistry#anyRequest()` -> `AbstractRequestMatcherRegistry#requestMatchers(RequestMatcher... requestMatchers)` -> `AbstractConfigAttributeRequestMatcherRegistry#chainRequestMatchers(List requestMatchers)` -> `ExpressionUrlAuthorizationConfigurer#chainRequestMatchersInternal(List requestMatchers)` -> `return new AuthorizedUrl(requestMatchers);` - -`http.authorizeRequests().anyRequest().authenticated();`是需要所有请求登录后才能访问 - -1. `authorizeRequests`是创建了一个`ExpressionUrlAuthorizationConfigurer`并添加入 configurer 中。 -2. `anyRequest`是创建了一个`new AuthorizedUrl(requestMatchers)`,其中 `requestMatchers` - 是`AnyRequestMatcher.INSTANCE;`也就是`AnyRequestMatcher`对象。里面`matches(HttpServletRequest request)`直接返回 ture -3. `authenticated()`也就是授权,`ExpressionInterceptUrlRegistry#addMapping`。里面放入了一个`UrlMapping`,`UrlMapping` - 的俩个属性,一个是`AnyRequestMatcher`(所有请求),`configAttrs`表示`SecurityConfig`。`SecurityConfig` - 的值为`private static final String authenticated = "authenticated"` - -> http.formLogin(); - -创建了一个`FormLoginConfigurer`,也就是`SecurityConfigurer`。关注`init`和`configure`方法。后面统一讲解 - -> http.httpBasic(); - -`HttpBasicConfigurer`类 - -> http.build() - -进行构建,这个就是非常重要的一个方法,build 对象,老规矩。进入`AbstractConfiguredSecurityBuilder#doBuild()`方法 -`beforeInit();`: 还是没有什么 - -`init()`: 调用里面所有的`configurers`里面的`init 方法`,后面`HttpSecurity#doBuild`统一讲解,先把流程捋一遍 - -接下来`SecurityFilterChain`就已经创建好了,看一下里面的方法 - -```java -/** - * Defines a filter chain which is capable of being matched against an - * {@code HttpServletRequest}. in order to decide whether it applies to that request. - *

- * Used to configure a {@code FilterChainProxy}. - * - * @author Luke Taylor - * @since 3.1 - */ -public interface SecurityFilterChain { - - boolean matches(HttpServletRequest request); - - List getFilters(); -} -``` - -肯定是先匹配,如果成功了,就返回里面所有的 filter 进行过滤,比如刚刚设置的所有请求需要登录,也还有我们需要排除的请求 - -`SecurityAutoConfiguration`类就已经大致讲完了, - -### @EnableGlobalAuthentication - -当前注解在`@EnableSecurity`中会自动加上 - -#### @Import(AuthenticationConfiguration.class) - -`AuthenticationConfiguration`上面`@Import(ObjectPostProcessorConfiguration.class)`。 以前使用的`ObjectPostProcessor` -就是在这里注入的,注入`AutowireBeanFactoryObjectPostProcessor`对象 - -#### AuthenticationManagerBuilder - -```java -@Bean -public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor objectPostProcessor, - ApplicationContext context) { - LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); - AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context); - DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder( - objectPostProcessor, defaultPasswordEncoder); - if (authenticationEventPublisher != null) { - result.authenticationEventPublisher(authenticationEventPublisher); - } - return result; -} -``` - -这里面返回了一个`AuthenticationManagerBuilder`的 bean,也就是上面`HttpSecurityConfiguration#httpSecurity()`的时候需要的类,这个类也是一个`SecurityBuilder`。 - -```java -LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); -``` - -首先创建了一个`LazyPasswordEncoder`,就是`PasswordEncoder`,用来管理密码的 - -```java -AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context); -``` - -这个就是在 `SecurityAutoConfiguration` 中创建的 springSecurity 的发布订阅,用来订阅事件 - -```java -DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder); -``` - -就是 `AuthenticationManagerBuilder` 的真正实现了。接下来回到`getAuthenticationManager()`方法 - -```java -public AuthenticationManager getAuthenticationManager() throws Exception { - if (this.authenticationManagerInitialized) { - return this.authenticationManager; - } - AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); - if (this.buildingAuthenticationManager.getAndSet(true)) { - return new AuthenticationManagerDelegator(authBuilder); - } - for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) { - authBuilder.apply(config); - } - this.authenticationManager = authBuilder.build(); - if (this.authenticationManager == null) { - this.authenticationManager = getAuthenticationManagerBean(); - } - this.authenticationManagerInitialized = true; - return this.authenticationManager; -} -``` - -```java -AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); -``` - -获取到 `DefaultPasswordEncoderAuthenticationManagerBuilder` - -```java -for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) { - authBuilder.apply(config); -} -``` - -需要注意的是,`this.globalAuthConfigurers`就是上面三个类, - -```java -@Bean -public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer( - ApplicationContext context) { - return new EnableGlobalAuthenticationAutowiredConfigurer(context); -} - -@Bean -public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer( - ApplicationContext context) { - return new InitializeUserDetailsBeanManagerConfigurer(context); -} - -@Bean -public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer( - ApplicationContext context) { - return new InitializeAuthenticationProviderBeanManagerConfigurer(context); -} -``` - -调用了`apply`也就是`add`方法。添加到`configurers`中 - -然后调用`build`并返回。 又是到了 `doBuild()` 这里 - -> beforeInit(); - -没有 - -> init(); - -上面三个类的`init`方法 - -1. `EnableGlobalAuthenticationAutowiredConfigurer#init` -2. `InitializeUserDetailsBeanManagerConfigurer#init` 调用了`auth.apply(new InitializeUserDetailsManagerConfigurer());` - 这个类比上面类名字少了一个 bean,并且没有后 init 方法 只有`configure`方法。 里面创建的`DaoAuthenticationProvider` - ,里面默认有一个`passwordEncoder`,在无参构造方法里面。而`UserDetailsService`和`DaoAuthenticationProvider` - 是同一个,也就是在`UserDetailsServiceAutoConfiguration#inMemoryUserDetailsManager`这里创建的。里面继承了,所以是同一个 -3. `InitializeAuthenticationProviderBeanManagerConfigurer#init` - 跟 2 一样,apply 了一个`InitializeAuthenticationProviderManagerConfigurer` - -> beforeConfigure(); - -没有 - -> configure(); - -调用里面的`configure`方法 - -2: `InitializeUserDetailsManagerConfigurer#configure`方法 - -```java -@Override -public void configure(AuthenticationManagerBuilder auth) throws Exception { - if (auth.isConfigured()) { - return; - } - UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class); - if (userDetailsService == null) { - return; - } - PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class); - UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class); - DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); - provider.setUserDetailsService(userDetailsService); - if (passwordEncoder != null) { - provider.setPasswordEncoder(passwordEncoder); - } - if (passwordManager != null) { - provider.setUserDetailsPasswordService(passwordManager); - } - provider.afterPropertiesSet(); - auth.authenticationProvider(provider); -} -``` - -获取所有`UserDetailsService`和`PasswordEncoder`和`UserDetailsPasswordService`,使用`DaoAuthenticationProvider` -进行管理,然后添加到`AuthenticationManagerBuilder#authenticationProviders`中 - -3: `InitializeAuthenticationProviderManagerConfigurer#configure`方法,把 spring 中的所有`AuthenticationProvider` -添加到`AuthenticationManagerBuilder#authenticationProviders`中 - -然后又到了熟悉的`AuthenticationManagerBuilder#performBuild` - -```java -ProviderManager providerManager = new ProviderManager(this.authenticationProviders, - this.parentAuthenticationManager); -if (this.eraseCredentials != null) { - providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials); -} -if (this.eventPublisher != null) { - providerManager.setAuthenticationEventPublisher(this.eventPublisher); -} -providerManager = postProcess(providerManager); -return providerManager; -``` - -首先使用`ProviderManager`管理`authenticationProviders`和`parentAuthenticationManager`,这里的`eraseCredentials` -和`CredentialsContainer`类有关,也就是敏感数据。接着的`eventPublisher`就是发布订阅了,默认会创建的 -然后`providerManager = postProcess(providerManager);`就是注入 spring 容器中,接着返回 -,这里返回的其实是`ProviderManager`对象了,接着就是到了`HttpSecurity`的创建了,后面`HttpSecurity#doBuild()` -时候再讲`HttpSecurity`的构建 - -这里面的`LazyPasswordEncoder`这个类也很有意思,手动制造一个懒加载类 - -## @EnableGlobalMethodSecurity - -这里有个很坑的地方,里面的`prePostEnabled`,`securedEnabled`这些属性,不是直接在`GlobalMethodSecuritySelector` -中进行处理的,放在了`GlobalMethodSecuritySelector#methodSecurityMetadataSource` -这个 bean 里面进行处理,然后开启`prePostEnabled`之后,就会加载`PrePostAnnotationSecurityMetadataSource`类。 这个我找了半天,后面无意中才发现 - -这个注解也添加了`@EnableGlobalAuthentication`注解 - -主要是看`GlobalMethodSecuritySelector`类 -里面加载了`AutoProxyRegistrar`,这个就是 springaop 的类,创建代理对象的一个类。会创建`InfrastructureAdvisorAutoProxyCreator` -类来创建代理对象。关键是`GlobalMethodSecurityConfiguration`和`MethodSecurityMetadataSourceAdvisorRegistrar`这俩个类 - -`MethodSecurityMetadataSourceAdvisorRegistrar`类里面都用到了`GlobalMethodSecurityConfiguration`。我就放在一起了 - -### MethodSecurityMetadataSourceAdvisorRegistrar - -这个类里面就是往 spring 中注册了一个`MethodSecurityMetadataSourceAdvisor`对象 - -#### MethodSecurityMetadataSourceAdvisor - -这个类就是`PointcutAdvisor`,使用`AbstractAutoProxyCreator`创建代理对象的是,会获取`Pointcut` -来判断是否需要代理对象,然后使用`Advice`来进行其余操作。这是 springaop 的内容就不过多讲解了 - -aop 首先获取`pointcut`,进行匹配,当前的为 - -```java -class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { - - @Override - public boolean matches(Method m, Class targetClass) { - MethodSecurityMetadataSource source = MethodSecurityMetadataSourceAdvisor.this.attributeSource; - return !CollectionUtils.isEmpty(source.getAttributes(m, targetClass)); - } - -} -``` - -也就是`StaticMethodMatcherPointcut`,`ClassFilter` -默认都是 true,方法匹配为`MethodSecurityMetadataSourceAdvisor#attributeSource`进行匹配。而`methodSecurityMetadataSource` -是在`GlobalMethodSecurityConfiguration#methodSecurityMetadataSource`里面进行创建的。这俩个类后面讲。只要匹配成功就和 aop 一样流程了 - -这里的`Advice`就是`MethodSecurityInterceptor`类。在`GlobalMethodSecurityConfiguration#methodSecurityInterceptor`中创建 - -##### MethodSecurityInterceptor - -> isPrePostEnabled - -添加`PrePostAnnotationSecurityMetadataSource`类,主要关注`getAttributes`方法后面会讲。 -这里面就是我们常用的注解了,然后构建成`ConfigAttribute`并返回。里面的构建主要用的是`PrePostInvocationAttributeFactory` -的实现,只有一个实现 - -> isSecuredEnabled - -这个就是`@Secured`注解的处理。 逻辑基本和上面一样 - -最后返回一个`DelegatingMethodSecurityMetadataSource`对象,就是`MethodSecurityInterceptor`中用到的对象 - -匹配成功的 aop 都会进入`MethodSecurityInterceptor#invoke` - -```java -@Override -public Object invoke(MethodInvocation mi) throws Throwable { - InterceptorStatusToken token = super.beforeInvocation(mi); - Object result; - try { - result = mi.proceed(); - } finally { - super.finallyInvocation(token); - } - return super.afterInvocation(token, result); -} -``` - -这个一看就是标准 aop - -###### super.beforeInvocation(mi) - -这个里面就有授权了,`Authorization`和`authentication`不一样,一个是认证一个是授权。这个是授权,简单说就是角色 - -> Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object); - -接着来到了`DelegatingMethodSecurityMetadataSource#getAttributes` - -```java -for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) { - attributes = s.getAttributes(method, targetClass); - if (attributes != null && !attributes.isEmpty()) { - break; - } -} -``` - -`this.methodSecurityMetadataSources`里面的值,就是`GlobalMethodSecurityConfiguration#methodSecurityMetadataSource` -里面的`sources`. -构建出来返回`attributes`。 - -> Authentication authenticated = authenticateIfRequired(); - -这个就是获取当前认证信息 - -> attemptAuthorization(object, attributes, authenticated); - -使用`accessDecisionManager`进行授权。放到`MethodInterceptor`中进行讲解 -里面还有授权失败发布事件`publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex));` - -接着就是授权成功发送事件,接着就是返回一个`InterceptorStatusToken`对象 - -###### result = mi.proceed(); - -执行业务 - -###### super.finallyInvocation(token); - -是否刷新`InterceptorStatusToken`,前面传参是 false - -###### return super.afterInvocation(token, result); - -后处理器,与前处理器基本一样。剩下的`MethodInterceptor`中进行讲解 - -#### MethodInterceptor - -创建出`MethodSecurityInterceptor`对象给`MethodInterceptor`用也就是`securityMetadataSource`属性 - -##### this.methodSecurityInterceptor = isAspectJ() ? new AspectJMethodSecurityInterceptor(): new MethodSecurityInterceptor(); - -一般都是`MethodSecurityInterceptor` - -##### accessDecisionManager() - -这个就是`AbstractSecurityInterceptor#attemptAuthorization`的授权方法 - -```java -protected AccessDecisionManager accessDecisionManager(){ - List>decisionVoters=new ArrayList<>(); - if(prePostEnabled()){ - ExpressionBasedPreInvocationAdvice expressionAdvice=new ExpressionBasedPreInvocationAdvice(); - expressionAdvice.setExpressionHandler(getExpressionHandler()); - decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice)); - } - if(jsr250Enabled()){ - decisionVoters.add(new Jsr250Voter()); - } - RoleVoter roleVoter=new RoleVoter(); - GrantedAuthorityDefaults grantedAuthorityDefaults=getSingleBeanOrNull(GrantedAuthorityDefaults.class); - if(grantedAuthorityDefaults!=null){ - roleVoter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix()); - } - decisionVoters.add(roleVoter); - decisionVoters.add(new AuthenticatedVoter()); - return new AffirmativeBased(decisionVoters); -} -``` - -前提条件必须开启`prePostEnabled` - -这里面返回的也是`AffirmativeBased`,有时候我们自定义也会使用这个,只要有一个`AccessDecisionVoter`通过就认为是有权限的,这里就不过多讲解了 - -里面的`GrantedAuthorityDefaults`对象,也可以是我们自定义的一个前缀,默认前缀为`ROLE_` - -我们一般自定义的,会使用`.accessDecisionManager(accessDecisionManager())`,在`HttpSecurity#doBuild()`中进行讲解 - -##### afterInvocationManager - -与上面前处理一样 - -##### methodSecurityMetadataSource - -这个就是`MethodSecurityMetadataSource`对象了 - -总结一下这里,就是实现 springaop 的`AbstractPointcutAdvisor`对象`MethodSecurityMetadataSourceAdvisor`, 进行 aop 加载,处理 - -## EnableMethodSecurity - -这个注解没有`@EnableGlobalMethodSecurity`这么强大,代码基本跟`@EnableGlobalMethodSecurity`一样 - -## HttpSecurity#doBuild() - -接下来是最后一块内容了,主要是看里面初始化,构建了哪些类 - -又是熟悉的`AbstractConfiguredSecurityBuilder#doBuild()` - -首先看看我们一开始创建`HttpSecurity`的时候添加了哪些类 `HttpSecurityConfiguration#httpSecurity()` - -```java -@Bean(HTTPSECURITY_BEAN_NAME) -@Scope("prototype") -HttpSecurity httpSecurity() throws Exception { - WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder( - this.context); - AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder( - this.objectPostProcessor, passwordEncoder); - authenticationBuilder.parentAuthenticationManager(authenticationManager()); - authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher()); - HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects()); - // @formatter:off - http - .csrf(withDefaults()) - .addFilter(new WebAsyncManagerIntegrationFilter()) - .exceptionHandling(withDefaults()) - .headers(withDefaults()) - .sessionManagement(withDefaults()) - .securityContext(withDefaults()) - .requestCache(withDefaults()) - .anonymous(withDefaults()) - .servletApi(withDefaults()) - .apply(new DefaultLoginPageConfigurer<>()); - http.logout(withDefaults()); - // @formatter:on - applyDefaultConfigurers(http); - return http; -} -``` - -`CsrfConfigurer`,`ExceptionHandlingConfigurer`,`HeadersConfigurer`,`SessionManagementConfigurer`,`SecurityContextConfigurer`,`RequestCacheConfigurer`,`AnonymousConfigurer`,`ServletApiConfigurer`,`DefaultLoginPageConfigurer`,`LogoutConfigurer` -还有我们添加到`META-INF/spring.factories`中的`AbstractHttpConfigurer.class`类 - -接着回到这里, 我们是自定义了一个`SecurityFilterChain`,所以在这里面进行构建 - -首先` http.formLogin();`添加了`FormLoginConfigurer` - -`http.authorizeRequests()` 添加了`ExpressionUrlAuthorizationConfigurer`,这个只有`configure` 没有`init` - -` http.csrf()` 添加了`CsrfConfigurer` - -`http.userDetailsService(userDetailsService())` 添加了一个自定义的`UserDetailsService` - -### FormLoginConfigurer - -也是一个`SecurityConfigurer` - -首先创建对象给属性赋值 - -```java -authFilter = UsernamePasswordAuthenticationFilter() -loginPage = "/login" -this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage); -``` - -接着来到`init` - -```java -@Override -public void init(B http) throws Exception { - updateAuthenticationDefaults(); - updateAccessDefaults(http); - registerDefaultAuthenticationEntryPoint(http); -} -``` - -> updateAuthenticationDefaults(); - -```java -/** - * Updates the default values for authentication. - * - * @throws Exception - */ -protected final void updateAuthenticationDefaults() { - if (this.loginProcessingUrl == null) { - loginProcessingUrl(this.loginPage); - } - if (this.failureHandler == null) { - failureUrl(this.loginPage + "?error"); - } - LogoutConfigurer logoutConfigurer = getBuilder().getConfigurer(LogoutConfigurer.class); - if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) { - logoutConfigurer.logoutSuccessUrl(this.loginPage + "?logout"); - } -} -``` - -`loginProcessingUrl(this.loginPage);` - -1. 设置登录页面 -2. `this.authFilter`也就是`UsernamePasswordAuthenticationFilter`的`RequestMatcher` - 设置为`new AntPathRequestMatcher(loginProcessingUrl, "POST")` - -`failureUrl(this.loginPage + "?error");` - -1. 设置失败页面 -2. `this.failureHandler`设置为`new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl)` - 里面的`authenticationFailureUrl`是`/login + "?error"` - -`getBuilder().getConfigurer(LogoutConfigurer.class);` 就是前面加入的那一堆`Configurer`中的一个 . 这个默认就是当前设置的值,不用理会 - -> updateAccessDefaults(http); - -里面默认为 false - -> registerDefaultAuthenticationEntryPoint(http); - -获取上面`Configurer`里面的`ExceptionHandlingConfigurer` - -在`ExceptionHandlingConfigurer`中有俩个属性, `defaultEntryPointMappings`和`defaultDeniedHandlerMappings`, 基本看注释就能知道是做什么的 -,这个注释是 map 的 value 类上的注释 - -```java -/** - * 开始一个身份验证方案。 - 在调用该方法之前, ExceptionTranslationFilter 将使用请求的目标 URL 填充 HttpSession 属性 abstractathenticationprocessingfilter.SPRING_SECURITY_SAVED_REQUEST _key。 - 实现应根据需要修改 ServletResponse 上的标头,以开始身份验证过程。 - */ -private LinkedHashMap defaultEntryPointMappings=new LinkedHashMap<>(); - -/** - * 处理拒绝访问失败。 - */ -private LinkedHashMap defaultDeniedHandlerMappings=new LinkedHashMap<>(); -``` - -我们这里的是添加`defaultDeniedHandlerMappings`, `key`是`RequestMatcher`,是否匹配。 `value`是匹配成功就执行 -这个类里面也是只有`configure()`,没有`init()`, 后面讲解 - -先说里面的`value`,就是当前类的`this.authenticationEntryPoint` 也就是创建类时候的`LoginUrlAuthenticationEntryPoint` - -`key`就是`AndRequestMatcher`,但是里面聚合了俩个`RequestMatcher`, 一个是`MediaTypeRequestMatcher` -,还有一个是`NegatedRequestMatcher` - -其实到了这一步,我们只需要了解其中一个,剩下的都大同小异了 - -举例: `ExceptionHandlingConfigurer` - -`ExceptionHandlingConfigurer`这个类就是刚刚在`FormLoginConfigurer` -中处理的那个,往这个里面添加了`defaultEntryPointMappings`属性 - -然后我们找到`ExceptionHandlingConfigurer`中的`configure(H http)`方法, 里面就是创建了一个`ExceptionTranslationFilter` -过滤器,添加到了`http`中, 代码是这一段 - -```java -@Override -public void configure(H http) { - AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http); - ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, - getRequestCache(http)); - AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http); - exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler); - exceptionTranslationFilter = postProcess(exceptionTranslationFilter); - http.addFilter(exceptionTranslationFilter); -} -``` - -接着我们打开`ExceptionTranslationFilter`,这就是一个`Filter` -,找到`doFilter(ServletRequest request, ServletResponse response, FilterChain chain)` -方法,就是在处理`catch (Exception ex) {` -的时候,做的一些事情,接着继续打开`handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,FilterChain chain, RuntimeException exception)` -方法, - -```java -private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, - FilterChain chain, RuntimeException exception) throws IOException, ServletException { - if (exception instanceof AuthenticationException) { - handleAuthenticationException(request, response, chain, (AuthenticationException) exception); - } else if (exception instanceof AccessDeniedException) { - handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception); - } -} -``` - -发现最后还是到了 - -```java -protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, - AuthenticationException reason) throws ServletException, IOException { - // SEC-112: Clear the SecurityContextHolder's Authentication, as the - // existing Authentication is no longer considered valid - SecurityContext context = SecurityContextHolder.createEmptyContext(); - SecurityContextHolder.setContext(context); - this.requestCache.saveRequest(request, response); - this.authenticationEntryPoint.commence(request, response, reason); -} -``` - -里面就有`this.authenticationEntryPoint.commence(request, response, reason);`这段代码, -创建的是`DelegatingAuthenticationEntryPoint` - -```java -@Override -public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException, ServletException { - for (RequestMatcher requestMatcher : this.entryPoints.keySet()) { - logger.debug(LogMessage.format("Trying to match using %s", requestMatcher)); - if (requestMatcher.matches(request)) { - AuthenticationEntryPoint entryPoint = this.entryPoints.get(requestMatcher); - logger.debug(LogMessage.format("Match found! Executing %s", entryPoint)); - entryPoint.commence(request, response, authException); - return; - } - } - logger.debug(LogMessage.format("No match found. Using default entry point %s", this.defaultEntryPoint)); - // No EntryPoint matched, use defaultEntryPoint - this.defaultEntryPoint.commence(request, response, authException); -} -``` - -基本上流程就完成了,请求先走过滤器,然后走不同的 filter,报错就到了这一步,进行错误处理,其余都基本一致了 - -## 备注 - -剩下的一些处理基本和上面这个流程一致,还有几个注解需要注意下 - -`@CsrfToken` - -`@CurrentSecurityContext` - -`@AuthenticationPrincipal` - -这三个注解是在`WebMvcSecurityConfiguration`类进行处理的,只要启动了`@EnableWebSecurity`注解,就会启动 - -```java - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Documented -@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, - HttpSecurityConfiguration.class}) -@EnableGlobalAuthentication -@Configuration -public @interface EnableWebSecurity { - - /** - * Controls debugging support for Spring Security. Default is false. - * @return if true, enables debug support with Spring Security - */ - boolean debug() default false; - -} -``` - -使用方法 - -```java -@GetMapping("/get") -// public Users getUser(String username,@CsrfToken CsrfToken token, @AuthenticationPrincipal Users customUser, @CurrentSecurityContext Authentication authentication) { -public Users getUser(String username, @AuthenticationPrincipal Users customUser, @CurrentSecurityContext SecurityContext securityContext) { - return userInfoService.getUsers(username); -} -``` - -里面的`SpringWebMvcImportSelector`类注入了`WebMvcSecurityConfiguration`,这就是 springmvc 中`HandlerMethodArgumentResolver` -的处理,也就是参数的处理,比如我们添加的`@PathVariable`,`@RequestBody`等,都是`HandlerMethodArgumentResolver` -的实现类处理的,当然还有`HandlerMethodReturnValueHandler`,这些就是`DispatcherServlet`里面的处理了 diff --git a/docs/SpringSecurity/SpringSecurity自定义用户认证.md b/docs/SpringSecurity/SpringSecurity自定义用户认证.md index fc235f2..e69de29 100644 --- a/docs/SpringSecurity/SpringSecurity自定义用户认证.md +++ b/docs/SpringSecurity/SpringSecurity自定义用户认证.md @@ -1,319 +0,0 @@ -# Spring Security 自定义用户认证 - -在**Spring Boot 中开启 Spring Security**一节中我们简单地搭建了一个 Spring Boot + Spring Security 的项目,其中登录页、用户名和密码都是由 Spring Security 自动生成的。Spring Security 支持我们自定义认证的过程,如使用自定义的登录页替换默认的登录页,用户信息的获取逻辑、登录成功或失败后的处理逻辑等。这里将在上一节的源码基础上进行改造。 - -## 配置自定义登录页 - -为了方便起见,我们直接在`src/main/resources/resources`目录下创建一个`login.html`(不需要 Controller 跳转): - -```html - - - - - 登录 - - - - - - -``` - -要怎么做才能让 Spring Security 跳转到我们自己定义的登录页面呢?很简单,只需要在 `BrowserSecurityConfig` 的 `configure` 中添加一些配置: - -```java -@Configuration -public class BrowserConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.formLogin() // 表单登录 - .loginPage("/login.html") // 自定义登录页 - .loginProcessingUrl("/login") // 登录认证路径 - .and() - .authorizeRequests() // 授权配置 - .antMatchers("/login.html", "/css/**", "/error").permitAll() // 无需认证 - .anyRequest().authenticated() // 其他所有请求都需要认证 - .and() - .csrf().disable(); // 禁用 CSRF - } -} -``` - -上面代码中`.loginPage("/login.html")`指定了跳转到登录页面的请求 URL,`.loginProcessingUrl("/login")`对应登录页面 form 表单的`action="/login"`,`.antMatchers("/login.html", "/css/", "/error").permitAll()`表示跳转到登录页面的请求不被拦截。 - -这时候启动系统,访问`http://localhost:8080/hello`,会看到页面已经被重定向到了`http://localhost:8080/login.html`: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/d6bd19a2-08d3-4ba6-921c-5b5f57370a16.jpg) - -## 配置用户信息的获取逻辑 - -Spring Security 默认会为我们生成一个用户名为 user,密码随机的用户实例,当然我们也可以定义自己用户信息的获取逻辑,只需要实现 Spring Security 提供的**_UserDetailService_**接口即可,该接口只有一个抽象方法**_loadUserByUsername_**,具体实现如下: - -```java -@Service -public class UserDetailService implements UserDetailsService { - @Autowired - private PasswordEncoder passwordEncoder; - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - return new User(username, passwordEncoder.encode("123456"), AuthorityUtils.createAuthorityList("admin")); - } -} -``` - -通过以上配置,我们定义了一个用户名随机,密码统一为 123456 的用户信息的获取逻辑。这样,当我们启动项目,访问`http://localhost:8080/login`,只需要输入任意用户名以及 123456 作为密码即可登录系统。 - -## 源码解析 - -### BrowserConfig 配置解析 - -我们首先来梳理下 `BrowserConfig` 中的配置是如何被 Spring Security 所加载的。 - -首先找到调用 `BrowserConfig` 的 `configure()` 的地方,在其父类 `WebSecurityConfigurerAdapter` 的 `getHttp()` 中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/12629a18-56ef-4286-9ab9-c124dc3d6791.png) - -往上一步找到调用 `getHttp()` 的地方,在同个类的 `init()` 中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/2b54af34-7d68-4f40-8726-d02d18e03dea.png) - -往上一步找到调用`init()`的地方,在 `AbstractConfiguredSecurityBuilder` 的`init()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6e009bf1-aba3-4b89-8e86-d3d110e0f4a7.png) - -在`init()`被调用时,它首先会遍历`getConfigurers()`返回的集合中的元素,调用其`init()`,点击`getConfigurers()`查看,发现其读取的是`configurers`属性的值,那么`configurers`是什么时候被赋值的呢?我们在同个类的`add()`中找到`configurers`被赋值的代码: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ed65fd2a-8a9f-4808-bc16-36128b4af47a.png) - -往上一步找到调用`add()`的地方,在同个类的`apply()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1e929fec-d1ab-44b5-89bc-6e5ebcda1daf.png) - -往上一步找到调用`apply()`的地方,在`WebSecurityConfiguration`的`setFilterChainProxySecurityConfigurer()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1840f96a-6a31-4fce-8a98-02fa7fc60fbf.png) - -我们可以看到,在`setFilterChainProxySecurityConfigurer()`中,首先会实例化一个`WebSecurity`(`AbstractConfiguredSecurityBuilder`的实现类)的实例,遍历参数`webSecurityConfigurers`,将存储在其中的元素作为参数传递给`WebSecurity`的`apply()`,那么`webSecurityConfigurers`是什么时候被赋值的呢?我们根据`@Value`中的信息找到`webSecurityConfigurers`被赋值的地方,在`AutowiredWebSecurityConfigurersIgnoreParents`的`getWebSecurityConfigurers()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/eb7a5916-4049-4682-9a11-10f1f1f94c74.png) - -我们重点看第二句代码,可以看到这里会提取存储在 bean 工厂中类型为`WebSecurityConfigurer.class`的 bean,而`BrowserConfig`正是`WebSecurityConfigurerAdapter`的实现类。 - -解决完`configurers`的赋值问题,我们回到`AbstractConfiguredSecurityBuilder`的`init()`处,找到调用该方法的地方,在同个类的`doBuild()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/522574e3-bacc-4794-a17e-492bc2b4457d.png) - -往上一步找到调用`doBuild()`的地方,在`AbstractSecurityBuilder`的`build()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/fb22f2ca-3d9a-420f-b77a-f9c0f737d9ad.png) - -往上一步找到调用`doBuild()`的地方,在`WebSecurityConfiguration`的`springSecurityFilterChain()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/a5c61feb-ca72-4768-94bd-1b0a8cf8af70.png) - -至此,我们分析完了`BrowserConfig`被 Spring Security 加载的过程。现在我们再来看看当我们自定义的配置被加载完后,`http`各属性的变化,在`BrowserConfig`的`configure()`末尾打上断点,当程序走到断点处时,查看`http`属性: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6c72c09b-742c-4415-851a-8ca5292a4969.png) - -我们配置的`.loginPage("/login.html")`和`.loginProcessingUrl("/login")`在`FormLoginConfigurer`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/51ba02f0-bae6-4c08-9adf-7ee0f12b05d3.png) - -配置的`.antMatchers("/login.html", "/css/", "/error").permitAll()`在`ExpressionUrlAuthorizationConfigurer`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/52390725-d87d-42b1-9071-ea21e445e1e6.png) - -这样,当我们访问除`"/login.html", "/css/", "/error"`以外的路径时,在`AbstractSecurityInterceptor`(`FilterSecurityInterceptor`的父类)的`attemptAuthorization()`中会抛出`AccessDeniedException`异常(最终由`AuthenticationTrustResolverImpl`的isAnonymous()`进行判断) - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/558b8b1c-be32-44c4-8d8f-5f0d231741f8.png) - -当我们访问`"/login.html", "/css/", "/error"`这几个路径时,在`AbstractSecurityInterceptor`(`FilterSecurityInterceptor`的父类)的`attemptAuthorization()`中正常执行(最终由`SecurityExpressionRoot`的`permitAll()`进行判断) - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4612c27e-dd9f-4e60-92dc-fc9858496ec5.png) - -### login.html 路径解析 - -当我们请求的资源需要经过认证时,Spring Security 会将请求重定向到我们自定义的登录页,那么 Spring 又是如何找到我们自定义的登录页的呢?下面就让我们来解析一下: - -我们首先来到`DispatcherServlet`中,`DispatcherServlet`是 Spring Web 处理请求的入口。当 Spring Web 项目启动后,第一次接收到请求时,会调用其`initStrategies()`进行初始化: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/964ec4a4-6039-4205-8a87-ea2febcc00b6.png) - -我们重点关注`initHandlerMappings(context);`这句,`initHandlerMappings()`用于初始化处理器映射器(处理器映射器可以根据请求找到对应的资源),我们来到`initHandlerMappings()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/0a97b011-34ed-4d57-945e-95c8e6bafc8e.png) - -可以看到,当程序走到`initHandlerMappings()`中时,会从 bean 工厂中找出`HandlerMapping.class`类型的 bean,将其存储到`handlerMappings`属性中。这里看到一共找到 5 个,分别是:`requestMappingHandlerMapping`(将请求与标注了`@RequestMapping`的方法进行关联)、`weclomePageHandlerMapping`(将请求与主页进行关联)、`beanNameHandlerMapping`(将请求与同名的 bean 进行关联)、`routerFunctionMapping`(将请求与`RouterFunction`进行关联)、`resourceHandlerMapping`(将请求与静态资源进行关联),这 5 个 bean 是在`WebMvcAutoConfiguration$EnableWebMvcConfiguration`中配置的: - -`requestMappingHandlerMapping:` - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/cd7aee86-66f8-4197-99d1-1c9275e33bee.png) - -`weclomePageHandlerMapping:` - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ef28372b-de89-46ff-8679-8b8feca04a7a.png) - -`beanNameHandlerMapping`、`routerFunctionMapping`、`resourceHandlerMapping`在`EnableWebMvcConfiguration`的父类(`WebMvcConfigurationSupport`)中配置: - -`beanNameHandlerMapping:` - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8d05ac54-f034-47d4-b750-67b2e3b3cd14.png) - -`routerFunctionMapping:` - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/481b88aa-028d-4392-8c0a-365f1d0e2ae9.png) - -`resourceHandlerMapping:` - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/fcdab503-2735-46bd-a5b6-226dc348e78c.png) - -我们将目光锁定在`resourceHandlerMapping`上,当`resourceHandlerMapping`被初始化时,会调用`addResourceHandlers()`为`registry`添加资源处理器,我们找到实际被调用的`addResourceHandlers()`,在`DelegatingWebMvcConfiguration`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6eca7b58-80f9-4e98-8483-62b4ef751854.png) - -可以看到这里实际调用的是`configurers`属性的`addResourceHandlers()`,而`configurers`是一个 final 类型的成员变量,其值是`WebMvcConfigurerComposite`的实例,我们来到`WebMvcConfigurerComposite`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/c365e5ab-a7a3-4ebf-8a25-09cd2e049f22.png) - -可以看到这里实际调用的是`delegates`属性的`addResourceHandlers()`,`delegates`是一个 final 类型的集合,集合的元素由`addWebMvcConfigurers()`负责添加: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/895afead-ea6b-4ac6-8138-fbe0a223daf9.png) - -我们找到调用`addWebMvcConfigurers()`的地方,在`DelegatingWebMvcConfiguration`的`setConfigurers()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/a20e06a4-5a43-4edf-bea1-53fc9bc929e9.png) - -可以看到当`setConfigurers()`被初始化时,Spring 会往参数`configurers`中传入两个值,我们关注第一个值,是一个`WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter`的实例,注意它的属性`resourceProperties`,是一个`WebProperties$Resources`的实例,默认情况下,在实例化`WebMvcAutoConfigurationAdapter`时,由传入参数`webProperties`进行赋值:`webProperties.getResources()`: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/97b6f22c-414d-4a16-aa8e-c3deece2f7cd.png) - -我们进入参数`webProperties`的类中,可以看到`getResources()`是直接实例化了一个`Resources`,其属性`staticLocations`是一个含有 4 个值的 final 类型的字符串数组,这 4 个值正是 Spring 寻找静态文件的地方: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4d84fe43-2646-4a6f-a580-f39f6416d02d.png) - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/25899949-dac3-4873-a2af-7abfe0e97615.png) - -我们回到`WebMvcAutoConfiguration`的`addResourceHandlers()`中:![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/003ff2fa-022e-47cb-8aa9-343ed7c40c4a.png) - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ccd16b38-d724-4c03-949e-3b6ba03268a9.png) - -在`addResourceHandlers()`中,会为`registry`添加两个资源处理器,当请求路径是“/webjars/”时,会在”classpath:/META-INF/resources/webjars/“路径下寻找对应的资源,当请求路径是“/\*\*”时,会在”classpath:/META-INF/resources/“、”classpath:/resources/“、”classpath:/static/“、”classpath:/public/“路径下寻找对应的资源。 - -现在我们通过访问`http://localhost:8080/login.html`来验证这个过程。 - -请求首先来到`DispatcherServlet`的`doDispatch()`中,由于是对静态资源的请求,当程序走到`mappedHandler = getHandler(processedRequest);`时,通过`getHandler()`返回`SimpleUrlHandlerMapping`(即`resourceHandlerMapping`的类型)的`HandlerExecutionChain`: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4c9c302d-2ce0-4b5b-beb6-c76d2e94038f.png) - -然后由实际的处理器进行处理: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1590bae4-2e3a-4f97-b02d-d918c49cac22.png) - -程序一路调试,来到`ResourceHttpRequestHandler`的`handleRequest()`中,通过调用`Resource resource = getResource(request);`找到请求对应的资源: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/0879e131-da5f-491e-a153-42770ce8b975.png) - -而在`getResource()`中,实际是将请求路径(即`login.html`)与前面配置的路径进行拼接(组合成`/resources/login.html`这样的路径),再通过类加载器来寻找资源。 - -后面源码深入过深,就不一一展开了,只截取其中比较重要的几段代码: - -`PathResourceResolver`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6c58e27d-dd29-48fc-b597-8067e1c97786.png) - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/c7ef78df-c2ab-4f89-b5ad-45561a91ffcc.png) - -`ClassPathResource`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/42c5125e-0dc2-4c0c-9434-af4a9efd2d5d.png) - -`ClassLoader`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/7842f83d-5417-4cb2-bb30-d70f98c3053f.png) - -`URLClassLoader`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/870891e9-f8ea-4097-98c9-829b1cdcf145.png) - -最终,类加载器会在如上两个路径下找到登录页并返回。 - -### UserDetailService 配置解析 - -我们定义的用户信息的获取逻辑是如何被 Spring Security 应用的呢?让我们通过阅读源码来了解一下。 - -还记得前面我们讲**_BrowserConfig 配置_**被加载的过程吗?**_UserDetailService_**也是在这个过程中被一起加载完成的,回到**BrowserConfig 配置解析**的第一幅图中,如下: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/68490740-e03c-4353-b5fc-ac99c0cf0435.png) - -在断点处位置,`authenticationManager()`会返回一个**_AuthenticationManager_**实例,我们进入`authenticationManager()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/e7a1a684-64db-41d0-a5c0-d9a841d86cc1.png) - -在`authenticationManager()`中,**_AuthenticationManager_**转由**_AuthenticationConfiguration_**中获取,我们进入`getAuthenticationManager()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/d9ae84ae-d60c-4d9c-a7fd-cddeb1142f95.png) - -程序来到**_AuthenticationConfiguration_**的`getAuthenticationManager()`中,**_AuthenticationManager_**转由**_AuthenticationManagerBuilder_**中获取,我们进入`build()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/19f71152-f456-4db7-a1d5-f79aaa37253b.png) - -程序来到**_AbstractConfiguredSecurityBuilder_**的`doBuild()`中,这里在构建**_AuthenticationManager_**实例时,需要初始化 3 个配置类,我们重点关注第 3 个配置类:**_org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer_**,这个配置类是在**_AuthenticationConfiguration_**中引入的: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/9f06c823-645d-413b-8bb6-1d81b8f329ea.png) - -我们来到**_InitializeUserDetailsBeanManagerConfigurer_**的`init()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/cd7d6cb9-c6e7-4570-aab8-309adcb15e16.png) - -这里会新建一个**_InitializeUserDetailsManagerConfigurer_**实例添加到**_AuthenticationManagerBuilder_**中。我们回到`doBuild()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/3ea76980-417d-4c0c-9330-e0bb241c6a47.png) - -可以看到配置类变成了 5 个,其中就有刚刚新建的**_InitializeUserDetailsManagerConfigurer_**,程序接下来会调用各个配置类的`configure()`进行配置,我们来到**_InitializeUserDetailsManagerConfigurer_**的`configure()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ec39a9f6-c97d-4b7d-8843-d20358c1d194.png) - -可以看到在`configure()`中,就会去 bean 工厂中寻找**_UserDetailsService_**类型的 bean,若是我们没有自定义**_UserDetailsService_**的实现类的话,Spring Security 默认会生成一个**_InMemoryUserDetailsManager_**的实例: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/c6a7370c-4afb-4c5d-aa35-ba9c3406b1ed.png) - -**_InMemoryUserDetailsManager_**是在**_UserDetailsServiceAutoConfiguration_**类中配置的: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/476f8954-abe3-4e26-bfe1-8c5b4abbf0e0.png) - -解决完**_UserDetailsService_**的加载问题,现在我们来看看 Spring Security 是如何通过**_UserDetailsService_**获取用户信息的。 - -通过**Spring Boot 中开启 Spring Security**一节的学习我们知道,登录判断的逻辑是在**_UsernamePasswordAuthenticationFilter_**中进行的,因此我们在**_UsernamePasswordAuthenticationFilter_**的`attemptAuthenticatio()`中打上断点,然后启动项目,访问登录页,输入用户名和密码点击登录后,程序来到**_UsernamePasswordAuthenticationFilter_**中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1282014b-fc29-4c2b-9316-9fdd638653c9.png) - -这里将验证的逻辑交由**_AuthenticationManager_**进行,我们进入`authenticate()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/5d511c93-3614-40e0-b3c9-9673c573d60f.png) - -程序来到**_ProviderManager_**的`authenticate()`中,这里将验证的逻辑委托给其父类进行,再次点击进入`authenticate()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8dddd63e-c567-4b41-a9d9-8ef8aa6f2a92.png) - -这里将验证的逻辑交由**_AuthenticationProvider_**进行,我们进入`authenticate()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ec796a9b-7c65-49a2-9f9f-7685af7bd57b.png) - -程序来到**_AbstractUserDetailsAuthenticationProvider_**的`authenticate()`中,这里会根据用户名去寻找对应的用户实例,我们进入`retrieveUser()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/3980e264-c073-456a-b808-715edd85633a.png) - -程序来到**_DaoAuthenticationProvider_**的`retrieveUser()`中,可以看到正是在这里,会从**_UserDetailsService_**的`loadUserByUsername()`中寻找对应的用户信息。 - -## 参考 - -1. [Spring Security 自定义用户认证](https://mrbird.cc/Spring-Security-Authentication.html) diff --git a/docs/SpringSecurity/SpringSecurity请求全过程解析.md b/docs/SpringSecurity/SpringSecurity请求全过程解析.md index 4d28186..c7d0f16 100644 --- a/docs/SpringSecurity/SpringSecurity请求全过程解析.md +++ b/docs/SpringSecurity/SpringSecurity请求全过程解析.md @@ -34,7 +34,7 @@ public class HelloController { 这时候我们直接启动项目,访问http://localhost:8080/hello,可以看到页面跳转到一个登陆页面: -![image-20210811091508157](../../images/SpringSecurity/image-20210811091508157.png) +![image-20210811091508157](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091508157.png) 默认的用户名为 user,密码由 Sping Security 自动生成,回到 IDEA 的控制台,可以找到密码信息: @@ -50,11 +50,11 @@ Spring Security 默认为我们开启了一个简单的安全配置,下面让 当 Spring Boot 项目配置了 Spring Security 后,Spring Security 的整个加载过程如下图所示: -![image-20210811091633434](../../images/SpringSecurity/image-20210811091633434.png) +![image-20210811091633434](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091633434.png) 而当我们访问http://localhost:8080/hello时,代码的整个执行过程如下图所示: -![image-20210811091659121](../../images/SpringSecurity/image-20210811091659121.png) +![image-20210811091659121](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091659121.png) 如上图所示,Spring Security 包含了众多的过滤器,这些过滤器形成了一条链,所有请求都必须通过这些过滤器后才能成功访问到资源。 @@ -62,69 +62,69 @@ Spring Security 默认为我们开启了一个简单的安全配置,下面让 首先,通过前面可以知道,当有请求来到时,最先由**_DelegatingFilterProxy_**负责接收,因此在**_DelegatingFilterProxy_**的doFilter()的首行打上断点: -![image-20210811091719470](../../images/SpringSecurity/image-20210811091719470.png) +![image-20210811091719470](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091719470.png) 接着**_DelegatingFilterProxy_**会将请求委派给**_FilterChainProxy_**进行处理,在**_FilterChainProxy_**的首行打上断点: -![img](../../images/SpringSecurity/56ac5128-eab7-4b92-912f-ff50bac68a4f.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/56ac5128-eab7-4b92-912f-ff50bac68a4f.png) **_FilterChainProxy_**会在doFilterInternal()中生成一个内部类**_VirtualFilterChain_**的实例,以此来调用 Spring Security 的整条过滤器链,在**_VirtualFilterChain_**的doFilter()首行打上断点: -![image-20210811091755498](../../images/SpringSecurity/image-20210811091755498.png) +![image-20210811091755498](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091755498.png) 接下来**_VirtualFilterChain_**会通过**_currentPosition_**依次调用存在**_additionalFilters_**中的过滤器,其中比较重要的几个过滤器有:**_UsernamePasswordAuthenticationFilter_**、**_DefaultLoginPageGeneratingFilter_**、**_AnonymousAuthenticationFilter_**、**_ExceptionTranslationFilter_**、**_FilterSecurityInterceptor_**,我们依次在这些过滤器的doFilter()的首行打上断点: -![image-20210811091815473](../../images/SpringSecurity/image-20210811091815473.png) +![image-20210811091815473](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091815473.png) 准备完毕后,我们启动项目,然后访问http://localhost:8080/hello,程序首先跳转到**_DelegatingFilterProxy_**的断点上: -![image-20210811091833065](../../images/SpringSecurity/image-20210811091833065.png) +![image-20210811091833065](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091833065.png) 此时**_delegate_**还是 null 的,接下来依次执行代码,可以看到**_delegate_**最终被赋值一个**_FilterChainProxy_**的实例: -![img](../../images/SpringSecurity/f045b025-bd97-4222-8a02-51634be6745b.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/f045b025-bd97-4222-8a02-51634be6745b.png) 接下来程序依次跳转到**_FilterChainProxy_**的doFilter()和**_VirtualFilterChain_**的doFilter()中: -![img](../../images/SpringSecurity/90d3e369-510f-45cb-982d-241d2eedb55c.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/90d3e369-510f-45cb-982d-241d2eedb55c.png) -![image-20210811092048784](../../images/SpringSecurity/image-20210811092048784.png) +![image-20210811092048784](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811092048784.png) 接着程序跳转到**_AbstractAuthenticationProcessingFilter_**(**_UsernamePasswordAuthenticationFilter_**的父类)的doFilter()中,通过requiresAuthentication()判定为 false(是否是 POST 请求): -![img](../../images/SpringSecurity/2e5440bc-9488-4213-a030-0d25153bb2ea.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/2e5440bc-9488-4213-a030-0d25153bb2ea.png) 接着程序跳转到**_DefaultLoginPageGeneratingFilter_**的doFilter()中,通过isLoginUrlRequest()判定为 false(请求路径是否是/login): -![img](../../images/SpringSecurity/47a7bca4-d858-4cb1-b126-347805b74053.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/47a7bca4-d858-4cb1-b126-347805b74053.png) 接着程序跳转到**_AnonymousAuthenticationFilter_**的doFilter()中,由于是首次请求,此时SecurityContextHolder.getContext().getAuthentication()为 null,因此会生成一个**_AnonymousAuthenticationToken_**的实例: -![img](../../images/SpringSecurity/6b1aded6-5229-47ba-b192-78a7c2622b8c.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6b1aded6-5229-47ba-b192-78a7c2622b8c.png) 接着程序跳转到**_ExceptionTranslationFilter_**的doFilter()中,**_ExceptionTranslationFilter_**负责处理**_FilterSecurityInterceptor_**抛出的异常,我们在 catch 代码块的首行打上断点: -**![img](../../images/SpringSecurity/8efa0b1c-2b32-4d5b-9655-985374326e10.png)** +**![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8efa0b1c-2b32-4d5b-9655-985374326e10.png)** 接着程序跳转到**_FilterSecurityInterceptor_**的doFilter()中,依次执行代码后程序停留在其父类(**_AbstractSecurityInterceptor_**)的attemptAuthorization()中: -![img](../../images/SpringSecurity/d6e99143-6207-43a5-8d04-f0c81baa11b4.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/d6e99143-6207-43a5-8d04-f0c81baa11b4.png) **_accessDecisionManager_**是**_AccessDecisionManager_**(访问决策器)的实例,**_AccessDecisionManager_**主要有 3 个实现类:**_AffirmativeBased_**(一票通过),**ConsensusBased**(少数服从多数)、UnanimousBased(一票否决),此时**_AccessDecisionManager_**的的实现类是**_AffirmativeBased_**,我们可以看到程序进入**_AffirmativeBased_**的decide()中: -![img](../../images/SpringSecurity/6724647c-34ee-4a57-8cfa-b46f57400d14.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6724647c-34ee-4a57-8cfa-b46f57400d14.png) 从上图可以看出,决策的关键在voter.vote(authentication, object, configAttributes)这句代码上,通过跟踪调试,程序最终进入**_AuthenticationTrustResolverImpl_**的isAnonymous()中: -![img](../../images/SpringSecurity/4beaa02f-a93d-4d95-9ad1-0d7213cb0e46.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4beaa02f-a93d-4d95-9ad1-0d7213cb0e46.png) isAssignableFrom()判断前者是否是后者的父类,而**_anonymousClass_**被固定为**_AnonymousAuthenticationToken.class_**,参数**_authentication_**由前面**_AnonymousAuthenticationFilter_**可以知道是**_AnonymousAuthenticationToken_**的实例,因此isAnonymous()返回 true,**_FilterSecurityInterceptor_**抛出**_AccessDeniedException_**异常,程序返回**_ExceptionTranslationFilter_**的 catch 块中: -![img](../../images/SpringSecurity/8e1ac9db-5987-484d-abf4-4c6535c60cc6.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8e1ac9db-5987-484d-abf4-4c6535c60cc6.png) 接着程序会依次进入**_DelegatingAuthenticationEntryPoint_**、**_LoginUrlAuthenticationEntryPoint_**中,最后由**_LoginUrlAuthenticationEntryPoint_**的commence()决定重定向到/login: -![img](../../images/SpringSecurity/1b03bdd4-6773-4b39-a664-fdf65d104403.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1b03bdd4-6773-4b39-a664-fdf65d104403.png) 后续对/login的请求同样会经过之前的执行流程,在**_DefaultLoginPageGeneratingFilter_**的doFilter()中,通过isLoginUrlRequest()判定为 true(请求路径是否是/login),直接返回**_login.html_**,也就是我们开头看到的登录页面。 diff --git a/docs/Tomcat/servlet-api源码赏析.md b/docs/Tomcat/servlet-api源码赏析.md index 7a13bcd..f1c8627 100644 --- a/docs/Tomcat/servlet-api源码赏析.md +++ b/docs/Tomcat/servlet-api源码赏析.md @@ -215,7 +215,7 @@ public interface ServletResponse { 其主要部分的类图 如下。 -![avatar](../../images/Tomcat/Servlet主要类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Tomcat/Servlet主要类图.png) 下面看一下 javax.servlet.http 包下的内容,它提供了很多 我经常用到的类和接口,比如:HttpServlet、HttpServletRequest、HttpServletResponse。其源码如下。 diff --git a/docs/nacos/nacos-discovery.md b/docs/nacos/nacos-discovery.md index 797560e..e72a14d 100644 --- a/docs/nacos/nacos-discovery.md +++ b/docs/nacos/nacos-discovery.md @@ -128,7 +128,7 @@ public static void registerGlobalNacosProperties(AnnotationAttributes attributes ``` -![image-20200821111938485](../../images/nacos/image-20200821111938485.png) +![image-20200821111938485](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821111938485.png) ## registerNacosCommonBeans @@ -168,7 +168,7 @@ public static void registerInfrastructureBean(BeanDefinitionRegistry registry, 属性读取,从 application 配置文件中读取数据转换成 java 对象。 -![image-20200821132413628](../../images/nacos/image-20200821132413628.png) +![image-20200821132413628](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821132413628.png) ## NacosDiscoveryAutoRegister @@ -228,11 +228,11 @@ public void onApplicationEvent(WebServerInitializedEvent event) { - 注册的参数 - ![image-20200821133350982](../../images/nacos/image-20200821133350982.png) + ![image-20200821133350982](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821133350982.png) ## 服务注册 -![image-20200821133445090](../../images/nacos/image-20200821133445090.png) +![image-20200821133445090](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821133445090.png) - 注册一个实例 1. 将 instance 对象转换成 BeatInfo 对象