Spring Security 教程:从入门到实践 – wiki基地


Spring Security 教程:从入门到实践

在当今互联网应用开发中,安全性是不可或缺的一环。无论是用户认证、权限控制,还是防御常见的网络攻击,一个健壮的安全框架都能极大地提升应用的可靠性和用户信任度。对于 Java 生态系统而言,Spring Security 无疑是事实上的标准安全框架。它强大、灵活,并且与 Spring 家族完美集成。

本教程将带领你从零开始,逐步深入理解 Spring Security 的核心概念、基本配置,直至掌握如何在实际项目中应用它,构建安全可靠的应用程序。

1. Spring Security 是什么?为什么选择它?

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是 Spring 项目的一部分,专注于为 Java 应用程序提供声明式的安全解决方案。

为什么选择 Spring Security?

  1. 全面性: 它提供了认证(Authentication)和授权(Authorization)两大核心功能,并涵盖了如 CSRF 保护、Session 管理、记住我(Remember Me)、OAuth2、JWT 等多种安全特性。
  2. 灵活性: Spring Security 采用了高度模块化的设计,你可以根据需求自由选择和组合所需的组件。无论是基于内存、数据库的用户管理,还是集成 LDAP、CAS 等外部认证系统,都能轻松实现。
  3. 易用性: 虽然配置选项众多,但 Spring Boot 的自动配置为 Spring Security 提供了极佳的开箱即用体验。对于常见的安全需求,只需少量配置甚至无需配置即可工作。
  4. 与 Spring 生态集成: 作为 Spring 家族的一员,它与 Spring MVC、Spring Boot、Spring Data 等其他 Spring 项目无缝集成,开发体验一致。
  5. 社区活跃: 拥有庞大的用户社区和完善的文档,遇到问题很容易找到解决方案。

简而言之,如果你在构建基于 Spring 的 Java 应用程序,Spring Security 是处理安全问题的首选框架。

2. 核心概念:理解 Spring Security 的基石

要掌握 Spring Security,首先需要理解它的一些核心概念。它们构成了框架的基础工作原理。

  • Authentication (认证): 证明用户身份的过程。例如,用户输入用户名和密码,系统验证其有效性。Spring Security 中的认证涉及 AuthenticationManagerAuthentication 对象和 AuthenticationProvider
  • Authorization (授权): 在用户身份被认证后,决定该用户是否有权访问某个资源(如 URL、方法)的过程。例如,只有管理员用户才能访问后台管理页面。授权涉及 AccessDecisionManagerGrantedAuthority
  • Principal (主体): 代表当前用户的身份。通常是用户名或者一个表示用户身份的对象。在 Spring Security 中,通过 SecurityContextHolder.getContext().getAuthentication().getPrincipal() 获取。
  • Credentials (凭证): 用于证明身份的信息,最常见的是密码。也可能是验证码、令牌等。
  • GrantedAuthority (授权): 代表赋予主体的权限或角色。例如,ROLE_ADMINREAD_PRIVILEGE。一个主体可以拥有多个 GrantedAuthority
  • SecurityContext (安全上下文): 存储当前主体的 Authentication 对象。默认情况下,它存储在与请求相关的 ThreadLocal 中,以便在同一请求的任何地方都能访问到当前用户信息。在 Web 应用中,通常与 HTTP Session 绑定。
  • AuthenticationManager: 认证管理器,负责协调各种 AuthenticationProvider 来完成认证过程。
  • AuthenticationProvider: 具体的认证提供者,负责使用特定方式(如用户名/密码、LDAP、OAuth2)验证 Authentication 请求。
  • AccessDecisionManager (访问决策管理器): 授权过程的核心,根据 GrantedAuthority 和资源的安全配置(如 URL 模式、方法上的注解),决定是否允许访问。它通常委托给多个 AccessDecisionVoter
  • SecurityFilterChain (安全过滤器链): Spring Security 在 Servlet 过滤器基础上构建的,是处理安全请求的入口。一个请求到达时会经过一系列安全过滤器,每个过滤器负责处理特定的安全功能(如认证、授权、CSRF 保护、Session 管理等)。这是 Spring Security 工作流程的核心。

