0%

springSecurity简介

Spring Security 核心原理与组件详解

Spring Security 是基于 Spring 框架的安全解决方案,专注于为 Java 应用提供身份认证和授权功能。它利用 Spring AOP 和 Servlet 过滤器实现,其核心本质是一个过滤器链,通过一系列过滤器协同工作完成安全控制。

核心概念解析

主体(Principal)

使用系统的用户、设备或其他系统,即 “谁在使用系统”。在 Spring Security 中,主体通常通过 Authentication 对象表示。

认证(Authentication)

确认主体身份的过程,即 “证明你是谁”。认证成功后,用户信息会被存储在安全上下文中。

授权(Authorization)

授予主体操作权限的过程,即 “你能做什么”。通过判断主体是否拥有特定权限来允许或拒绝访问。

快速入门依赖

使用 Spring Boot 集成 Spring Security 只需添加以下依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

添加依赖后,应用会自动启用安全保护:

  • 默认生成一个用户名为 user 的用户
  • 随机生成密码并打印在控制台
  • 所有请求需要认证后才能访问
  • 默认提供一个登录页面

核心组件详解

1. SecurityContextHolder

用于存储当前安全上下文信息的工具类,包含当前登录用户的认证信息。

存储策略

  • MODE_THREADLOCAL:默认策略,使用 ThreadLocal 存储,仅当前线程可见
  • MODE_INHERITABLETHREADLOCAL:子线程可继承父线程的安全上下文
  • MODE_GLOBAL:全局共享一个安全上下文

使用示例

1
2
3
4
5
6
7
8
// 获取当前认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

// 获取当前用户
Object principal = authentication.getPrincipal();

// 判断是否已认证
boolean isAuthenticated = authentication.isAuthenticated();

注意:默认情况下,每次请求结束后,Spring Security 会自动清除当前线程的安全上下文,避免内存泄漏。

2. Authentication

