请写一篇关于【Spring Security基础:快速搭建安全认证系统】的技术文章

产品别催我
2026-01-04 15:21
阅读 1175

作者注:坐标杭州,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

最热最新
暂无评论
产品别催我Lv.1
0
影响力
0
文章
0
粉丝