SecurityContextHolder
将给定的 SecurityContext 与当前执行线程关联。
此类提供了一系列操作委托给 SecurityConextHolderStrategy 实例的静态方法。
这个类的目的是提供一种方便的方法来指定应该用于给定JVM的策略。
这是一个JVM范围的设置,因为该类中的所有内容都是静态的,以便于调用代码。
该类定义了三个常量 MODE_THREADLOCAL、MODE_INHERITABLETHREADLOCAL、MODE_GLOBAL用来指定实际的SecurityConextHolderStrategy 实例,用户也可以用自定义SecurityConextHolderStrategy具体实现的完全限定类名指定。
有两种方法可以指定:
第一种方法是通过以SYSTEM_PROPERTY为关键字的系统属性指定它。
第二种方法是在使用类之前调用setStrategyName(String)。
如果这两种方法都没有使用,该类将默认使用MODE_THREADLOCAL,这是向后兼容的,具有较少的JVM不兼容性,并且适用于服务器(而MODE_GLOBAL绝对不适合服务器使用)。
public class SecurityContextHolder {
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
private static SecurityContextHolderStrategy strategy;
private static int initializeCount = 0;
static {
initialize();
}
// 改变首选策略。对于给定的JVM,不要多次调用此方法,因为它将重新初始化策略,并对使用旧策略的任何现有线程产生不利影响
public static void setStrategyName(String strategyName) {
SecurityContextHolder.strategyName = strategyName;
initialize();
}
// 从当前线程显式清除SecurityContext
public static void clearContext() {
strategy.clearContext();
}
public static SecurityContext getContext() {
return strategy.getContext();
}
// 将新SecurityContext与当前执行线程关联
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
public static SecurityContextHolderStrategy getContextHolderStrategy() {
return strategy;
}
public static int getInitializeCount() {
return initializeCount;
}
public static SecurityContext createEmptyContext() {
return strategy.createEmptyContext();
}
private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
// 默认值
strategyName = MODE_THREADLOCAL;
}
if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
} else {
// 尝试加载自定义策略
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
} catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
initializeCount++;
}
}
SecurityContext
定义与当前执行线程关联的最小安全信息的接口。安全上下文存储在SecurityConextHolder中。
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
Authentication
表示身份验证请求的令牌,或AuthenticationManager.authenticate(Authentication)方法处理请求后经过身份验证的主体的令牌。
一旦请求通过身份验证,身份验证通常会存储在由SecurityConextHolder通过所使用的身份验证机制管理的线程本地SecurityContext中。
无需使用Spring Security的任何一种身份验证机制,即可通过创建一个身份验证实例并使用以下代码来实现显式身份验证:
SecurityContextHolder.getContext().setAuthentication(anAuthentication);
请注意,除非身份验证的已验证属性设置为真,否则它仍将由遇到它的任何安全拦截器(用于方法或Web调用)进行身份验证。
在大多数情况下,框架透明地负责为您管理安全上下文和身份验证对象。
public interface Authentication extends Principal, Serializable {
// 获取权限列表
Collection<? extends GrantedAuthority> getAuthorities();
// 获取凭据,登录密码或者短信验证码、访问token
Object getCredentials();
// 获取认证详情,可自定义
Object getDetails();
// 获取认证信息,用户名或者手机号码等
Object getPrincipal();
// 是否认过证标识
boolean isAuthenticated();
// 设置是否认证过
void setAuthenticated(boolean authenticated) throws IllegalArgumentException;
}
AuthenticationProvider
该类表示可以处理特定的 Authentication 实现;认证器的接口类,几乎所有的认证认证逻辑类都要实现这个接口。
public interface AuthenticationProvider {
// 认证接口方法
// 使用与AuthenticationManager.authenticate(Authentication)相同的约定执行身份认证操作
// 参数:身份验证请求(Authentication )
// 返回:认证通过的包括凭据额身份验证对象(Authentication ),如果当前 AuthenticationProvider 不支持传入的 Authentication 时,
// 可能返回null; 这样就会下一个 AuthenticationProvider
// 认证失败throw AuthenticationException
Authentication authenticate(Authentication authentication) throws AuthenticationException;
// 是否支持对给定类型的AuthenticationToken类进行认证
boolean supports(Class<?> authentication);
}
AbstractUserDetailsAuthenticationProvider
允许子类重写和使用UserDetail对象的基AuthenticationProvider。该类旨在响应UsernamePasswordAuthenticationToken身份验证请求。
验证成功后,将创建一个UsernamePasswordAuthenticationToken并将其返回给调用方。令牌将包括用户名的字符串表示形式或从身份验证存储库返回的UserDetail作为其主体。如果正在使用容器适配器,则使用字符串是合适的,因为它需要用户名的字符串表示形式。如果您需要访问经过身份验证的用户的其他属性(如电子邮件地址、人性化名称等),则使用UserDetail是合适的。由于不建议使用容器适配器,并且UserDetail实现提供了额外的灵活性,因此默认情况下将返回UserDetail。若要覆盖此默认值,请将setForceEpalAsString设置为True。
缓存通过存储放在UserCache中的UserDetail对象来处理。这确保了可以验证具有相同用户名的后续请求,而无需查询UserDetailsService。应该注意的是,如果用户似乎提供了不正确的密码,则将查询UserDetailsService以确认使用了最新的密码进行比较。缓存可能只适用于无状态应用程序。例如,在普通的Web应用程序中,SecurityContext存储在用户的会话中,并且不会在每次请求时对用户进行重新身份验证。因此,默认的缓存实现是NullUserCache。
public interface AbstractUserDetailsAuthenticationProvider {
/*
允许子类对给定身份验证请求返回的(或缓存的)UserDetail执行任何附加检查。通常情况下,子类至少会将Authentication.getCredentials()与
UserDetails.getPassword()进行比较。如果需要自定义逻辑来比较UserDetail和/或UsernamePasswordAuthenticationToken的其他属性,
则这些属性也应出现在此方法中。
userDetail: 从RetrieseUser(字符串,UsernamePasswordAuthenticationToken)或User缓存检索到的。
authentication: 需要进行身份验证的当前请求
如果无法验证凭据,则会抛出身份验证异常(通常为BadCredentialsException,即AuthenticationServiceException)
*/
protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException;
}
DaoAuthenticationProvider
从 UserDetailsService 查询用户详细信息的 AuthationProvider 实现
AuthenticationManager
身份验证管理器;处理身份验证(Authentication)请求
public interface AuthenticationManager {
/*
尝试对传递的 Authentication 对象进行身份验证,如果成功,则返回完全填充的 Authentication 证对象(包括授予的权限)。
AuthenticationManager 必须遵守以下有关例外情况的契约:
如果帐户被禁用,throw DisabledException
如果帐户被锁定,throw LockedException
如果提供了不正确的凭据,throw BadCredentialsException
虽然上述例外是可选的,但 AuthenticationManager 必须始终检测凭据。
应按上述顺序检测异常并在适用时抛出(即,如果帐户被禁用或锁定,则身份验证请求立即被拒绝,并且不执行凭据检测过程)。
这可防止针对禁用或锁定的帐户检测凭据。
参数:
authentication 身份验证请求对象
返回:
完全经过身份验证的对象,包括凭据
异常:如果身份验证失败,则抛出AuthenticationException
*/
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
ProviderManager
迭代通过一组身份验证提供程序的身份验证请求。
在提供非空响应之前,通常会按顺序尝试 AuthationProvider。非空响应表示提供程序有权决定身份验证请求,不再尝试其他 provider 。
如果后续 provider 成功验证了请求,则忽略先前的验证异常,并使用成功的验证。
如果没有后续 provider 提供非空响应或新的身份验证异常,则将使用收到的最后一个身份验证异常。
如果没有 provider 返回非空响应,或者没有可以处理当前 身份验证的 provider ,则ProviderManager 将抛出 ProviderNotFoundException。
还可以设置父身份验证管理器,如果配置的提供程序都不能执行身份验证,也会尝试这样做。这是为了支持名称空间配置选项,这通常不是必需的特性。
此过程的例外情况是提供程序抛出AccountStatusException,在这种情况下,将不会查询列表中的其他提供程序。
身份验证后,如果返回的身份验证对象实现了CredentialsContainer接口,则将从该对象中清除凭据。可以通过修改eraseCredentialsAfterAuthentication属性来控制此行为。
发布事件
身份验证事件发布被委托给已配置的AuthationEventPublisher,它缺省为不发布事件的空实现,因此,如果您自己配置Bean,如果您想要接收事件,则必须注入一个发布者Bean。
标准实现是DefaultAuthenticationEventPublisher,它将常见异常映射到事件(在身份验证失败的情况下),并在身份验证成功时发布一个AuthenticationSuccessEvent。
如果您使用的是名称空间,则 http 配置将自动使用该Bean的一个实例,因此您将自动从应用程序的Web部分接收事件。
请注意,当该实现从 “parent” AuthationManager 获得身份验证结果(或异常)时,它还会发布身份验证失败事件(如果已设置)。因此,在这种情况下,通常不应将父对象配置为发布事件,否则会出现重复事件。
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
/*
尝试对传递的身份验证对象进行身份验证。
将连续尝试身份验证提供程序列表,直到身份验证提供程序指示它能够对传递的身份验证对象类型进行身份验证。然后将尝试使用该身份验证提供程序进 行身份验证。
如果多个身份验证提供者支持传递的身份验证对象,则第一个能够成功验证身份验证对象的身份验证对象将确定结果,从而重写先前支持身份验证提供者 引发的任何可能的身份验证异常。身份验证成功后,不会尝试任何后续的身份验证提供程序。如果任何支持的AuthenticationProvider未成功进行身份验证,则将重新抛出最后抛出的AuthenticationException异常。
参数:
authentication 身份验证请求对象
返回:
完全经过身份验证的对象,包括凭据
异常:如果身份验证失败,则抛出AuthenticationException
*/
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
AuthenticationManagerBuilder
用于创建 AuthenticationManager 的 SecurityBuilder。允许轻松地在内存中构建身份验证、LDAP身份验证、基于JDBC的身份验证、添加UserDetailsService和添加AuthenticationProvider。
public class AuthenticationManagerBuilder
extends
AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
/*
允许提供父身份验证管理器,如果此身份验证管理器无法尝试对提供的身份验证进行身份验证,则将尝试该父身份验证管理器。
*/
public AuthenticationManagerBuilder parentAuthenticationManager(
AuthenticationManager authenticationManager) {
......
}
/*
根据传入的自定义UserDetailsService添加身份验证。然后,它返回一个DaoAuthenticationConfigurer,以允许定制身份验证。
此方法还确保UserDetailsService可用于getDefaultUserDetailsService()方法。请注意,其他UserDetailsService可能会覆盖此 UserDetailsService作为默认设置。
返回:
允许定制DAO身份验证的DaoAuthenticationConfigureer。
异常:
如果在添加基于UserDetailsService的身份验证时出错
*/
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
T userDetailsService) throws Exception {
......
}
/*
根据传入的自定义AuthationProvider添加身份验证。由于AuthenticationProvider实现是未知的,因此所有定制都必须在外部完成,并立即返回AuthenticationManagerBuilder。
此方法不能确保UserDetailsService可用于getDefaultUserDetailsService()方法。请注意,如果在添加AuthationProvider时发生错误,则可能会引发异常。
返回:
一个AuthenticationManagerBuilder
*/
public AuthenticationManagerBuilder authenticationProvider(
AuthenticationProvider authenticationProvider) {
this.authenticationProviders.add(authenticationProvider);
return this;
}
}
AuthenticationManagerBuilder类在spring-security-config-5.2.4-RELEAS.jar中的org.springframework.security.config.annotation.authentication.configuration包中的配置类
AuthenticationConfiguration中对AuthenticationManagerBuilder类中定义了beanAuthenticationConfiguration.class
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
AuthenticationConfiguration.LazyPasswordEncoder defaultPasswordEncoder = new AuthenticationConfiguration.LazyPasswordEncoder(context);
AuthenticationEventPublisher authenticationEventPublisher = (AuthenticationEventPublisher)getBeanOrNull(context, AuthenticationEventPublisher.class);
AuthenticationConfiguration.DefaultPasswordEncoderAuthenticationManagerBuilder result = new AuthenticationConfiguration.DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
if (authenticationEventPublisher != null) {
result.authenticationEventPublisher(authenticationEventPublisher);
}
return result;
}
HttpSecurity
HttpSecurity类似于名称空间配置中的Spring Security的XML http 元素。它允许为特定的 http 请求配置基于Web的安全性。默认情况下,它将应用于所有请求,但可以使用questMatcher(RequestMatcher)或其他类似方法进行限制。
用法示例:
最基本的基于表单的配置如下所示。配置将要求所请求的任何URL都需要具有角色“Role_User”的用户。它还定义了一个内存身份验证方案,其中的用户具有用户名“USER”、密码“PASSWORD”和角色“ROLE_USER”。有关其他示例,请参阅HttpSecurity上各个方法的Java文档。
@Configuration
@EnableWebSecurity
public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
public final class HttpSecurity extends
AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity> {
}
AbstractAuthenticationProcessingFilter
基于浏览器的基于HTTP的身份验证请求的抽象处理器。
验证过程:
筛选器要求您设置身份验证管理器属性。需要一个身份验证管理器来处理通过实现类创建的身份验证请求令牌。
如果请求与setRequiresAuthenticationRequestMatcher(RequestMatcher).匹配,则此过滤器将截取请求并尝试从该请求执行身份验证。
身份验证由temtemptAuthentication方法执行,该方法必须由子类实现。
验证成功:
如果身份验证成功,则生成的身份验证对象将被放入当前线程的SecurityContext中,该线程被保证已由先前的过滤器创建。
在成功登录后,将调用配置的AuthationSuccessHandler将重定向带到适当的目的地。默认行为是在SavedRequestAwareAuthenticationSuccessHandler中实现的,它将利用ExceptionTranslationFilter设置的任何DefaultSavedRequest,并将用户重定向到其中包含的URL。否则,它将重定向到Webapp根目录“/”。您可以通过注入此类的不同配置实例或使用不同的实现来自定义此行为。
有关更多信息,请参阅SuccessfulAuthentication(HttpServletRequest,HttpServletResponse,FilterChain,Authentication)方法。
验证失败:
如果身份验证失败,它将委托给已配置的AuthenticationFailureHandler,以允许将失败信息传递给客户端。默认实现是SimpleUrlAuthenticationFailureHandler,它向客户端发送401错误代码。它也可以配置失败URL作为替代。同样,您可以在这里注入您需要的任何行为。
发布事件:
如果身份验证成功,则将通过应用程序上下文发布一个InteractiveAuthationSuccessEvent。如果身份验证不成功,则不会发布任何事件,因为这通常会通过特定于AuthenticationManager的应用程序事件来记录。
会话认证策略:
该类有一个可选的SessionAuthenticationStrategy,它将在成功调用temtemptAuthentication()后立即被调用。可以注入不同的实现,以实现会话固定攻击预防或控制主体可能同时拥有的会话数量等功能。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
/**
* @param DefaultFilterProcessUrl–FilterProcessUrl的默认值
*/
protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
setFilterProcessesUrl(defaultFilterProcessesUrl);
}
/**
* Creates a new instance
*
* @param 用于确定是否需要身份验证的RequestMatcher
*/
protected AbstractAuthenticationProcessingFilter(
RequestMatcher requiresAuthenticationRequestMatcher) {
Assert.notNull(requiresAuthenticationRequestMatcher,
"requiresAuthenticationRequestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
}
/*
调用requiresAuthentication方法以确定请求是否为身份验证请求,是否应由此筛选器处理。
如果是鉴权请求,则调用temtemptAuthentication进行鉴权。然后有三种可能的结果:
1、返回一个身份验证对象。将调用已配置的SessionAuthenticationStrategy(以处理任何与会话相关的行为,如创建新会话以防止会话固定攻击),然后调用successfulAuthentication(HttpServletRequest、HttpServletResponse、FilterChain、Authentication)方法。
2、在身份验证过程中发生身份验证异常。将调用不成功的身份验证方法。
3、返回空,表示鉴权过程未完成。然后该方法将立即返回,假设子类已经完成了继续身份验证过程所需的任何工作(如重定向)。假设返回的身份验证对象不为空时,此方法将接收稍后的请求。
*/
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed(立即返回,因为子类已指示它尚未完成)
// authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error("An internal error occurred while trying to authenticate the user.", failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed(认证失败)
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
/*
执行实际身份验证。
实施应执行以下操作之一:
1、为经过身份验证的用户返回填充的身份验证令牌,指示身份验证成功。
2、返回NULL,表示身份验证过程仍在进行中。在返回之前,实现应该执行完成该过程所需的任何附加工作。
3、如果身份验证过程失败,则引发身份验证异常
*/
public abstract Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException,
ServletException;
/**
* 设置过滤处理Url,也就是过滤器拦截的登录认证请求Url
*/
public void setFilterProcessesUrl(String filterProcessesUrl) {
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(filterProcessesUrl));
}
/**
* 认证失败后的处理方法
*/
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
// 清空与当前线程绑定的SecurityContext
SecurityContextHolder.clearContext();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication request failed: " + failed.toString(), failed);
this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
this.logger.debug("Delegating to authentication failure handler " + this.failureHandler);
}
// 清除之前持久化保存的认证信息并设置response中的cookie失效
this.rememberMeServices.loginFail(request, response);
// 认证失败处理器回调,这个处理器回调也可以在自定义的认证处理过滤器中进行自定义设置
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
/**
* 设置认证管理器
*/
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* 设置认证成功处理器
*/
public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) {
Assert.notNull(successHandler, "successHandler cannot be null");
this.successHandler = successHandler;
}
/**
* 设置认证失败失败处理器
*/
public void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
}
UsernamePasswordAuthenticationFilter
处理表单提交身份验证请求
在SpringSecurity3.0之前的版本中,它被称为AuthenticationProcessingFilter。
登录表单必须向此过滤器提供两个参数:用户名和密码。
要使用的默认参数名称包含在静态字段SPRING_SECURITY_FORM_USERNAME_KEY和SPRING_SECURITY_FORM_PASSWORD_KEY中。
也可以通过设置 usernameParameter 和 passwordParameter 属性来更改参数名称。
默认情况下,此Fileter 只处理URL为 /login的请求。
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
/*
默认拦截 /login路径,且必须时POST请求方法
*/
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
/**
* attemptAuthentication方法中从HttpServletRequest请求对象中
* 提取username和password参数并封装成UsernamePasswordAuthenticationToken对象
* 最后调用其认证管理器的authentication方法,并返回该方法的返回值
* 也就是
*/
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
/**下面这个方法也就是ProviderManager#authenticate方法
* 在前面的ProviderManager#authenticate方法源码中已经分析过了,
* 这里就不啰嗦了
*/
return this.getAuthenticationManager().authenticate(authRequest);
}
}
}