springsecurity自动配置解析:

SecurityFilterAutoConfiguration:
spring security自动配置的源码简单分析 - 程序晓猿 - 博客园(详细但是也挺难的)

1
@EnableConfigurationProperties({SecurityProperties.class})注解,Spring Boot会自动将这些属性加载到应用程序的上下文中,使得我们可以通过@Autowired注解或其他方式方便地访问和使用这些属性。
1
表示SecurityFilterAutoConfiguration的自动配置必须要在

请求到达spring security的处理过程:

一个请求到达spring security后首先会被DelegatingFilterProxy这个过滤器拦截到,这是一个代理过滤器,它会使用内部的代理目标delegate 也就是从容器中获取名称是springSecurityFilterChain的过滤器(而这个过滤器是由由WebSecurity 构建的FilterChainProxy这个类型的过滤器来处理请求,)总结下请求的处理路径就是:
1.DelegatingFilterProxy ----->2.FilterChainProxy
详细看下FilterChainProxy这个过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
public class FilterChainProxy extends GenericFilterBean {
/*
这个属性很重要,过滤器链的集合,从这个属性的类型可以知道spring security支持多个过滤器链
一个链条对象SecurityFilterChain中会包含多个过滤器,
当然一般情况我们的应用中只需要配置一个链条对象
*/
private List<SecurityFilterChain> filterChains;

@Override
//下面的方法用于拦截并处理HTTP请求。首先检查是否已经应用了过滤器,如果没有则将一个标记属
// 性设置到请求中,然后执行内部的过滤器处理方法。最后,清除SecurityContextHolder中的上下
// 文,并移除标记属性。如果已经应用了过滤器,则直接执行内部处理方法。
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
else {
//请求进来后会执行到这里来执行真正的过滤逻辑
doFilterInternal(request, response, chain);
}
}

private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {

FirewalledRequest fwRequest = firewall
.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall
.getFirewalledResponse((HttpServletResponse) response);

/* 首先通过这个getFilters方法来从过滤器链的集合filterChains中匹配到一个过滤器链
对象SecurityFilterChain,返回这个链条里边的所有过滤器
*/
List<Filter> filters = getFilters(fwRequest);

if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest)
+ (filters == null ? " has no matching filters"
: " has an empty filter list"));
}

fwRequest.reset();

chain.doFilter(fwRequest, fwResponse);

return;
}
// 根据filters构造虚拟过滤器链对象VirtualFilterChain来遍历执行filters中的每个过滤器
// VirtualFilterChain是当前类中的内部类,遍历的原理就是这个类
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}

// 这个方法用来根据request对象从filterChains中匹配出一个SecurityFilterChain对象并返回
// 此链条中的所有过滤器
private List<Filter> getFilters(HttpServletRequest request) {
//从这个方法可以看出SecurityFilterChain中一定存在两个方法 matches,getFilters
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}

