从零开始用Spring Security保护你的Java应用
大家好,我是一名从中文系转行做后端开发的程序员。当初学Java的时候,面对各种框架简直头大,尤其是Spring Security这种听起来就很高大上的安全框架。记得第一次看到Authentication、Authorization这些英文单词时,我还以为要学密码学了!但其实,Spring Security并没有想象中那么难。今天我就用最通俗的语言,手把手带你搭建一个基础的安全认证系统。
为什么要学Spring Security?
在我们开发Web应用时,总会遇到这样的需求:有些页面只有登录用户才能访问,管理员能看到普通用户看不到的功能,用户的密码需要安全存储等等。这些都是应用安全的范畴。
Spring Security就是Spring生态中专门用来处理这些安全问题的框架。它能帮你轻松实现:
- 用户登录认证
- 权限控制(谁能访问什么资源)
- 密码加密存储
- 防止常见的安全攻击(比如CSRF)
虽然现在区块链技术很火,很多同学都想直接学区块链开发,但我建议先打好Web开发的基础。因为即使是区块链应用的后台管理系统,也需要用到这些基础的安全机制。技术学习要循序渐进,先把地基打牢!
开发环境准备
必备工具清单
| 工具 | 版本要求 | 说明 |
|---|---|---|
| JDK | 11或更高 | Java开发环境 |
| Maven | 3.6+ | 项目依赖管理 |
| IDE | IntelliJ IDEA或VS Code | 代码编辑器 |
| Spring Boot | 2.7.x或3.x | 快速开发框架 |
创建项目
我推荐使用Spring Initializr来快速创建项目。访问 https://start.spring.io,选择以下配置:
- Project: Maven Project
- Language: Java
- Spring Boot: 2.7.18 (稳定版本)
- Dependencies:
- Spring Web
- Spring Security
- Spring Boot DevTools (开发时自动重启)
点击"Generate"下载项目压缩包,解压后用IDE打开即可。
如果你喜欢命令行,也可以这样创建:
# 使用Spring Boot CLI(如果已安装)
spring init --dependencies=web,security my-security-app
# 或者手动创建pom.xml
项目结构预览
创建完成后,你会看到这样的基本结构:
my-security-app/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/demo/
│ │ └── DemoApplication.java
│ └── resources/
│ └── application.properties
└── pom.xml
核心概念扫盲
在我刚开始学的时候,最困惑的就是这些专业术语。让我用大白话解释一下:
认证(Authentication)vs 授权(Authorization)
- 认证 = "你是谁?" → 验证用户名密码是否正确
- 授权 = "你能做什么?" → 验证你有没有权限访问某个功能
比如你去银行:
- 柜员让你出示身份证和银行卡 → 这是认证
- 柜员确认你能取自己的钱但不能取别人的 → 这是授权
Security的核心组件
- SecurityFilterChain: 安全过滤器链,所有请求都要经过它检查
- UserDetailsService: 负责从数据库或其他地方加载用户信息
- PasswordEncoder: 密码加密器,确保密码安全存储
- AuthenticationManager: 认证管理器,负责验证用户身份
默认的安全行为
有趣的是,只要你添加了Spring Security依赖,什么都不配置也会有安全保护!启动应用后访问任何页面,都会跳转到一个自动生成的登录页。
默认用户名是 user,密码会在控制台输出,类似这样:
Using generated security password: 8d9e3a1b-4c5d-4e6f-7g8h-9i0j1k2l3m4n
这个特性让我当初吓了一跳,以为程序出bug了,后来才知道这是Spring Security的"约定优于配置"哲学。
实战:搭建基础认证系统
现在让我们一步步构建一个简单的用户认证系统。
第一步:基础配置
创建一个配置类 SecurityConfig.java:
package com.example.demo.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.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(authz -> authz
.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.withDefaultPasswordEncoder()
.username("alice")
.password("password123")
.roles("USER")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("bob")
.password("admin123")
.roles("ADMIN", "USER")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
关键点解释:
@EnableWebSecurity启用Web安全功能authorizeHttpRequests()配置哪些路径需要认证InMemoryUserDetailsManager在内存中存储用户(仅用于演示!)
第二步:创建控制器
创建 HomeController.java:
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomeController {
@GetMapping("/public/home")
@ResponseBody
public String publicHome() {
return "这是公共主页,无需登录";
}
@GetMapping("/user/dashboard")
@ResponseBody
public String userDashboard() {
return "欢迎来到用户仪表板!";
}
@GetMapping("/admin/panel")
@ResponseBody
public String adminPanel() {
return "管理员专属面板";
}
@GetMapping("/login")
public String login() {
return "login"; // 返回登录页面模板名
}
}
第三步:创建登录页面
在 src/main/resources/templates/ 目录下创建 login.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>登录</title>
<meta charset="UTF-8">
</head>
<body>
<h2>用户登录</h2>
<!-- 登录失败时显示错误信息 -->
<div th:if="${param.error}">
<p style="color:red;">用户名或密码错误!</p>
</div>
<!-- 登录成功后的提示 -->
<div th:if="${param.logout}">
<p>您已成功登出!</p>
</div>
<form th:action="@{/login}" method="post">
<div>
<label>用户名:</label>
<input type="text" name="username" required/>
</div>
<div>
<label>密码:</label>
<input type="password" name="password" required/>
</div>
<div>
<button type="submit">登录</button>
</div>
</form>
</body>
</html>
别忘了在 pom.xml 中添加Thymeleaf依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
第四步:测试我们的系统
启动应用后,按以下顺序测试:
- 访问
http://localhost:8080/public/home→ 应该直接显示内容 - 访问
http://localhost:8080/user/dashboard→ 跳转到登录页 - 用用户名
alice,密码password123登录 → 成功进入仪表板 - 访问
http://localhost:8080/admin/panel→ 应该被拒绝(因为我们还没配置权限)
第五步:添加基于角色的权限控制
修改 SecurityConfig.java 中的 filterChain 方法:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN") // 只有ADMIN角色能访问
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER或ADMIN都能访问
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.permitAll()
);
return http.build();
}
注意: Spring Security会自动给角色加前缀 ROLE_,所以配置时写 hasRole("ADMIN"),实际检查的是 ROLE_ADMIN。
现在用 alice 用户尝试访问 /admin/panel 会被拒绝,而用 bob 用户就可以正常访问。
常见问题与解决方案
问题1:为什么登录总是失败?
可能原因:
- 密码没有正确加密
- 用户名/密码输入错误
- CSRF令牌问题
解决方案:
确保使用 PasswordEncoder。上面的例子用了 User.withDefaultPasswordEncoder(),但在生产环境中应该显式配置:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 创建用户时
UserDetails user = User.builder()
.username("alice")
.password(passwordEncoder().encode("password123"))
.roles("USER")
.build();
问题2:如何禁用CSRF保护?
在开发API时,可能需要禁用CSRF(但生产环境不建议):
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 禁用CSRF
// ... 其他配置
return http.build();
}
问题3:如何自定义用户来源(比如从数据库)?
创建自定义的 UserDetailsService:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
return User.builder()
.username(user.getUsername())
.password(user.getPassword()) // 应该是已加密的密码
.roles(user.getRoles().toArray(new String[0]))
.build();
}
}
然后在配置类中注入:
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Bean
public UserDetailsService userDetailsService() {
return customUserDetailsService;
}
问题4:静态资源(CSS/JS)被拦截怎么办?
在 authorizeHttpRequests 中添加:
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
或者更通用的方式:
.requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll()
学习建议与下一步
我踩过的坑
- 不要在生产环境使用内存用户存储 - 我当初为了省事在demo里用内存存储,结果上线后用户数据全丢了!
- 密码一定要加密 - 明文存储密码是重大安全漏洞
- 理解过滤器链的顺序 - Spring Security是基于Filter的,了解执行顺序很重要
推荐学习路径
- 基础巩固:先掌握Spring Boot和基本的Web开发
- 深入Security:学习JWT认证、OAuth2、方法级安全等高级特性
- 实战项目:做一个完整的博客系统,包含用户注册、登录、权限管理
- 安全最佳实践:学习HTTPS、CORS配置、安全头设置等
关于区块链的思考
虽然这篇文章主要讲Spring Security,但我想说:安全是所有应用的基础,包括区块链。即使你未来想从事区块链开发,也需要理解传统的Web安全机制。很多区块链项目的管理后台、钱包服务都离不开这些基础的安全框架。
而且,区块链本身解决的是分布式信任问题,但应用层的安全(比如用户认证、API保护)仍然需要传统安全技术来保障。所以不要觉得学这些"老技术"没用,它们是构建可靠系统的基石。
扩展阅读建议
- Spring Security官方文档(虽然是英文,但例子很清晰)
- 《Spring Security实战》这本书(适合入门后深入学习)
- OWASP Top 10(了解常见的Web安全漏洞)
结语
回想起我当初从文科生转码的经历,最大的感悟就是:技术没有想象中那么可怕,关键是要动手实践。Spring Security看起来复杂,但只要按照本文的步骤一步步来,你很快就能掌握基础用法。
记住,每个优秀的程序员都是从"Hello World"开始的。不要害怕犯错,遇到问题就查文档、看源码、问社区。编程的世界很友好,只要你愿意学习。
现在就去创建你的第一个安全应用吧!如果有任何问题,欢迎在评论区讨论。技术分享的意义就在于互相帮助,共同进步。
祝你编码愉快!

评论 0