零基础搞定Spring Security安全认证系统

数据库守门员
2026-06-25 22:19
阅读 741

大家好,我是你们的老朋友,一个985毕业、常年在掘金写入门教程的全栈工程师,同时也是一名后端讲师。在我的教学过程中,发现很多初学者对“后端安全”这个概念既敬畏又迷茫。我当初学的时候也是一头雾水,看着满屏的过滤器和拦截器,根本不知道从何下手。

今天写这篇教程,就是为了帮大家打破这种恐惧。我们将用最简单的语言、最直接的代码,手把手带你快速搭建一个安全认证系统。无论你是刚接触Java的新手,还是想系统复习安全框架的开发者,这篇文章都能让你有所收获。

环境准备

在开始写代码之前,我们需要准备好开发环境。工欲善其事,必先利其器。

  1. JDK版本:推荐使用 JDK 17 或更高版本。Spring Boot 3.x 已经全面拥抱 JDK 17,这也是目前的行业主流。
  2. 构建工具:Maven 3.6+。
  3. 开发工具:你可以使用传统的 IntelliJ IDEA。不过,作为一名喜欢尝鲜的全栈工程师,我强烈推荐你试试最近很火的 AI 代码编辑器 Windsurf。它内置了非常强大的 AI 辅助编程能力,在你写 Spring Security 这种配置繁琐的框架时,AI 能帮你自动补全很多样板代码,极大提升效率。

环境准备好后,我们去 Spring Initializr 初始化一个项目。勾选以下依赖:

  • Spring Web
  • Spring Security
  • Lombok(用来简化实体类代码)

项目结构搭建好后,我们就正式进入核心概念的学习。

核心概念解析

在动手之前,我们必须搞懂几个核心概念。遇到不懂的技术名词,我建议大家多利用 AI 工具,比如用 文心一言 或者 豆包 来帮你生成通俗的解释,但一定要结合代码去验证,不能只停留在理论层面。

认证与授权的区别

这是新手最容易混淆的两个概念。我们用去高档小区拜访朋友来打个比方:

概念 英文 通俗解释 小区比喻
认证 Authentication 验证“你是谁”。证明你的身份是合法的。 保安查你的身份证,确认你是张三。
授权 Authorization 验证“你能干嘛”。确认你是否有权限做某事。 确认你是张三后,保安看你是否有进入某栋楼的门禁卡。

在 Spring Security 中,认证和授权是紧密相连的。只有先通过了认证(证明你是谁),系统才能给你分配相应的角色,进而进行授权(决定你能访问哪些接口)。

SecurityContext 与 Authentication

SecurityContext 是 Spring Security 的核心上下文,它就像一个保险箱,里面装着当前登录用户的所有安全信息。而 Authentication 就是保险箱里的核心凭证,它包含了用户的用户名、密码、以及拥有的权限列表。

当用户登录成功后,Spring Security 会把 Authentication 对象存入 SecurityContext 中。后续请求到来时,框架会直接从上下文里取出这个凭证进行校验。

FilterChain(过滤器链)

这是 Spring Security 的灵魂。你可以把它想象成机场的安检通道。

[客户端请求] 
   ↓
[UsernamePasswordAuthenticationFilter] (处理表单登录)
   ↓
[BasicAuthenticationFilter] (处理HTTP Basic认证)
   ↓
[ExceptionTranslationFilter] (处理认证/授权异常)
   ↓
[FilterSecurityInterceptor] (核心授权拦截器,检查是否有权限)
   ↓
[目标 Controller]

每一个 Filter 负责处理一种特定的安全逻辑。请求必须像过安检一样,一层一层通过这些过滤器,最终才能到达我们的业务代码。

实战项目:搭建安全认证系统

理论讲完了,我们直接上代码。这里顺便提一句,虽然咱们今天学的是 Java 的 Spring Security,但如果你以后想学 Go 语言的 Gin 框架,里面的中间件认证思想也是完全互通的,底层逻辑都是拦截和校验,学会了 Spring Security 的精髓,转其他语言也会非常快。

第一步:引入依赖与基础配置

pom.xml 中确保已经引入了 Spring Security 依赖。然后我们创建一个配置类,这是 Spring Security 6.x 版本的推荐写法。

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.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 禁用 CSRF,为了方便我们使用 Postman 测试,实际生产环境建议开启
            .csrf(csrf -> csrf.disable())
            // 配置请求授权规则
            .authorizeHttpRequests(auth -> auth
                // 放行登录接口和公共接口
                .requestMatchers("/api/login", "/api/public/**").permitAll()
                // 其他所有请求都需要认证
                .anyRequest().authenticated()
            )
            // 配置表单登录(Spring Security 会自带一个默认的登录页面)
            .formLogin(form -> form
                .loginProcessingUrl("/api/login")
                .permitAll()
            );
            
        return http.build();
    }
}

