Spring Security:认证、授权与过滤器链

FreeGuideOnline 最新 2026-06-17

认识 Spring Security:保护你的应用

在构建 Web 应用时,安全是永远绕不开的话题。Spring Security 是 Spring 家族中负责认证(Authentication)与授权(Authorization)的框架,同时完美融入了 Servlet 过滤器链(Filter Chain)机制。它可以帮助你轻松实现“谁可以访问”和“能访问什么”的控制。

本教程将从零开始,带你理解 Spring Security 的核心概念,并逐步搭建一个带有安全防护的 Spring Boot 应用。


环境准备与第一个安全项目

快速起步:引入依赖

创建一个标准的 Spring Boot + Spring Web 项目,并加入 Spring Security 起步依赖:

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

如果你使用的是 Spring Initializr,直接勾选 Spring Security 即可。

启动应用后,Spring Security 会自动配置一套默认的安全规则:所有请求都需要认证,并为你生成一个随机密码(控制台会打印 Using generated security password: ...),默认用户名为 user。访问任何 URL 都会跳转到默认的登录页面。

核心架构初探:过滤器链

在 Servlet 容器中,一个请求会经过一系列过滤器(Filter)的处理,然后才到达 Servlet。Spring Security 正是通过过滤器链来介入安全控制的。

最重要的一个过滤器是 FilterChainProxy,它内部维护了一个由多个安全过滤器组成的链条。这些过滤器各自负责不同的职责,例如:

  • SecurityContextPersistenceFilter:负责在请求之间持久化安全上下文。
  • UsernamePasswordAuthenticationFilter:处理表单登录的认证请求。
  • ExceptionTranslationFilter:将安全相关异常转换为合适的 HTTP 响应(如重定向到登录页)。
  • FilterSecurityInterceptor:执行最终的授权判断,决定是否允许访问。

你可以这样理解:过滤器链就是一道道安检门,只有持有有效“门票”(认证信息)且符合“入场规则”(授权)的请求才能到达最终目的地。


深入认证(Authentication)

认证要回答的核心问题是:“你是谁?

认证的基本流程

  1. 用户提供凭证(用户名 / 密码)。
  2. 系统将凭证封装为 Authentication 对象(通常未认证)。
  3. AuthenticationManager 委托给一个或多个 AuthenticationProvider 进行实际校验。
  4. 校验通过,返回一个已认证的 Authentication 对象(包含权限信息),存储到 SecurityContextHolder 中。

在 Spring Security 中,SecurityContextHolder 默认使用 ThreadLocal 存储,因此在同一线程的任何地方都可以获取当前用户信息。

示例:基于内存的认证配置

为了快速验证,我们可以使用基于内存的用户存储。新建一个配置类:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public UserDetailsService users() {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("alice")
            .password("12345")
            .roles("USER")
            .build();

        UserDetails admin = User.withDefaultPasswordEncoder()
            .username("bob")
            .password("admin123")
            .roles("ADMIN", "USER")
            .build();

        return new InMemoryUserDetailsManager(user, admin);
    }
}

注意withDefaultPasswordEncoder() 仅适合演示或测试,生产环境必须使用 BCryptPasswordEncoder 等方式加密密码。

现在重启应用,就可以使用 alicebob 的账号登录了。

获取当前登录用户

在控制器中,可以通过多种方式获取当前用户:

@GetMapping("/me")
public String currentUser(Authentication authentication) {
    return "当前用户: " + authentication.getName();
}

// 或者使用 @AuthenticationPrincipal 注解
@GetMapping("/me2")
public String currentUser(@AuthenticationPrincipal UserDetails userDetails) {
    return "当前用户: " + userDetails.getUsername();
}

核心授权(Authorization)

授权回答了问题:“你能做什么?

角色与权限的声明

在基于内存的配置中,我们通过 .roles("USER") 定义了角色。Spring Security 中的角色通常以 ROLE_ 为前缀,因此 roles("USER") 实际存储的权限是 ROLE_USER

授权可以基于角色,也可以基于更细粒度的权限(如 READ, WRITE)。使用 .authorities("READ_PROFILE") 可以设置自定义权限字符串。

示例:基于 URL 的访问控制

