Article

身份验证(安全服务)Spring Security

更新于:2026-07-14

第一章:Spring Security 入门基础

1.1 什么是 Spring Security(概念)

概念名称说明注意事项
Spring SecuritySpring 生态中用于为 Java 应用提供全面安全服务的框架,支持认证(Authentication)和授权(Authorization)功能,适用于基于 Spring 的 Web 应用和方法级安全控制。是 Spring 官方项目,与 Spring Boot、Spring MVC 深度集成;不仅限于 Web 层,也可用于方法调用、域对象安全等场景
认证(Authentication)确认用户身份的过程,即”你是谁”。例如通过用户名密码登录系统。是访问受保护资源的前提步骤;成功后会生成一个包含用户信息的 Authentication 对象
授权(Authorization)在用户身份确认后,判断其是否有权限执行某项操作或访问某个资源,即”你能做什么”。基于角色(Role)或权限(Authority)进行控制;可在 URL、方法、视图等多个层级进行配置
过滤器链(Filter Chain)Spring Security 通过一组 Servlet Filter 构成的安全过滤器链来拦截请求并执行安全逻辑。默认包含十几种过滤器,如认证、授权、CSRF 防护等;请求必须通过整个链才能访问目标资源

1.2 Spring Security 的核心功能与优势

概念名称说明注意事项
统一安全模型提供一致的认证与授权机制,支持多种安全协议和模式。核心接口如 AuthenticationManager、UserDetailsService 具有高度可扩展性;支持自定义实现以适配不同业务需求
Web 请求安全可对 HTTP 请求路径进行细粒度访问控制,支持基于表达式的权限判断。使用 HttpSecurity DSL 进行配置;支持 antMatcher、mvcMatcher 等多种匹配方式
方法级安全支持在服务层方法上添加安全注解(如 @PreAuthorize),实现更精细的权限控制。需启用 @EnableMethodSecurity 或 @EnableGlobalMethodSecurity;适用于复杂业务逻辑中的权限校验
防御常见攻击内置对 CSRF、CORS、Session Fixation、Clickjacking 等常见安全威胁的防护机制。多数防护默认开启(如 CSRF 在表单登录时自动启用);可根据需要显式关闭或调整策略
多种认证方式支持支持表单登录、HTTP Basic、OAuth2、JWT、LDAP、Remember-Me 等多种认证方式。可组合使用多种认证机制;扩展性强,支持自定义 AuthenticationProvider
与 Spring 生态无缝集成与 Spring Boot、Spring MVC、Spring Data、Spring Cloud 等组件天然兼容。使用 starter 可快速引入并自动配置;支持条件化配置(@ConditionalOnMissingBean)

1.3 安全框架对比:Spring Security vs Shiro

对比维度Spring SecurityApache Shiro
所属生态Spring 官方项目,深度集成 Spring/Spring Boot独立框架,不依赖任何特定生态
学习曲线较陡峭,概念抽象,配置复杂相对简单直观,API 易懂
功能丰富性功能极其全面,支持 OAuth2、JWT、SAML、LDAP、OpenID Connect 等核心功能完整,但高级协议支持较弱
方法级安全原生支持 @PreAuthorize 等 SpEL 表达式注解支持注解,但表达式能力有限
过滤器机制基于 Servlet Filter,与 Spring Web 深度绑定同样基于 Filter,但更轻量,可用于非 Spring 环境
配置方式Java Config / Lambda DSL / XML,推荐使用 Java 配置Ini 文件 / Java API,灵活性高
社区与维护活跃,持续更新,紧跟 Spring 发展社区相对较小,更新频率较低
适用场景Spring/Spring Boot 项目,尤其是微服务架构老旧系统、非 Spring 项目、小型应用

1.4 环境搭建与基本依赖配置(Maven/Gradle)

名称语法用途代码示例注意事项
spring-boot-starter-securityMaven: spring-boot-starter-security / Gradle: implementation 'org.springframework.boot:spring-boot-starter-security'引入 Spring Security 核心模块,自动配置默认安全行为见下方代码块使用 Spring Boot 时只需此依赖即可启用安全功能;自动保护所有端点,要求用户登录
spring-security-web<groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId>提供 Web 层安全支持,如 Filter、SecurityContextPersistenceFilter 等见下方代码块非 Spring Boot 项目需手动引入;包含核心 Web 安全过滤器
spring-security-config<groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId>提供安全配置支持,如 @EnableWebSecurity、Java Config DSL见下方代码块必须引入才能使用 Java 配置类;包含 AuthenticationManagerBuilder 等配置工具

Maven 依赖:

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

<!-- 非 Spring Boot 项目需额外引入 -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>6.2.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>6.2.0</version>
</dependency>

Gradle 依赖:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-security'
}

1.5 第一个 Spring Security 应用:默认安全配置体验

名称语法用途代码示例注意事项
@EnableWebSecurity@EnableWebSecurity启用 Spring Security 的 Web 安全配置,注册默认过滤器链见下方代码块在 Spring Boot 中通常不需要显式添加,因为自动配置已包含;若自定义配置类,建议使用
SecurityFilterChain Bean@Bean SecurityFilterChain filterChain(HttpSecurity http)定义请求过滤规则和安全行为,替代旧版 WebSecurityConfigurerAdapter见下方代码块Spring Security 6 推荐方式;必须定义至少一个 SecurityFilterChain Bean 来避免默认锁定
默认用户名user当未配置用户时,Spring Boot 自动生成一个随机密码并输出到控制台,用户名固定为 “user”控制台输出:Using generated security password: 8a2d7b1e-5f3c-4d8a-b9c0-...仅用于开发测试;生产环境必须自定义用户管理
默认登录页/loginSpring Security 自动生成的登录页面路径访问 /hello(受保护资源)→ 跳转至 /login页面由框架内置生成;可通过 formLogin().loginPage("/custom-login") 自定义
默认登出/logout默认登出端点,发送 POST 请求即可退出登录发送 POST 请求到 /logout必须是 POST 请求(防止 CSRF 滥用);可通过 logout().logoutUrl("/sign-out") 修改
自动生成密码N/A应用启动时,若无用户配置,控制台打印随机生成的临时密码Using generated security password: abcdef123456每次重启都会变化;仅适用于开发环境

基本安全配置示例(Spring Security 6 推荐方式):

@Configuration
@EnableWebSecurity
public class DefaultSecurityConfig {

}

@Configuration
@EnableWebSecurity
public class BasicSecurityConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .formLogin(Customizer.withDefaults());
        return http.build();
    }
}

第二章:认证(Authentication)机制详解

2.1 认证流程核心组件:AuthenticationManager、ProviderManager、AuthenticationProvider

名称语法用途代码示例注意事项
AuthenticationManagerAuthentication authenticate(Authentication authentication) throws AuthenticationException认证管理顶层接口,负责接收认证请求并返回认证结果见下方代码块是认证流程的入口;实际常用实现是 ProviderManager
ProviderManagerpublic class ProviderManager implements AuthenticationManager { ... }AuthenticationManager 的默认实现,委托给多个 AuthenticationProvider 进行认证默认由 Spring Security 自动配置,通常无需手动创建实例支持多个 AuthenticationProvider,按顺序尝试;可设置 eraseCredentialsAfterAuthentication 控制凭证清除行为
AuthenticationProviderpublic interface AuthenticationProvider { ... }提供具体的认证逻辑实现,如用户名密码、LDAP、OAuth2 等见下方代码块必须实现 supports() 方法以声明支持的认证类型;若认证失败应抛出对应 AuthenticationException

AuthenticationManager Bean 配置:

@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return http.getSharedObject(AuthenticationManagerBuilder.class)
        .build();
}

自定义 AuthenticationProvider:

public class CustomAuthenticationProvider implements AuthenticationProvider {
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

    @Override
    public Authentication authenticate(Authentication authentication) {
        // 自定义认证逻辑
        return new UsernamePasswordAuthenticationToken(...);
    }
}

2.2 用户详情服务:UserDetailsService 与 InMemoryUserDetailsManager