//内部类用来依次执行上边getFilters方法获取到的filters中的所有过滤器
private static class VirtualFilterChain implements FilterChain {
/* 这个变量记录了原始的FilterChain对象,也就是FilterChainProxy这个过滤器的doFilter方法中的参 数FilterChain,通过这个FilterChain可以把请求从FilterChainProxy转给其他的过滤器(请求已经被 spring security处理完了接着去执行其他的过滤器)

这里需要普及一下Filter的相关知识,当我们的应用中配置多个过滤器(不仅是spirng security的过滤器) 时,是通过FilterChain实现从filter1转给filter2的
下面是Filter接口的源码
public interface Filter {

//这个方法参数chain就是用来实现把请求从filter1转给filter2的
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
}

一般我们实现Filter接口时都要实现此方法,并通过chain.doFilter方法实现把请求交给下一个过滤器。
public MyFilter implements Filter{
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException{

...一些过滤的逻辑
//最后把请求交给下一个顾虑器时都会执行
chain.doFilter(request, response);
}
}

*/
private final FilterChain originalChain;
private final List<Filter> additionalFilters;
private final FirewalledRequest firewalledRequest;
private final int size;
private int currentPosition = 0;

private VirtualFilterChain(FirewalledRequest firewalledRequest,
FilterChain chain, List<Filter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
this.size = additionalFilters.size();
this.firewalledRequest = firewalledRequest;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
//这个方法通过currentPosition来控制递归调用additionalFilters中的所有过滤器
if (currentPosition == size) {
//这里表示执行到了最后一个过滤器
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " reached end of additional filter chain; proceeding with original chain");
}

// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
// spring security已经处理完了,把请求转给其他的过滤器
originalChain.doFilter(request, response);
}
else {
currentPosition++;
// 取出additionalFilters中currentPosition位置的过滤器
Filter nextFilter = additionalFilters.get(currentPosition - 1);

if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " at position " + currentPosition + " of " + size
+ " in additional filter chain; firing Filter: '"
+ nextFilter.getClass().getSimpleName() + "'");
}
//因为VirtualFilterChain实现了FilterChain接口,所以这里
//利用this把请求又转给了自己,相当于递归,通过currentPosition的递增会
//遍历执行完additionalFilters中的全部过滤器。
nextFilter.doFilter(request, response, this);
}
}
}
}

所以真正处理请求的其实是FilterChainProxy中维护的 SecurityFilterChain ,再更新下上边说的请求处理路径
1.DelegatingFilterProxy ----->2.FilterChainProxy ---->3.SecurityFilterChain
总结:
先是在SecurityFilterAutoConfiguration中给容器中加入了一个DelegatingFilterProxyRegistrationBean ,这个bean最后会给应用中注册一个DelegatingFilterProxy,这个代理过滤器会从容器中找出名称为springSecurityFilterChain的过滤器来执行它;
而这个springSecurityFilterChain是在WebSecurityConfiguration(这个类可以靠SecurityAutoConfiguration导入到容器中的)
)中由springSecurityFilterChain这个方法加到spring容器中的,在这个方法中会调用构建器WebSecurity.build方法创建出一个FilterChainProxy过滤器放到spring容器中;
当执行到WebSecurity.performBuild方法,会先调用SecurityFilterChain的构建器HttpSecurity的build方法构建出DefaultSecurityFilterChain对象,该对象内部会持有多个过滤器,然后把该对象作为参数创建出FilterChainProxy 对象返回。
可以看到这个过程中WebSecurity 是一个很关键的角色,它创建了DefaultSecurityFilterChain和FilterChainProxy

  • [ ] SecurityFilterAutoConfiguration->DelegatingFilterProxyRegistrationBean->DelegatingFilterProxy->FilterChainProxy->DefaultSecurityFilterChain;这里其中的FilterChainProxy->DefaultSecurityFilterChain是在websecurity中创建的。

webSecurity这个构建器用来创建Filter,,其中它具体构造过滤链还是落在WebSecurityConfigurerAdapter上的configure方法。
WebSecurity是一个接口,它定义了Spring Security框架提供的安全功能的抽象接口。WebSecurityConfigurer是一个抽象类,用于配置Web应用程序的安全性。WebSecurityConfigurerAdapter是WebSecurityConfigurer的子类,它通过实现WebSecurityConfigurer中的抽象方法来配置Web应用程序的安全性。它还提供了默认实现来简化配置过程。因此,WebSecurityConfigurerAdapter是WebSecurityConfigurer的适配器类,用于方便地配置Web应用程序的安全性。
个人总结:我们一般配置安全信息配置类一般是要重写WebSecurityConfigurerAdapter,为什么要重写这个?因为spring从SecurityFilterAutoConfiguration自动加载过滤链的时候需要提取加载WebSecurityAutoConfiguration到容器中,而WebSecurityAutoConfiguration会加载WebSecurityConfiguration到容器中,其中真正过滤器链的创建还是要落在WebSecurityConfigurerAdapter上的configure方法上,当我们没有配置安全信息类时就启动时就加载WebSecurityConfigurerAdapter的其他默认配置类到容器中,比如:DefaultConfigurerAdapter。
🍃【Spring专题】「技术原理」Spring Security的核心功能和加载运行流程的原理分析 - 掘金(这个简单些)
Spring Security——认证、授权的工作原理_spring security 授权原理-CSDN博客

