认证相关重要概念
SecurityContextHolder
Spring Security身份验证模型的核心是SecurityContextHolder
。它包含SecurityContext
。
Spring Security 的 SecurityContextHolder
,用于存储通过身份验证的人员的详细信息。
SecurityContext
从 SecurityContextHolder
中可获取 SecurityContext
。 SecurityContext
中包含一个身份验证对象。
Authentication
身份验证在Spring Security中有两个主要用途:
AuthenticationManager
的输入,用于提供用户的身份验证的凭据。在这种情况下使用时,isAuthenticated()
返回false。- 代表当前经过身份验证的用户。可以从
SecurityContext
获得当前的身份验证。
其中包含三块内容:
-
principal
用户的抽象。使用用户名/密码进行身份验证时,通常是
UserDetails
的实例。 -
credentials
通常是密码。在许多情况下,将在验证用户身份后清除此内容,以确保它不会泄漏。
-
authorities
GrantedAuthoritys
是授予用户的权限。通常是角色。
GrantedAuthority
GrantedAuthoritys
是授予用户的权限。通常是角色。
Spring Security 的过滤器,要能够识别这些权限可以访问哪些资源,并在权限不足时抛出异常。
使用基于用户名/密码的身份验证时,GrantedAuthoritys
通常由UserDetailsService
加载。
AuthenticationManager
AuthenticationManager
是用于定义 Spring Security 的过滤器如何执行身份验证的API。验证过后,由调用AuthenticationManager
的控制器(即Spring Security
的Filters
)在SecurityContextHolder
上设置返回的Authentication
。
最常见的实现是ProviderManager。
ProviderManager
ProviderManager
委托给 authenticationprovider
列表进行身份验证。
每个AuthenticationProvider
可以指出身份验证应该是成功的、失败的,或者指出它不能做出决定,并允许下游的AuthenticationProvider
做出决定。
每个AuthenticationProvider
可以执行特定类型的身份验证。例如,一个AuthenticationProvider可能能够验证用户名/密码,而另一个可能能够验证SAML断言。
AuthenticationProvider
可以将多个AuthenticationProviders
注入ProviderManager
。每个AuthenticationProvider
执行特定的身份验证类型。例如,DaoAuthenticationProvider
支持基于用户名/密码的身份验证,而JwtAuthenticationProvider
支持对JWT
令牌的身份验证。
AuthenticationEntryPoint
AuthenticationEntryPoint用于发送HTTP响应,以从客户端请求凭据。就是当用户未经认证,访问系统路径时,AuthenticationEntryPoint实现可能会执行重定向到登录页面,使用WWW-Authenticate标头进行响应等等操作。
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter
被当作基础过滤器,用于验证用户的凭据,可以对提交给它的任何身份验证请求进行身份验证。
-
当用户提交认证凭据时,
AbstractAuthenticationProcessingFilter
将从要验证的HttpServletRequest
创建一个Authentication
。创建的身份验证类型取决于AbstractAuthenticationProcessingFilter
的子类。例如,
UsernamePasswordAuthenticationFilter
根据在HttpServletRequest
中提交的用户名和密码创建UsernamePasswordAuthenticationToken
。 -
把
Authentication
传递到AuthenticationManager
进行身份验证。 -
如果验证失败,则流程结束,登陆失败。
- 清除
SecurityContextHolder
- 调用
RememberMeServices.loginFail
。如果remember me
没有配置,则不会调用。 - 调用
AuthenticationFailureHandler
- 清除
-
如果验证成功,则表明登陆成功
- 把登录通知到
SessionAuthenticationStrategy
- 把
Authentication
设置到SecurityContextHolder
上。 - 调用
RememberMeServices.loginSuccess
。如果remember me
没有配置,则不会调用。 ApplicationEventPublisher
发布一个InteractiveAuthenticationSuccessEvent
。- 调用
AuthenticationSuccessHandler
- 把登录通知到
BCryptPasswordEncoder
BCryptPasswordEncoder
实现使用广泛支持的bcrypt
算法对密码进行哈希处理。一般我们会用该类,对密码进行加密。
Username/Password Authentication
验证用户身份的最常见方法之一是验证用户名和密码。因此,Spring Security为使用用户名和密码进行身份验证提供了全面的支持。
从 HttpServletRequest
获取用户名、密码,自带以下机制:
- Form Login
- Basic Authentication
- Digest Authentication
储存机制,提供以下几种:
- 基于内存的简单存储 (
In-Memory Authentication
) - 关系型数据库存储 (
JDBC Authentication
) - 自定义数据存储 (
UserDetailsService
)
基于内存的简单存储
Spring Security 默认密码随机生成,并打印到控制台。也就是上面说的,基于内存的简单存储。
2020-11-09 19:18:39.634 INFO 17384 --- [ main] .s.s.UserDetailsServiceAutoConfiguration : Using generated security password: 59423ac9-f9a0-41b5-a4c9-f508102217c4
复制代码
我们打开 UserDetailsServiceAutoConfiguration
类,主要方法如下
@Bean
@ConditionalOnMissingBean(
type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
@Lazy
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
ObjectProvider<PasswordEncoder> passwordEncoder) {
// 从配置文件中获取 user
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
// 创建内存用户管理器
return new InMemoryUserDetailsManager(
User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
.roles(StringUtils.toStringArray(roles)).build());
}
private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
String password = user.getPassword();
// 如果密码是自动生成的,打印到控制台
if (user.isPasswordGenerated()) {
logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
}
if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
return password;
}
return NOOP_PASSWORD_PREFIX + password;
}
复制代码
我们暂且不过深看源码,只需要指导,该类向 Spring
注入了一个 InMemoryUserDetailsManager
内存用户管理器,并且创建了一个用户。由此实现了,用户名/密码登陆。
Spring Security
的InMemoryUserDetailsManager
实现了UserDetailsService
,以支持对在内存中基于用户名/密码的身份验证。
在配置文件中,配置用户名密码也是如此。
自定义存储登陆流程
用户名/密码登陆,Spring Security 也提供了几种用户存储方式:内存储存、通过 JDBC 连接关系数据库、自定义储存等。最常用的就是自定义储存方式,其实就是存到数据库中,自己存,自己取。
官网上的图,可以让我们很清晰的了解到自定义存储的认证过程。
- 通过读取用户名和密码的身份验证过滤器将
UsernamePasswordAuthenticationToken
传递给AuthenticationManager
,该身份管理器由ProviderManager
实现。 ProviderManager
中使用AuthenticationProvider
的实现类DaoAuthenticationProvider
。DaoAuthenticationProvider
从UserDetailsService
调用方法获取UserDetails
。DaoAuthenticationProvider
使用PasswordEncoder
验证上一步返回的UserDetails
上的密码。- 身份验证成功后,返回的身份验证的类型为
UsernamePasswordAuthenticationToken
,其主体为我们配置的UserDetailsService
返回的UserDetails
。最终,返回的UsernamePasswordAuthenticationToken
将由身份验证过滤器在SecurityContextHolder
上设置。
所以一般自定义储存登陆,都要扩展UserDetailService
和UserDetails
。
授权相关重要概念
Authentication
中包含 GrantedAuthority
,表示该主体的权限。
GrantedAuthority
对象由AuthenticationManager
插入Authentication
对象,并在以后做出授权决策时由AccessDecisionManager
读取。
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
复制代码
Spring Security
包含一个的GrantedAuthority
实现,即SimpleGrantedAuthority
,构造方法接收一个String
。在 Spring Security
体系中,一般都使用SimpleGrantedAuthority
。
AccessDecisionManager
Spring Security
提供了拦截器,用于控制访问,例如拦截http请求。
AccessDecisionManager
由AbstractSecurityInterceptor
调用,并决策该请求是否被允许。
AccessDecisionManager接口包含三种方法:
// 决策该请求是否被允许
void decide(Authentication authentication, Object secureObject,
Collection<ConfigAttribute> attrs) throws AccessDeniedException;
// 在开始时,AbstractSecurityInterceptor将调用support(ConfigAttribute)方法,以确定AccessDecisionManager是否可以处理传递的ConfigAttribute。
boolean supports(ConfigAttribute attribute);
// 安全拦截器实现调用support(Class)方法,以确保配置的AccessDecisionManager支持安全拦截器将显示的安全对象的类型。
boolean supports(Class clazz);
复制代码
FilterSecurityInterceptor
FilterSecurityInterceptor
为HttpServletRequests
提供授权。它是Security
过滤器之一。
FilterSecurityInterceptor
从SecurityContextHolder
获得Authentication
。FilterSecurityInterceptor
根据传递到FilterSecurityInterceptor
中的HttpServletRequest
,HttpServletResponse
和FilterChain
创建一个FilterInvocation
。- 将
FilterInvocation
传递给SecurityMetadataSource
以获取ConfigAttribute
s。 - 将
Authentication
,FilterInvocation
和ConfigAttributes
传递给AccessDecisionManager
。 - 如果授权被拒绝,则抛出
AccessDeniedException
。ExceptionTranslationFilter
处理AccessDeniedException
。 - 如果授予访问权限,
FilterSecurityInterceptor
继续使用FilterChain
,允许应用程序正常处理。
默认情况下,Spring Security的授权将要求对所有请求进行身份验证。
FilterInvocationSecurityMetadataSource
上面说到,要使用SecurityMetadataSource
获取ConfigAttribute
s。
FilterInvocationSecurityMetadataSource
继承 SecurityMetadataSource
,而且没有扩展方法。在扩展时,一般实现此方法,实现角色与url
匹配。
public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource {
}
复制代码