Spring Security上手没那么难,别被吓跑了

星河程序员
2026-01-04 00:37
阅读 735

去年双11前夜,我正窝在家里的“战地办公室”(其实就是客厅角落那张二手宜家桌子)疯狂敲代码。远程办公两年,咖啡机都快成我的第二台编译器了。那天凌晨三点,产品突然在 Slack 里@我:“老铁,用户登录这块能不能加个手机号验证码?明天上线。” 我盯着屏幕,差点把键盘砸了——系统用的还是最原始的 Session 认证,连基本的 CSRF 都没配。

那一刻我意识到:安全不是功能,是底线。而作为 GitHub Copilot 付费用户(用了快两年,真香),我早就该把 Spring Security 拎出来好好练练了。毕竟,跳槽面试时,十个 Java 岗位九个问安全,剩下那个可能在招 Go 工程师(别问我怎么知道的,简历石沉大海过)。


为什么前端总背锅?其实是后端没兜住底

很多团队有个误区:安全是“加”出来的功能。产品经理画个登录框,前端调个接口,后端随便校验下用户名密码——完事。结果呢?XSS、CSRF、越权访问轮番上演,最后锅甩给前端“没做输入过滤”。

我在某次技术分享会上就吐槽过:前端可以防君子,但防不了小人;真正的防线,必须在后端。Spring Security 就是那个能帮你筑起城墙的工具,而不是贴几张符纸。


别被文档劝退,三步搭起基础认证

说实话,第一次看 Spring Security 官方文档,我也懵了。一堆 AuthenticationManagerUserDetailsServicePasswordEncoder,感觉像在配置 Kubernetes 的 RBAC(毕竟我搞云原生的,看到 YAML 就亲切)。但其实,90% 的业务场景,只需要三板斧

  1. 用户身份认证(你是谁)
  2. 授权控制(你能干啥)
  3. 防御常见攻击(别搞事情)

下面直接上代码,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() 只用于前后端分离调试!正式环境必须开启,并配合前端传 _csrf token。否则你的表单提交会被拦截——别问我怎么知道的,线上事故现场至今历历在目。

第二步:用户数据从哪来?

默认情况下,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。解决办法有两个:

  1. 前端在登录请求头中带上 X-CSRF-TOKEN(需先 GET /csrf 获取);
  2. 改用 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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