Spring Security 基础:快速搭建安全认证系统(零基础入门教程)

青山不改需求改
2025-12-18 06:43
阅读 2058

作者简介:我是某大厂后端开发工程师,有3年 Java 后端经验,业余时间在 B 站做技术 UP 主。今天这篇教程,是我结合自己当初学 Spring Security 的踩坑经历,以及带实习生、面试应届生时看到的常见问题,专门写给完全零基础的新手朋友的一份入门指南。


为什么你要学 Spring Security?它和你的简历、面试有什么关系?

我当初学的时候,以为“用户登录”就是写个 if (password == "123456") 就完事了。结果第一次实习就被导师狠狠教育了一顿:“你这代码连盐都没加,密码明文存数据库,黑客一拖库你就完了!”

后来我才明白:安全不是功能,而是底线

Spring Security 是 Java 生态中最主流的安全框架,几乎所有中大型 Java 后端项目都会用到它。无论你是想进大厂、还是想做企业级开发,掌握它几乎是必选项。

  • 📌 简历上写一句“熟悉 Spring Security”,比写“会用 HashMap”更能打动面试官。
  • 💬 面试题高频考点:比如“Spring Security 的认证流程?”、“如何自定义登录逻辑?”、“JWT 和 Session 的区别?”——这些我都将在本文中用最直白的方式讲清楚。
  • 🧠 思维升级:你会从“能跑就行”转向“安全可靠”,这是从学生思维到工程师思维的关键一步。

📝 小提醒:虽然本文讲的是 Java 的 Spring Security,但如果你主攻 Python,也别划走!安全的核心思想是相通的。Django 有 django.contrib.auth,Flask 有 Flask-Login,理解原理后迁移非常快。而且很多公司后端技术栈是混合的,懂一点 Java 安全体系对你的职业发展很有帮助。


第一步:环境准备(手把手教你搭起来)

我们用最简单的组合:Spring Boot + Spring Security + Maven + JDK 17

所需工具清单

工具 版本建议 说明
JDK 17 或 21 推荐使用 LTS 版本
IDE IntelliJ IDEA 免费社区版即可
构建工具 Maven 本文使用 Maven,Gradle 类似
浏览器 Chrome / Edge 用于测试登录页面

创建项目(30秒搞定)

  1. 打开 https://start.spring.io
  2. 填写以下信息:
    • Project: Maven
    • Language: Java
    • Spring Boot: 3.2.x(最新稳定版)
    • Group: com.example
    • Artifact: security-demo
  3. Dependencies 搜索框中添加:
    • Spring Web
    • Spring Security
  4. 点击 Generate 下载 ZIP 包,解压后用 IDEA 打开。

✅ 项目结构应该是这样的:

security-demo/
├── src/
│   └── main/
│       ├── java/com/example/securitydemo/
│       │   └── SecurityDemoApplication.java
│       └── resources/
└── pom.xml

验证环境是否成功

启动 SecurityDemo Application,访问 http://localhost:8080

你会发现:自动跳转到了一个登录页面!

这就是 Spring Security 的“默认保护”机制——哪怕你一行代码没写,它也会保护所有接口!

默认用户名是 user,密码在控制台日志里,类似:

Using generated security password: a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8

⚠️ 注意:这个密码每次启动都会变!仅用于开发测试。


第二步:核心概念扫盲(用大白话解释专业词)

很多新手被“认证”、“授权”、“过滤器链”这些词吓退。其实它们很简单:

1. 认证(Authentication) vs 授权(Authorization)

  • 认证 = 你是谁?
    比如:输入用户名密码 → 系统确认你是张三。
  • 授权 = 你能干什么?
    比如:张三只能看自己的订单,不能删别人的。

一句话记住:先认证,再授权。

2. Spring Security 的工作原理(简化版)

