Spring Security 入门:手把手教你搭建安全认证系统

AI算法
2025-12-28 04:44
阅读 789

大家好,我是老李,一名从985高校毕业、现在在一线大厂做后端开发的全栈工程师。平时除了写业务代码,我也喜欢在掘金分享一些新手友好的技术教程。今天这篇《Spring Security 入门:手把手教你搭建安全认证系统》就是专门为完全零基础的同学准备的。

我当初学 Spring Security 的时候,被各种 Filter、Provider、AuthenticationManager 搞得晕头转向。网上很多教程一上来就讲 OAuth2、JWT,结果连最基础的“怎么让用户登录”都搞不明白。所以今天,我就用最朴实的语言,带你从零开始,快速搭建一个带用户名密码登录的安全系统。


为什么需要 Spring Security?

想象一下:你写了一个后台管理系统,任何人都能直接访问 /admin/deleteAllUsers 接口——这显然不行!我们需要一套机制来:

  • 验证用户身份(你是谁?)
  • 控制权限(你能做什么?)

Spring Security 就是 Spring 家族中专门负责安全控制的框架。它能帮你轻松实现登录认证、权限校验、防 CSRF 攻击等功能。

📌 注意:虽然标题里提到了 Java、Python、面试题、算法,但本文核心是 Java + Spring Security。Python 和算法会在文末学习建议中关联,面试题也会穿插讲解。


环境准备:5 分钟搭好开发环境

你需要以下工具:

工具 版本建议 说明
JDK 17 或 21 推荐使用 LTS 版本
Maven 3.8+ 项目依赖管理
IDE IntelliJ IDEA 社区版即可
Spring Boot 3.x 本文基于 3.2

第一步:创建 Spring Boot 项目

打开 start.spring.io,选择:

  • Project: Maven
  • Language: Java
  • Spring Boot: 3.2.x
  • Dependencies: Spring Web, Spring Security

点击 “Generate” 下载 ZIP,解压后用 IDEA 打开。

第二步:验证项目是否跑起来

src/main/java/com/example/demo 下新建一个控制器:

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello, World!";
    }
}

启动应用,访问 http://localhost:8080/hello

你会发现:页面跳转到了登录页! 而且用户名是 user,密码在控制台输出(类似 Using generated security password: a1b2c3d4-...)。

这就是 Spring Security 的“默认保护”——任何接口都会被拦截,除非你配置放行。


核心概念:3 个关键词搞懂 Spring Security

别被术语吓到,其实就三件事:

1. 认证(Authentication)—— “你是谁?”

比如用户输入用户名 alice 和密码 123456,系统验证是否正确。

2. 授权(Authorization)—— “你能干什么?”

比如只有 ADMIN 角色才能删除用户,普通用户只能查看。

3. 安全上下文(SecurityContext)—— “记住你是谁”

一旦登录成功,Spring 会把你的身份信息存到 SecurityContext 中,后续请求可以直接获取。

💡 我当初学的时候总混淆“认证”和“授权”。记住:先认证,再授权。就像进公司:先刷工卡(认证),再看你能进哪些楼层(授权)。


实战:自定义用户名密码登录

默认的随机密码没法用,我们来改成固定账号。

步骤 1:创建配置类

新建 SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll() // 放行公开接口
                .anyRequest().authenticated()              // 其他所有请求都要认证
            )
            .formLogin(form -> form
                .loginPage("/login")        // 自定义登录页(可选)
                .permitAll()
            )
            .logout(logout -> logout
                .permitAll()
            );
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.builder()
            .username("alice")
            .password("{noop}123456") // {noop} 表示不加密
            .roles("USER")
            .build();
        return new InMemoryUserDetailsManager(user);
    }
}

🔍 关键点解释:

  • {noop}123456:Spring Security 默认要求密码加密,{noop} 表示“明文”,仅用于开发!
  • InMemoryUserDetailsManager:把用户存在内存里,适合演示。真实项目要用数据库。

步骤 2:测试登录

重启应用,访问 http://localhost:8080/hello,会被重定向到 /login

输入:

  • 用户名:alice
  • 密码:123456

登录成功后,就能看到 "Hello, World!"。


进阶:从内存用户到数据库用户

实际项目肯定不能写死账号。下面我们改成从数据库读取。

1. 添加依赖

pom.xml 中加入:

<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>

2. 创建用户实体

@Entity
@Table(name = "users")
public class AppUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private String role; // 简化:只存一个角色
    // getter/setter 略
}

3. 实现 UserDetailsService

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        AppUser user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        return User.builder()
            .username(user.getUsername())
            .password("{noop}" + user.getPassword()) // 依然用明文,生产务必加密!
            .roles(user.getRole())
            .build();
    }
}

4. 替换配置中的 Bean

SecurityConfig 中,把原来的 userDetailsService() 方法删掉,改为:

@Autowired
private CustomUserDetailsService customUserDetailsService;

@Bean
public UserDetailsService userDetailsService() {
    return customUserDetailsService;
}

5. 初始化数据

application.properties 中加:

spring.sql.init.mode=always

然后在 src/main/resources 下创建 schema.sqldata.sql

-- schema.sql
CREATE TABLE users (id INT PRIMARY KEY, username VARCHAR(50), password VARCHAR(50), role VARCHAR(20));

-- data.sql
INSERT INTO users VALUES (1, 'bob', 'bob123', 'ADMIN');

重启后,用 bob / bob123 登录即可。


常见问题 & 避坑指南

❌ 问题 1:登录总是失败,提示“Bad credentials”

原因:Spring Security 默认要求密码加密(如 BCrypt)。如果你存的是明文,必须加 {noop} 前缀。

解决方案:开发阶段用 {noop},上线前务必改用 PasswordEncoder

❌ 问题 2:静态资源(CSS/JS)也被拦截了

原因:Security 配置拦截了所有请求。

解决方案:在 authorizeHttpRequests 中放行静态资源:

.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()

❌ 问题 3:如何获取当前登录用户?

解决方案:在 Controller 中注入:

@GetMapping("/me")
public String getCurrentUser(Authentication auth) {
    return "当前用户: " + auth.getName();
}

或者用 SecurityContextHolder

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

学习建议:下一步该学什么?

你现在掌握了 Spring Security 最基础的认证功能。接下来可以:

  1. 密码加密:学习 BCryptPasswordEncoder,替换 {noop}
  2. JWT 集成:适用于前后端分离项目(这时候你会用到 Python 写的测试脚本 来模拟请求)。
  3. 方法级权限:用 @PreAuthorize("hasRole('ADMIN')") 控制方法访问。
  4. OAuth2 / 微信登录:实现第三方登录。

关于面试题 & 算法

Spring Security 是 Java 后端面试高频考点!常见问题包括:

  • Spring Security 的过滤器链有哪些?
  • 如何自定义登录逻辑?
  • CSRF 是什么?如何防御?

算法 能力决定了你能否设计高效的安全策略(比如限流、验证码生成)。建议刷 LeetCode 简单/中等题,重点掌握哈希、树、图——这些在权限模型(RBAC)中都会用到。

🌟 我的建议:先搞定基础认证 → 再学 JWT → 最后挑战 OAuth2。每一步都写个小项目,比死记硬背强十倍。


结语

Spring Security 看似复杂,其实核心就两点:认证 + 授权。只要你理解了这个主线,剩下的都是配置细节。

希望这篇教程能帮你少走弯路。如果你觉得有用,欢迎点赞收藏,也欢迎在评论区提问——我当初也是从零开始,深知新手的困惑。

下期预告:《Spring Security + JWT:打造无状态认证系统》,我们不见不散!

评论 0

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