Spring Security 中文文档 :: Spring Security Reference
1. 密码存储
最早是明文存储,但是攻击者获得数据库的数据后就能得到用户密码。
于是将密码单向hash后存储,然后攻击者利用彩虹表(算法高级(23)-彩虹表(Rainbow Table)_彩虹表下载-CSDN博客)可以快速的根据hash值获取密码。
于是人们将密码加盐后hash,这种方式增加了存储密码的复杂度,使得预先根据简单字符串生成的彩虹表不会那么容易的匹配到存储的复杂密码hash值。
但是现代硬件每秒几十亿次hash计算,可以轻松的暴力破解(依次尝试各种字节组合,并匹配存储的hash值)。
2. 配置类加载
spring-boot-autoconfigure-3.4.4.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports
springboot会扫描并加载该文件中的配置类
org.springframework.boot.autoconfigure.security.servlet.SpringBootWebSecurityConfiguration.SecurityFilterChainConfiguration#defaultSecurityFilterChain:springboot默认在该方法中注册SecurityFilterChain
filter注册到servlet容器的过程:
首先,DelegatingFilterProxyRegistrationBean(实现了ServletContextInitializer)在SecurityFilterAutoConfiguration(在imports文件中)中被注册为一个bean,然后,其在context容器初始化时注册了DelegatingFilterProxy过滤器。SecurityFilterAutoConfiguration中使用springSecurityFilterChain作为受委托的filter。springSecurityFilterChain通过WebSecurityConfiguration注册为bean,WebSecurityConfiguration是由SpringBootWebSecurityConfiguration的内部类WebSecurityEnablerConfiguration注册的,SpringBootWebSecurityConfiguration是在SecurityAutoConfiguration(在imports中)中导入的。
springSecurityFilterChain是一个FilterChainProxy类型的bean
DefaultSecurityFilterChain在SecurityFilterChainConfiguration中被注册
@Bean @Order(2147483642) SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((requests) -> { ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)requests.anyRequest()).authenticated(); }); http.formLogin(Customizer.withDefaults()); // 表单认证,即使用UsernamePasswordAuthenticationFilter http.httpBasic(Customizer.withDefaults()); return (SecurityFilterChain)http.build(); }
UserDetailsServiceAutoConfiguration:注册了InMemoryUserDetailsManager作为UserDetailsService用来获取用户信息。
3. 过滤器
DelegatingFilterProxy:使用过滤器bean来实现过滤,是sevlet容器标准与spring bean之间的桥梁。sevlet容器可以通过自己的标准注册filter,但是无法检测到spring bean,于是先注册DelegatingFilterProxy作为标准过滤器,然后DelegatingFilterProxy将工作委托给spring的过滤器bean。DelegatingFilterProxy的另一个好处是延迟查找filter bean,servlet容器需要在启动前完成filter的注册,但是使用DelegatingFilterProxy的话可以在servlet容器启动后再完成filter bean的加载。作为一个Filter的代理,将工作委托给filter bean。
FilterChainProxy:一个filter bean,可以链式调用多个filter,包含在DelegatingFilterProxy,被委托来执行过滤工作。包含多个SecurityFilterChain,每个SecurityFilterChain含有0个或多个filter,根据请求确定使用哪个SecurityFilterChain。
SecurityFilterChain:包含多个filter。
多个SecurityFilterChain的图示:
ExceptionTranslationFilter:将 AccessDeniedException 和 AuthenticationException 翻译成 HTTP 响应。
异常处理图示:
1 首先,
ExceptionTranslationFilter
调用FilterChain.doFilter(request, response)
来调用应用程序的其他部分。2 如果用户没有被认证,或者是一个
AuthenticationException
,那么就 开始认证。
SecurityContextHolder 被清理掉。
HttpServletRequest
被保存起来,这样一旦认证成功,它就可以用来重放原始请求。
AuthenticationEntryPoint
用于请求客户的凭证。例如,它可以重定向到一个登录页面或发送一个WWW-Authenticate
头。3 否则,如果是
AccessDeniedException
,那么就是 Access Denied。AccessDeniedHandler
被调用来处理拒绝访问(access denied)。
RequestCacheAwareFilter:使用 RequestCache 来保存 HttpServletRequest
保存认证前的请求,以在认证后继续访问:
@Bean DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception { HttpSessionRequestCache requestCache = new HttpSessionRequestCache(); requestCache.setMatchingRequestParameterName("continue"); http // ... .requestCache((cache) -> cache .requestCache(requestCache) ); return http.build(); }
不保存请求:
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
RequestCache nullRequestCache = new NullRequestCache();
http
// ...
.requestCache((cache) -> cache
.requestCache(nullRequestCache)
);
return http.build();
}
注意:当你把你的 filter 声明为 Spring Bean 时要小心,可以用 @Component
注解它,也可以在配置中把它声明为 Bean,因为 Spring Boot 会自动 在嵌入式容器中注册它。这可能会导致 filter 被调用两次,一次由容器调用,一次由 Spring Security 调用,而且顺序不同。另外自定义的filter可以从 OncePerRequestFilter 中继承,而不是实现 Filter
,这是一个基类,用于每个请求只调用一次的 filter,并提供一个带有 HttpServletRequest
和 HttpServletResponse
参数的 doFilterInternal
方法。
自定义过滤器:
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.addFilterBefore(new TenantFilter(), AuthorizationFilter.class);
return http.build();
}
4. 日志
logging.level.org.springframework.security=TRACE
5. 认证
流程:
当一个未认证的请求到达后,首先AuthorizationFilter抛出AccessDeniedException,然后ExceptionTranslationFilter使用AuthenticationEntryPoint(LoginUrlAuthenticationEntryPoint)返回一个响应,该响应向客户端要求用户凭证(如重定向到登录页)。
当含有凭证的请求到达后,AbstractAuthenticationProcessingFilter(如UsernamePasswordAuthenticationFilter )用来验证该凭证,它首先从HttpServletRequest中提取出待验证的Authentication(如oauth2的token、用户名密码UsernamePasswordAuthenticationToken),然后将Authentication传给AuthenticationManager执行验证,验证通过后在SecurityContextHolder中设置Authentication,验证失败则清空SecurityContextHolder。
AuthenticationManager:用来执行Authentication中凭证的验证工作,其实现一般为ProviderManager。
ProviderManager:通过AuthenticationProvider列表来验证凭证,多个AuthenticationProvider依次执行。
AuthenticationProvider:具体执行验证工作的组件,有多种类型,如 DaoAuthenticationProvider验证用户名密码,JwtAuthenticationProvider验证令牌。
Authentication:
Authentication 接口在Spring Security中主要有两个作用。
对 AuthenticationManager 的一个输入,用于提供用户为验证而提供的凭证。当在这种情况下使用时,
isAuthenticated()
返回false
。代表当前认证的用户。你可以从 SecurityContext 中获得当前的
Authentication
。认证(
Authentication
)包含了:
principal
: 识别用户。当用用户名/密码进行认证时,这通常是 UserDetails 的一个实例。
credentials
: 通常是一个密码。在许多情况下,这在用户被认证后被清除,以确保它不会被泄露。
authorities
: GrantedAuthority 实例是用户被授予的高级权限。两个例子是角色(role)和作用域(scope)。
GrantedAuthority:代表一个权限,一个用户可能有多个权限。
JdbcUserDetailsManager :用来通过jdbc获取用户信息,默认的表结构在文件org/springframework/security/core/userdetails/jdbc/users.ddl中
SecurityContextRepository:用来存储securitycontext供后续的用户请求使用。HttpSessionSecurityContextRepository将securitycontext存在session中根据cookied的sessionid识别用户。
SecurityContextHolderFilter:负责将securitycontext从SecurityContextRepository中取出并设置到SecurityContextHolder中。