Spring Security上手没那么难,别被吓跑了
去年双11前夜,我正窝在家里的“战地办公室”(其实就是客厅角落那张二手宜家桌子)疯狂敲代码。远程办公两年,咖啡机都快成我的第二台编译器了。那天凌晨三点,产品突然在 Slack 里@我:“老铁,用户登录这块能不能加个手机号验证码?明天上线。” 我盯着屏幕,差点把键盘砸了——系统用的还是最原始的 Session 认证,连基本的 CSRF 都没配。
那一刻我意识到:安全不是功能,是底线。而作为 GitHub Copilot 付费用户(用了快两年,真香),我早就该把 Spring Security 拎出来好好练练了。毕竟,跳槽面试时,十个 Java 岗位九个问安全,剩下那个可能在招 Go 工程师(别问我怎么知道的,简历石沉大海过)。
为什么前端总背锅?其实是后端没兜住底
很多团队有个误区:安全是“加”出来的功能。产品经理画个登录框,前端调个接口,后端随便校验下用户名密码——完事。结果呢?XSS、CSRF、越权访问轮番上演,最后锅甩给前端“没做输入过滤”。
我在某次技术分享会上就吐槽过:前端可以防君子,但防不了小人;真正的防线,必须在后端。Spring Security 就是那个能帮你筑起城墙的工具,而不是贴几张符纸。
别被文档劝退,三步搭起基础认证
说实话,第一次看 Spring Security 官方文档,我也懵了。一堆 AuthenticationManager、UserDetailsService、PasswordEncoder,感觉像在配置 Kubernetes 的 RBAC(毕竟我搞云原生的,看到 YAML 就亲切)。但其实,90% 的业务场景,只需要三板斧:
- 用户身份认证(你是谁)
- 授权控制(你能干啥)
- 防御常见攻击(别搞事情)
下面直接上代码,Copilot 这时候就派上用场了——它能自动生成大部分样板代码,省下我写 CRUD 的时间去研究 K8s Ingress。
第一步:依赖 & 基础配置
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
然后,一个极简配置类:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable() // 开发阶段先关掉,生产务必开启!
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
)
.logout(logout -> logout
.logoutSuccessUrl("/login?logout")
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 别再用明文或 MD5 了!
}
}
划重点:
.csrf().disable()只用于前后端分离调试!正式环境必须开启,并配合前端传_csrftoken。否则你的表单提交会被拦截——别问我怎么知道的,线上事故现场至今历历在目。
第二步:用户数据从哪来?
默认情况下,Spring Security 会用内存用户(InMemoryUserDetailsManager),这显然不能用于生产。我们需要对接数据库:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword()) // 数据库存的是 BCrypt 加密后的
.roles(user.getRoles().toArray(new String[0]))
.build();
}
}
这里要注意两点:
- 密码必须加密存储,
BCrypt是目前最稳妥的选择; - 角色名存
ROLE_ADMIN,但在hasRole("ADMIN")中不用带前缀。
第三步:前后端联调那些坑
前端同事(对,就是那个总说“后端接口好慢”的哥们)一开始调 /login 发现返回 403。原因?Spring Security 默认启用了 CSRF。解决办法有两个:
- 前端在登录请求头中带上
X-CSRF-TOKEN(需先 GET/csrf获取); - 改用 JWT 无状态认证(适合纯 API 场景)。
我们选了后者,因为产品说以后要接小程序和 App。于是改造如下:
// 简化版 JWT 认证流程
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
JWT 虽然方便,但别忘了设置合理的过期时间(比如 2 小时),并实现 token 刷新机制。否则用户半夜被踢下线,产品经理又该找你喝茶了。
求职加分项:这些细节面试官最爱问
最近帮朋友模拟面试,发现很多人只会背“Spring Security 基于过滤器链”,却说不出实际防护逻辑。如果你能在简历里写上:
- “通过
@PreAuthorize("hasPermission(#id, 'USER', 'WRITE')")实现细粒度权限控制” - “集成 OAuth2.0 支持第三方登录”
- “使用
HttpFirewall防范路径遍历攻击”
那你大概率能进二面。毕竟,安全意识是高级工程师的分水岭。
顺便提一句,Go 语言生态里也有类似 Casbin 这样的库,但 Java 社区的 Spring Security 依然是企业级应用的首选——稳定、成熟、文档全(虽然有点啰嗦)。
生产环境血泪教训
上线前,我们做了三件事:
| 事项 | 错误做法 | 正确做法 |
|---|---|---|
| 密码策略 | 允许 123456 | 强制 8 位+大小写+数字 |
| 登录失败 | 直接提示“用户名或密码错误” | 统一返回“凭证无效”,防撞库 |
| 日志记录 | 不记录登录行为 | 记录 IP、时间、成功/失败,用于审计 |
另外,千万别在日志里打印密码!曾经有个实习生把 user.getPassword() 打印到 stdout,差点被运维拉去“喝茶”。现在我们的 CI 流程里加了 SonarQube 规则,一检测到敏感字段直接阻断构建。
最后说两句
用了 Spring Security 之后,我再也不怕产品临时改需求了。上周五晚上,他又来了一句:“能不能加个微信扫码登录?” 我淡定回了句:“行,OAuth2.0 协议走起,三天。” 然后继续喝我的冷萃——安全感,真的来自于扎实的技术底座。
如果你还在用 if (user != null) 做认证,或者觉得安全是“以后再说”的事,那真的该动手试试了。毕竟,在这个数据泄露频发的时代,你写的每一行代码,都可能是别人的安全防线。
(对了,Copilot 刚刚还帮我自动补全了一个 JwtUtil 类,省了我二十分钟。付费真不亏。)

评论 0