Spring Security 实战指南:从入门到精通

简介: 本文详细介绍了Spring Security在Java Web项目中的应用,涵盖登录、权限控制与安全防护等功能。通过Filter Chain过滤器链实现请求拦截与认证授权,核心组件包括AuthenticationProvider和UserDetailsService,负责用户信息加载与密码验证。文章还解析了项目结构,如SecurityConfig配置类、User实体类及自定义登录逻辑,并探讨了Method-Level Security、CSRF防护、Remember-Me等进阶功能。最后总结了Spring Security的核心机制与常见配置,帮助开发者构建健壮的安全系统。

theme: cyanosis

一、为什么要用 Spring Security?

市面上常见的 Java Web 项目,大多需要“登录”、“权限控制”、“安全防护”等功能。如果你手写这些功能,可能会面临:

  1. 密码存储方式:如何保证密码不被明文泄露?如何应对彩虹表攻击?
  2. 登录流程:如何验证用户名/密码?如何处理会话(session)或 token?
  3. 权限控制:如何区分管理员和普通用户?如何管理访问 URL、页面的权限?
  4. 防护措施:如何防止 CSRF 攻击、XSS 攻击、Session Fixation 攻击等?

Spring Security 正是为了解决以上安全问题而生的。它提供了开箱即用的认证和授权机制,还能灵活定制,让你把更多精力放在业务逻辑上。

image.png

二、Spring Security 的整体架构

要理解 Spring Security 的工作原理,最重要的是理解它的 Filter Chain(过滤器链) 以及 AuthenticationProviderUserDetailsService 等核心概念。

2.1 Filter Chain(过滤器链)

当一个请求进来时,会首先进入到一系列由 Spring Security 管理的过滤器( Filter )。它们按照特定的顺序执行,每一个过滤器都可能对请求进行拦截、认证或授权等处理。

常见过滤器包括:

  • UsernamePasswordAuthenticationFilter:处理表单登录的用户名、密码提交。
  • BasicAuthenticationFilter:处理 HTTP Basic 认证。
  • SecurityContextPersistenceFilter:负责在 SecurityContextHolder 中持久化用户信息。
  • CsrfFilter:处理跨站请求伪造 (CSRF) 防护。

以及其他若干过滤器。在 Spring Boot 中,一般我们只需要通过 HttpSecurity 配置这些过滤器的顺序和条件即可,无需手动管理所有类。

2.2 认证(Authentication) 与 授权(Authorization)

  • 认证:是谁在登录?需要校验用户名、密码,或者 token 是否有效。
  • 授权:这个用户是否有权限访问特定功能?常见的方式是基于角色 (Role) 或权限 (Authority) 来做控制。

在 Spring Security 中,认证和授权大都通过过滤器链 + AuthenticationProvider + UserDetailsService 来完成:

  1. UserDetailsService:告诉 Spring Security,如何根据用户名(或其他标识)加载用户信息,比如密码、角色。
  2. PasswordEncoder:用什么算法加密、验证密码,如 BCrypt、PBKDF2 等。
  3. AuthenticationProvider:将“用户信息 + 用户输入的凭证”进行验证,验证通过后把角色信息等存入 SecurityContextHolder
  4. AccessDecisionManager + SecurityInterceptor:检查用户是否有访问某个 URL 的权限(授权)。

三、项目结构与关键文件

一个典型的 Spring Boot + Spring Security 项目中,跟 Security 相关的常见文件有:

  1. SecurityConfig(核心配置类)
  2. User 实体类(存放用户信息)
  3. UserRepository(与数据库交互)
  4. UserService(业务逻辑,比如注册、加密密码)
  5. UserDetailsServiceImpl(告诉 Spring Security 如何加载用户信息)
  6. 登录 / 注册 / 权限相关的 Controller(处理前端请求)

下面让我们一个个来看看。

3.1 pom.xml / build.gradle

要使用 Spring Security,需要在项目依赖中引入:

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

这样就能自动拉取所有与 Security 相关的组件。

3.2 SecurityConfig