Spring Security 工作流程概览 (以基于表单认证的 Web 应用为例):

  1. 用户尝试访问一个受保护的资源(如 /admin)。
  2. SecurityFilterChain 中的某个过滤器(例如 FilterSecurityInterceptor)发现该资源需要认证。
  3. 如果用户未认证,过滤器链会中断,并通常会将用户重定向到登录页面(由 ExceptionTranslationFilter 捕获异常并处理)。
  4. 用户在登录页面输入用户名和密码,提交表单。
  5. 表单提交请求被 UsernamePasswordAuthenticationFilter 捕获。
  6. UsernamePasswordAuthenticationFilter 创建一个 UsernamePasswordAuthenticationToken (它是 Authentication 接口的一个实现) 并将其提交给 AuthenticationManager
  7. AuthenticationManager 委托给适当的 AuthenticationProvider(例如,如果使用内存或数据库用户,会是 DaoAuthenticationProvider)。
  8. AuthenticationProvider 查找用户(通过 UserDetailsService),验证密码(通过 PasswordEncoder)。
  9. 如果认证成功,AuthenticationProvider 返回一个完全填充了用户详情和 GrantedAuthorityAuthentication 对象。
  10. AuthenticationManager 接收到成功的 Authentication 对象后,将其设置到 SecurityContextHolder 中。
  11. SessionManagementFilterSecurityContext 存储到 HTTP Session 中,以便后续请求使用。
  12. UsernamePasswordAuthenticationFilter 将用户重定向到之前尝试访问的资源(或默认成功页面)。
  13. 用户再次访问受保护资源,这次 SecurityContextHolder 中已有认证信息。
  14. FilterSecurityInterceptor 使用 AccessDecisionManager 检查用户是否有权访问该资源。
  15. 如果授权成功,请求继续处理,最终到达目标控制器。

理解这个流程对于调试和定制 Spring Security 至关重要。

3. 入门实践:构建一个基本的 Spring Boot 安全应用

让我们通过一个简单的 Spring Boot 应用来实践 Spring Security 的基本配置。

步骤 1: 创建 Spring Boot 项目

使用 Spring Initializr 创建一个 Maven 或 Gradle 项目。至少添加以下依赖:

  • Spring Web (用于构建 Web 应用)
  • Spring Security

如果是 Maven 项目,pom.xml 中应包含类似如下的依赖:

“`xml


org.springframework.boot
spring-boot-starter-web


org.springframework.boot
spring-boot-starter-security


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


“`

步骤 2: 运行应用并观察默认行为

启动 Spring Boot 应用。你会注意到控制台输出了一个自动生成的密码,类似:

Using generated security password: <generated-password>

