Spring Security 登录认证源码
# Spring Security 登录认证源码
# 1. 登录校验流程

# 2. 源码原理初探
SpringSecurity 的核心机制是一个 过滤器链,它内部包含了多个过滤器,分别提供不同的功能。
在下图,我们可以观察到核心过滤器,如下图所示:

核心过滤器功能解析:
- UsernamePasswordAuthenticationFilter
- 负责处理登录页面提交的用户名和密码。
- 入门案例中的认证工作主要由这个过滤器完成。
- ExceptionTranslationFilter
- 用于处理过滤器链中抛出的异常,如
AccessDeniedException和AuthenticationException。
- 用于处理过滤器链中抛出的异常,如
- FilterSecurityInterceptor
- 负责权限校验。
调试方式:
我们可以通过 Debug 模式 查看当前系统中 SpringSecurity 过滤器链的顺序和种类。
执行以下代码可以获取过滤器链中的过滤器:
run.getBean(DefaultSecurityFilterChain.class);
// 通过断点观察过滤器链
2
调试时的过滤器链示例如下:

通过观察,可以清楚了解过滤器链中各个过滤器的顺序和功能。

# 3. 源码认证流程详解
完整的认证流程如下图所示:

# 1. 认证流程概述
Spring Security 登录认证流程是以 过滤器链 为核心,通过一系列组件协作完成用户认证和授权。主要流程如下:
- 用户发送登录请求(默认是
/login,也可以自定义)。 - 请求被过滤器
UsernamePasswordAuthenticationFilter拦截。 - 用户名和密码被封装为认证令牌
UsernamePasswordAuthenticationToken。 - 调用
AuthenticationManager进行认证。 AuthenticationManager委托AuthenticationProvider验证用户身份。AuthenticationProvider调用UserDetailsService加载用户信息。- 验证密码是否匹配。
- 如果认证成功,将用户信息存储到
SecurityContextHolder。 - 如果认证失败,执行失败处理逻辑。
# 2. 登录请求拦截
说明: 当用户提交登录请求时,Spring Security 默认使用 UsernamePasswordAuthenticationFilter 来拦截该请求。
核心源码:
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 1. 判断当前请求是否需要进行认证处理
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
// 2. 提取用户名和密码
String username = obtainUsername(request); // 默认读取 "username" 参数
String password = obtainPassword(request); // 默认读取 "password" 参数
// 3. 封装认证令牌
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(username, password);
// 4. 调用认证管理器进行认证
Authentication authResult;
try {
authResult = this.getAuthenticationManager().authenticate(authRequest);
} catch (AuthenticationException failed) {
// 认证失败处理
unsuccessfulAuthentication(request, response, failed);
return;
}
// 5. 认证成功处理
successfulAuthentication(request, response, chain, authResult);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
详细解析:
- 拦截登录请求:
- 判断当前请求是否需要认证,默认拦截
/login路径的POST请求。 - 可以通过配置
formLogin().loginPage("/customLogin")自定义登录路径。
- 判断当前请求是否需要认证,默认拦截
- 提取用户名和密码:
- 默认从请求体中读取
username和password参数。 - 如果需要自定义参数名,可以重写
obtainUsername和obtainPassword方法。
- 默认从请求体中读取
- 封装认证令牌:
- 使用
UsernamePasswordAuthenticationToken将用户名和密码封装为认证令牌。 - 认证令牌的作用是将用户的认证信息传递到后续的认证流程中。
- 使用
- 调用认证管理器:
- 调用
AuthenticationManager的authenticate方法,开始用户身份验证。
- 调用
- 处理认证结果:
- 如果认证成功,调用
successfulAuthentication。 - 如果认证失败,调用
unsuccessfulAuthentication。
- 如果认证成功,调用
# 默认行为
默认情况下,UsernamePasswordAuthenticationFilter 只处理 /login 的请求,但这个行为是可以通过配置进行更改的。
- 默认请求路径:
UsernamePasswordAuthenticationFilter的默认请求路径是/login,并且仅处理POST方法。- 这个默认路径是 Spring Security 预设的,符合大多数场景下的登录请求需求。
- 认证逻辑:
- 当客户端向
/login提交用户名和密码时,UsernamePasswordAuthenticationFilter会尝试从请求中提取这两个参数,并调用AuthenticationManager进行认证。 - 认证成功后,它会将用户的
Authentication信息存储到SecurityContextHolder,并返回认证成功的响应。
- 当客户端向
- 适配表单登录:
UsernamePasswordAuthenticationFilter默认用于处理基于表单的登录请求。
# 自定义行为
如果需要自定义 UsernamePasswordAuthenticationFilter 的行为,例如更改登录路径或处理其他请求,你可以通过以下方式实现,使用 Spring Security 配置类改变过滤器处理的路径:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginProcessingUrl("/custom-login") // 改为自定义路径
.permitAll();
}
2
3
4
5
6
7
loginProcessingUrl()方法可以更改UsernamePasswordAuthenticationFilter处理的路径。
# 3. 封装认证令牌
说明: 认证令牌是 Spring Security 的核心组件之一,用于封装用户的认证信息。在默认登录认证流程中,UsernamePasswordAuthenticationToken 是最常用的认证令牌。
核心源码:
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal; // 用户名
private Object credentials; // 密码
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false); // 初始状态为未认证
}
}
2
3
4
5
6
7
8
9
10
11
详细解析:
principal:用户标识信息,通常是用户名。credentials:用户凭证,通常是密码。- 初始状态:令牌创建时
setAuthenticated(false),表示未认证状态。
# 4. 调用 AuthenticationManager
说明: AuthenticationManager 是 Spring Security 认证流程的核心接口,其默认实现是 ProviderManager,负责分发认证请求给具体的认证提供者。
核心源码(ProviderManager):
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
AuthenticationException lastException = null;
Authentication result = null;
// 遍历所有注册的 AuthenticationProvider
for (AuthenticationProvider provider : this.providers) {
// 判断是否支持当前认证令牌类型
if (!provider.supports(authentication.getClass())) {
continue;
}
try {
// 调用具体的 AuthenticationProvider 进行认证
result = provider.authenticate(authentication);
if (result != null) {
return result; // 认证成功
}
} catch (AuthenticationException ex) {
lastException = ex;
}
}
// 如果没有任何 Provider 成功认证,抛出异常
if (lastException != null) {
throw lastException;
}
throw new ProviderNotFoundException("No AuthenticationProvider found for " + authentication.getClass().getName());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
详细解析:
- 分发认证请求:
- 遍历所有注册的
AuthenticationProvider,找到支持当前认证令牌的提供者。 
- 默认情况下,Spring Security 会注册
DaoAuthenticationProvider。
- 遍历所有注册的
- 调用认证提供者:
- 调用
AuthenticationProvider.authenticate()方法完成认证。
- 调用
- 认证失败:
- 如果所有提供者都未能成功认证,抛出
AuthenticationException。
- 如果所有提供者都未能成功认证,抛出
# 5. 调用 AuthenticationProvider
说明: AuthenticationProvider 是认证逻辑的核心组件,负责具体的认证实现。 DaoAuthenticationProvider 是 Spring Security 中最常用的认证提供者,适用于基于用户名和密码的认证。

