Spring Security基础:快速搭建安全认证系统
上周五晚上十一点半,我还在公司改一个认证相关的线上 Bug。测试同学在群里@我说:“登录接口又 403 了,大佬快看看是不是你改的?”——其实根本不是我改的,但谁让我是后端里唯一碰过 Security 的人呢?没办法,只能打开 IDE,一边骂骂咧咧一边 debug。
说起来,我在网易游戏已经待了三年多,做过两款上线的手游服务端,从最初只会写 CRUD 到后来被逼着搞高并发、分布式缓存、微服务治理……现在看着自己写的代码能跑在几百万玩家的手机上,多少还是有点成就感的(虽然工资没涨多少)。最近在考虑换个环境,于是开始整理一些平时用得比较多的技术栈,顺便写点博客记录下开发心得。今天这篇,就聊聊 Spring Security ——这个让人又爱又恨的安全框架。
为什么又来折腾 Security?
其实我们项目早期用的是自研的 Token 认证逻辑,简单粗暴:前端传个 token,后端查 Redis,校验通过就放行。那会儿团队只有三个人,PM 还天天催功能上线,哪有时间搞什么 OAuth2、JWT 标准流程?结果去年双11活动期间,因为没做权限粒度控制,某个运营接口被误调,差点把全服玩家的经验值清零……运维大哥当场血压飙升,拉着我们开了三天复盘会。
从那以后,领导拍板:“安全模块必须重构,标准方案,别再自己造轮子了。”于是我就被“委以重任”——毕竟组里没人愿意碰 Security,都说它配置复杂、文档晦涩、报错信息像天书。
说实话,一开始我也挺抗拒的。但跳槽面了几家公司后发现,几乎每家都问 Spring Security 的原理和扩展点。为了不被 HR 当成“只会写业务逻辑的 CRUD Boy”,我咬牙啃了两周官方文档 + StackOverflow,总算搭出了一套能跑的认证系统。
架构设计:资源、权限、角色怎么分?
在游戏行业,资源(Resource) 的概念特别重要。比如:
/api/player/profile:玩家个人信息(普通玩家可读)/api/gm/banPlayer:封禁玩家(仅 GM 可调用)/api/admin/reloadConfig:热更配置(运维+策划白名单)
这些接口背后代表的是不同的受保护资源。而 Spring Security 的核心思想就是:访问资源前,先验证身份和权限。
我最初的误区是把“角色”和“权限”混在一起。比如给 GM 赋予 ROLE_GM,然后在接口上写 @PreAuthorize("hasRole('GM')")。但很快发现这不够灵活——有些 GM 只能封玩家,不能发奖励;有些策划需要临时获得某些管理权限。
所以最后我们采用了 RBAC(基于角色的访问控制)+ 权限粒度到接口级别 的混合模型:
| 模型层级 | 说明 | 示例 |
|---|---|---|
| 用户(User) | 系统中的实体 | player_123, gm_zhang |
| 角色(Role) | 一组权限的集合 | ROLE_PLAYER, ROLE_GM, ROLE_ADMIN |
| 权限(Permission) | 具体的操作能力 | player:read, gm:ban, admin:reload |
数据库设计上,我们用了四张表:
user (id, username, password, enabled)
role (id, name)
permission (id, code, description) -- code 如 'gm:ban'
user_role (user_id, role_id)
role_permission (role_id, permission_id)
这样,后续加新权限只需要在 permission 表里插入一条记录,再关联到对应角色,完全不用动代码。
快速搭建:别被配置劝退
很多人一看到 WebSecurityConfigurerAdapter 就头大。其实只要理解几个核心组件,搭起来并不难:
- AuthenticationManager:负责“你是谁”
- AccessDecisionManager:负责“你能干什么”
- UserDetailsService:从 DB/Redis 加载用户信息
- PasswordEncoder:密码加密(千万别用明文!)
下面是我简化后的配置类(省略了异常处理器、CORS 等细节):
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 别再用 MD5 了,求你了
}
@Bean
public UserDetailsService userDetailsService(UserMapper userMapper) {
return username -> {
User user = userMapper.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
// 注意:这里要加载用户的权限列表
List<GrantedAuthority> authorities = loadAuthorities(user.getId());
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.isEnabled(),
true, true, true,
authorities
);
};
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable() // 前后端分离项目一般关掉 CSRF
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/login").permitAll()
.requestMatchers("/api/player/**").hasAuthority("player:read")
.requestMatchers("/api/gm/**").hasAuthority("gm:ban")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
关键点在于:
- 使用
hasAuthority()而不是hasRole(),因为我们权限是细粒度的 - 关闭 Session,用 JWT 做无状态认证(适合游戏后端高并发场景)
- 自定义
JwtAuthenticationFilter在请求头里解析 Token 并设置Authentication
踩坑实录:那些让我想砸电脑的瞬间
1. 权限没生效?检查 GrantedAuthority 的前缀!
Spring Security 默认会给 hasRole('GM') 自动加上 ROLE_ 前缀,变成 ROLE_GM。但如果你用的是 hasAuthority('gm:ban'),那就不能加前缀!我一开始在数据库里存的是 ROLE_GM,结果权限校验永远失败,debug 了整整一个下午……
2. 密码加密方式不一致
本地开发用 BCryptPasswordEncoder,但测试环境用的旧系统是 SHA256。结果测试同学登录一直失败,还以为是我接口写错了。后来统一了加密策略,并写了数据迁移脚本才解决。
3. Filter 顺序问题
自定义的 JWT Filter 必须放在 UsernamePasswordAuthenticationFilter 之前,否则 Security 根本不会走你的逻辑。这个坑我踩了两次,每次都是“本地好好的,上测试环境就不行”。
性能与运维:上线后的心得
虽然 Security 功能强大,但在高并发场景下也要注意性能:
- UserDetails 缓存:每次请求都查 DB 加载权限?达咩!我们在
UserDetailsService里加了 Redis 缓存,key 是user:{id}:authorities,TTL 30 分钟。权限变更时主动删除缓存。 - 避免频繁权限校验:对于高频接口(如心跳包),直接放行,不做权限检查。
- 日志埋点:记录所有 403 请求,方便事后审计。曾经靠这个日志抓到一个越权调用的外挂。
另外,千万别在线上开 DEBUG 日志!Security 的日志量巨大,有一次不小心打开了,磁盘瞬间爆满,运维直接冲进办公室吼我名字……
写在最后:技术分享的意义
回看这三年,从只会用 MyBatis 写 SQL,到现在能独立设计认证体系,其实最大的收获不是技术本身,而是系统性思维。以前觉得“能跑就行”,现在会考虑:权限模型是否可扩展?性能瓶颈在哪?出了问题怎么快速定位?
写这篇博客,一是给自己留个笔记(下次跳槽面试能吹),二是希望帮到正在被 Security 折磨的兄弟。毕竟,谁还没在深夜对着 AccessDeniedException 发过呆呢?
对了,如果你也在网易(或者别的大厂)做游戏后端,欢迎交流~说不定哪天就成了同事(或者竞争对手 😏)。
开发心得总结:
- 安全不是功能,是基础设施,越早引入越好
- 权限模型要灵活,别被“角色”框死
- 配置复杂?多看源码,少信博客(包括这篇,记得验证!)
- 生产环境务必压测 + 监控,别等炸了才后悔
好了,不说了,产品又在群里喊:“这个需求很简单,明天上线!”——我先去改代码了。

评论 0