访问应用的任何一个页面(例如 http://localhost:8080/),你会被重定向到一个默认的登录页面。输入用户名 user 和控制台输出的密码,即可登录。这展示了 Spring Security 的开箱即用能力。

步骤 3: 定义简单的控制器

创建一个简单的 REST 控制器来提供一些测试用的端点:

“`java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

@GetMapping("/")
public String home() {
    return "Welcome to the Home Page!";
}

@GetMapping("/public")
public String publicPage() {
    return "This is a public page, accessible by anyone.";
}

@GetMapping("/private")
public String privatePage() {
    return "This is a private page, only accessible by authenticated users.";
}

@GetMapping("/admin")
public String adminPage() {
    return "This is an admin page, only accessible by users with ADMIN role.";
}

}
“`

现在,//public 应该可以无需认证访问,而 /private/admin 需要认证。默认情况下,Spring Security 保护了所有路径,所以目前访问任何路径都需要登录。

步骤 4: 自定义安全配置

我们需要创建一个配置类来定制安全规则。在较新的 Spring Security 版本(6.x+)中,推荐使用 SecurityFilterChain bean 来配置。

“`java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.UserDetails;
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.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // 启用Spring Security的Web安全功能
public class SecurityConfig {

// 配置 SecurityFilterChain 来定义安全规则
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authorizeRequests ->
            authorizeRequests
                .requestMatchers("/public", "/").permitAll() // 允许所有人访问 /public 和 /
                .requestMatchers("/admin").hasRole("ADMIN") // 只有拥有ADMIN角色的用户才能访问 /admin
                .anyRequest().authenticated() // 所有其他请求都需要认证
        )
        .formLogin(formLogin ->
            formLogin
                .loginPage("/login") // 配置自定义登录页面的URL (如果需要)
                .permitAll() // 允许所有人在登录页面访问,否则会陷入无限重定向
        )
        .logout(logout ->
            logout
                .permitAll() // 允许所有人在注销页面访问
        );
    return http.build();
}

// 配置用户详情服务 (这里使用内存存储用户,实际应用中会使用数据库等)
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
    UserDetails user = User.withUsername("user")
        .password(passwordEncoder.encode("password")) // 密码需要编码
        .roles("USER") // 定义角色
        .build();

    UserDetails admin = User.withUsername("admin")
        .password(passwordEncoder.encode("adminpass"))
        .roles("ADMIN", "USER") // ADMIN用户也可以拥有USER角色
        .build();

    return new InMemoryUserDetailsManager(user, admin);
}

// 配置密码编码器,Spring Security强制要求使用密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(); // 推荐使用BCryptPasswordEncoder
}

}
“`

代码解释:

  • @Configuration: 标记这是一个配置类。
  • @EnableWebSecurity: 启用 Spring Security 的 Web 安全功能。
  • securityFilterChain(HttpSecurity http): 定义一个 SecurityFilterChain bean,这是 Spring Security 6.x+ 推荐的配置方式。
    • http.authorizeHttpRequests(...): 配置请求授权。
      • requestMatchers("/public", "/").permitAll(): 匹配 /public/ 路径,并允许所有访问 (permitAll())。
      • requestMatchers("/admin").hasRole("ADMIN"): 匹配 /admin 路径,要求用户拥有 ADMIN 角色 (hasRole("ADMIN"))。
      • anyRequest().authenticated(): 匹配所有 其他 未被前面规则匹配的请求,要求用户必须通过认证 (authenticated())。注意规则的顺序很重要,更具体的规则应该放在前面。
    • http.formLogin(...): 配置表单登录。permitAll() 是必需的,以确保未认证用户可以访问登录页面。如果省略 .loginPage("/login"),会使用 Spring Security 提供的默认登录页面。
    • http.logout(...): 配置注销功能。permitAll() 也是必需的。
  • userDetailsService(PasswordEncoder passwordEncoder): 定义一个 UserDetailsService bean,负责加载用户详情。这里使用了简单的 InMemoryUserDetailsManager 将用户存储在内存中,并使用 User.withUsername(...).password(...).roles(...) 构建 UserDetails 对象。
  • passwordEncoder(): 定义一个 PasswordEncoder bean。 Spring Security 强制要求使用密码编码器来存储和比对密码,以增强安全性。BCryptPasswordEncoder 是推荐的实现,它使用 bcrypt 强哈希算法。请务必对存储的密码进行编码。

步骤 5: 创建自定义登录页面 (可选)

如果你配置了 .loginPage("/login"),你需要创建一个处理 /login 请求的控制器和相应的视图。

创建控制器:

“`java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {

@GetMapping("/login")
public String login() {
    return "login"; // 返回视图名称,例如 login.html
}

}
“`

创建视图 (以 Thymeleaf 为例,在 src/main/resources/templates/ 目录下创建 login.html):

“`html




Login Page

Custom Login Page



Invalid username and password.

You have been logged out.


“`

注意:表单的 action 属性指向 /login (默认值),method 必须是 POST。用户名输入的 name 属性必须是 username,密码输入必须是 password (默认值)。这些都可以通过 formLogin() 配置来修改,但使用默认值通常最简单。

步骤 6: 测试应用

重启应用。

  • 访问 http://localhost:8080/http://localhost:8080/public:应该可以直接访问。
  • 访问 http://localhost:8080/privatehttp://localhost:8080/admin:你会被重定向到登录页面(如果是自定义的 /login,否则是默认页面)。
  • 使用用户 user 和密码 password 登录:
    • 再次访问 /private:应该可以成功访问。
    • 再次访问 /admin:会被拒绝访问 (通常返回 403 Forbidden)。
  • 使用用户 admin 和密码 adminpass 登录:
    • 访问 /private:应该可以成功访问。
    • 访问 /admin:应该可以成功访问。
  • 访问 /logout (默认的注销 URL,通过 POST 请求触发):你应该会被注销并可能重定向回登录页面。

至此,你已经成功构建了一个包含基本认证和基于角色的 URL 授权的 Spring Security 应用。

4. 深入授权:方法级别安全

除了基于 URL 的授权,Spring Security 还支持方法级别的安全控制,允许你更细粒度地控制哪个用户可以调用类中的哪个方法。这通常通过注解来实现。

步骤 1: 启用方法安全

在你的安全配置类 (SecurityConfig) 上添加 @EnableMethodSecurity 注解:

“`java
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
// … 其他导入

@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 启用方法安全
public class SecurityConfig {
// … SecurityFilterChain, UserDetailsService, PasswordEncoder beans
}
“`

@EnableMethodSecurity 是 Spring Security 6.x+ 推荐的注解,它默认启用了对 @PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter 以及 @Secured@RolesAllowed 的支持。

步骤 2: 在方法上使用注解

修改你的 HomeController 类,在方法上添加安全注解:

“`java
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

@GetMapping("/")
public String home() {
    return "Welcome to the Home Page!";
}

@GetMapping("/public")
public String publicPage() {
    return "This is a public page, accessible by anyone.";
}

// 保持原样,或者也加上认证要求,但通常URL级别的authenticated()已经足够
@GetMapping("/private")
@PreAuthorize("isAuthenticated()") // 要求用户已认证
public String privatePage() {
    return "This is a private page, only accessible by authenticated users.";
}

// 只有ADMIN角色才能访问
@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')") // 要求用户拥有ADMIN角色
public String adminPage() {
    return "This is an admin page, only accessible by users with ADMIN role.";
}

// 只有拥有特定权限(非角色)的用户才能访问
@GetMapping("/specific-permission")
@PreAuthorize("hasAuthority('SPECIFIC_PERMISSION')") // 要求用户拥有 SPECIFIC_PERMISSION 权限
public String specificPermissionPage() {
    return "This page requires SPECIFIC_PERMISSION.";
}

}
“`

注解解释:

  • @PreAuthorize("isAuthenticated()"): 在方法执行 之前 检查用户是否已认证。
  • @PreAuthorize("hasRole('ADMIN')"): 在方法执行 之前 检查用户是否拥有 ADMIN 角色。hasRole() 方法会自动在角色名前面加上 ROLE_ 前缀进行匹配,所以用户的权限列表中需要有 ROLE_ADMIN
  • @PreAuthorize("hasAuthority('SPECIFIC_PERMISSION')"): 在方法执行 之前 检查用户是否拥有 SPECIFIC_PERMISSION 权限。hasAuthority() 进行的是精确匹配,不会自动添加 ROLE_ 前缀。通常角色也是一种权限,hasRole('ADMIN') 等价于 hasAuthority('ROLE_ADMIN')。推荐使用 hasAuthority 来匹配所有的授权字符串(包括角色和更细粒度的权限)。
  • Spring Expression Language (SpEL): @PreAuthorize 中的字符串是 SpEL 表达式,非常灵活。你可以使用 principal 对象访问当前用户详情,使用 hasAnyRole, hasAnyAuthority, isAnonymous(), isRememberMe(), isFullyAuthenticated() 等内置表达式,甚至调用自定义 Bean 的方法进行复杂的权限判断。

更新用户详情服务以包含权限:

为了测试 @PreAuthorize("hasAuthority('SPECIFIC_PERMISSION')"),我们需要给用户添加这个权限。修改 userDetailsService bean:

“`java
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails user = User.withUsername(“user”)
.password(passwordEncoder.encode(“password”))
// 注意:roles() 是 authorities() 的简写,会自动添加 “ROLE_” 前缀
// .roles(“USER”)
// 或者使用 authorities() 明确指定权限
.authorities(“ROLE_USER”)
.build();

UserDetails admin = User.withUsername("admin")
    .password(passwordEncoder.encode("adminpass"))
    .authorities("ROLE_ADMIN", "ROLE_USER", "SPECIFIC_PERMISSION") // 给admin用户添加 SPECIFIC_PERMISSION
    .build();

return new InMemoryUserDetailsManager(user, admin);

}
“`

现在,你可以重新测试应用。你会发现 URL 级别的安全规则和方法级别的安全规则都在生效。方法级别的安全通常在 URL 级别的安全 之后 执行(如果都适用)。

5. 实际应用中的重要考量

到目前为止,我们已经掌握了 Spring Security 的基础。但在实际应用中,还有一些重要的方面需要考虑。

5.1 持久化用户数据

将用户存储在内存中只适用于示例或测试。在生产环境中,你需要从数据库或其他外部存储加载用户。这需要实现 UserDetailsService 接口。

“`java
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
// … 其他导入

@Service // 或者使用 @Component
public class CustomUserDetailsService implements UserDetailsService {

// 假设你有一个 UserRepository 来从数据库查询用户
// @Autowired
// private UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // 实际应用中:从数据库根据用户名查询用户
    // Optional<com.example.yourapp.model.User> userOptional = userRepository.findByUsername(username);
    // com.example.yourapp.model.User user = userOptional.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));

    // 示例:硬编码一个用户,模拟从数据库加载
    if ("realuser".equals(username)) {
        // 返回 Spring Security 的 UserDetails 对象,包含了用户名、密码和权限
        return User.withUsername("realuser")
            .password("$2a$10$yourEncodedPassword") // 这里应该是数据库中存储的编码后的密码
            .authorities("ROLE_USER", "READ_PERMISSION")
            .build();
    } else if ("realadmin".equals(username)) {
         return User.withUsername("realadmin")
            .password("$2a$10$yourOtherEncodedPassword")
            .authorities("ROLE_ADMIN", "ROLE_USER", "READ_PERMISSION", "WRITE_PERMISSION")
            .build();
    } else {
        throw new UsernameNotFoundException("User not found with username: " + username);
    }
}

}
“`

然后,在你的 SecurityConfig 中,Spring Security 会自动找到并使用你的 CustomUserDetailsService bean。你不再需要手动创建 InMemoryUserDetailsManager bean。

“`java
// 在 SecurityConfig 类中
// … 其他导入

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

// 将 InMemoryUserDetailsManager Bean 移除或注释掉

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    // ... 保持原有的 http 配置
    return http.build();
}

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

// Spring 会自动注入 CustomUserDetailsService 到 DaoAuthenticationProvider 中
// 因此不需要在这里显式配置 UserDetailsService Bean,除非你有多个实现需要指定

}
“`

5.2 密码安全

永远不要以明文形式存储密码。Spring Security 强制要求使用 PasswordEncoder。确保你配置了 PasswordEncoder bean,并且在存储用户时对密码进行了编码。当用户登录时,Spring Security 会使用相同的编码器来验证用户输入的密码是否与存储的编码后密码匹配。

5.3 CSRF (跨站请求伪造) 保护

Spring Security 默认开启 CSRF 防护,这对于基于 Session 的 Web 应用至关重要。它通过在表单或 AJAX 请求中添加一个随机生成的 Token 来防止跨站请求。

  • 对于使用 Thymeleaf 等模板引擎生成的 HTML 表单,通常会自动集成 CSRF Token。
  • 对于前端使用 JavaScript 发起的 AJAX 请求,你需要手动将 CSRF Token 添加到请求头或请求参数中。Spring Security 会将 CSRF Token 放在一个名为 _csrf 的请求属性中,你可以在 HTML 页面中访问它,然后通过 JS 读取并发送。

“`html








“`

如果你的应用是无状态的 RESTful API (例如使用 JWT),通常不需要 CSRF 保护,因为没有 Session 来遭受劫持。在这种情况下,你可以在 SecurityFilterChain 配置中禁用 CSRF:

java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ... 其他配置
.csrf(csrf -> csrf.disable()); // 禁用CSRF防护
return http.build();
}