表示认证信息的接口,继承自 java.security.Principal,包含以下核心方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Authentication extends Principal, Serializable {
// 获取权限集合
Collection<? extends GrantedAuthority> getAuthorities();

// 获取凭证(通常是密码,认证后会被清除)
Object getCredentials();

// 获取额外信息(如IP地址、会话ID等)
Object getDetails();

// 获取主体(通常是UserDetails对象)
Object getPrincipal();

// 是否已认证
boolean isAuthenticated();

// 设置认证状态
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

最常用的实现类是 UsernamePasswordAuthenticationToken,用于存储用户名密码认证信息。

3. AuthenticationManager

认证的核心接口,定义了认证方法:

1
2
3
4
public interface AuthenticationManager {
// 认证方法:接收一个未认证的Authentication,返回一个已认证的Authentication
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

默认实现类:ProviderManager

ProviderManager 本身不直接处理认证,而是委托给一组 AuthenticationProvider 实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ProviderManager implements AuthenticationManager {
private List<AuthenticationProvider> providers;

@Override
public Authentication authenticate(Authentication authentication) {
for (AuthenticationProvider provider : providers) {
if (provider.supports(authentication.getClass())) {
return provider.authenticate(authentication);
}
}
throw new ProviderNotFoundException("No provider found");
}
}

在配置类中配置自定义认证提供者:

1
2
3
4
5
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 添加自定义认证提供者
auth.authenticationProvider(customAuthenticationProvider);
}

4. AuthenticationProvider

具体处理认证逻辑的接口,ProviderManager 会遍历所有 AuthenticationProvider 直到找到支持当前认证类型的实现:

1
2
3
4
5
6
7
public interface AuthenticationProvider {
// 执行认证
Authentication authenticate(Authentication authentication) throws AuthenticationException;

// 是否支持某种认证类型
boolean supports(Class<?> authentication);
}

常用实现类:DaoAuthenticationProvider

这是处理用户名密码认证的默认实现,工作流程:

  1. 调用 UserDetailsService 获取用户信息
  2. 验证密码(使用 PasswordEncoder)
  3. 检查用户状态(是否锁定、过期等)

核心方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 检查密码等额外认证逻辑
protected void additionalAuthenticationChecks(
UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) {
// 密码验证逻辑
}

// 获取用户信息
protected UserDetails retrieveUser(
String username,
UsernamePasswordAuthenticationToken authentication) {
// 调用UserDetailsService.loadUserByUsername()
}

5. UserDetails

封装用户详细信息的接口,包含认证和授权所需的用户信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface UserDetails extends Serializable {
// 获取用户权限
Collection<? extends GrantedAuthority> getAuthorities();

// 获取密码(存储的正确密码)
String getPassword();

// 获取用户名
String getUsername();

// 账户是否未过期
boolean isAccountNonExpired();

// 账户是否未锁定
boolean isAccountNonLocked();

// 凭证是否未过期
boolean isCredentialsNonExpired();

// 账户是否启用
boolean isEnabled();
}

Spring Security 提供了默认实现 User,也可自定义实现类扩展用户信息。

6. UserDetailsService

用于加载用户信息的接口,是应用程序与安全框架之间获取用户数据的桥梁:

1
2
3
4
public interface UserDetailsService {
// 根据用户名加载用户信息
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

自定义实现示例(从数据库加载用户):

1
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
@Service
public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库查询用户
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户不存在"));

// 转换为UserDetails返回
return User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities(getAuthorities(user.getRoles()))
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.enabled(true)
.build();
}

private Collection<? extends GrantedAuthority> getAuthorities(List<String> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
}

7. GrantedAuthority

表示用户权限的接口,通常通过角色或权限字符串表示:

1
2
3
4
public interface GrantedAuthority extends Serializable {
// 返回权限字符串(如"ROLE_ADMIN"、"USER_CREATE")
String getAuthority();
}

最常用的实现类是 SimpleGrantedAuthority

认证流程详解

以表单登录为例,Spring Security 的认证流程如下:

1. UsernamePasswordAuthenticationFilter 拦截登录请求

该过滤器专门处理 /login 路径的 POST 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 仅允许POST方法
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("不支持的认证方法: " + request.getMethod());
}

// 获取用户名和密码
String username = obtainUsername(request);
String password = obtainPassword(request);

// 封装为未认证的Authentication对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);

// 设置额外信息(如IP地址、会话ID)
setDetails(request, authRequest);

// 委托给AuthenticationManager进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}

2. AuthenticationManager 处理认证

ProviderManager 会找到支持 UsernamePasswordAuthenticationTokenDaoAuthenticationProvider 进行处理。

3. DaoAuthenticationProvider 执行认证逻辑

  1. 调用 UserDetailsService.loadUserByUsername() 获取用户信息
  2. 验证密码(使用 PasswordEncoder
  3. 检查用户状态(是否锁定、过期等)
  4. 生成已认证的 Authentication 对象

4. 认证结果处理

  • 认证成功:将 Authentication 存入 SecurityContextHolder,并重定向到首页
  • 认证失败:清除安全上下文,重定向到登录页并显示错误信息

配置用户名密码

除了使用默认用户,还可以通过配置文件自定义:

1
2
3
4
5
6
spring:
security:
user:
name: admin
password: 123456
roles: ADMIN

在实际应用中,通常会使用自定义 UserDetailsService 从数据库加载用户信息,如前面的示例所示。

总结

Spring Security 的核心工作流程可以概括为:

  1. 过滤器拦截请求,获取用户提交的认证信息
  2. 将认证信息封装为 Authentication 对象
  3. AuthenticationManager 委托 AuthenticationProvider 进行认证
  4. AuthenticationProvider 通过 UserDetailsService 获取用户信息并验证
  5. 认证成功后,将 Authentication 存入 SecurityContextHolder
  6. 后续请求通过安全上下文判断用户身份和权限

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10