零基础也能学会:用Spring Security快速搭建Java安全认证系统

熔断背锅人
2026-05-03 14:37
阅读 1449

大家好,我是掘金上那个总爱写“保姆级教程”的985全栈工程师。今天想和大家聊聊Spring Security——一个让无数Java初学者又爱又怕的框架。

我当初学的时候,也是一头雾水:为什么登录要配置这么多类?为什么前端请求老是403?为什么明明用户名密码对了却进不去?后来我才明白,不是Spring Security难,而是没人用“人话”讲清楚它的核心逻辑。

所以今天这篇教程,不讲理论堆砌,只聚焦一件事:如何用最简单的方式,在你的Java项目里快速加上用户名密码登录功能,并让前端能正常调用。全程手把手,代码可直接运行!


为什么你需要Spring Security?

想象一下:你写了个后台管理系统,任何人都能访问用户列表、删除数据——这显然不行。Spring Security就是帮你做“谁可以访问什么”的守门人。它能自动拦截未登录请求、验证身份、防止CSRF攻击等。

而好消息是:Spring Boot + Spring Security 的组合,已经把90%的复杂配置自动化了。我们只需要几行代码,就能拥有一个带登录页的安全系统。


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

你需要安装:

  • JDK 17(推荐,兼容性最好)
  • IntelliJ IDEA(社区版免费)
  • Maven(IDEA内置,无需单独装)
  • 一个现代浏览器(Chrome/Firefox)

💡 避坑提示:别用JDK 8!虽然Spring Security支持,但新版本默认配置更简洁。JDK 17是当前LTS版本,强烈推荐。

创建项目

  1. 打开 start.spring.io
  2. 选择:
    • Project: Maven
    • Language: Java
    • Spring Boot: 3.x(最新稳定版)
    • Java: 17
  3. 添加依赖:
    • Spring Web
    • Spring Security
  4. 点击“Generate”,下载并解压到本地
  5. 用IDEA打开项目

✅ 此时你的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个关键词搞懂Spring Security

别被文档吓到,其实就三个核心角色:

角色 作用 类比
Authentication(认证) “你是谁?” 检查身份证
Authorization(授权) “你能干什么?” 查看通行证权限
SecurityFilterChain(过滤链) 所有请求先过这道安检门 机场安检通道