5.4 跨域 (CORS)

如果你的前端应用与后端 API 部署在不同的域名或端口,会遇到跨域问题。Spring Security 可以与 Spring MVC 的 CORS 配置协同工作。

你可以在全局配置 CORS:

“`java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

@Bean
public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true); // 允许携带Cookie
    config.addAllowedOriginPattern("*"); // 允许所有来源 (生产环境应限制特定域名)
    config.addAllowedHeader("*"); // 允许所有请求头
    config.addAllowedMethod("*"); // 允许所有HTTP方法 (GET, POST, PUT等)
    source.registerCorsConfiguration("/**", config); // 对所有路径生效
    return new CorsFilter(source);
}

}
“`

Spring Security 的 CORS 集成方式是,如果检测到 CorsConfiguration bean,它会在 CorsFilter 中应用 CORS 配置,并且这个 CorsFilter 会放在 Spring Security 过滤器链的 前面。这确保了 CORS 预检请求 (OPTIONS) 可以通过,而无需经过认证/授权检查。

5.5 无状态安全 (JWT/OAuth2)

对于现代的单页应用 (SPA) 或移动应用,使用无状态认证方式更为常见,例如 JWT (JSON Web Token) 或 OAuth2。在这种模式下,服务器不维护用户的 Session 状态。用户登录成功后,服务器返回一个 Token 给客户端,客户端在后续请求中携带这个 Token。

  • JWT: 服务器生成一个包含用户信息的签名 Token,客户端存储并发送。服务器通过验证 Token 的签名来确认身份。Spring Security 提供了集成 JWT 的方式,通常需要自定义过滤器来解析和验证 Token,并设置 SecurityContextHolder
  • OAuth2: 主要用于授权第三方应用访问用户数据,但也常用于认证(OpenID Connect 是基于 OAuth2 的认证层)。Spring Security 提供了强大的 OAuth2 支持,可以作为 OAuth2 客户端、资源服务器或授权服务器。