个人简单总结:就是安全的过滤链配置是通过重写WebSecurityConfigurerAdapter中的configure实现的,而这个过滤链接包括认证和授权:而下面的认证授权过滤链几乎都集中到WebSecurityConfigurerAdapter中了。
认证:

授权:

获取ip地址:

SpringSecurity配置过滤器的时候对.and的理解:

Spring Security 配置中的 and 到底该怎么理解?-腾讯云开发者社区-腾讯云
springsecurity有15个过滤器,and结束的地方是每个过滤器相关配置结束的地方。

其他:

1.我们往容器中注入了UserDetailsService后,我们配置文件中配置的

1
2
3
4
5
spring:
security:
user:
name: admin
password: 888888

就会失效。这是因为在这种情况下,Spring Security将使用注入的UserDetailsService返回的UserDetails对象来进行身份验证,而不再使用配置文件中的用户名和密码。
2.当我们希望系统想从指定的数据源获取
3.

1
2
3
UserDetails是一个接口,表示用户的身份信息。它包含了用户的基本信息,如用户名、密码、角色等。
WebAuthenticationDetails是一个类,表示Web应用程序中的身份验证细节。它包含了用户进行身份验证时的相关信息,如用户的IP地址、浏览器的 Referer 等
UserDetails是UserDetailsService获取的。

4.When should I override the configure(AuthenticationManagerBuilder auth) from Spring Security in a Spring Boot app?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注入 UserDetailsService 实例和重写 configure(AuthenticationManagerBuilder auth) 方法之间的
区别在于配置的灵活性和控制权。

注入 UserDetailsService 实例:

通过将 UserDetailsService 实例注入到容器中,Spring Security 将自动使用该实例进行身份验证。
这种方式适用于简单的身份验证需求,例如基于数据库的验证,只需要提供用户详细信息的查询逻辑。
你无法直接在代码中对身份验证流程进行更详细的配置,因为验证过程是由 Spring Security 自动处理的。
重写 configure(AuthenticationManagerBuilder auth) 方法:

通过重写 configure(AuthenticationManagerBuilder auth) 方法,你可以更精确地配置身份验证过程。
你可以定义多个身份验证提供者,并为每个提供者指定不同的验证逻辑、密码编码器等。
可以使用多种身份验证方式,如数据库、LDAP、OAuth 等。
可以自定义用户详细信息的加载方式和逻辑。
可以添加其他的身份验证配置,如记住我功能、会话管理、多因素身份验证等。
你可以在代码中直接控制身份验证的细节,并根据需要进行自定义和扩展。

springsecurity中的 ThreadLocal如何理解:

1
2
3
4
5
6
7
8
9
10
11
在Spring Security中,ThreadLocal主要用于存储当前用户的信息。当用户进行认证并获得访问权限后,
Spring Security会将用户信息存储到ThreadLocal中,以便在请求的处理过程中可以获取到当前用户的信息
。 具体来说,Spring Security提供了SecurityContextHolder类来管理用户信息的存储和获取。
SecurityContextHolder类中有一个getContext()方法返回一个SecurityContext对象,该对象中有一个
getAuthentication()方法返回一个Authentication对象,该对象中存储了用户的信息。
在每个请求的处理过程中,Spring Security会将Authentication对象存储到ThreadLocal中,
以便在需要访问用户信息的地方可以通过SecurityContextHolder.getContext().getAuthentication()
来获取到用户的信息。 需要注意的是,ThreadLocal的使用可以保证每个请求都有独立的用户信息
存储空间,避免了多个线程间共享用户信息导致的安全问题。但是,ThreadLocal的使用也会带来一定
的性能开销,因为每个线程都需要存储和获取自己的ThreadLocal对象。因此,在使用ThreadLocal
存储用户信息时,需要注意权衡安全和性能的关系。