零基础也能搞懂:用Spring Security三步搭建登录系统

Django老掌柜
2026-01-05 13:45
阅读 1288

大家好,我是一个从中文系转行做后端开发的“野生程序员”。当初学 Spring Security 的时候,光是“认证”和“授权”这两个词就让我翻了半小时字典。更别说那些 AuthenticationManagerUserDetailsService 之类的术语,简直像天书。但今天我想告诉你:安全框架没那么可怕。只要理解核心逻辑,再配合一点动手实践,你完全可以在一个下午搭出自己的登录系统。

这篇文章就是写给像我当年一样——对 Java 有基础(会写 Hello World 级别的代码)、但从未接触过安全模块的你。我们会用最直白的语言,避开复杂的理论推导,直接上手做一个能跑起来的“产品级”认证功能。顺便说一句,虽然我们做的是后端安全,但最后也会和前端 JavaScript 打个照面,毕竟真正的产品从来不是孤立存在的。


为什么你需要 Spring Security?

想象一下:你做了一个博客网站,用户能发文章。如果没有安全机制,任何人都可以删掉别人的文章,甚至冒充管理员——这显然不行。

Spring Security 就是帮你自动拦截“坏人”、放行“好人”的守门员。它能处理:

  • 用户登录(认证)
  • 权限控制(比如只有作者能编辑自己的文章)
  • 防止 CSRF 攻击(一种伪造用户操作的黑客手段)
  • 会话管理(登录状态保持)

而你不需要从零造轮子,只需配置几行代码。


第一步:环境准备(5分钟搞定)

⚠️ 前提:你已安装 JDK 17+ 和 Maven(或 Gradle),并会用 IDEA 或 VS Code 创建 Spring Boot 项目。

1. 创建 Spring Boot 项目

访问 https://start.spring.io,选择:

  • Project: Maven
  • Language: Java
  • Spring Boot: 3.x(最新稳定版)
  • Dependencies: Spring Web, Spring Security

点击 Generate,下载 ZIP 并解压导入 IDE。

2. 检查依赖(pom.xml)

确保你的 pom.xml 包含以下关键依赖:

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

3. 启动试试看

创建主类 SecurityDemoApplication.java

@SpringBootApplication
public class SecurityDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SecurityDemoApplication.class, args);
    }
}

运行后访问 http://localhost:8080 —— 咦?怎么跳到一个登录页了?

别慌!这是 Spring Security 的默认行为:只要引入依赖,所有接口自动被保护,需要登录才能访问。

控制台会打印一行密码,形如:

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

用户名是 user,密码就是上面那串。你可以用它登录。

💡 我当初以为这是 bug,其实是 feature!Spring Security 默认开启“基本保护”,防止你上线后忘加安全措施。


第二步:搞懂三个核心概念(用大白话说)

在深入代码前,先理清三个关键词:

术语 通俗解释 类比
认证(Authentication) “你是谁?” 刷脸进公司大门
授权(Authorization) “你能干什么?” 工牌权限:普通员工不能进机房
凭证(Credentials) 登录时提供的信息 身份证 + 密码

Spring Security 的工作流很简单:

  1. 用户提交用户名/密码(凭证)
  2. 系统验证是否合法(认证)
  3. 如果合法,记录用户角色(如 ADMIN, USER)
  4. 后续请求根据角色决定能否访问资源(授权)

第三步:实战!搭建自定义登录系统

默认的随机密码显然不能用于真实产品。下面我们来实现:

  • 自定义用户名密码
  • 返回 JSON 格式的登录结果(方便前端 JavaScript 调用)
  • 区分普通用户和管理员权限

1. 配置用户信息

创建配置类 SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public UserDetailsService userDetailsService() {
        // 内存中存储两个用户(仅用于演示!真实项目要用数据库)
        UserDetails user = User.builder()
                .username("alice")
                .password("{noop}123456") // {noop} 表示不加密
                .roles("USER")
                .build();

        UserDetails admin = User.builder()
                .username("bob")
                .password("{noop}admin123")
                .roles("ADMIN", "USER")
                .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // 暂时关闭CSRF(简化前端调用)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()     // 公开接口
                .requestMatchers("/admin/**").hasRole("ADMIN") // 管理员专属
                .anyRequest().authenticated()                 // 其他需登录
            )
            .formLogin(form -> form.disable()) // 禁用默认表单登录
            .httpBasic(httpBasic -> httpBasic.disable()); // 禁用浏览器弹窗

        return http.build();
    }
}