核心源码(AbstractUserDetailsAuthenticationProvider):
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 提取用户名和密码
String username = authentication.getName();
String password = (String) authentication.getCredentials();
// 调用 UserDetailsService 加载用户信息
UserDetails user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
// 验证密码
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
// 认证成功,返回认证后的令牌
return createSuccessAuthentication(authentication, user);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
详细解析:
- 提取用户名和密码:
- 从认证令牌中提取用户名和密码。
- 加载用户信息:
- 调用
UserDetailsService.loadUserByUsername()加载用户信息。
- 调用
- 验证密码:
- 使用
PasswordEncoder.matches()验证用户输入的密码与数据库中存储的密码是否匹配。
- 使用
- 返回认证结果:
- 如果用户名和密码验证成功,返回一个新的认证令牌,标记为已认证。
通过
AbstractUserDetailsAuthenticationProvider下的retrieveUser方法 进入DaoAuthenticationProvider的实现


# 6. 加载用户信息
默认加载用户信息的机制
当没有显式配置 UserDetailsService 时,Spring Security 会默认使用 InMemoryUserDetailsManager 来加载用户信息。InMemoryUserDetailsManager 是一个实现了 UserDetailsService 接口的类,它会将用户信息存储在内存中,适用于小型应用或开发环境。
源码解析:InMemoryUserDetailsManager
InMemoryUserDetailsManager 是 Spring Security 提供的一个简单实现,它将用户信息保存在内存中的一个 Map 里。下面是 InMemoryUserDetailsManager 的关键部分代码:
public class InMemoryUserDetailsManager implements UserDetailsService {
private final Map<String, UserDetails> users = new HashMap<>();
// 默认的构造函数,加载一些内存中的用户
public InMemoryUserDetailsManager(UserDetails... users) {
for (UserDetails user : users) {
this.users.put(user.getUsername(), user);
}
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = users.get(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
return user;
}
// 添加或更新用户
public void createUser(UserDetails user) {
users.put(user.getUsername(), user);
}
// 删除用户
public void deleteUser(String username) {
users.remove(username);
}
// 更新用户
public void updateUser(UserDetails user) {
users.put(user.getUsername(), user);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
如何启用内存用户管理
默认情况下,Spring Security 会自动启用 InMemoryUserDetailsManager,如果你没有配置任何 UserDetailsService,它会使用该类作为用户加载的实现。你可以通过 AuthenticationManagerBuilder 配置用户信息,以下是一个例子:
org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// 配置 HTTP 安全性
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 限制/admin/**路径访问权限
.antMatchers("/user/**").hasRole("USER") // 限制/user/**路径访问权限
.anyRequest().authenticated() // 其他所有路径都需要认证
.and()
.formLogin()
.permitAll(); // 允许所有用户访问登录页面
return http.build();
}
// 配置认证管理器
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder()) // 使用密码加密器
.withUser("user").password("{noop}password").roles("USER") // 定义用户 user 和密码
.and()
.withUser("admin").password("{noop}admin").roles("ADMIN"); // 定义用户 admin 和密码
return authenticationManagerBuilder.build();
}
// 密码加密器配置
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 使用 BCrypt 加密密码
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
- 通过
http.getSharedObject(AuthenticationManagerBuilder.class)获取AuthenticationManagerBuilder实例。 inMemoryAuthentication()表示使用内存中的用户存储。passwordEncoder(passwordEncoder())用于配置密码加密器,这里使用BCryptPasswordEncoder。withUser("user").password("{noop}password").roles("USER")用于创建用户名为user的用户,并指定其密码为password,角色为USER。{noop}表示不使用密码编码器,即密码为明文。- 同样,创建用户名为
admin的用户,并指定其角色为ADMIN。
内存加载的局限性
- 适用场景:
InMemoryUserDetailsManager适用于一些简单场景,比如原型开发、原型测试、或者小型应用的快速启动。在这种情况下,用户数据是硬编码在配置中的,并且存储在 JVM 内存中。 - 数据持久性:使用
InMemoryUserDetailsManager的数据并不会持久化存储。即使应用重启,所有的用户数据都会丢失。对于大规模应用或者生产环境,通常需要使用数据库来存储用户信息。
总结
- 默认行为:Spring Security 默认提供
InMemoryUserDetailsManager来加载用户信息,这意味着在没有显式配置UserDetailsService的情况下,它会在内存中管理用户信息。 - 用法:你可以使用
AuthenticationManagerBuilder来配置内存中的用户,或者在其他地方自定义InMemoryUserDetailsManager。 - 适用场景:适用于快速开发、测试或小型应用,不适用于生产环境。
如果你需要持久化存储用户信息(如从数据库加载用户信息),则可以实现 UserDetailsService 接口,并将其配置到 Spring Security 中。
# 7. 密码校验
说明: Spring Security 提供了 PasswordEncoder 接口,用于对密码进行加密和匹配验证。默认使用 BCryptPasswordEncoder。
源码解析(密码校验):
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
throw new BadCredentialsException("Bad credentials");
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
throw new BadCredentialsException("Bad credentials");
}
}
2
3
4
5
6
7
8
9
10
11
# 8. 认证成功处理
说明: 当用户成功通过认证(例如,用户名和密码验证、JWT 验证等),Spring Security 会将认证信息存储在 SecurityContextHolder 中。SecurityContextHolder 是一个静态容器,它用于存储当前线程中的安全上下文,主要包含 Authentication 对象,该对象存储了当前认证用户的详细信息(如用户名、角色、权限等)。
源码解析:
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult)
throws IOException, ServletException {
// 将认证结果(用户信息)存储到 SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(authResult);
// 调用成功处理器,执行认证成功后的逻辑,如生成JWT、跳转页面等
successHandler.onAuthenticationSuccess(request, response, authResult);
}
2
3
4
5
6
7
8
9
# 如何获取认证信息
认证成功后,Spring Security 会将认证信息存储到 SecurityContextHolder 中。你可以从 SecurityContextHolder 中获取当前用户的认证信息,通常通过 Authentication 对象来访问。
获取 Authentication 对象
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Authentication 对象是当前认证的核心,里面包含了用户的认证信息,如 Principal(用户信息)、Authorities(角色/权限)等。
# 获取认证用户信息
Authentication.getPrincipal() 返回的是当前用户的主体信息,通常是实现了 UserDetails 接口的对象。
// 获取 `Authentication` 对象
public static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
public static LoginUser getLoginUser() {
try {
// 返回当前认证用户的主体信息,通常是 `UserDetails` 的实现类
return (LoginUser) getAuthentication().getPrincipal();
} catch (Exception e) {
// 异常处理,如果获取用户信息失败,抛出自定义异常
throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 认证信息的结构
Authentication 对象是 Spring Security 中的核心接口,包含了用户的各种信息,主要字段如下:
getPrincipal():返回当前认证用户的主体信息,通常是UserDetails的实现类。getAuthorities():返回当前用户的权限(角色)。getCredentials():返回用户的凭据(例如密码),通常认证成功后,凭据就不会再被使用。getDetails():返回与认证相关的附加信息。isAuthenticated():判断当前用户是否已经认证。
# 获取用户的角色和权限
Authentication.getAuthorities() 返回的是用户的权限(或角色)。它是一个 Collection<? extends GrantedAuthority> 类型,通常是用户的角色。
public static Collection<? extends GrantedAuthority> getAuthorities() {
return getAuthentication().getAuthorities();
}
2
3
你可以遍历该集合,获取用户的所有角色或权限。
例如,获取当前用户的所有角色:
public static List<String> getRoles() {
Collection<? extends GrantedAuthority> authorities = getAuthorities();
return authorities.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
}
2
3
4
5
6
# 判断用户是否具有某个角色
你可以通过 Authentication.getAuthorities() 获取当前用户的角色,然后判断用户是否具有某个特定角色。例如:
public static boolean hasRole(String role) {
Collection<? extends GrantedAuthority> authorities = getAuthorities();
return authorities.stream()
.anyMatch(authority -> authority.getAuthority().equals(role));
}
2
3
4
5
# 9. 认证失败处理
说明: 如果认证失败,Spring Security 会调用失败处理器返回错误响应。
源码解析:
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed)
throws IOException, ServletException {
failureHandler.onAuthenticationFailure(request, response, failed);
}
2
3
4
5
# 10. 开发者需要自定义的部分
- 实现
UserDetailsService加载用户信息。 - 配置
PasswordEncoder用于加密和匹配密码。 - 可选:自定义登录页面和认证成功/失败处理逻辑。