配置无状态安全通常涉及禁用 CSRF 和 Session 管理:

java
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ... 请求授权配置
.csrf(csrf -> csrf.disable()) // 禁用CSRF
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 设置Session创建策略为无状态
// 添加自定义的 JWT 认证过滤器或其他无状态认证过滤器
// .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
// ... 其他配置
return http.build();
}

实现 JWT 认证过滤器、与 OAuth2 提供者集成等都是更高级的主题,超出了本入门教程的范围,但了解 Spring Security 对这些的支持方向非常重要。

5.6 异常处理

当认证失败 (AuthenticationException) 或授权失败 (AccessDeniedException) 时,Spring Security 需要有相应的处理机制。

  • AuthenticationException: 默认情况下,如果用户尝试访问受保护资源但未认证,ExceptionTranslationFilter 会捕获异常并启动 “认证入口点” (AuthenticationEntryPoint)。对于 Web 应用,默认的入口点是重定向到登录页面。对于 REST API,通常会返回 401 Unauthorized 状态码。你可以通过 http.exceptionHandling().authenticationEntryPoint(...) 自定义认证入口点。
  • AccessDeniedException: 如果用户已认证,但尝试访问没有权限的资源,ExceptionTranslationFilter 会捕获异常并启动 “访问拒绝处理程序” (AccessDeniedHandler)。默认情况下,这会导致返回 403 Forbidden 状态码。你可以通过 http.exceptionHandling().accessDeniedHandler(...) 自定义访问拒绝处理程序,例如跳转到一个错误页面或返回特定的 JSON 响应。

