上面我们一起开始了Spring Security的初体验,并通过简单的配置甚至零配置就可以完成一个简单的认证流程。可能我们都有很大的疑惑,这中间到底发生了什么,为什么简单的配置就可以完成一个认证流程啊,可我啥都没看见,没有写页面,没有写接口。这一篇我们将深入到源码层面一起来了解一下spring security到底是怎么工作的。

准备工作

在开始源码理解前,我们先来做一项基本的准备工作,从日志中去发现线索,因为我们发现什么都没有配置的情况下,他也可以正常的工作,并给我们预置了一个临时的用户user。那么他肯定是在工程启动的时候做了什么事情,上一篇我们也提到了是如果生成user用户和密码的。这篇我们将仔细的去了解一下。

1、首先我们配置在applicaiton.yml中调整一下日志级别

logging:
level:
org.springframework.security: debug

我们将security相关的日志打印出来,一起来启动或者运行的时候到底发生了什么。

2、启动spring-security-basic 工程

!!!找到了

日志过滤

(1) Eagerly initializing {org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration=org.springframework.boot.autoconfigure.security.servlet.WebSecurityEnablerConfiguration@52e04737}
(2) Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).
(3) Adding web access control expression 'authenticated', for any request
(4) Validated configuration attributes

逐个解析

1、WebSecurityEnablerConfiguration

告诉我们它初始化了一个配置类WebSecurityEnablerConfiguration 不管!找到源码再说

@Configuration(
proxyBeanMethods = false
)
@ConditionalOnBean({WebSecurityConfigurerAdapter.class})
@ConditionalOnMissingBean(
name = {"springSecurityFilterChain"}
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {
public WebSecurityEnablerConfiguration() {
}
}

???怎么只有这么一点东西,这个类为什么会在初始化的时候启动?这里简单的指出来

首先找到spring-boot-autoconfigure-版本.jar下的META-INF/spring.factorites文件,其中有这样一段

org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\

我们可以暂时不去深究这是什么意思,总之,在springboot启动的时候,会将这里配置走一遍(后期可能也会写一点关于springboot启动原理的文章...)我们一个一个来看一下

1.1 SecurityAutoConfiguration
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({DefaultAuthenticationEventPublisher.class})
@EnableConfigurationProperties({SecurityProperties.class})
@Import({SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class, SecurityDataConfiguration.class})
public class SecurityAutoConfiguration {
public SecurityAutoConfiguration() {
} @Bean
@ConditionalOnMissingBean({AuthenticationEventPublisher.class})
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}

在这个类中我们重点关注

@EnableConfigurationProperties({SecurityProperties.class})
@Import({SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class, SecurityDataConfiguration.class})

首先是SecurityProperties

@ConfigurationProperties(
// A 前缀
prefix = "spring.security"
)
public class SecurityProperties {
// ..
private SecurityProperties.User user = new SecurityProperties.User();
// ... public static class User {
// 默认指定一个
private String name = "user";
// 默认随机密码
private String password = UUID.randomUUID().toString();
private List<String> roles = new ArrayList();
// 默认密码是系统生成的(重点关注一下)
private boolean passwordGenerated = true;
// ...
public void setPassword(String password) {
// 如果指定了自定义了密码,那就false 并覆盖password
if (StringUtils.hasLength(password)) {
this.passwordGenerated = false;
this.password = password;
}
}
//.....
}
// .....
}

篇幅问题这里我删除了很多代码。直接看里面的注释就好了,这也就是为什么我们不配置任何信息,也有一个默认的用户,以及我们用配置信息覆盖了默认用户的关键信息所在。

其次是@Import注解,这个其实就是xml配置方式中的标签 引入另外的配置,这里引入了SpringBootWebSecurityConfiguration WebSecurityEnablerConfiguration SecurityDataConfiguration