名称语法用途代码示例注意事项
UserDetailsServiceUserDetails loadUserByUsername(String username) throws UsernameNotFoundException定义加载用户信息的标准接口,用于认证时获取用户凭证见下方代码块是认证流程中获取用户信息的关键接口;方法名易误导:实际可基于邮箱、手机号等非用户名字段查询
InMemoryUserDetailsManagerpublic class InMemoryUserDetailsManager implements UserDetailsService, UserDetailsManager { ... }基于内存存储的 UserDetailsService 实现,用于测试或简单场景见下方代码块仅适用于开发/测试环境;密码明文存储不安全,生产环境禁用

自定义 UserDetailsService:

@Service
public class CustomUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) {
        // 从数据库加载用户
        throw new UsernameNotFoundException("User not found");
    }
}

InMemoryUserDetailsManager 配置:

@Bean
public UserDetailsService userDetailsService() {
    User.UserBuilder users = User.withDefaultPasswordEncoder();
    return new InMemoryUserDetailsManager(
        users.username("user").password("password").roles("USER").build(),
        users.username("admin").password("admin").roles("ADMIN").build()
    );
}

2.3 自定义用户信息加载:实现 UserDetailsService 接口

loadUserByUsername 是加载用户详细信息的核心方法,被 AuthenticationProvider 调用。

实现示例(集成数据库):

@Service
public class DatabaseUserDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
        UserEntity user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));

        return org.springframework.security.core.userdetails.User
            .builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .authorities(user.getRoles().split(","))
            .accountExpired(!user.isActive())
            .credentialsExpired(false)
            .disabled(!user.isActive())
            .build();
    }
}

注意事项: 必须处理用户不存在情况,抛出 UsernameNotFoundException;可集成 JPA、MyBatis 等持久层框架。

2.4 用户凭证模型:UserDetails 与 GrantedAuthority

名称说明代码示例注意事项
UserDetails表示用户的完整安全信息,包含权限、状态等见下方代码块所有 getter 方法都会被认证流程检查;任一状态为 false 将导致认证失败
GrantedAuthority表示用户所拥有的权限或角色,通常以字符串形式返回(如 “ROLE_ADMIN”)SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");角色建议以 “ROLE_” 开头;权限可自定义命名,如 “user:read”、“order:write”
User (builder)Spring Security 提供的 UserDetails 实现类,支持链式构建见下方代码块生产环境可自定义实现 UserDetails;builder() 方式更安全(自动编码密码)

UserDetails 接口定义:

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    String getPassword();
    String getUsername();
    boolean isAccountNonExpired();
    boolean isAccountNonLocked();
    boolean isCredentialsNonExpired();
    boolean isEnabled();
}

使用 Builder 构建 User 实例:

User.builder()
    .username("john")
    .password("$2a$10$...")
    .authorities("ROLE_USER", "read")
    .accountExpired(false)
    .accountLocked(false)
    .credentialsExpired(false)
    .disabled(false)
    .build();

2.5 密码编码器:PasswordEncoder 接口与常用实现

名称说明代码示例注意事项
PasswordEncoder定义密码编码与验证的标准接口见下方代码块必须注册为 Bean 供框架使用;不应使用 NoOpPasswordEncoder(明文存储)
BCryptPasswordEncoder基于 BCrypt 算法的强哈希编码器,推荐使用见下方代码块每次编码结果不同(因盐值随机);支持 strength 参数控制计算强度(默认 10)
NoOpPasswordEncoder明文编码器,不进行任何加密处理NoOpPasswordEncoder.getInstance()已废弃,仅用于测试;生产环境绝对禁止使用
DelegatingPasswordEncoder支持多种编码算法的代理编码器,能识别前缀(如 {bcrypt}{noop})自动选择策略自动配置Spring Security 5+ 默认使用;提升迁移和兼容性

PasswordEncoder Bean 配置:

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

使用示例:

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encoded = encoder.encode("password");
boolean matches = encoder.matches("password", encoded);

2.6 认证异常处理:AuthenticationException 及其子类

名称说明注意事项
AuthenticationException认证失败的顶层异常,所有认证相关异常的父类必须在认证流程中捕获处理;可用于自定义异常响应
BadCredentialsException用户名或密码错误最常见的认证异常;建议统一处理避免暴露具体错误
UsernameNotFoundException用户名不存在由 UserDetailsService 抛出;实际应返回通用错误防止用户名探测
AccountExpiredException用户账户已过期由 UserDetails.isAccountNonExpired() 返回 false 触发;可引导用户联系管理员
LockedException用户账户被锁定多次失败后可手动或自动锁定;可结合失败次数计数器实现
CredentialsExpiredException用户凭证(密码)已过期,需更换强制密码定期更换策略的实现基础;可跳转至修改密码页面
DisabledException用户账户被禁用用于软删除或临时停用用户;与锁定不同,禁用通常是长期状态

第三章:授权(Authorization)控制

3.1 基于角色与权限的访问控制:hasRole()、hasAuthority() 等表达式

名称用途代码示例注意事项
hasRole()判断用户是否具有指定角色,自动添加 “ROLE_” 前缀hasRole('ADMIN')参数无需写 “ROLE_“;实际检查的是 “ROLE_ADMIN”
hasAnyRole()判断用户是否具有任意一个指定角色hasAnyRole('USER', 'ADMIN')多角色用逗号分隔;满足其一即可访问
hasAuthority()判断用户是否具有指定权限(精确匹配)hasAuthority('read')不自动添加前缀;区分大小写
hasAnyAuthority()判断用户是否具有任意一个指定权限hasAnyAuthority('read', 'write')权限列表可动态配置;常用于细粒度权限控制
permitAll()允许所有用户访问,无需认证permitAll()用于静态资源、登录页等公开路径
denyAll()拒绝所有用户访问denyAll()调试或临时关闭功能时使用

HttpSecurity 配置示例:

