一、核心概念

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架,用于保护基于 Spring 的应用程序。

1.1 核心组件

组件 说明
AuthenticationManager 身份验证管理器,负责处理认证请求
UserDetailsService 加载用户特定数据的核心接口
PasswordEncoder 密码编码器,用于密码加密与验证
SecurityFilterChain 安全过滤器链,定义请求的处理流程
GrantedAuthority 用户权限(角色/权限)
Authentication 当前用户的认证信息
SecurityContext / SecurityContextHolder 存储当前安全上下文(包含认证信息)

1.2 关键术语

  • Authentication(认证):确认“你是谁”。
  • Authorization(授权):决定“你能做什么”。
  • Principal(主体):当前用户。
  • Credentials(凭证):如密码、Token。
  • Authorities(权限):用户拥有的权限(如 ROLE_ADMIN, READ_USER)。

二、详细操作步骤(适合快速上手)

步骤 1:创建 Spring Boot 项目

使用 start.spring.io 或 IDE 创建项目,添加以下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- 可选:数据库支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

步骤 2:配置基本安全配置类

创建配置类 SecurityConfig.java

package com.example.demo.config;

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.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // 启用 Spring Security
public class SecurityConfig {

    // 定义安全过滤器链
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**").permitAll() // 公共路径放行
                .requestMatchers("/admin/**").hasRole("ADMIN") // /admin 需 ADMIN 角色
                .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER 或 ADMIN
                .anyRequest().authenticated() // 其他所有请求都需要认证
            )
            .formLogin(form -> form
                .loginPage("/login") // 自定义登录页路径
                .loginProcessingUrl("/doLogin") // 登录表单提交地址
                .defaultSuccessUrl("/home", true) // 登录成功后跳转
                .permitAll() // 登录页所有人可访问
            )
            .logout(logout -> logout
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout")
                .permitAll()
            )
            .csrf().disable(); // 开发阶段可关闭 CSRF(生产需开启)

        return http.build();
    }

    // 用户详情服务:内存用户
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.builder()
            .username("user")
            .password(passwordEncoder().encode("password"))
            .roles("USER")
            .build();

        UserDetails admin = User.builder()
            .username("admin")
            .password(passwordEncoder().encode("admin"))
            .roles("ADMIN")
            .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

    // 密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

步骤 3:创建控制器测试

package com.example.demo.controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @GetMapping("/public/hello")
    public String hello() {
        return "Hello, Public!";
    }

    @GetMapping("/user/profile")
    public String userProfile() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return "Hello, " + auth.getName() + "! You are logged in.";
    }

    @GetMapping("/admin/dashboard")
    public String adminDashboard() {
        return "Welcome to Admin Dashboard!";
    }

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

    @GetMapping("/login")
    public String loginPage() {
        return "Please login";
    }
}

步骤 4:运行并测试

  1. 启动应用。
  2. 访问 http://localhost:8080/public/hello → 直接访问。
  3. 访问 http://localhost:8080/user/profile → 跳转到登录页。
  4. 使用用户名 user 密码 password 登录。
  5. 访问 /admin/dashboard → 拒绝访问(user 无 ADMIN 角色)。
  6. 使用 admin/admin 登录后可访问。

三、常见错误与解决方案

错误 原因 解决方案
There is no PasswordEncoder mapped for the id "null" 未配置 PasswordEncoder 或密码未编码 确保 UserDetailsService 返回的密码已用 PasswordEncoder 编码
403 Forbidden 权限不足或 CSRF 阻止 检查角色/权限配置;开发时可 http.csrf().disable()
登录失败但无错误信息 登录处理路径不匹配 检查 loginProcessingUrl 是否与表单 action 一致
循环重定向 安全配置不当 检查 permitAll() 是否包含登录页、静态资源等
@PreAuthorize 不生效 未启用方法级安全 添加 @EnableMethodSecurity

四、注意事项

  1. 永远不要在生产环境关闭 CSRFhttp.csrf().disable() 仅用于开发测试。
  2. 密码必须加密存储:使用 BCryptPasswordEncoderSCryptPasswordEncoder 等。
  3. 避免内存用户用于生产:应使用数据库、LDAP 或 OAuth2。
  4. 角色前缀:Spring Security 默认角色需以 ROLE_ 开头(如 hasRole("ADMIN") 实际检查 ROLE_ADMIN)。
  5. Session 管理:合理配置会话超时、并发控制等。

五、使用技巧

5.1 获取当前用户信息

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();

5.2 方法级安全

@Configuration
@EnableMethodSecurity // 启用方法安全注解
public class MethodSecurityConfig {
}

// 在 Service 或 Controller 方法上使用
@PreAuthorize("hasRole('ADMIN')")
public void deleteUserData() { ... }

@PostAuthorize("returnObject.owner == authentication.name")
public UserData getUserData(Long id) { ... }

5.3 自定义登录成功/失败处理器

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .formLogin(form -> form
            .successHandler(myAuthenticationSuccessHandler())
            .failureHandler(myAuthenticationFailureHandler())
        );
    return http.build();
}

@Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler() {
    return (request, response, authentication) -> {
        // 自定义逻辑,如记录日志、重定向
        response.sendRedirect("/custom-home");
    };
}

六、最佳实践

  1. 最小权限原则:只授予必要的权限。
  2. 使用 HTTPS:保护传输中的凭证。
  3. 定期轮换密钥/密码:增强安全性。
  4. 日志审计:记录登录、权限变更等关键操作。
  5. 使用 OAuth2 / OpenID Connect:对于现代应用,优先考虑第三方认证。
  6. 分离配置:将安全配置与业务逻辑分离。

七、性能优化

  1. 缓存用户信息:对于数据库用户,使用 Spring Cache 缓存 UserDetailsService 结果。
  2. 减少权限检查开销
    • 避免在循环内进行 @PreAuthorize
    • 使用角色继承(RoleHierarchy)减少重复检查。
  3. Session 优化
    • 对于无状态 API,使用 JWT 代替 Session。
    • 配置合理的 Session 超时时间。
  4. 异步认证:对于耗时认证逻辑(如远程调用),考虑异步处理(需谨慎)。

八、扩展:JWT 集成(无状态认证)

适用于前后端分离、微服务架构。

  1. 添加依赖:jjwt-api, jjwt-impl, jjwt-jackson
  2. 创建 JwtUtil 工具类生成/解析 Token。
  3. 自定义 OncePerRequestFilter 拦截请求验证 JWT。
  4. 配置 SecurityFilterChain 放行登录路径,添加 JWT 过滤器。

示例代码较长,此处略,可按需提供。


总结

通过以上步骤,你已掌握 Spring Boot 集成 Spring Security 的核心知识。从基础配置到高级技巧,结合最佳实践,可快速构建安全可靠的应用。

下一步建议

  • 集成数据库用户(JpaUserDetailsManager)。
  • 实现 OAuth2 登录(如 GitHub、微信)。
  • 学习 Spring Security OAuth2 Resource Server(保护 REST API)。

本文档基于 Spring Boot 3.x + Spring Security 6.x 编写。