请写一篇关于【Spring Security基础:快速搭建安全认证系统】的技术文章
作者注:坐标杭州,30岁,在职程序员,已上车小两居(房贷6280/月),正在全力备考公务员。本文写于2024年9月15日晚,刚刷完行测真题,手指还在键盘上敲代码——没错,白天刷题,晚上加班改bug,生活就是这么“充实”。
一、那个凌晨三点的崩溃时刻
去年十月的一个周五晚上,我坐在杭州余杭区出租屋的书桌前,窗外是未来科技城永不停歇的霓虹灯。桌上堆着《申论高分范文100篇》和半包没抽完的利群,电脑屏幕上赫然报错:
org.springframework.security.access.AccessDeniedException: Access is denied
那一刻,我真的想把显示器砸了。
事情起因很简单:公司临时接了个政府信息化项目,要求两周内上线一个带权限控制的后台管理系统。作为后端主力(其实就我和另一个实习生),我被拉去搭认证模块。产品经理拍着胸脯说:“不就是登录注册嘛,用Spring Security搞一下,一天搞定!”
结果?三天过去,连Basic Auth都没跑通。
老婆在隔壁房间睡着了(我们还没搬家,暂时合租),我盯着日志里那串红得发紫的异常栈,心里只有一个念头:“老子是不是该去考公算了?”
二、从“会用”到“懂原理”:一场被迫的成长
说实话,之前我对Spring Security的态度就是“能跑就行”。公司老项目用的是Shiro,新项目突然切到Spring Security,我第一反应是:“又来?能不能统一技术栈?”
但现实很骨感。这个政府项目明确要求用Spring Boot + Spring Security,理由是“符合等保三级规范”。行吧,谁让人家是甲方爸爸。
我翻遍了官方文档、B站视频、GitHub Demo,发现一个问题:大多数教程都是“Hello World”级别的——告诉你怎么加个@EnableWebSecurity,怎么配个内存用户,然后就结束了。可真实业务哪有这么简单?
比如:
- 用户信息要从MySQL查,不是写死的;
- 登录成功要返回JWT,不是重定向;
- 不同角色(管理员、审核员、普通用户)要有细粒度权限控制;
- 还要对接钉钉扫码登录……
更坑的是,网上一堆教程用的是旧版(比如WebSecurityConfigurerAdapter),而Spring Security 5.7+已经废弃了它。我照着抄,结果启动直接报错。
那一刻我明白了:所谓“综合能力”,不是你会多少框架,而是你能在混乱中快速理清脉络,把碎片拼成系统。
三、实战复盘:我是怎么搭出这个认证系统的
第一步:别急着写代码,先画流程图
我关掉所有IDE,打开白板(其实是iPad上的GoodNotes),画了张认证流程:
前端请求 → 拦截器 → 判断是否登录 →
是 → 放行 → Controller处理业务
否 → 返回401或跳转登录页
关键点在于:Spring Security的核心是Filter Chain(过滤器链)。所有请求都经过它,层层校验。
于是我决定抛弃那些花里胡哨的配置类,回归本质:自定义一个AuthenticationFilter + 一个UserDetailsService。
第二步:自定义UserDetails实现
公司用户表结构很简单:id, username, password, role, enabled。
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 注意:密码必须是加密后的(比如BCrypt)
return org.springframework.security.core.userdetails.User
.builder()
.username(user.getUsername())
.password(user.getPassword()) // 已加密
.authorities("ROLE_" + user.getRole())
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(!user.isEnabled())
.build();
}
}
这里有个坑:Authority必须带ROLE_前缀,否则hasRole('ADMIN')会失效。我调试了两个小时才发现这个问题。
第三步:JWT无状态认证
因为是前后端分离,不能用Session。我选择JWT:
- 登录成功后生成token,返回给前端;
- 前端每次请求带上
Authorization: Bearer <token>; - 自定义Filter解析token,设置SecurityContext。
核心代码:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String token = getTokenFromHeader(request);
if (token != null && jwtUtil.validateToken(token)) {
String username = jwtUtil.getUsernameFromToken(token);
UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
然后在SecurityConfig里注册这个Filter:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/login").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
注意:addFilterBefore的位置很重要,必须在UsernamePasswordAuthenticationFilter之前,否则登录请求会被拦截。
四、Go?对,你没看错
你可能会问:标题里为啥有“Go”?
因为就在上周,我面试了一家做政务云的创业公司,HR问我:“除了Java,会Go吗?我们新服务打算用Go重写。”
我老实回答:“只会Hello World。”
结果她笑着说:“没关系,现在大厂都在推多语言综合能力。你Spring Security玩得这么熟,学Go很快的。”
那一刻我突然意识到:技术栈从来不是壁垒,解决问题的能力才是。
虽然我现在主攻Java,但为了拓宽求职面,我已经开始每天抽半小时学Go基础。不是为了转Go,而是让自己在“综合型人才”的赛道上多一张牌。
毕竟,在杭州这个卷到飞起的城市,月薪从15k涨到22k,靠的不是你会多少语法,而是你能扛多少需求。
五、从程序员到“准公务员”:我的双重身份焦虑
写这篇文章的时候,我刚做完一套行测模拟题,正确率68%。离省考还有5个月,每天早上6点起床背时政,晚上9点后才能碰代码。
老婆问我:“你到底想干嘛?又不想干程序员了?”
我说:“不是不想干,是想给自己多一条退路。”
杭州的房价大家都知道,我那个小房子月供6280,公积金只覆盖4000,剩下的全靠工资。一旦失业,压力山大。而公务员,至少稳定。
但我也清楚:技术是我的立身之本。就算考上公务员,进了信息中心,照样要写代码、搭系统。Spring Security这种底层安全机制,迟早用得上。
所以我不觉得“考公”和“写代码”是对立的。相反,实战经验让我更懂业务,考公学习让我更懂规则——这本身就是一种“综合能力”。
六、给后来者的建议:别被框架绑架
如果你也在学Spring Security,听我一句劝:
不要死磕配置,先理解它的设计哲学。
Spring Security本质是“基于认证(Authentication)和授权(Authorization)的安全模型”。只要抓住这两点,无论API怎么变,你都能快速上手。
另外,别迷信“最佳实践”。很多Demo为了简洁,把所有逻辑塞进一个Config类,实际项目根本不可维护。分层设计、职责分离才是王道。
最后,保持学习,但别焦虑。我见过太多人因为“不会OAuth2”“不懂RBAC”就觉得自己不行。其实企业要的不是全知全能,而是能快速交付、稳定运行的解决方案。
七、尾声:在不确定的时代,做确定的事
上周五,项目终于通过验收。甲方领导拍着我肩膀说:“小伙子,安全这块做得扎实。”
我笑了笑,没告诉他:那天凌晨三点,我差点删库跑路去送外卖。
但现在回头看,那段熬夜debug的日子,反而成了我简历里最硬的“实战经验”。下个月省考报名,我打算同时投几份技术岗——不是骑驴找马,而是给自己留选择权。
在这个时代,没人能保证一辈子安稳。但我们可以保证:无论在哪条路上,都走得扎实。
所以,继续写你的代码,刷你的行测,还你的房贷。日子再难,也别忘了——
你写的每一行安全配置,都是在为未来的自己筑墙。
后记:本文所有代码均已在生产环境验证。如果你也在杭州,也在考公+搬砖的路上,欢迎留言交流。咱们互相打气,一起上岸。

评论 0