springSecurity简介

Spring Security 核心原理与组件详解

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

核心概念解析

主体(Principal)

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

认证(Authentication)

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

授权(Authorization)

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

快速入门依赖

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

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

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

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

核心组件详解

1. SecurityContextHolder

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

存储策略

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

使用示例

// 获取当前认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

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

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

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

2. Authentication

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

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

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

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

默认实现类:ProviderManager

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

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");
    }
}

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

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

4. AuthenticationProvider

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

public interface AuthenticationProvider {
    // 执行认证
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
    
    // 是否支持某种认证类型
    boolean supports(Class<?> authentication);
}

常用实现类:DaoAuthenticationProvider

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

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

核心方法:

// 检查密码等额外认证逻辑
protected void additionalAuthenticationChecks(
    UserDetails userDetails, 
    UsernamePasswordAuthenticationToken authentication) {
    // 密码验证逻辑
}

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

5. UserDetails

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

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

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

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

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

@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

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

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

最常用的实现类是 SimpleGrantedAuthority

认证流程详解

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

1. UsernamePasswordAuthenticationFilter 拦截登录请求

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

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,并重定向到首页
  • 认证失败:清除安全上下文,重定向到登录页并显示错误信息

配置用户名密码

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

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. 后续请求通过安全上下文判断用户身份和权限