认证相关重要概念
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以获取ConfigAttributes。 - 将
Authentication,FilterInvocation和ConfigAttributes传递给AccessDecisionManager。 - 如果授权被拒绝,则抛出
AccessDeniedException。ExceptionTranslationFilter处理AccessDeniedException。 - 如果授予访问权限,
FilterSecurityInterceptor继续使用FilterChain,允许应用程序正常处理。
默认情况下,Spring Security的授权将要求对所有请求进行身份验证。
FilterInvocationSecurityMetadataSource
上面说到,要使用SecurityMetadataSource获取ConfigAttributes。
FilterInvocationSecurityMetadataSource 继承 SecurityMetadataSource,而且没有扩展方法。在扩展时,一般实现此方法,实现角色与url匹配。
public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource {
}