🔑 关键点说明:

  • {noop} 表示密码明文存储(仅开发用!生产必须加密
  • hasRole("ADMIN") 会自动匹配带 ROLE_ 前缀的角色(Spring Security 约定)
  • 关闭 CSRF 是为了方便前后端分离调试(后续可重新开启)

2. 创建登录接口(供 JavaScript 调用)

新建控制器 AuthController.java

@RestController
public class AuthController {

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        // 实际项目中这里应触发认证流程
        // 为简化,我们直接返回成功(Spring Security 会在过滤器链中处理)
        return ResponseEntity.ok(Map.of("message", "Login successful"));
    }

    // 登录请求体
    public static class LoginRequest {
        private String username;
        private String password;
        // getter/setter 省略(可用 Lombok 简化)
    }
}

等等,这个接口好像没做什么?其实 Spring Security 的魔法发生在“过滤器链”中。当我们发送 POST /login 请求时,Security 会自动捕获并验证凭证。

但为了让前端 JavaScript 能拿到 token 或 session,我们需要自定义认证成功/失败处理器

3. 返回 JSON 格式的登录结果

修改 SecurityConfig.java 中的 filterChain 方法,加入:

http
    // ... 其他配置
    .formLogin(form -> form
        .loginProcessingUrl("/login") // 指定登录接口
        .successHandler((request, response, authentication) -> {
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write("{\"status\":\"success\",\"username\":\"" 
                + authentication.getName() + "\"}");
        })
        .failureHandler((request, res, exception) -> {
            res.setContentType("application/json;charset=utf-8");
            res.getWriter().write("{\"status\":\"error\",\"message\":\"Invalid credentials\"}");
        })
    );

现在,前端可以用 JavaScript 发送登录请求:

// 前端 JavaScript 示例(如 Vue/React/Axios)
fetch('/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ username: 'alice', password: '123456' })
})
.then(res => res.json())
.then(data => {
  if (data.status === 'success') {
    alert('欢迎, ' + data.username);
    // 跳转首页等操作
  }
});

✅ 这样,你的后端就变成了一个标准的 RESTful 认证服务,前端无论用 Vue、React 还是原生 JS 都能轻松对接。


第四步:测试你的安全系统

启动应用,用 curl 或 Postman 测试:

场景1:公开接口(无需登录)

curl http://localhost:8080/public/info
# 应返回 200(需先创建对应接口)

场景2:普通用户访问受限接口

# 先登录
curl -X POST -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"123456"}' \
  http://localhost:8080/login

# 再访问普通接口(假设你有一个 /user/profile)
curl http://localhost:8080/user/profile
# 应成功

# 尝试访问管理员接口
curl http://localhost:8080/admin/dashboard
# 应返回 403 Forbidden

场景3:管理员访问

用 bob / admin123 登录后,即可访问 /admin/** 路径。


新手常踩的坑 & 解决方案

问题 原因 解决方法
登录一直失败 密码未加 {noop} 前缀 明文密码必须写成 {noop}yourpass
接口返回 403 而非 401 权限不足 vs 未登录混淆 401=没登录,403=登录了但没权限
CSRF 错误 前端未携带 token 开发阶段可暂时关闭 CSRF(如上文),上线前务必开启
角色不生效 忘记加 ROLE_ 前缀 .roles("ADMIN") 会自动变成 ROLE_ADMIN
静态资源被拦截 Security 拦截了 /css, /js authorizeHttpRequests 中放行 /static/**

🛑 特别注意:永远不要在生产环境使用 {noop}
正确做法是用 PasswordEncoder 加密:

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

存储密码时先加密:passwordEncoder.encode("123456")


下一步学习建议

你已经掌握了 Spring Security 的“骨架”。接下来可以:

  1. 接入数据库
    InMemoryUserDetailsManager 替换为从 MySQL 读取用户
  2. JWT 无状态认证
    适合移动端或微服务架构,避免 Session 管理
  3. OAuth2 集成
    支持微信、GitHub 等第三方登录
  4. 细粒度权限控制
    @PreAuthorize("hasPermission(...)") 控制方法级权限

我当初花了两周才理清这些概念。但只要你记住:Security = 拦截器 + 规则配置,就不会迷失在细节里。


结语:安全不是附加功能,而是产品底线

作为转行者,我深刻体会到:很多初学者把安全当成“高级话题”,想等业务做完再加。但现实是——没有安全的系统等于没有系统。Spring Security 虽然初期有点陡峭,但它能让你用最少的代码守住产品底线。

今天你搭建的这个简单登录系统,已经比市面上许多“裸奔”的初创产品强多了。下一步,不妨把它集成到你的个人项目中,让 JavaScript 前端和 Java 后端真正协作起来,打造一个完整的产品闭环

记住:每个大神,都曾对着 403 Forbidden 发呆过。你现在的每一步,都在缩短与他们的距离。加油!

评论 0

最热最新
暂无评论
Django老掌柜Lv.1
0
影响力
0
文章
0
粉丝