http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/admin/**").hasRole("ADMIN")
    .requestMatchers("/dashboard").hasAnyRole("USER", "ADMIN")
    .requestMatchers("/api/data").hasAuthority("data:read")
    .requestMatchers("/api/edit").hasAnyAuthority("post:write", "comment:write")
    .requestMatchers("/login", "/css/**").permitAll()
    .requestMatchers("/private").denyAll()
    .anyRequest().authenticated()
);

3.2 方法级安全注解:@PreAuthorize、@PostAuthorize、@Secured

名称用途代码示例注意事项
@PreAuthorize在方法执行前进行权限检查@PreAuthorize("hasRole('ADMIN')")使用 SpEL 表达式;支持方法参数引用(如 #id
@PostAuthorize在方法执行后进行权限检查,可用于基于返回值的授权@PostAuthorize("returnObject.owner == authentication.name")性能开销大,因方法已执行;少用,主要用于数据过滤
@Secured基于角色的简单方法安全控制@Secured("ROLE_ADMIN")不支持 SpEL;角色必须带 “ROLE_” 前缀
@PreFilter在方法执行前对集合参数进行过滤@PreFilter("filterObject.owner == authentication.name")filterObject 代表集合中每个元素;用于参数预处理
@PostFilter在方法执行后对返回的集合进行过滤@PostFilter("filterObject.owner == authentication.name")filterObject 代表返回集合中每个元素;常用于多租户数据隔离

使用示例:

@Service
public class UserService {

    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(Long id) { ... }

    @PostAuthorize("returnObject.user == authentication.principal")
    public Document getDocument(Long id) { ... }

    @Secured("ROLE_USER")
    public void userOperation() { ... }

    @Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
    public void adminOrManager() { ... }

    public void processOrders(@PreFilter("filterObject.status == 'ACTIVE'") Collection orders) { ... }

    @PostFilter("hasRole('USER') or filterObject.public")
    public List getAllDocuments() { ... }
}

3.3 启用方法安全:@EnableMethodSecurity 与 @EnableGlobalMethodSecurity

名称用途代码示例注意事项
@EnableMethodSecuritySpring Security 6+ 推荐的方式启用方法级安全@EnableMethodSecurity默认启用 @PreAuthorize、@PostAuthorize、@Secured;支持 JSR-250 注解(需设置 jsr250Enabled = true
@EnableGlobalMethodSecurity旧版方式(Spring Security 5.x),仍可用@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)prePostEnabled: 启用 @PreAuthorize / @PostAuthorize;securedEnabled: 启用 @Secured;jsr250Enabled: 启用 @RolesAllowed

配置示例:

// Spring Security 6+ 推荐
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig { }

// Spring Security 5.x 旧版
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig { }

3.4 安全表达式语言(SpEL)在授权中的应用

名称用途代码示例注意事项
authentication获取当前 Authentication 对象@PreAuthorize("authentication.principal.username == #username")可访问 principal、credentials、authorities 等属性
principal获取当前用户主体(通常是 UserDetails)@PreAuthorize("principal.username == #username")等价于 authentication.principal
hasRole()SpEL 内置表达式,检查角色@PreAuthorize("hasRole('ADMIN')")自动处理 “ROLE_” 前缀
hasAnyRole()检查是否具有任意一个角色@PreAuthorize("hasAnyRole('USER', 'ADMIN')")参数为字符串数组
hasAuthority()检查权限@PreAuthorize("hasAuthority('order:write')")精确匹配权限字符串
hasAnyAuthority()检查是否具有任意一个权限@PreAuthorize("hasAnyAuthority('data:read', 'data:write')")常用于细粒度控制
#parameter引用方法参数@PreAuthorize("#id == authentication.principal.id")参数名需与方法定义一致;支持复杂表达式
T(Class)调用静态方法或访问常量@PreAuthorize("hasRole(T(com.example.Role).ADMIN.name())")用于动态引用类常量

参数引用示例:

@PreAuthorize("#id == authentication.principal.id")
public User getUser(Long id) { ... }

3.5 匿名用户与未认证访问控制(概念)

概念名称说明注意事项
匿名认证(Anonymous Authentication)Spring Security 会为未登录用户创建一个 AnonymousAuthenticationToken,使其在安全上下文中仍有一个 Authentication 对象默认启用;角色为 ROLE_ANONYMOUS;便于统一处理认证/未认证逻辑
permitAll允许所有用户(包括匿名用户)访问该资源常用于登录页、注册页、静态资源;配置在 HttpSecurity 中
isAnonymous()SpEL 表达式,判断当前用户是否为匿名用户可用于条件判断:hasRole('USER') or isAnonymous();与 permitAll 配合使用实现开放策略
未认证用户(Unauthenticated)指尚未通过任何认证流程的用户,但在 Spring Security 中会被自动赋予匿名身份实际不存在”完全无身份”状态;所有请求都有 Authentication 对象
匿名用户限制匿名用户通常权限极低,只能访问公开资源不能访问需要 ROLE_USER 或更高权限的接口;可通过 remember-me 提升为持久化认证

第四章:Web 安全配置详解

4.1 HttpSecurity 配置结构与常用方法

名称语法用途代码示例注意事项
HttpSecurityHttpSecurity httpSpring Security Web 安全配置的核心 DSL 对象,用于构建安全规则链见下方代码块必须调用 build() 返回 SecurityFilterChain Bean;配置顺序不影响执行顺序(由过滤器链决定)
csrf()http.csrf(Customizer)配置 CSRF(跨站请求伪造)防护策略http.csrf(csrf -> csrf.disable());默认启用(表单登录时);前后端分离常设为 disable(配合 JWT)
authorizeHttpRequestshttp.authorizeHttpRequests(Customizer)配置基于 HTTP 请求的访问控制规则(Spring Security 6 推荐)见下方代码块替代旧版 authorizeRequests();必须至少配置一条规则
formLoginhttp.formLogin(Customizer)配置基于表单的登录机制见下方代码块默认生成 /login 页面;可自定义登录页、成功/失败处理
logouthttp.logout(Customizer)配置登出行为http.logout(logout -> logout.logoutUrl("/sign-out").permitAll());默认 POST /logout;可清除 Cookie、Session、CSRF Token 等

基础配置示例:

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/public/**").permitAll()
            .anyRequest().authenticated())
        .formLogin(form -> form
            .loginPage("/login")
            .permitAll());
    return http.build();
}

CSRF 配置(启用状态下的 Cookie 存储):

http.csrf(csrf -> csrf
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));

4.2 URL 路径权限控制:authorizeHttpRequests / authorizeRequests

名称语法用途代码示例注意事项
requestMatchersrequestMatchers(String...)匹配指定路径模式的请求见下方代码块支持 Ant 风格路径;多个模式可用可变参数
mvcMatchermvcMatcher(String)基于 Spring MVC 的路径匹配,支持上下文路径http.authorizeHttpRequests(auth -> auth.mvcMatcher("/user/{id}").access(new WebExpressionAuthorizationManager("hasRole('USER')")))更精确,考虑 DispatcherType;推荐用于 MVC 应用
servletPathservletPath(String)指定 Servlet 的路径前缀(如 /app)见下方代码块影响所有后续匹配规则;与 contextPath 不同
anyRequestanyRequest()匹配所有未被前面规则覆盖的请求见下方代码块必须放在最后;用于兜底策略

完整路径控制示例:

http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/admin/**").hasRole("ADMIN")
    .requestMatchers("/api/**").authenticated()
    .requestMatchers("/css/**", "/js/**").permitAll()
    .anyRequest().denyAll()
);

servletPath 配置示例:

http.servletPath("/app")
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/dashboard").authenticated());
// 实际匹配 /app/dashboard

4.3 表单登录配置:formLogin 与登录流程控制

名称语法用途代码示例注意事项
loginPageform.loginPage(String)指定自定义登录页面路径form.loginPage("/login.html")页面需暴露为公开路径;默认 /login 会被覆盖
loginProcessingUrlform.loginProcessingUrl(String)指定表单提交的目标 URL(认证处理端点)form.loginProcessingUrl("/authenticate")默认为 /login(POST);需与前端表单 action 一致
defaultSuccessUrlform.defaultSuccessUrl(String, boolean)登录成功后默认跳转路径form.defaultSuccessUrl("/home", true)第二个参数 force 表示是否始终跳转(忽略原请求)
successHandlerform.successHandler(AuthenticationSuccessHandler)自定义登录成功处理器form.successHandler(new CustomSuccessHandler())可实现重定向、JSON 响应等;覆盖 defaultSuccessUrl
failureHandlerform.failureHandler(AuthenticationFailureHandler)自定义登录失败处理器form.failureHandler(new CustomFailureHandler())可返回错误码、记录日志等
usernameParameterform.usernameParameter(String)自定义用户名表单字段名form.usernameParameter("email")默认为 “username”;需与前端 input name 一致
passwordParameterform.passwordParameter(String)自定义密码表单字段名form.passwordParameter("pwd")默认为 “password”

4.4 登出机制配置:logout 与登出处理器

名称语法用途代码示例注意事项
logoutUrllogout.logoutUrl(String)自定义登出请求的 URLhttp.logout(logout -> logout.logoutUrl("/sign-out"))默认为 /logout(POST);可配置多个 URL
logoutSuccessUrllogout.logoutSuccessUrl(String)登出成功后跳转的 URLhttp.logout(logout -> logout.logoutSuccessUrl("/login?logout"))默认跳转到登录页
logoutSuccessHandlerlogout.logoutSuccessHandler(LogoutSuccessHandler)自定义登出成功处理器http.logout(logout -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))可返回 JSON、重定向等;覆盖 logoutSuccessUrl
invalidateHttpSessionlogout.invalidateHttpSession(boolean)是否使 HttpSession 失效http.logout(logout -> logout.invalidateHttpSession(true))默认 true;会清除会话数据
deleteCookieslogout.deleteCookies(String...)登出时删除指定 Cookiehttp.logout(logout -> logout.deleteCookies("JSESSIONID", "remember-me"))常用于清除 remember-me Cookie
clearAuthenticationlogout.clearAuthentication(boolean)是否清除 SecurityContext 中的 Authentication默认 true设为 false 则保留认证信息(少见)

4.5 CSRF 防护机制与配置

名称语法用途代码示例注意事项
disablecsrf.disable()禁用 CSRF 防护http.csrf(csrf -> csrf.disable())常用于 REST API + JWT 场景;仅在可信环境使用
csrfTokenRepositorycsrf.csrfTokenRepository(CsrfTokenRepository)配置 CSRF Token 存储策略http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))默认使用 HttpSession;Cookie 方式便于前端读取
requireCsrfProtectionMatchercsrf.requireCsrfProtectionMatcher(RequestMatcher)自定义哪些请求需要 CSRF 保护http.csrf(csrf -> csrf.requireCsrfProtectionMatcher(new MediaTypeRequestMatcher(...)))默认保护非 GET/HEAD/TRACE/OPTIONS 请求
sessionAuthenticationStrategy通过其他机制配置防止 Session Fixation,常与 CSRF 联动http.sessionManagement(session -> session.sessionAuthenticationStrategy(new ChangeSessionIdAuthenticationStrategy()))changeSessionId() 可增强安全性

4.6 CORS 配置与跨域安全策略

名称语法用途代码示例注意事项
corshttp.cors(Customizer)启用并配置 CORS 策略http.cors(Customizer.withDefaults())需配合 @Bean CorsConfigurationSource 使用
CorsConfigurationSource@Bean CorsConfigurationSource提供 CORS 配置源见下方代码块必须注册为 Bean;allowCredentials = true 时 origin 不能为 ”*“
setAllowedOriginsconfig.setAllowedOrigins(List)设置允许的来源config.setAllowedOrigins(Arrays.asList("https://example.com"))推荐具体域名,避免 ”*“;配合 allowCredentials 使用需精确
setAllowedOriginPatternsconfig.setAllowedOriginPatterns(List)设置允许的来源模式(支持 ”*” 通配)config.setAllowedOriginPatterns(Arrays.asList("https://*.example.com"))Spring 5.3+ 支持;更灵活的跨域控制

CORS 配置示例:

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOriginPatterns(Arrays.asList("*"));
    config.setAllowedMethods(Arrays.asList("GET", "POST"));
    config.setAllowedHeaders(Arrays.asList("*"));
    config.setAllowCredentials(true);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
}

4.7 安全响应头配置:X-Frame-Options、HSTS 等

名称语法用途代码示例注意事项
frameOptionshttp.headers(headers -> headers.frameOptions(fo -> fo.disable()))配置 X-Frame-Options 头headers.frameOptions(fo -> fo.sameOrigin())默认 DENY,防止点击劫持;sameOrigin 允许同源嵌套
hstsheaders.httpStrictTransportSecurity(hsts -> ...)启用 HSTS(HTTP 严格传输安全)见下方代码块强制浏览器使用 HTTPS;配置后不可逆,慎用
contentTypeOptionsheaders.contentTypeOptions(Customizer)启用 X-Content-Type-Options 防 MIME 欺骗headers.contentTypeOptions(Customizer.withDefaults())默认启用,设为 nosniff
xssProtectionheaders.xssProtection(Customizer)配置 X-XSS-Protection 头headers.xssProtection(xss -> xss.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))浏览器 XSS 过滤器;新版浏览器已弃用
contentSecurityPolicyheaders.contentSecurityPolicy(csp -> ...)配置 CSP(内容安全策略)headers.contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'; script-src 'self'"))强大的前端安全控制;配置复杂,需逐步调试

HSTS 配置示例:

headers.httpStrictTransportSecurity(hsts -> hsts
    .maxAgeInSeconds(31536000)
    .includeSubdomains(true));

4.8 会话管理:SessionManagementConfigurer 与并发控制

名称语法用途代码示例注意事项
sessionManagementhttp.sessionManagement(Customizer)配置会话管理策略见下方代码块控制会话创建、失效、并发等行为
maximumSessionssession.maximumSessions(int)设置单用户最大并发会话数session.maximumSessions(1)超出限制时阻止新登录或踢出旧会话
maxSessionsPreventsLoginmaxSessionsPreventsLogin(boolean)达到最大会话数时是否阻止新登录true 阻止新登录 / false 踢出最早会话默认 false
expiredUrlexpiredUrl(String)会话过期后跳转的 URLsession.expiredUrl("/login?expired")仅当并发控制启用时有效
invalidSessionUrlinvalidSessionUrl(String)无效会话(如 ID 不存在)时跳转 URLsession.invalidSessionUrl("/login")与 expiredUrl 不同
sessionCreationPolicysession.sessionCreationPolicy(SessionCreationPolicy)控制会话创建策略session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)STATELESS:不创建会话(JWT 场景);ALWAYS:总是创建

会话管理配置示例:

http.sessionManagement(session -> session
    .invalidSessionUrl("/login?expired")
    .maximumSessions(1)
    .maxSessionsPreventsLogin(true)
    .expiredUrl("/login?expired"));

第五章:高级认证机制

5.1 基于数据库的认证:整合 JPA/MyBatis 实现 UserDetailsService

名称用途代码示例注意事项
UserRepository使用 JPA 访问用户数据见下方代码块需确保 findByUsername 查询高效(加索引)
MyBatis Mapper使用 MyBatis 访问用户数据见下方代码块SQL 需防止注入;结果映射正确
PasswordEncoder注入密码编码器用于验证encoder.matches(rawPassword, user.getPassword())必须使用编码器验证,不可明文比较

JPA 方式:

@Repository
interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

@Service
public class DatabaseUserDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("..."));
        return buildUserDetails(user);
    }
}

MyBatis 方式:

@Mapper
public interface UserMapper {
    User selectByUsername(String username);
}

// 在 UserDetailsService 中使用
public UserDetails loadUserByUsername(String username) {
    User user = userMapper.selectByUsername(username);
    if (user == null) throw new UsernameNotFoundException("...");
    return User.builder()...build();
}

密码验证:

if (!encoder.matches(rawPassword, user.getPassword())) {
    throw new BadCredentialsException("Invalid password");
}

5.2 JWT 认证原理与流程(概念)

概念名称说明注意事项
JWT(JSON Web Token)一种开放标准(RFC 7519),用于在各方之间安全传输信息作为 JSON 对象,通常用于认证和信息交换包含 Header、Payload、Signature 三部分;可被签名(HMAC/RS256)和/或加密
无状态认证服务器不保存会话状态,用户信息和权限包含在 Token 中,每次请求携带 Token 即可适合分布式、微服务架构;Token 一旦签发无法主动失效(需配合黑名单)
Token 签发流程用户登录 → 服务验证凭据 → 生成 JWT → 返回客户端通常在 /login 端点完成;Token 存储在 localStorage 或 Cookie
Token 验证流程客户端请求携带 Token(通常在 Authorization: Bearer <token>)→ 服务解析并验证签名 → 提取用户信息 → 设置 SecurityContext需自定义过滤器拦截请求;验证签名和过期时间
JWT 缺点无法主动注销、Token 较长、Payload 信息可能泄露可通过短期 Token + Refresh Token 缓解;敏感信息不应放入 Payload

5.3 自定义 JWT 过滤器:JwtAuthenticationFilter 实现

名称语法用途代码示例注意事项
OncePerRequestFilterpublic class JwtAuthenticationFilter extends OncePerRequestFilter确保过滤器在每次请求中只执行一次见下方代码块推荐继承此类
doFilterInternalprotected void doFilterInternal(...)执行过滤逻辑见下方代码块必须调用 filterChain.doFilter() 继续流程;捕获异常避免中断
Bearer Token 解析request.getHeader("Authorization")从请求头提取 JWT Token见下方代码块注意空格和前缀
SecurityContextHolderSecurityContextHolder.getContext().setAuthentication(auth)将认证信息放入安全上下文见下方代码块后续过滤器和控制器可获取用户信息

完整 JWT 过滤器实现:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain filterChain) {
        String token = extractToken(request);
        if (token != null && jwtUtil.validate(token)) {
            String username = jwtUtil.getUsername(token);
            UsernamePasswordAuthenticationToken auth =
                new UsernamePasswordAuthenticationToken(...);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        filterChain.doFilter(request, response);
    }

    private String extractToken(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }
}

5.4 OAuth2 协议核心角色与四种授权模式(概念)

概念名称说明注意事项
Resource Owner资源所有者,即用户,授权客户端访问其资源通常是最终用户
Client客户端应用,请求访问用户资源如 Web 应用、移动 App
Authorization Server认证服务器,负责颁发访问令牌如 Google、GitHub、自建 Auth Server
Resource Server资源服务器,存储用户数据并接受带 Token 的请求如用户 API、订单系统
授权码模式(Authorization Code)最安全的模式,客户端获取授权码后换取 Token适用于有后端的应用;支持 PKCE 增强安全
简化模式(Implicit)前端应用直接获取 Token(通过 URL fragment)已不推荐,因 Token 暴露在 URL
密码模式(Resource Owner Password Credentials)用户直接提供用户名密码给客户端,客户端换取 Token仅适用于高度信任的客户端;Spring Security 6 已弃用
客户端模式(Client Credentials)客户端以自身身份获取 Token,不涉及用户用于服务间通信;无用户上下文

5.5 OAuth2 客户端集成:spring-security-oauth2-client

名称语法用途代码示例注意事项
spring-security-oauth2-clientMaven 依赖提供 OAuth2 客户端支持见下方代码块用于实现第三方登录
application.yml 配置spring.security.oauth2.client.registration.google配置 OAuth2 客户端注册信息见下方代码块支持 google、github、facebook 等;可自定义 provider
OAuth2AuthorizedClientService@Autowired OAuth2AuthorizedClientService获取已授权的客户端信息OAuth2AuthorizedClient client = clientService.loadAuthorizedClient("google", "username")可获取 access_token
@RegisteredOAuth2AuthorizedClient@RegisteredOAuth2AuthorizedClient("google")注入已授权客户端见下方代码块简化 Token 获取

Maven 依赖:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-client</artifactId>
</dependency>

application.yml 配置:

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: your-client-id
            client-secret: your-secret
            scope: profile,email

控制器中获取 Token:

public String home(@RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient client) {
    String token = client.getAccessToken().getTokenValue();
    // ...
}

5.6 OAuth2 资源服务器配置:JWT Token 验证

名称语法用途代码示例注意事项
spring-security-oauth2-resource-serverMaven 依赖启用 OAuth2 资源服务器功能见下方代码块用于验证 JWT Token
issuer-urispring.security.oauth2.resourceserver.jwt.issuer-uri指定 JWT 签发者 URI,自动获取公钥见下方代码块需支持 .well-known/jwks.json
jwk-set-urispring.security.oauth2.resourceserver.jwt.jwk-set-uri指定 JWK Set URI 手动配置公钥地址jwk-set-uri: https://idp.example.com/oauth2/jwks用于无法使用 issuer-uri 的场景
JwtDecoder Bean@Bean自定义 JwtDecoderreturn NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build()可完全控制解码逻辑

Maven 依赖:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>

application.yml 配置:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://idp.example.com

5.7 单点登录(SSO)基础实现原理(概念)

概念名称说明注意事项
SSO(Single Sign-On)用户一次登录,即可访问多个相互信任的系统,无需重复认证提升用户体验;常见于企业应用集成
中央认证服务器(CAS)负责统一处理用户认证请求,生成票据(Ticket)如 Keycloak、Auth0、自建系统;所有客户端信任该服务器
服务票据(Service Ticket)CAS 发放的一次性票据,客户端用其换取用户信息防重放攻击;有效期短
登录流程用户访问 App1 → 重定向至 CAS → 用户登录 → CAS 返回 ST → App1 验证 ST 获取用户信息 → 登录成功 → 访问 App2 时自动登录基于重定向和票据交换;首次登录需输入凭证
会话共享多种实现方式:共享 Cookie(同域)、Token 传递、OAuth2/OpenID ConnectOpenID Connect 是现代 SSO 主流方案;基于 JWT 实现身份传递

第六章:自定义安全组件

6.1 登录成功处理器:AuthenticationSuccessHandler

名称语法用途代码示例注意事项
AuthenticationSuccessHandler接口定义登录成功后的处理逻辑,用于自定义跳转、记录日志、返回 JSON 等public class CustomSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
// 重定向或写入 JSON 响应
response.sendRedirect("/dashboard");
}
}
- 必须实现 onAuthenticationSuccess 方法
- 不应调用 filterChain.doFilter()
SimpleUrlAuthenticationSuccessHandlerSpring 提供的默认实现,支持配置成功跳转 URL@Bean
AuthenticationSuccessHandler successHandler() {
SimpleUrlAuthenticationSuccessHandler handler =
new SimpleUrlAuthenticationSuccessHandler("/home");
handler.setAlwaysUseDefaultTargetUrl(true);
return handler;
}
- 可设置 defaultTargetUrl 和 alwaysUseDefault
- 适用于简单重定向场景
SavedRequestAwareAuthenticationSuccessHandler继承 SimpleUrlAuthenticationHandler,支持跳转到用户原始请求地址@Bean
AuthenticationSuccessHandler successHandler() {
return new SavedRequestAwareAuthenticationSuccessHandler();
}
- 默认行为:登录后跳转到引发登录的页面
- 适合需要保持用户上下文的场景

6.2 登录失败处理器:AuthenticationFailureHandler

名称语法用途代码示例注意事项
AuthenticationFailureHandler接口定义认证失败后的处理逻辑,如返回错误信息、记录失败次数public class CustomFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("{\"error\": \"" + exception.getMessage() + "\"}");
}
}
- 可根据 exception 类型区分错误(如账户锁定、密码错误)
- 通常用于 REST API 返回 JSON 错误
SimpleUrlAuthenticationFailureHandlerSpring 提供的默认实现,登录失败后重定向到指定页面@Bean
AuthenticationFailureHandler failureHandler() {
return new SimpleUrlAuthenticationFailureHandler("/login?error");
}
- 适用于表单登录失败跳转
- 可结合 request.getParameter(“username”) 显示错误信息
异常类型判断exception instanceof BadCredentialsException根据不同异常类型执行不同逻辑if (exception instanceof LockedException) {
// 账户已锁定
} else if (exception instanceof DisabledException) {
// 账户被禁用
}
- 常见异常:BadCredentialsException, LockedException, DisabledException, AccountExpiredException

6.3 访问拒绝处理器:AccessDeniedHandler

名称语法用途代码示例注意事项
AccessDeniedHandler接口处理授权失败(403 Forbidden)的情况,如权限不足public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().write("{\"error\": \"Access Denied\"}");
}
}
- 仅在已认证用户访问无权限资源时触发
- 未认证访问受保护资源由 AuthenticationEntryPoint 处理
SimpleAccessDeniedHandlerSpring 提供的简单实现,仅设置状态码new SimpleAccessDeniedHandler();- 默认返回 403 状态码
- 无自定义响应内容
配置方式http.exceptionHandling(e -> e.accessDeniedHandler(...))在 HttpSecurity 中注册处理器http.exceptionHandling(e ->
e.accessDeniedHandler(new CustomAccessDeniedHandler()));
- 必须通过 exceptionHandling() 配置
- 可与 authenticationEntryPoint 配合使用

6.4 自定义过滤器:实现 Filter 并注册到过滤器链

名称语法用途代码示例注意事项
Filter 接口public class CustomFilter implements Filter创建自定义过滤器,拦截请求并执行安全逻辑public class CustomFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 前置处理
chain.doFilter(request, response);
// 后置处理
}
}
- 必须调用 chain.doFilter() 继续流程
- 可修改请求/响应
OncePerRequestFilterpublic class CustomOnceFilter extends OncePerRequestFilter确保过滤器在单次请求中只执行一次,推荐使用public class CustomOnceFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ... {
// 业务逻辑
filterChain.doFilter(request, response);
}
}
- 避免在异步请求中重复执行
- Spring Security 内部多数过滤器继承此类
注册为 Bean@Component 或 @Bean将过滤器注册为 Spring Bean@Component
public class CustomFilter { ... }
- 仅注册为 Bean 不会自动加入安全链

6.5 替换或插入过滤器:addFilterBefore、addFilterAfter、addFilterAt

名称语法用途代码示例注意事项
addFilterBeforehttp.addFilterBefore(filter, TargetFilter.class)在指定过滤器之前添加自定义过滤器http.addFilterBefore(new JwtAuthenticationFilter(jwtUtil),
UsernamePasswordAuthenticationFilter.class);
- TargetFilter 必须是安全链中存在的过滤器
- 常用于在认证前插入 JWT 解析
addFilterAfterhttp.addFilterAfter(filter, TargetFilter.class)在指定过滤器之后添加自定义过滤器http.addFilterAfter(new AuditFilter(),
FilterSecurityInterceptor.class);
- 适用于审计、日志等后置操作
- 注意目标过滤器是否存在
addFilterAthttp.addFilterAt(filter, TargetFilter.class)替换指定位置的过滤器(不推荐,易出错)不常用,可能破坏默认流程- 实际很少使用,因会替换原有过滤器
- 更推荐 addFilterBefore/After
常见过滤器顺序UsernamePasswordAuthenticationFilter → DefaultLoginPageGeneratingFilter → LogoutFilter → BasicAuthenticationFilter → FilterSecurityInterceptor了解默认过滤器顺序有助于定位插入点查看 SecurityFilterChain 的 getFilters() 方法- 插入点选择影响执行顺序和结果

6.6 安全上下文管理:SecurityContext 与 SecurityContextHolder

名称语法用途代码示例注意事项
SecurityContextSecurityContextHolder.getContext()存储当前安全上下文,包含 Authentication 对象SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context.getAuthentication();
- 每个请求独享一个上下文(由 SecurityContextPersistenceFilter 管理)
SecurityContextHolder静态工具类,用于访问 SecurityContextString username = SecurityContextHolder.getContext()
.getAuthentication().getName();
- 默认使用 ThreadLocal 策略(MODE_THREADLOCAL)
- 异步时需注意上下文传递
Authenticationcontext.getAuthentication()表示当前用户的认证信息,包含 principal、credentials、authorities 等Object principal = auth.getPrincipal();
// 通常是 UserDetails 对象
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
- principal:用户主体(UserDetails 或用户名)
- credentials:凭证(登录后通常设为 null)
- authorities:权限列表
设置认证信息context.setAuthentication(auth)手动设置认证信息(如 JWT 过滤器中)UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authToken);
- 通常在自定义认证过滤器中使用
- 需确保 UserDetails 已加载
策略模式SecurityContextHolder.setStrategyName(...)设置上下文存储策略SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLE_THREADLOCAL);- MODE_THREADLOCAL:默认,基于线程
- MODE_GLOBAL:JVM 全局(不推荐)

第七章:安全最佳实践与调优

7.1 多环境安全配置管理(dev/test/prod)

名称语法用途代码示例注意事项
Profile 配置文件application-{profile}.yml为不同环境提供独立的安全配置application-dev.yml:
spring.security.oauth2.client.registration.github.client-secret: dev-secret
logging.level.org.springframework.security: DEBUG

application-prod.yml:
spring.security.oauth2.client.registration.github.client-secret: ${GITHUB_CLIENT_SECRET}
security.headers.hsts: all
- 敏感配置使用环境变量占位符 ${}
- 开发环境可禁用 CSRF、开启调试日志
@Profile 注解@Profile("prod")条件化注册安全 Bean@Configuration
@Profile("prod")
public class ProductionSecurityConfig {
@Bean
public SecurityFilterChain prodFilterChain(HttpSecurity http) throws Exception {
http.headers().hsts(hsts -> hsts.maxAgeInSeconds(31536000));
return http.build();
}
}
- 避免生产环境 Bean 在开发时加载
- 可组合使用 @Profile(“!dev”)
ConditionalOnProperty@ConditionalOnProperty根据配置属性条件化启用配置@Bean
@ConditionalOnProperty(name = "app.security.mfa.enabled", havingValue = "true")
public MfaAuthenticationFilter mfaFilter() { ... }
- 实现功能开关(Feature Toggle)
- 便于灰度发布
ConfigurationProperties@ConfigurationProperties("app.security")类型安全地绑定安全配置@ConfigurationProperties("app.security")
public class SecurityProperties {
private boolean csrfEnabled = true;
private List<String> allowedOrigins = Arrays.asList("https://example.com");
// getters and setters
}
- 集中管理配置项
- 支持嵌套对象和列表

7.2 敏感信息加密与配置保护

名称语法用途代码示例注意事项
Jasypt 加密jasypt.encryptor.password对配置文件中的敏感值进行加解密application.yml:
spring:
datasource:
password: ENC(ABCD1234...) # 加密后的密码

pom.xml:
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
</dependency>
- 启动时需提供主密码(jasypt.encryptor.password)
- 加密值格式为 ENC(cipherText)
环境变量${DB_PASSWORD}从操作系统环境变量读取敏感信息spring.datasource.password: ${DB_PASSWORD}- 部署时通过 export DB_PASSWORD=… 设置
- CI/CD 流水线中配置为 secret
Hashicorp Vaultspring.cloud.vault.*集中式密钥管理服务spring.cloud.vault:
host: vault.example.com
scheme: https
authentication: TOKEN
token: ${VAULT_TOKEN}
- 需引入 spring-cloud-starter-vault-config
- 适用于大规模微服务架构
AWS KMS / Azure Key Vault云服务商密钥管理利用云平台安全服务保护密钥使用云 SDK 或 Spring Cloud AWS/Azure 模块集成- 高安全性,支持审计和轮换
- 成本较高,适合关键系统

7.3 安全事件监听:AuthenticationSuccessEvent 等

名称语法用途代码示例注意事项
ApplicationListenerimplements ApplicationListener<AuthenticationSuccessEvent>监听认证成功事件@Component
public class AuthSuccessEventListener implements ApplicationListener<AuthenticationSuccessEvent> {
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
String username = event.getAuthentication().getName();
log.info("User {} logged in successfully", username);
}
}
- 可用于记录登录日志、更新用户状态
@EventListener@EventListener基于注解的事件监听(推荐)@EventListener
public void handleAuthenticationSuccess(AuthenticationSuccessEvent event) {
// 处理逻辑
}

@EventListener
public void handleAuthenticationFailure(AuthenticationFailureEvent event) {
String username = event.getAuthentication().getName();
loginAttemptService.recordFailure(username);
}
- 更简洁,支持条件过滤 @EventListener(condition = “#event.authentication.authorities.?[…].size() > 0”)
常见安全事件AuthenticationSuccessEvent / AuthenticationFailureBadCredentialsEvent / InteractiveAuthenticationSuccessEvent / SessionDestroyedEvent监听关键安全事件@EventListener
public void onSessionDestroyed(SessionDestroyedEvent event) {
// 清理与会话相关的缓存或资源
}
- AuthenticationFailureEvent 有多种子类型(如 BadCredentials, LockedException)
- 事件在事务提交后发布(默认)
异步处理@Async异步执行耗时监听逻辑@EventListener
@Async
public void handleLoginSuccess(AuthenticationSuccessEvent event) {
// 发送通知、更新统计等
}
- 需启用 @EnableAsync
- 避免阻塞主请求流程

7.4 用户详情缓存优化策略

名称语法用途代码示例注意事项
CachingUserDetailsServicenew CachingUserDetailsService(delegate)包装 UserDetailsService 实现缓存@Bean
public UserDetailsService userDetailsService(UserDetailsService delegate) {
return new CachingUserDetailsService(delegate);
}
- 使用 Spring 默认 UserCache(基于 ConcurrentMap)
- 缓存 UserDetails 对象
自定义 UserCacheuserCache.setUserCache(...)替换默认缓存实现为 Redis 等@Bean
public UserCache userCache(RedisConnectionFactory factory) {
RedisCache redisCache = new RedisCache(factory, "user-cache", Duration.ofMinutes(30));
return new SpringCacheUserCache(redisCache);
}
- 需引入 spring-boot-starter-data-redis
- 实现分布式缓存
缓存失效策略userCache.removeUserFromCache(username)手动清除缓存(如用户信息更新后)@Service
public class UserService {
@Autowired
private UserCache userCache;
public void updateUser(String username, UserUpdateForm form) {
// 更新数据库
userCache.removeUserFromCache(username); // 清除旧缓存
}
}
- 避免缓存与数据库不一致
- 可结合事件机制自动失效
缓存粒度缓存整个 UserDetails 对象减少数据库查询次数UserDetails userDetails = userCache.getUserFromCache(username);
if (userDetails == null) {
userDetails = delegate.loadUserByUsername(username);
userCache.putUserInCache(userDetails);
}
- 适合用户信息不频繁变更的场景
- 注意内存占用

7.5 常见安全漏洞防范:CSRF、XSS、Session Fixation

漏洞类型防范措施配置示例注意事项
CSRF(跨站请求伪造)启用 CSRF 保护,使用 Token 验证http.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));
// 前端从 Cookie 读取 XSRF-TOKEN 并设置为请求头 X-XSRF-TOKEN
- 表单登录必须启用
- REST API 使用 JWT 时可禁用
XSS(跨站脚本)输出编码、CSP 策略、输入过滤http.headers(headers -> headers
.xssProtection(xss -> xss.headerValue(ENABLED_MODE_BLOCK))
.contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'; script-src 'self'"))
);
- 避免在页面拼接用户输入
- 使用 Thymeleaf 等模板引擎自动转义
Session Fixation(会话固定)登录后更换 Session IDhttp.sessionManagement(session -> session
.sessionAuthenticationStrategy(new ChangeSessionIdAuthenticationStrategy())
);
- 默认策略为 migrateSession()(Servlet 3.1+)
- 可防止攻击者预设 Session ID
会话劫持使用安全 Cookie、HTTPS、HttpOnlyhttp.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/login")
)
.securityContext(sc -> sc.requireAllSecureCookies(true));
- secure=true:仅 HTTPS 传输
- httpOnly=true:JS 无法访问
敏感操作二次验证对关键操作(如改密、支付)要求重新认证自定义过滤器或 AOP 拦截,检查 Authentication 时间戳或要求重新输入密码- 提升安全性
- 影响用户体验,需权衡

第八章:Spring Security 6 与响应式编程

8.1 Spring Security 6 主要变更与新特性(概念)

名称语法用途代码示例注意事项
Lambda DSLhttp.authorizeHttpRequests(authz -> {...})新的函数式配置风格,替代旧的链式调用@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/public/**").permitAll()
.anyExchange().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
- 更清晰的代码结构
- 需使用 ServerHttpSecurity(响应式)或 HttpSecurity(阻塞)
Security 6 默认策略收紧内置默认安全增强提高默认安全性,减少配置遗漏风险无需额外代码,框架自动启用- 如:CSRF 默认启用
- 需注意迁移旧项目时的兼容性
OAuth2 Authorization Server 支持spring-security-oauth2-authorization-server内建 OAuth2 授权服务器功能引入 spring-security-oauth2-authorization-server 依赖- 不再依赖 Spring Security OAuth
- 支持 PKCE、JWT 等标准
CSRF 改进CsrfTokenRequestAttributeHandler更灵活的 CSRF Token 管理http.csrf(csrf -> csrf
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
);
- 更好支持 REST API 和单页应用(SPA)

8.2 Lambda DSL 配置方式:新风格安全配置

名称语法用途代码示例注意事项
Lambda 配置块authorizeHttpRequests(authz -> {...})使用 Lambda 表达式配置安全规则http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/css/**", "/js/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(login -> login
.loginPage("/login")
.permitAll()
);
- 推荐用于 Spring Security 6+
- 可读性更强,作用域更清晰
方法引用与命名变量formLogin(CustomConfig::loginConfig)提高配置复用性和可测试性private static void loginConfig(FormLoginSpec login) {
login.loginPage("/login").permitAll();
}

http.formLogin(YourClass::loginConfig);
- 适合复杂配置抽取
- 便于单元测试
条件化配置if (env.isProd()) { ... }根据环境动态配置安全策略http.authorizeHttpRequests(authz -> {
authz.requestMatchers("/dev/**");
if (!isProduction) {
authz.permitAll();
} else {
authz.hasRole("DEV");
}
});
- 避免硬编码环境判断
- 推荐使用 @Profile 替代

8.3 响应式安全:WebFlux 与 Spring Security 集成

名称语法用途代码示例注意事项
ServerHttpSecurityServerHttpSecurity响应式安全配置主类(类比 HttpSecurity)@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.anyExchange().authenticated()
)
.httpBasic(withDefaults());
return http.build();
}
- 必须返回 SecurityWebFilterChain
- 使用 @EnableWebFluxSecurity
Reactor Context 与 SecurityContextMono.subscriberContext()在响应式流中访问安全上下文public Mono<String> getCurrentUsername() {
return Mono.subscriberContext()
.map(ctx -> {
Authentication auth = ctx.get(SecurityContext.class).getAuthentication();
return auth.getName();
});
}
- 不再使用 ThreadLocal
- 上下文通过 ContextView 传递
响应式认证入口点ServerAuthenticationEntryPoint处理未认证请求的响应http.exceptionHandling(e -> e
.authenticationEntryPoint((exchange, ex) ->
Mono.fromRunnable(() -> {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
})
)
);
- 返回 Mono<Void>
- 适用于 REST API 返回 JSON
响应式授权决策ReactiveAuthorizationManager响应式权限判断.authorizeExchange(exchanges -> exchanges
.pathMatchers("/api/**")
.access((auth, context) ->
auth.map(a -> a.getAuthorities())
.map(au -> au.contains(new SimpleGrantedAuthority("USER")))
.defaultIfEmpty(false)
)
)
- 使用 Mono<Boolean> 进行判断
- 支持异步权限查询

8.4 响应式用户详情服务:ReactiveUserDetailsService

名称语法用途代码示例注意事项
ReactiveUserDetailsService接口响应式加载用户详情,返回 Mono<UserDetails>@Component
public class MongoReactiveUserDetailsService implements ReactiveUserDetailsService {
@Autowired
private ReactiveUserRepository userRepository;
@Override
public Mono<UserDetails> findByUsername(String username) {
return userRepository.findByUsername(username)
.switchIfEmpty(Mono.error(new UsernameNotFoundException("User not found")))
.map(user -> User.withUsername(user.getUsername())
.password(user.getPassword())
.authorities("USER")
.build());
}
}
- 适合与 MongoDB、R2DBC 等响应式数据源集成
- 避免阻塞线程
InMemoryReactiveUserDetailsManager内存式响应式用户管理器(测试用)@Bean
public ReactiveUserDetailsService reactiveUserDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryReactiveUserDetailsManager(user);
}
- 仅用于演示或测试
- 密码编码器使用需注意安全
响应式密码编码ReactivePasswordEncoder响应式密码验证(较少直接使用)框架自动集成 PasswordEncoder 到响应式流程- 通常仍使用 BCryptPasswordEncoder
- 框架负责适配
与 WebClient 集成在 WebClient 请求中携带认证信息调用下游服务时传递安全上下文webClient.get()
.uri("/api/data")
.attributes(SecurityContextServerWebExchangeWebFilter.
securityContext(Mono.just(securityContext)))
.retrieve()
.bodyToMono(String.class);
- 实现服务间安全上下文传播
- 适用于微服务场景

第九章:实战项目集成

9.1 REST API 安全设计:无状态 JWT 方案

名称语法用途代码示例注意事项
JWT 生成Jwts.builder().signWith().compact()创建签名的 JWT TokenString token = Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getAuthorities())
.setExpiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
- 使用强密钥(HS512 或 RS256)
- 避免在 payload 中存储敏感信息
JWT 解析与验证Jwts.parser().setSigningKey().parseClaimsJws()验证 Token 并提取用户信息try {
Jws<Claims> claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token);
String username = claims.getBody().getSubject();
} catch (JwtException e) {
throw new BadCredentialsException("Invalid JWT");
}
- 捕获 JwtException 处理无效 Token
- 验证过期时间、签名等
JWT 过滤器OncePerRequestFilter在请求链中拦截并验证 JWTpublic class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
String token = extractToken(request);
if (token != null && jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsername(token);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null, getAuthorities(username));
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
- 必须在 UsernamePasswordAuthenticationFilter 之前插入
- 支持 Bearer 前缀
安全配置禁用 SessionsessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)确保应用无状态http
.csrf().disable()
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authz -> authz
.requestMatchers("/auth/login", "/auth/refresh").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthenticationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
- 禁用 CSRF(若使用 JWT)
- 不创建 HttpSession

9.2 前后端分离登录:Token 刷新与存储策略

名称语法用途代码示例注意事项
Refresh Token长生命周期 Token用于获取新的 Access Token,避免频繁登录public class TokenResponse {
private String accessToken;
private String refreshToken;
// 构造函数、getter/setter
}

// 登录成功返回
return ResponseEntity.ok(new TokenResponse(accessToken, refreshToken));
- Refresh Token 应存储在安全位置(如 HttpOnly Cookie)
- 设置较短过期时间(如 7 天)
Token 刷新端点/auth/refresh接收 Refresh Token 并返回新 Access Token@PostMapping("/refresh")
public ResponseEntity<?> refreshToken(@RequestBody RefreshRequest request) {
if (refreshTokenService.validate(request.getRefreshToken())) {
String username = refreshTokenService.getUsername(request.getRefreshToken());
String newAccessToken = jwtUtil.generateToken(username);
return ResponseEntity.ok(new TokenResponse(newAccessToken, request.getRefreshToken()));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
- 验证 Refresh Token 有效性
- 可实现”一次使用即失效”策略
存储策略:HttpOnly Cookieresponse.addCookie()安全存储 Refresh Token,防范 XSSCookie cookie = new Cookie("refreshToken", refreshToken);
cookie.setHttpOnly(true);
cookie.setSecure(true); // HTTPS
cookie.setPath("/auth/refresh");
cookie.setMaxAge(604800); // 7 days
response.addCookie(cookie);
- 前端 JS 无法访问,防止 XSS 窃取
- 需配合 SameSite 属性
存储策略:LocalStoragelocalStorage.setItem("token", token)前端存储 Access Token(仅限 SPA)// 登录后
localStorage.setItem("accessToken", response.data.accessToken);
// 请求拦截器
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
- 易受 XSS 攻击
- 适合低安全要求场景
CORS 配置@CrossOrigin 或 CorsConfiguration允许前端域名访问后端 API@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(Arrays.asList("https://yourapp.com"));
config.setAllowedMethods(Arrays.asList("*"));
config.setAllowedHeaders(Arrays.asList("*"));
config.setExposedHeaders(Arrays.asList("Authorization"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
- allowCredentials=true 时 origin 不能为 *
- 暴露 Authorization 头供前端读取

9.3 多租户系统中的权限隔离

名称语法用途代码示例注意事项
租户标识解析请求头、子域名、路径识别当前请求所属租户String tenantId = request.getHeader("X-Tenant-ID");
if (tenantId == null) {
tenantId = resolveFromSubdomain(request);
}
SecurityContextHolder.getContext().setTenantId(tenantId); // 自定义扩展
- 常见方式:Header、Host、URL Path
- 需在过滤器早期解析
数据库隔离策略Schema Per Tenant / Row Level数据存储层面的隔离- 独立 Schema:每个租户独立数据库或 schema
- 共享表 + tenant_id:所有租户数据在同一表,通过 tenant_id 字段区分
- Schema 隔离:安全性高,成本高
- Row Level:成本低,需严格 WHERE 条件
JPA 多租户支持@TenantId、Hibernate 多租户集成 JPA 实现自动租户过滤// 实体类
@Entity
public class Document {
@Id private Long id;
private String content;
private String tenantId; // 租户标识
}

// Hibernate 配置开启多租户
spring.jpa.properties.hibernate.multiTenancy=SCHEMA
- 使用 Hibernate MultiTenantConnectionProvider
- 避免手动拼接 SQL
Spring Security 动态权限hasPermission() 表达式基于租户和资源的细粒度授权http.authorizeHttpRequests(authz -> authz
.requestMatchers("/docs/{id}").access((authentication, object) ->
hasPermission(authentication, object, "read", getTenantId()))
);
- 自定义 PermissionEvaluator
- 结合 ObjectIdentity 和 Sid
租户上下文过滤器TenantContextFilter设置当前线程的租户上下文public class TenantContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
String tenantId = resolveTenantId((HttpServletRequest) request);
TenantContext.setCurrentTenant(tenantId);
try {
chain.doFilter(request, response);
} finally {
TenantContext.clear();
}
}
}
- 使用 ThreadLocal 存储租户 ID
- 必须在安全过滤器之前执行

9.4 动态权限管理:数据库驱动权限配置

名称语法用途代码示例注意事项
权限数据模型Role, Permission, RolePermission 表存储角色与权限的映射关系CREATE TABLE role_permission (
role_id BIGINT,
permission_id BIGINT,
PRIMARY KEY (role_id, permission_id)
);
- 支持 RBAC 模型
- 可扩展为资源-操作矩阵
动态加载 UserDetailsUserDetailsService 查询数据库根据用户加载其角色和权限public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
List<Permission> perms = permissionService.findPermissionsByUser(user.getId());
List<GrantedAuthority> authorities = perms.stream()
.map(p -> new SimpleGrantedAuthority(p.getName()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(), authorities);
}
- 缓存 UserDetails 减少 DB 查询
- 注意 N+1 查询问题
动态安全配置SecurityFilterChain 动态构建从数据库读取 URL 权限规则@Bean
public SecurityFilterChain filterChain(HttpSecurity http, PermissionService permissionService) throws Exception {
List<UrlPermission> rules = permissionService.loadUrlPermissions();
ExpressionUrlAuthorizationManager manager = ExpressionUrlAuthorizationManager
.withDefaultRolePrefix().requestMatchers(rules);
http.authorizeHttpRequests(authz -> authz.withObjectPostProcessor(manager));
return http.build();
}
- 启动时加载或定期刷新
- 可结合 @RefreshScope(Spring Cloud)
权限变更事件@EventListener(PermissionChangedEvent.class)权限更新后刷新缓存或通知@EventListener
public void onPermissionChange(PermissionChangedEvent event) {
userDetailsService.clearCache(); // 清除用户缓存
securityRuleCache.refresh(); // 刷新安全规则缓存
}
- 保证权限变更实时生效
- 避免系统重启

9.5 与 Spring Boot Admin 集成的安全配置

名称语法用途代码示例注意事项
SBA Client 安全配置spring.boot.admin.client.*客户端注册时携带安全凭据spring.boot.admin.client.url: https://admin.example.com
spring.boot.admin.client.username: ${SBA_CLIENT_USER}
spring.boot.admin.client.password: ${SBA_CLIENT_PASS}
spring.boot.admin.client.instance.name: My Application
- 使用环境变量保护凭据
- 确保 SBA Server 已启用安全
SBA Server 安全配置spring.security.user.*保护 SBA Server 管理界面spring.security.user.name: admin
spring.security.user.password: ${SBA_ADMIN_PASS}
spring.boot.admin.ui.public-url: https://admin.example.com
- 强密码策略
- 可集成 OAuth2 或 LDAP
自定义登录页@Controller + login.html为 SBA Server 提供自定义登录界面@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login";
}
}
- 放置在 src/main/resources/templates/login.html
- 需配置 spring.boot.admin.ui.login-form=true
健康端点保护management.endpoints.web.exposure.include=*控制敏感端点暴露# Client 配置
management.endpoints.web.exposure.include=health,info,env,metrics
management.endpoint.health.show-details=when-authorized
spring.security.oauth2.client.registration.admin-client.scope=health.read,info.read
- 避免暴露 shutdown、env 等高危端点
- 细粒度授权
OAuth2 集成spring.security.oauth2.client.*使用 OAuth2 登录 SBA Serverspring.security.oauth2.client.registration.github.client-id: ${GITHUB_CLIENT_ID}
spring.security.oauth2.client.registration.github.client-secret: ${GITHUB_CLIENT_SECRET}
spring.security.oauth2.client.registration.github.scope: read:user
- 支持 GitHub、Google 等提供商
- 提升用户体验