📌 重点:Spring Security默认会保护所有路径(/**),未认证用户访问会被重定向到自动生成的登录页。


实战:5步实现带登录的API接口

我们现在要实现:

  • 用户访问 /api/hello 必须登录
  • 前端通过表单提交用户名密码登录
  • 登录成功后返回JSON数据

第1步:创建一个受保护的接口

// src/main/java/com/example/demo/HelloController.java
package com.example.demo;

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

@RestController
public class HelloController {
    
    @GetMapping("/api/hello")
    public String hello() {
        return "你好!你已成功登录";
    }
}

第2步:配置安全规则(关键!)

// src/main/java/com/example/demo/SecurityConfig.java
package com.example.demo;

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

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/**").authenticated() // /api开头的路径需要认证
                .anyRequest().permitAll()                   // 其他路径(如登录页)放行
            )
            .formLogin(form -> form
                .loginPage("/login")        // 自定义登录页URL(可选)
                .permitAll()                // 登录页本身不需要认证
            )
            .logout(logout -> logout
                .permitAll()
            );
        return http.build();
    }

    // 内存中存储用户(仅演示用!生产环境要用数据库)
    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.builder()
            .username("admin")
            .password("{noop}123456") // {noop}表示不加密(仅测试!)
            .roles("USER")
            .build();
        return new InMemoryUserDetailsManager(user);
    }
}

🔑 关键解释

  • {noop}123456{noop}告诉Spring“密码没加密”,实际项目必须用PasswordEncoder加密!
  • .requestMatchers("/api/**").authenticated():只有带/api的路径才需要登录

第3步:启动项目,试试效果

  1. 运行 DemoApplication.main()
  2. 浏览器访问 http://localhost:8080/api/hello
  3. 自动跳转到登录页!

默认登录页长这样(Spring Security自动生成):

  • 用户名输入框
  • 密码输入框
  • 提交按钮

输入:

  • 用户名:admin
  • 密码:123456

登录成功后,自动跳回 /api/hello,显示:“你好!你已成功登录”

第4步:让前端调用更友好(返回JSON而非页面)

问题来了:前端通常是AJAX请求,不需要跳转页面,而是希望返回JSON错误信息

修改配置,支持JSON响应:

// 在SecurityConfig.java中新增
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
    return (request, response, authException) -> {
        response.setStatus(401);
        response.setContentType("application/json");
        response.getWriter().write("{\"error\":\"请先登录\"}");
    };
}

// 在filterChain方法中加入:
http
    .exceptionHandling(e -> e
        .authenticationEntryPoint(authenticationEntryPoint()) // 未认证时返回JSON
    )
    // ... 其他配置保持不变

现在前端用axios请求:

// 前端示例(Vue/React通用)
fetch('/api/hello')
  .then(res => {
    if (res.status === 401) {
      alert('请先登录!');
      // 跳转到登录页
    } else {
      return res.json();
    }
  });

第5步:处理登录成功/失败的JSON响应(可选进阶)

如果你希望登录也通过AJAX提交(而不是表单跳转),可以这样配置:

.formLogin(form -> form
    .successHandler((request, response, authentication) -> {
        response.setStatus(200);
        response.setContentType("application/json");
        response.getWriter().write("{\"status\":\"success\"}");
    })
    .failureHandler((request, response, exception) -> {
        response.setStatus(401);
        response.setContentType("application/json");
        response.getWriter().write("{\"error\":\"用户名或密码错误\"}");
    })
)

此时前端可这样登录:

// 登录请求
fetch('/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: 'username=admin&password=123456'
})
.then(res => res.json())
.then(data => {
  if (data.status === 'success') {
    // 登录成功,跳转页面
  }
});

新手常见问题解答

Q1:为什么密码要加 {noop}

A:Spring Security默认要求密码加密。{noop}是“No Operation”的缩写,表示“不要加密”。生产环境必须移除它,并使用BCrypt加密

Q2:如何用数据库存用户?

A:把 InMemoryUserDetailsManager 换成自定义的 UserDetailsService,从数据库查用户即可。后续我会单独写一篇《Spring Security + MyBatis实战》。

Q3:前端跨域怎么办?

A:在 SecurityConfig 中加入CORS配置:

http.cors(cors -> cors.configurationSource(request -> {
    var corsConfig = new CorsConfiguration();
    corsConfig.addAllowedOrigin("http://localhost:3000"); // 前端地址
    corsConfig.addAllowedMethod("*");
    corsConfig.addAllowedHeader("*");
    corsConfig.setAllowCredentials(true);
    return corsConfig;
}));

Q4:为什么登出后还能访问接口?

A:检查是否启用了Session。默认基于Session认证,登出即销毁Session。确保前端清除Cookie或Token。


下一步学习建议

  1. 密码加密:学习 BCryptPasswordEncoder,替换 {noop}
  2. JWT认证:适合前后端分离项目,替代Session
  3. 权限控制:用 @PreAuthorize("hasRole('ADMIN')") 控制接口权限
  4. OAuth2:接入微信/Google第三方登录

💡 我的经验:先跑通一个完整流程,再深入细节。很多新手卡在“完美配置”上,结果连第一个请求都没跑起来。记住:能跑起来的代码,才是好代码


结语

今天我们用不到100行代码,就搭建了一个带登录认证的Java后端系统,并让前端能友好地交互。Spring Security并没有想象中那么可怕,关键是理解它的“过滤链”思维。

如果你跟着做了一遍,恭喜你!你已经超过了80%只看不练的初学者。接下来,试着把用户数据换成数据库,或者加上验证码功能——实践才是掌握技术的唯一路径。

有问题欢迎在评论区留言,我会一一解答。下期见!

评论 0

最热最新
暂无评论
熔断背锅人Lv.1
0
影响力
0
文章
0
粉丝