第二步:自定义用户信息服务

在实际项目中,用户信息通常存在数据库里。为了快速演示,我们先使用内存模式。我们需要实现 UserDetailsService 接口。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;

@Configuration
public class UserDetailsConfig {

    // 密码编码器,Spring Security 强制要求密码必须加密存储
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        // 创建一个内存中的用户
        UserDetails user = User.builder()
                .username("admin")
                // 注意:这里必须使用 PasswordEncoder 加密后的密码
                .password(passwordEncoder().encode("123456"))
                .roles("ADMIN")
                .build();

        UserDetails normalUser = User.builder()
                .username("user")
                .password(passwordEncoder().encode("123456"))
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(user, normalUser);
    }
}

第三步:编写业务接口

我们写几个接口来测试我们的安全系统是否生效。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class TestController {

    // 公共接口,不需要登录即可访问
    @GetMapping("/public/hello")
    public String publicHello() {
        return "Hello, 这是一个公共接口!";
    }

    // 需要认证的接口
    @GetMapping("/private/hello")
    public String privateHello() {
        return "Hello, 你已经成功登录,这是私有接口!";
    }

    // 需要 ADMIN 角色才能访问的接口
    @GetMapping("/admin/dashboard")
    public String adminDashboard() {
        return "Hello Admin, 欢迎来到管理后台!";
    }
}

第四步:测试与验证

启动项目。当你直接访问 http://localhost:8080/api/private/hello 时,会被重定向到 Spring Security 默认的登录页面。

使用我们配置的 admin / 123456 登录后,再次访问该接口,就能成功看到返回的数据了。如果你尝试用 user 账号访问 /api/admin/dashboard,则会收到 403 Forbidden 错误,这就说明我们的授权逻辑生效了。

常见问题解答

我当初学的时候,在这里踩了无数的坑。为了让大家少走弯路,我总结了几个新手最常遇到的问题。

1. 为什么启动后控制台没有打印默认密码了?

在早期的 Spring Security 版本中,启动时控制台会打印一个默认的 user 密码。但在 Spring Security 5.7 之后,基于表单的默认安全配置被废弃了。如果你没有自定义 SecurityFilterChain,它依然会生成默认用户,但如果你像我们教程里一样自定义了配置,默认用户就不会生成了。所以,一定要自己配置 UserDetailsService

2. 访问接口一直报 403 Forbidden 怎么办?

遇到 403,不要慌,按以下三步排查:

  • 检查 CSRF:如果你是用 Postman 或 Ajax 发送 POST 请求,且没有携带 CSRF Token,就会被拦截。在测试阶段,可以在配置中加上 .csrf(csrf -> csrf.disable())
  • 检查权限配置:确认你的 authorizeHttpRequests 规则是否写错了,比如把需要放行的路径写成了需要认证。
  • 检查角色前缀:在代码中配置角色时写的是 roles("ADMIN"),Spring Security 底层会自动加上 ROLE_ 前缀。如果你在方法上使用 @PreAuthorize("hasRole('ADMIN')") 是没问题的,但如果用 hasAuthority('ADMIN') 就会报错,因为底层实际上是 ROLE_ADMIN

3. 密码必须加密吗?明文行不行?

绝对不行。Spring Security 6.x 强制要求使用 PasswordEncoder。如果你直接传明文密码,启动时或者登录时会直接抛出异常。必须使用 BCryptPasswordEncoder 等编码器对密码进行哈希处理。

学习建议与避坑指南

恭喜你,到这里你已经成功搭建了一个基础的 Spring Security 认证系统。但安全领域博大精深,这仅仅是个开始。作为你们的讲师,我给大家几点后续学习的建议:

  1. 不要死记硬背配置类:Spring Security 的配置类方法很多,不要试图背下来。理解 HttpSecurity 的链式调用本质,知道每个 Filter 的作用,用的时候查文档即可。
  2. 理解底层执行流程:遇到 Bug 时,学会打断点。把断点打在 FilterSecurityInterceptor 或者 ExceptionTranslationFilter 里,看看请求是怎么流转的,这比你看十遍博客都管用。
  3. 下一步学习路线:前后端分离是现在的绝对主流。接下来,你需要学习如何将 Spring Security 与 JWT (JSON Web Token) 结合,实现无状态的 Token 认证。再往后,可以挑战一下 OAuth2.0 协议,实现第三方登录(如微信、GitHub 登录)。

安全无小事,认证授权是后端开发的第一道大门。希望这篇教程能帮你推开这扇门。如果文章对你有帮助,欢迎在掘金给我点个赞,有任何问题,我们评论区见!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