修改 SecurityConfig,添加授权规则:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/", "/public/**").permitAll()      // 公开访问
            .requestMatchers("/user/**").hasRole("USER")          // 需要 USER 角色
            .requestMatchers("/admin/**").hasRole("ADMIN")        // 需要 ADMIN 角色
            .anyRequest().authenticated()                        // 其他都需要认证
        )
        .formLogin(Customizer.withDefaults())                    // 开启表单登录
        .logout(Customizer.withDefaults());                      // 开启登出

    return http.build();
}
  • permitAll():完全开放。
  • hasRole():检查 ROLE_ 前缀的角色。
  • authenticated():只需要认证,不检查角色。

现在访问 /admin 时,如果登录用户是 alice(只有 USER 角色)会被拒绝(通常是 403 拒绝访问);而 bob 可以正常访问。

方法级安全:@PreAuthorize

除了基于 URL 的规则,Spring Security 还支持在方法上直接声明安全约束。首先,在配置类上添加 @EnableMethodSecurity 注解:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
    // ...
}

然后在服务层或控制器方法上使用注解:

@RestController
public class AdminController {

    @GetMapping("/admin/dashboard")
    @PreAuthorize("hasRole('ADMIN')")
    public String adminDashboard() {
        return "管理后台仪表盘";
    }

    @GetMapping("/user/profile")
    @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
    public String userProfile() {
        return "用户个人信息";
    }
}

该方法级安全非常灵活,支持 Spring EL 表达式,可访问方法的参数等。


密码加密与用户存储进阶

BCryptPasswordEncoder

在生产环境中,密码必须加密存储。Spring Security 推荐使用 BCryptPasswordEncoder

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

然后在创建用户时使用该编码器加密密码。例如:

@Bean
public UserDetailsService users(PasswordEncoder encoder) {
    UserDetails user = User.builder()
        .username("alice")
        .password(encoder.encode("12345"))
        .roles("USER")
        .build();
    // ...
}

数据库用户认证

真实的应用通常从数据库加载用户。你需要实现 UserDetailsService 接口,并注入你的数据源。Spring Security 会自动使用它来匹配用户。

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) 
            throws UsernameNotFoundException {
        UserEntity entity = userRepository.findByUsername(username);
        if (entity == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        // 将实体转换为 Spring Security 的 UserDetails
        return org.springframework.security.core.userdetails.User
            .withUsername(entity.getUsername())
            .password(entity.getPassword())
            .roles(entity.getRoles().split(","))
            .build();
    }
}

然后配置你的 AuthenticationProvider 或直接暴露该 UserDetailsService 即可。


自定义过滤器:深入过滤器链

有时默认的过滤器链不能满足需求,我们可以添加自己的过滤器。比如在请求中提取自定义 Token 并完成认证。

创建自定义过滤器

public class MyApiKeyFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        String apiKey = request.getHeader("X-API-Key");
        if (apiKey != null && apiKey.equals("my-secret-key")) {
            // 构建认证对象,并设置为已认证
            Authentication auth = new UsernamePasswordAuthenticationToken(
                "api-user", null, List.of(new SimpleGrantedAuthority("ROLE_USER"))
            );
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        filterChain.doFilter(request, response); // 一定要继续调用链
    }
}

将过滤器插入到指定位置

SecurityFilterChain 配置中,使用 addFilterBeforeaddFilterAfter 将自定义过滤器注册到默认的过滤器链中:

http.addFilterBefore(new MyApiKeyFilter(), UsernamePasswordAuthenticationFilter.class);

这样 MyApiKeyFilter 就会在表单登录过滤器之前执行。


总结

Spring Security 的强大之处在于它对过滤器链、认证和授权进行了高度抽象,同时又保持了极大的灵活性。本教程覆盖了:

  • 过滤器链核心架构
  • 认证流程与内存用户
  • 基于 URL 和方法级的授权控制
  • 密码加密与用户服务扩展
  • 自定义过滤器

掌握这些内容后,你就能够构建出安全性较高的 Spring Boot 应用。下一步建议深入学习 OAuth2、JWT 以及 CSRF/CORS 等高级主题,进一步完善你的安全知识体系。