6. 最佳实践与总结

  • 始终使用 PasswordEncoder: 这是最基本的安全措施。
  • 最小权限原则: 只赋予用户完成其任务所需的最小权限。
  • 理解过滤器链: 了解 Spring Security 过滤器的顺序和作用有助于调试和高级定制。
  • 优先使用 @PreAuthorize: 相较于 @Secured@RolesAllowed@PreAuthorize 提供了更强大的 SpEL 支持,更加灵活。
  • 细化 URL 规则:authorizeHttpRequests 中,将更具体的路径规则放在前面。
  • 保护敏感信息: 不要在日志中输出敏感信息,尤其是密码。
  • 定期更新依赖: 及时获取安全补丁和新功能。
  • 前后端分离应用考虑无状态安全: 对于 RESTful API,JWT 或 OAuth2 通常是比 Session 更好的选择。

Spring Security 是一个功能全面、高度可定制的安全框架。本教程从核心概念出发,通过一个简单的 Spring Boot 应用示例,详细讲解了基本的认证、URL 授权、方法授权配置,并触及了实际应用中的关键议题,如用户持久化、密码安全、CSRF、CORS 以及无状态安全。

从入门到实践,Spring Security 可能初看起来有些复杂,因为它提供了巨大的灵活性来适应各种安全需求。但一旦掌握了其核心概念和工作流程,你会发现它能极大地简化应用程序安全功能的开发。

这仅仅是 Spring Security 强大功能的冰山一角。未来你可以继续深入学习 OAuth2、JWT、自定义过滤器、Remember Me、Session 管理策略、安全事件监听等更多高级主题,以构建更加安全、健壮的应用程序。祝你在 Spring Security 的学习和实践之路上顺利!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部