想象你进公司大楼:

  1. 门口保安(Security Filter Chain)拦住你
  2. 问你要工牌(UsernamePasswordAuthenticationToken
  3. 他去 HR 系统查你是不是员工(UserDetailsService
  4. 如果是,给你发临时门禁卡(SecurityContext
  5. 之后你去任何部门(访问 API),门禁系统自动识别你权限

整个过程对你是透明的——你只关心“能不能进”,不关心保安怎么查你。

3. 关键组件速览

组件 作用 类比
UserDetails 存储用户信息(用户名、密码、角色等) 你的电子工牌
UserDetailsService 根据用户名查用户数据 HR 数据库查询接口
PasswordEncoder 密码加密/校验 密码保险柜
SecurityFilterChain 定义哪些路径需要保护 大楼安防策略

第三步:实战!5分钟搭建自己的登录系统

现在我们来替换掉 Spring Security 的默认行为,实现自己的用户名密码登录。

步骤 1:创建用户实体类

// src/main/java/com/example/securitydemo/model/User.java
package com.example.securitydemo.model;

public class User {
    private String username;
    private String password; // 注意:这里存的是加密后的密码!
    private String role;     // 角色,如 "USER", "ADMIN"

    // 构造函数、getter/setter 省略(IDEA 可自动生成)
    public User(String username, String password, String role) {
        this.username = username;
        this.password = password;
        this.role = role;
    }
    
    // getter...
}

步骤 2:实现 UserDetailsService(查用户)

// src/main/java/com/example/securitydemo/service/CustomUserDetailsService.java
package com.example.securitydemo.service;

import com.example.securitydemo.model.User;
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;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

@Service
public class CustomUserDownLatchService implements UserDetailsService {

    // 模拟数据库(实际项目用 MyBatis/JPA 查询数据库)
    private static final Map<String, User> USER_DB = new HashMap<>();

    static {
        // 存两个用户,密码用 {noop} 表示不加密(仅演示!)
        USER_DB.put("alice", new User("alice", "{noop}123456", "USER"));
        USER_DB.put("admin", new User("admin", "{noop}admin123", "ADMIN"));
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = USER_DB.get(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在: " + username);
        }
        // 返回 Spring Security 要求的 UserDetails 对象
        return org.springframework.security.core.userdetails.User
                .withUsername(user.getUsername())
                .password(user.getPassword())
                .roles(user.getRole())
                .build();
    }
}

🔒 重要安全提示{noop} 表示“不加密”,绝对不能用于生产环境!后面我们会改成 BCrypt 加密。

步骤 3:配置 SecurityFilterChain(定义规则)

// src/main/java/com/example/securitydemo/config/SecurityConfig.java
package com.example.securitydemo.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.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@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();
    }

    // 仅用于演示!生产环境必须用 BCrypt
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

步骤 4:创建测试接口

// src/main/java/com/example/securitydemo/controller/TestController.java
@RestController
public class TestController {

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

    @GetMapping("/admin")
    public String admin() {
        return "Welcome, admin!";
    }

    @GetMapping("/public/info")
    public String publicInfo() {
        return "This is public info.";
    }
}

测试一下!

  1. 启动应用
  2. 访问 http://localhost:8080/public/info直接显示(因为 /public/** 放行了)
  3. 访问 http://localhost:8080/hello跳转到登录页
    • 用户名:alice,密码:123456 → 登录成功,显示 "Hello..."
  4. alice 访问 /admin403 Forbidden(因为 alice 不是 ADMIN)

✅ 恭喜!你已经实现了基础的认证 + 授权!


第四步:安全加固(从“能跑”到“靠谱”)

前面为了简化用了 {noop} 明文密码,现在我们把它改成 BCrypt 加密——这也是面试常问的点!

修改密码存储方式

  1. 删除 @Bean PasswordEncoder 中的 NoOpPasswordEncoder
  2. 替换为 BCrypt:
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
  1. 修改模拟数据库中的密码(用工具生成加密串):
// 在 main 方法里临时跑一次,生成加密密码
public static void main(String[] args) {
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    System.out.println(encoder.encode("123456"));   // 输出类似 $2a$10$...
    System.out.println(encoder.encode("admin123"));
}
  1. 把输出的加密串填回 USER_DB
static {
    USER_DB.put("alice", new User("alice", "$2a$10$...", "USER"));
    USER_DB.put("admin", new User("admin", "$2a$10$...", "ADMIN"));
}

💡 为什么用 BCrypt?

  • 它是慢哈希,防止暴力破解
  • 每次加密结果不同(带 salt),防彩虹表
  • Spring Security 原生支持,一行代码搞定

新手常见问题 & 避坑指南

❓ 问题1:为什么登录后访问接口还是跳登录页?

原因:前端没带 Cookie!Spring Security 默认用 Session 认证,浏览器会自动带 JSESSIONID Cookie,但 Postman 或 Axios 需要手动处理。

解决方案

  • 浏览器直接访问没问题
  • 用 Postman:先调登录接口,勾选 “Automatically persist cookies”
  • 前端用 Axios:设置 withCredentials: true

❓ 问题2:如何返回 JSON 而不是跳转页面?

默认是表单登录(跳 HTML 页面)。如果你做前后端分离,需要改成 JSON 响应:

.formLogin(form -> form
    .successHandler((request, response, authentication) -> {
        response.setContentType("application/json");
        response.getWriter().write("{\"code\":200, \"msg\":\"登录成功\"}");
    })
    .failureHandler((request, response, exception) -> {
        response.setContentType("application/json");
        response.getWriter().write("{\"code\":401, \"msg\":\"用户名或密码错误\"}");
    })
)

❓ 问题3:和 JWT 有什么区别?要不要学?

  • Session:服务端存状态(内存/Redis),适合传统 Web 应用
  • JWT:无状态,Token 自包含信息,适合 App、微服务

📌 建议:先掌握 Session 模式(本文内容),再学 JWT。面试官更看重你是否理解“状态管理”的本质,而不是只会用 JWT。


下一步学习建议(写进你的学习计划)

  1. 深入源码:读 UsernamePasswordAuthenticationFilter 源码,理解认证全流程
  2. 集成数据库:用 Spring Data JPA 连接 MySQL,替换 HashMap 模拟数据
  3. 权限粒度细化:学习 @PreAuthorize("hasRole('ADMIN')") 注解
  4. CSRF 防护:了解跨站请求伪造攻击及防御(Spring Security 默认开启)
  5. OAuth2.0:学微信/Google 第三方登录(大厂高频需求)

🎯 简历加分项:在 GitHub 上开源一个“基于 Spring Security 的 RBAC 权限管理系统”,哪怕只有基础功能,也能让面试官眼前一亮。


最后说两句

我当初学 Spring Security 时,也被那些抽象类、接口、过滤器搞晕过。但只要你记住:它只是帮你自动做了“验证身份 + 检查权限”这两件事,一切就清晰了。

安全没有银弹,但有最佳实践。希望这篇教程能成为你后端安全之路的第一块基石。

互动时间:如果你觉得有帮助,欢迎关注我的 B 站账号【你的B站ID】,我会持续更新《从零搭建企业级后端系统》系列视频。下期预告:《Spring Security + JWT 实现无状态认证》!

祝你编码无 bug,面试全通过! 🚀

评论 0

最热最新
暂无评论
青山不改需求改Lv.1
0
影响力
0
文章
0
粉丝