@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({WebSecurityConfigurerAdapter.class})
@ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class})
@ConditionalOnWebApplication(
type = Type.SERVLET
)
public class SpringBootWebSecurityConfiguration {
public SpringBootWebSecurityConfiguration() {
} @Configuration(
proxyBeanMethods = false
)
// 其实也没干啥,就是一个空的对象,什么也没覆盖
@Order(2147483642)
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
DefaultConfigurerAdapter() {
}
}
}

他们指向了一个关键的配置@ConditionalOnBean({WebSecurityConfigurerAdapter.class}) 需要WebSecurityConfigurerAdapter才会进行加载,那这个关键的类是什么时候加载的呢?这就回到了我们在日志中发现的第一个加载的类信息WebSecurityEnablerConfiguration 上面有个一非常关键的注解@EnableWebSecurity

瞧瞧干了啥

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
// 引入了配置类 WebSecurityConfiguration
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity { /**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
1.2 WebSecurityConfiguration

原来,首先他是个配置注解,也importWebSecurityConfiguration

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
// 1、声明一个 webSecurity 一起来看一下他是什么时候初始化的
private WebSecurity webSecurity;
// 2、是否为调试模式
private Boolean debugEnabled;
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers; private ClassLoader beanClassLoader;
// 3、关键点,后置对象处理器,用来初始化对象
@Autowired(required = false)
private ObjectPostProcessor<Object> objectObjectPostProcessor; @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
// 6 、如果每没初始化,直接指定获取对象 WebSecurityConfigurerAdapter
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
// 7、 开始构建对象 webSecurity
return webSecurity.build();
} // 4、通过setter方式注入 webSecurityConfigurers
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
// 获取 0 步中获取到的对象信息
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
// 5、 这里通过后置对象处理器来进行 webSecurity 的初始化
webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
} webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE); Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
// 放入到 AbstractConfiguredSecurityBuilder 的配置集合中
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
} // 0 先自动织入webSecurityConfigurers
// 关键点就是获取 beanFactory.getBeansOfType(WebSecurityConfigurer.class);
@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}
}

上面我们已经看到了步骤7,通常情况下都会去build

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean(); private O object; public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
// 这里调用doBuild的最终方法
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
} public final O getObject() {
if (!this.building.get()) {
throw new IllegalStateException("This object has not been built");
}
return this.object;
}
// 这里是抽象方法,直接找到其唯一的子类 AbstractConfiguredSecurityBuilder
protected abstract O doBuild() throws Exception;
}
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
// 前置检查
beforeInit();
// 初始化
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}

不知不觉我们已经找到了spring中的关键方法init了,很多时候我们在定义接口是都会有一个init方法来定义注入时调用

前面我们知道 SpringBootWebSecurityConfiguration 初始化了一个对象,同时也通过AutowiredWebSecurityConfigurersIgnoreParents拿到了WebSecurityConfigurerAdapter 的子类 DefaultConfigurerAdapter,现在开始init(),其实就是开始WebSecurityConfigurerAdapterinit()方法。说了这里可能有的同学就会比较熟悉了,这就是关键配置的适配器类。

代码稍后贴出来,暂时先不看,到这里为止,我们才梳理了springboot自动配置中的SecurityAutoConfiguration

下面我们才开始第二个类

2、 UserDetailsServiceAutoConfiguration

@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({AuthenticationManager.class})
@ConditionalOnBean({ObjectPostProcessor.class})
@ConditionalOnMissingBean(
value = {AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class},
type = {"org.springframework.security.oauth2.jwt.JwtDecoder", "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector"}
)
public class UserDetailsServiceAutoConfiguration {
private static final String NOOP_PASSWORD_PREFIX = "{noop}";
private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");
private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class); public UserDetailsServiceAutoConfiguration() {
} @Bean
@ConditionalOnMissingBean(
type = {"org.springframework.security.oauth2.client.registration.ClientRegistrationRepository"}
) // 这里加载了从配置文件或者默认生成的用户信息,以及加密方法
@Lazy
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties, ObjectProvider<PasswordEncoder> passwordEncoder) {
User user = properties.getUser();
List<String> roles = user.getRoles();
return new InMemoryUserDetailsManager(new UserDetails[]{org.springframework.security.core.userdetails.User.withUsername(user.getName()).password(this.getOrDeducePassword(user, (PasswordEncoder)passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build()});
} private String getOrDeducePassword(User user, PasswordEncoder encoder) {
String password = user.getPassword();
if (user.isPasswordGenerated()) {
logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
} return encoder == null && !PASSWORD_ALGORITHM_PATTERN.matcher(password).matches() ? "{noop}" + password : password;
}
}

这里也出现了一个info日志,当我们使用默认user用户时,密码会从这里打印在控制台

这个配置类的关键就是生成一个默认的InMemoryUserDetailsManager对象。

4、SecurityFilterAutoConfiguration

这个类就不详细介绍了,就是注册一些过滤器。


回到WebSecurityConfigurerAdapter 这个适配器类,我们关注基本的init()方法,其他的都是一些默认的配置

	public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
});
}

这里有一个关键的方法getHttp()

	protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
} DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
authenticationBuilder.authenticationEventPublisher(eventPublisher);
// 获取创建共享的对象
Map<Class<?>, Object> sharedObjects = createSharedObjects(); http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader); for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
// httpHttpSecurity 的表单配置
configure(http);
return http;
}

我们简单列举几个重要的方法

// 根据系统加载的AuthenticationManagerBuilder 在装配用户
protected UserDetailsService userDetailsService() {
AuthenticationManagerBuilder globalAuthBuilder = context
.getBean(AuthenticationManagerBuilder.class);
return new UserDetailsServiceDelegator(Arrays.asList(
localConfigureAuthenticationBldr, globalAuthBuilder));
}
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
// 资源保护
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
// 认证页面
.formLogin().and()
// HTTP Basic authentication.
.httpBasic();
}

上面我们都是通过启动日志的信息来理解应用在启动时到底做了什么,加载了什么关键信息,接下来我们将通过运行时的日志看来看一下我们在认证过程中是如何进行用户名密码的校验的。

登录流程

我们打开浏览器输入localhost:8080 由于我们没有进行登录,所以会被redirecting到登录页面。我们一起过滤一下控制台信息,抓取到关键的信息。

我们看到,这里加载了各种过滤器,当访问/时没发现并没有登录,则重定向到默认的/login页面,这也是spirng security的核心。今天重点讨论登录流程,我们先清空控制台,用正确的用户名和密码登录进去。

从控制台我们可以看到很多的过滤器,我们至关注认证流程的一部分,已上图为准。

1、UsernamePasswordAuthenticationFilter

这理解这个过滤器前,我们先从他的父类AbstractAuthenticationProcessingFilter 入手,既然是过滤器,我们既要入doFilter入手, 这里是关键的流程,子类只是做具体的实现,我们稍后再看

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// 请求的转化
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
} Authentication authResult;
try {
// 关键的认证方法,交由子类来实现,我们到子类看
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
} this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
} if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 返回认证成功
this.successfulAuthentication(request, response, chain, authResult);
}
}

上面的关键方法attemptAuthentication(request, response);UsernamePasswordAuthenticationFilter

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 {
// 通过“username”拿到用户名
String username = this.obtainUsername(request);
// 通过"password" 拿到密码
String password = this.obtainPassword(request);
if (username == null) {
username = "";
} if (password == null) {
password = "";
} username = username.trim();
// 传入UsernamePasswordAuthenticationToken构造方法,此类是Authentication的子类
// 此时还没有认证(false)
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// 交由AuthenticationManager 去处理
return this.getAuthenticationManager().authenticate(authRequest);
}
}

UsernamePasswordAuthenticationFilter 的关键流程中,我们将请求的参数进行符合入参的封装,

2、AuthenticationManager

AuthenticationManager 本身不包含认证逻辑,其核心是用来管理所有的 AuthenticationProvider,通过交由合适的 AuthenticationProvider 来实现认证。

3、AuthenticationProvider

Spring Security 支持多种认证逻辑,每一种认证逻辑的认证方式其实就是一种 AuthenticationProvider。通过 getProviders() 方法就能获取所有的 AuthenticationProvider,通过provider.supports()来判断 provider 是否支持当前的认证逻辑。

当选择好一个合适的 AuthenticationProvider 后,通过 provider.authenticate(authentication) 来让 AuthenticationProvider 进行认证。

public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled(); for (AuthenticationProvider provider : getProviders()) {
// 判断是否是其支持的provider
if (!provider.supports(toTest)) {
continue;
} if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
} try {
// 由具体的provider去进行处理
result = provider.authenticate(authentication); if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
} if (result == null && parent != null) {
// Allow the parent to try.
try {
// 如果还是没有结果,交由父类在处理一次
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
} if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
} // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
} // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
} // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
} throw lastException;
}

4、AbstractUserDetailsAuthenticationProvider

表单登录的 AuthenticationProvider 主要是由 AbstractUserDetailsAuthenticationProvider 来进行处理的,我们来看下它的 authenticate()方法。

public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported")); // Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName(); boolean cacheWasUsed = true;
// 默认从缓存中去,如果没有则调用retrieveUser
UserDetails user = this.userCache.getUserFromCache(username); if (user == null) {
cacheWasUsed = false; try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found"); if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
} Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
} try {
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
// 校验密码等信息
postAuthenticationChecks.check(user);
// 放入缓存
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
} Object principalToReturn = user; if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
// 认证成功后放入认证成功的信息,里面也是放入传入UsernamePasswordAuthenticationToken另一个构造方法
return createSuccessAuthentication(principalToReturn, authentication, user);
}

那么关键的retrieveUser里面是什么样呢?

	protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
// 用具体的UserDetailSercvice去获取用户信息
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}

由于我们的用户信息是在UserDetailsServiceAutoConfiguration 的配置类中生成了 InMemoryUserDetailsManager,所以这里的loadUserByUsername的代码则是这样

	public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails user = users.get(username.toLowerCase()); if (user == null) {
throw new UsernameNotFoundException(username);
} return new User(user.getUsername(), user.getPassword(), user.isEnabled(),
user.isAccountNonExpired(), user.isCredentialsNonExpired(),
user.isAccountNonLocked(), user.getAuthorities());
}

在内存中维护的用户中去获取,那么如果是其他的用户存储则需要对应的获取方式,如果是保存在数据库那么就需要通过sql语句去获取了,感兴趣的可以直接点击JdbcUserDetailsManager查看相关代码。

其实真个认证的流程到这里也就结束了,至于成功或失败后的逻辑最后还是回到了UsernamePasswordAuthenticationFilter中的结果,如果是成功this.successfulAuthentication(request, response, chain, authResult);

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
// 将认证结果放入到上下文中
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
// 后去的跳转等信息
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}

总结

以上便是spring security的认证流程,没想到篇幅会这么长,断点追踪的方式很痛苦,大致方向应该是对的,基本的认证流程也应该浮出水面了。本篇主要是从自动配置的方式出发,后续将展示其他的配置方式甚至自定义认证流程,加油!!!

(完)

认证与授权】Spring Security系列之认证流程解析的更多相关文章

  1. Spring Security OAuth2.0认证授权六:前后端分离下的登录授权

    历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...

  2. Spring Security OAuth2.0认证授权三:使用JWT令牌

    Spring Security OAuth2.0系列文章: Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二: ...

  3. Spring Security OAuth2.0认证授权四:分布式系统认证授权

    Spring Security OAuth2.0认证授权系列文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授 ...

  4. Spring Security OAuth2.0认证授权二:搭建资源服务

    在上一篇文章[Spring Security OAuth2.0认证授权一:框架搭建和认证测试](https://www.cnblogs.com/kuangdaoyizhimei/p/14250374. ...

  5. Spring Security OAuth2.0认证授权五:用户信息扩展到jwt

    历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...

  6. Spring Security 自定义登录认证(二)

    一.前言 本篇文章将讲述Spring Security自定义登录认证校验用户名.密码,自定义密码加密方式,以及在前后端分离的情况下认证失败或成功处理返回json格式数据 温馨小提示:Spring Se ...

  7. [权限管理系统(四)]-spring boot +spring security短信认证+redis整合

    [权限管理系统]spring boot +spring security短信认证+redis整合   现在主流的登录方式主要有 3 种:账号密码登录.短信验证码登录和第三方授权登录,前面一节Sprin ...

  8. spring security 表单认证的流程

    spring security表单认证过程 表单认证过程 Spring security的表单认证过程是由org.springframework.security.web.authentication ...

  9. Springboot集成Spring Security实现JWT认证

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 简介 Spring Security作为成熟且强大的安全框架,得到许多大厂的青睐.而作为前后端分离的SSO方案,JWT ...

随机推荐

  1. Java基础语法(11)-面向对象之关键字

    title: Java基础语法(11)-面向对象之关键字 blog: CSDN data: Java学习路线及视频 1.this this是什么 它在方法内部使用,即这个方法所属对象的引用: clas ...

  2. App压力稳定性测试之Monkey

    一.Monkey简介 Android系统自带monkey程序,模拟用户触摸屏幕.滑动Trackball.按键等操作来对设备上的程序进行压力测试,检测程序多久的时间会发生异常. Monkey的使用是在产 ...

  3. windows找不到文件gpedit.msc处理方法

    新建一个txt,输入 @echo offpushd "%~dp0"dir /b C:\Windows\servicing\Packages\Microsoft-Windows-Gr ...

  4. redis++:Redis的两种持久化 RDB 和 AOF

    Redis持久化备份数据的方式有两种:RDB(Redis DataBase) . AOF(Append Only  File). RDB 什么是RDB: 在指定时间间隔内,将内存中的数据集快照写入磁盘 ...

  5. 【cs224w】Lecture 5 - 谱聚类

    Spectral Clustering 前面的课程说到了 community detection 并介绍了两种算法.这次来说说另外一类做社区聚类的算法,谱聚类.这种算法一般分为三个步骤 pre-pro ...

  6. Python学习-第五节:面向对象

    概念: 核心是“过程”二字,“过程”指的是解决问题的步骤,即先干什么再干什么......,基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式.若程序一开始是要着手解决一个大的问题,面向 ...

  7. 惊呆了!不改一行 Java 代码竟然就能轻松解决敏感信息加解密|原创

    前言 出于安全考虑,现需要将数据库的中敏感信息加密存储到数据库中,但是正常业务交互还是需要使用明文数据,所以查询返回我们还需要经过相应的解密才能返回给调用方. ps:日常开发中,我们要有一定的安全意识 ...

  8. E - E CodeForces - 1100E(拓扑排序 + 二分)

    E - E CodeForces - 1100E 一个n个节点的有向图,节点标号从1到n,存在m条单向边.每条单向边有一个权值,代表翻转其方向所需的代价.求使图变成无环图,其中翻转的最大边权值最小的方 ...

  9. Light of future-冲刺Day 7

    目录 1.SCRUM部分: 每个成员进度 SCRUM 会议的照片 签入记录 代码运行截图 用户浏览界面 订单详情界面 管理员浏览界面 新增后台界面 2.PM 报告: 时间表 燃尽图 任务总量变化曲线 ...

  10. Http协议中Cookie使用详细介绍

    Cookie总是保存在客户端中,按在客户端中的存储位置,可分为内存Cookie和硬盘Cookie.内存Cookie由浏览器维护,保存在内存中,浏览器关闭后就消失了,其存在时间是短暂的.硬盘Cookie ...