这是最核心的配置类,主要做两件事:1) 设置过滤链规则2) 告诉 Spring Security 怎么做认证和授权

示例配置(逐句解析):

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

    // 构造函数注入一个自定义的 UserDetailsService,实现类会在后面介绍
    private final UserDetailsServiceImpl userDetailsService;

    public SecurityConfig(UserDetailsServiceImpl userDetailsService) {
   
        this.userDetailsService = userDetailsService;
    }

    /**
     * 1. PasswordEncoder Bean
     *    这里选用BCrypt加密算法,它会在保存密码和校验密码时发挥作用
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
   
        return new BCryptPasswordEncoder();
    }

    /**
     * 2. DaoAuthenticationProvider
     *    这是Spring Security用于做用户名/密码验证的提供者(Provider)
     *    告诉它:需要从userDetailsService加载用户信息,并用BCrypt做密码匹配
     */
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
   
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    /**
     * 3. 配置过滤链
     *    - 禁用CSRF(仅开发环境方便测试,生产视情况开启)
     *    - 配置授权规则,哪些路径开放、哪些需角色
     *    - 定义登录/注销的页面与处理URL
     *    - Session管理(限制单点登录等)
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
   
        http
            // 3.1 临时禁用CSRF
            .csrf(csrf -> csrf.disable())

            // 3.2 路径权限
            .authorizeHttpRequests(auth -> auth
                // 下列路径不需要登录即可访问(静态资源、登录页、注册接口等)
                .requestMatchers("/login", "/register", "/api/users/register", "/css/**", "/js/**").permitAll()
                // 只有ADMIN角色能访问 /admin/** 下的资源
                .requestMatchers("/admin/**").hasRole("ADMIN")
                // 其他所有请求,登录后可访问
                .anyRequest().authenticated()
            )

            // 3.3 登录表单配置
            .formLogin(form -> form
                .loginPage("/login")                  // 登录页面(自定义)
                .loginProcessingUrl("/perform_login") // Spring Security会拦截此URL执行登录逻辑
                .defaultSuccessUrl("/home", true)      // 登录成功后跳转页面
                .failureUrl("/login?error")            // 登录失败后跳转
                .permitAll()
            )

            // 3.4 登出配置
            .logout(logout -> logout
                .logoutUrl("/logout") // 处理登出请求的URL
                .logoutSuccessUrl("/login?logout") // 登出成功后跳转
                .permitAll()
            )

            // 3.5 指定自定义的AuthenticationProvider
            .authenticationProvider(authenticationProvider())

            // 3.6 Session管理(限制一个账号只能登录一次,可选)
            .sessionManagement(session -> session
                .maximumSessions(1)
                .expiredUrl("/login?expired")
            );

        return http.build();
    }
}

代码逐句解读

  1. @EnableWebSecurity:让 Spring Security 的自动配置生效。
  2. @Bean PasswordEncoder:BCrypt 会在注册时把明文密码加密存库,在登录时把用户输入的明文密码和数据库里的密文匹配。
  3. DaoAuthenticationProvider:Spring Security 内置的“用户名/密码验证”实现,会调用你自定义的 UserDetailsService 来加载用户信息。
  4. .requestMatchers(...):用来做授权规则匹配,比如某些 URL 只有特定角色才能访问。
  5. .formLogin():配置表单登录的具体跳转页面、请求 URL 等。
  6. .logout():登出逻辑,比如点击 /logout 就会清理 session 并重定向到登录页。
  7. .sessionManagement():可配置单点登录等。

3.3 User 实体类

用来在数据库里存放用户信息,一般至少包含:用户名、邮箱、加密后的密码、角色 等属性。

@Entity
@Table(name = "users")
public class User {
   
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username; // 用户名

    @Column(nullable = false, unique = true)
    private String email;    // 邮箱

    @Column(nullable = false)
    private String password; // 密码(BCrypt加密后)

    // 使用ElementCollection或多对多表存储角色集
    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
    @Column(name = "role")
    private Set<String> roles; // 存放用户的角色, e.g. ADMIN, USER

    // Getter/Setter省略
}

为什么用 ElementCollection\
因为有些时候一个用户会有多个角色(比如 ADMIN+USER),这样会在数据库自动生成一张 user_roles 表来保存关系。

3.4 UserRepository

用来和数据库进行交互,查询或保存 User 数据:

public interface UserRepository extends JpaRepository<User, Long> {
   
    // 根据用户名查找
    Optional<User> findByUsername(String username);

    // 根据邮箱查找
    Optional<User> findByEmail(String email);
}

3.5 UserService(业务逻辑,比如注册)

在这里我们一般会做两件事:

  1. 注册:检查用户名、邮箱是否已存在,若不存在则加密密码后保存到数据库。
  2. 查询:提供给其他地方调用,比如查用户信息。
@Service
public class UserService {
   

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public User registerUser(User user) {
   
        // 1. 检查是否已存在
        if (userRepository.findByUsername(user.getUsername()).isPresent()) {
   
            throw new RuntimeException("用户名已存在");
        }
        if (userRepository.findByEmail(user.getEmail()).isPresent()) {
   
            throw new RuntimeException("邮箱已被注册");
        }

        // 2. 加密密码
        user.setPassword(passwordEncoder.encode(user.getPassword()));

        // 3. 如果角色为空, 给个默认USER角色
        if (user.getRoles() == null || user.getRoles().isEmpty()) {
   
            user.setRoles(Set.of("USER"));
        }

        // 4. 保存到数据库
        return userRepository.save(user);
    }

    public Optional<User> getUserByUsername(String username) {
   
        return userRepository.findByUsername(username);
    }
}

注意:之所以在这里用 passwordEncoder.encode(...),就是为了让数据库里只保存 BCrypt 哈希之后的密码,避免明文泄露。

3.6 UserDetailsServiceImpl

这是 Spring Security“加载用户信息”的关键类。当用户在登录页面提交了用户名 + 密码时,DaoAuthenticationProvider 会调用这个类的 loadUserByUsername() 来获取用户的完整信息(包括密码、角色)。

如果找不到,抛出 UsernameNotFoundException,登录就会失败;如果找到,就把角色信息一并返回给 Security 进行下一步的密码验证和权限判断。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
   

    @Autowired
    private UserRepository userRepository;

    /**
     * 根据用户名加载用户信息
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   
        // 查找数据库中的 User
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));

        // 转换成 Spring Security 自带的 UserDetails 对象
        return org.springframework.security.core.userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                // roles() 会自动给每个角色加 "ROLE_" 前缀
                .roles(user.getRoles().toArray(new String[0]))
                .build();
    }
}

代码逐句解读

  1. UserDetailsService:Spring Security 官方规定的“用户信息读取”接口,必须实现 loadUserByUsername()
  2. UserRepository.findByUsername(...):从数据库查用户,如果没有就抛异常。
  3. org.springframework.security.core.userdetails.User.builder():这是一个工具类,把数据库里取到的用户信息(密码+角色)转换成 Spring Security 需要的 UserDetails
  4. .roles(...):接收角色数组,会自动拼上 ROLE_ 前缀,比如 ADMIN -> ROLE_ADMIN,这是 Spring Security 区分角色的惯例。

3.7 前端或 Controller

上面我们已经写完了所有核心的后端安全逻辑。在实际项目中,前端会访问这些接口,比如:

  • POST /api/users/register:注册用户
  • GET /login:登录页面
  • POST /perform_login:提交登录表单
  • GET /admin/**:只有管理员能访问

在 Controller 里,你可以写各种接口,比如:

@RestController
@RequestMapping("/api/users")
public class UserController {
   

    @Autowired
    private UserService userService;

    @PostMapping("/register")
    public ResponseEntity<?> register(@RequestBody User user) {
   
        try {
   
            User saved = userService.registerUser(user);
            return ResponseEntity.ok(saved);
        } catch (Exception e) {
   
            return ResponseEntity.badRequest().body(e.getMessage());
        }
    }

    @GetMapping("/{username}")
    public ResponseEntity<?> getUserInfo(@PathVariable String username) {
   
        return userService.getUserByUsername(username)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
}

与 Security 关联

  • 如果在 SecurityConfig 里限制了 /api/users/** 只能由登录用户访问,那么未登录访问就会被重定向到登录页面。
  • 如果只是注册开放,则要把 /api/users/register 配置成 permitAll()

四、Spring Security 运行流程(从请求到响应的全过程)

  1. 客户端发起登录请求:访问 /login,提交用户名、密码到 /perform_login
  2. Spring Security 拦截请求UsernamePasswordAuthenticationFilter 读取表单中的 username、password。
  3. 调用 UserDetailsServiceImpl:根据 username 去数据库查用户。
  4. 密码校验:用 PasswordEncoder(BCrypt) 对前端提交的明文密码做哈希,与数据库里的哈希做匹配。
  5. 认证成功:如果匹配成功,Spring Security 会生成 Authentication 对象放进 SecurityContextHolder
  6. 跳转成功页面/sales 或者 /home,这时用户已登录。
  7. 访问其他受保护资源:当用户带着会话(session) 或者 SecurityContext 再次请求受保护的 URL 时,会检查是否有ROLE_ADMIN/ROLE_USER等。若权限足够就访问成功,否则403拒绝。

五、常见进阶操作

  1. Method-Level Security:在方法上用注解 @PreAuthorize("hasRole('ADMIN')") 做权限控制,需要启用全局方法安全:

    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig {
          ... }
    

    然后在 Service 或 Controller 方法上写注解即可。

  2. CSRF Protection:生产环境可能需要开启 CSRF 并配置白名单(对于 AJAX / API 调用如何携带 CSRF Token 等)。

  3. Remember-Me:当用户勾选“记住我”,下次不登录也能保持身份,会在 Cookie 中存储一个持久化令牌。

  4. JWT:在分布式微服务中,可能用 JWT 替代 Session 做无状态认证,需要自己配置 OncePerRequestFilter 等。

  5. OAuth2:集成社交登录(如 GitHub、Google),需要使用 spring-boot-starter-oauth2-client 并做相应配置。

六、总结

  1. Spring Security 的核心离不开

    • FilterChain:把请求一级级过滤,进行认证、授权、CSRF 防护等。
    • AuthenticationProvider + UserDetailsService:决定了“如何加载用户”以及“如何校验密码”。
  2. 常见配置都在 SecurityConfig 里:区分哪些请求开放、哪些请求需登录、哪些需 ADMIN 角色等。

  3. 数据库表设计:关键是存储用户的角色、密码。Spring Security 不需要复杂的表结构,但要保证有一个唯一标识(用户名或邮箱)和一个加密密码。

  4. 自定义扩展也很灵活**:你可以改用手机号码登录、加多重验证(MFA)、编写自定义过滤器等,都是在 Spring Security 提供的框架之上扩展。

拥有了以上这套设计,一个基本的用户认证和权限管理系统就搭建完毕啦。之后你可以根据业务需求,深挖细节和安全策略(比如 XSS 防护、Remember-Me、单点登录 SSO 等),让系统更加健壮。

祝你开发愉快!(๑•̀ㅂ•́)و✧

目录
相关文章
|
2月前
|
人工智能 搜索推荐 Java
Spring AI与DeepSeek实战三:打造企业知识库
本文基于Spring AI与RAG技术结合,通过构建实时知识库增强大语言模型能力,实现企业级智能搜索场景与个性化推荐,攻克LLM知识滞后与生成幻觉两大核心痛点。
323 7
|
1月前
|
存储 人工智能 Java
Spring AI与DeepSeek实战四:系统API调用
在AI应用开发中,工具调用是增强大模型能力的核心技术,通过让模型与外部API或工具交互,可实现实时信息检索(如天气查询、新闻获取)、系统操作(如创建任务、发送邮件)等功能;本文结合Spring AI与大模型,演示如何通过Tool Calling实现系统API调用,同时处理多轮对话中的会话记忆。
376 57
|
1月前
|
缓存 安全 Java
深入解析HTTP请求方法:Spring Boot实战与最佳实践
这篇博客结合了HTTP规范、Spring Boot实现和实际工程经验,通过代码示例、对比表格和架构图等方式,系统性地讲解了不同HTTP方法的应用场景和最佳实践。
150 5
|
2月前
|
人工智能 自然语言处理 前端开发
Spring AI与DeepSeek实战二:打造企业级智能体
本文介绍如何基于Spring AI与DeepSeek模型构建企业级多语言翻译智能体。通过明确的Prompt设计,该智能体能自主执行复杂任务,如精准翻译32种ISO标准语言,并严格遵循输入格式和行为限制。代码示例展示了如何通过API实现动态Prompt生成和翻译功能,确保服务的安全性和可控性。项目已开源,提供更多细节和完整代码。 [GitHub](https://github.com/zlt2000/zlt-spring-ai-app) | [Gitee](https://gitee.com/zlt2000/zlt-spring-ai-app)
259 11
|
1月前
|
存储 安全 Java
Spring Security 入门与详解
Spring Security 是 Spring 框架中的核心安全模块,提供认证、授权及防护功能。本文详解其核心概念,包括认证(Authentication)、授权(Authorization)和过滤器链(Security Filter Chain)。同时,通过代码示例介绍基本配置,如 PasswordEncoder、UserDetailsService 和自定义登录页面等。最后总结常见问题与解决方法,助你快速掌握 Spring Security 的使用与优化。
162 0
|
2月前
|
人工智能 Java API
Spring AI与DeepSeek实战一:快速打造智能对话应用
在 AI 技术蓬勃发展的今天,国产大模型DeepSeek凭借其低成本高性能的特点,成为企业智能化转型的热门选择。而Spring AI作为 Java 生态的 AI 集成框架,通过统一API、简化配置等特性,让开发者无需深入底层即可快速调用各类 AI 服务。本文将手把手教你通过spring-ai集成DeepSeek接口实现普通对话与流式对话功能,助力你的Java应用轻松接入 AI 能力!虽然通过Spring AI能够快速完成DeepSeek大模型与。
606 11
|
3月前
|
JavaScript 前端开发 Java
Jeesite5:Star24k,Spring Boot 3.3+Vue3实战开源项目,架构深度拆解!让企业级项目开发效率提升300%的秘密武器
Jeesite5 是一个基于 Spring Boot 3.3 和 Vue3 的企业级快速开发平台,集成了众多优秀开源项目,如 MyBatis Plus、Bootstrap、JQuery 等。它提供了模块化设计、权限管理、多数据库支持、代码生成器和国际化等功能,极大地提高了企业级项目的开发效率。Jeesite5 广泛应用于企业管理系统、电商平台、客户关系管理和知识管理等领域。通过其强大的功能和灵活性,Jeesite5 成为了企业级开发的首选框架之一。访问 [Gitee 页面](https://gitee.com/thinkgem/jeesite5) 获取更多信息。
121 0
Jeesite5:Star24k,Spring Boot 3.3+Vue3实战开源项目,架构深度拆解!让企业级项目开发效率提升300%的秘密武器
|
5月前
|
存储 安全 Java
Spring Security 入门
Spring Security 是 Spring 框架中的安全模块,提供强大的认证和授权功能,支持防止常见攻击(如 CSRF 和会话固定攻击)。它通过过滤器链拦截请求,核心概念包括认证、授权和自定义过滤器。配置方面,涉及密码加密、用户信息服务、认证提供者及过滤器链设置。示例代码展示了如何配置登录、注销、CSRF防护等。常见问题包括循环重定向、静态资源被拦截和登录失败未返回错误信息,解决方法需确保路径正确和添加错误提示逻辑。
326 2
Spring Security 入门
|
4月前
|
人工智能 自然语言处理 Java
Spring Cloud Alibaba AI 入门与实践
本文将介绍 Spring Cloud Alibaba AI 的基本概念、主要特性和功能,并演示如何完成一个在线聊天和在线画图的 AI 应用。
791 7
|
5月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
163 7
Spring Boot 入门:简化 Java Web 开发的强大工具