1. 项目背景与痛点分析
在前后端分离架构中,单点登录(SSO)是提升用户体验和安全性的重要组件。我们团队基于ruoyi-vue-pro框架开发数据大屏时,曾采用前端存储appKey和appSecret的方案实现SSO功能。虽然通过加解密手段做了基础防护,但敏感信息暴露在前端始终存在安全隐患:
- 密钥泄露风险:即使经过加密,前端代码中的密钥仍可能被逆向工程破解
- 权限控制薄弱:前端验证容易被绕过,无法实现细粒度的权限管控
- 维护成本高:密钥变更需要重新部署前端,不符合安全运维规范
2. 解决方案设计
2.1 整体架构设计
我们决定开发独立的yudao-module-sso模块,将认证逻辑全部移至后端:
code复制前端页面 -> 后端SSO模块 -> OAuth2认证服务器
↑
应用配置中心
关键设计原则:
- 配置中心化:所有客户端配置通过application.yaml管理
- 接口标准化:提供统一的RESTful API供各业务模块调用
- 安全隔离:敏感操作全部在后端完成,前端只持有临时token
2.2 技术选型考量
选择Spring Security OAuth2作为基础框架的三大理由:
- 成熟度:Spring生态官方支持,企业级安全特性完备
- 扩展性:可灵活适配各种认证协议(如OAuth2.0、SAML)
- 集成度:与Spring Boot无缝集成,降低开发成本
3. 核心实现细节
3.1 模块结构设计
code复制yudao-module-sso
├── config # 安全配置
├── controller # 对外接口
├── client # OAuth2客户端实现
├── model # 数据模型
└── util # 工具类
3.2 关键配置实现
application.yaml典型配置示例:
yaml复制yudao:
sso:
oauth2-server: http://auth-server/api/oauth2
client-key: ${SSO_CLIENT_KEY}
client-secret: ${SSO_CLIENT_SECRET}
token-validity: 3600
refresh-token-validity: 86400
安全提示:建议将敏感配置通过环境变量注入(如${SSO_CLIENT_SECRET})
3.3 核心接口实现
3.3.1 授权码模式登录
java复制@PostMapping("/login-by-code")
public Result<OAuth2AccessToken> loginByCode(
@RequestParam String code,
@RequestParam String redirectUri) {
OAuth2Client client = loadConfiguredClient();
OAuth2AccessToken token = oauth2Service.getTokenByCode(client, code, redirectUri);
return success(token);
}
3.3.2 Token刷新机制
java复制@PostMapping("/refresh-token")
public Result<OAuth2AccessToken> refreshToken(
@RequestParam String refreshToken) {
OAuth2Client client = loadConfiguredClient();
return success(oauth2Service.refreshToken(client, refreshToken));
}
4. 安全增强措施
4.1 防御矩阵设计
| 攻击类型 | 防御措施 |
|---|---|
| CSRF | 随机state参数校验 |
| 重放攻击 | Token单次有效性+时效控制 |
| 信息泄露 | HTTPS传输+敏感字段脱敏 |
| 暴力破解 | 请求频率限制+失败锁定机制 |
4.2 关键安全配置
Spring Security配置示例:
java复制@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // 使用JWT可禁用CSRF
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/sso/login**").permitAll()
.anyRequest().authenticated();
}
5. 集成与测试
5.1 模块集成步骤
- 添加依赖:
xml复制<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-sso</artifactId>
<version>1.8.3</version>
</dependency>
- 配置扫描路径:
java复制@SpringBootApplication(scanBasePackages = {"cn.iocoder.yudao.sso"})
- 安全规则配置:
java复制.antMatchers("/sso/**").permitAll()
5.2 测试用例设计
java复制@Test
public void testLoginFlow() {
// 1. 获取授权码
String code = getAuthCode("testuser", "password");
// 2. 用code换token
TokenResult token = restTemplate.postForObject(
"/sso/login-by-code?code={code}",
null, TokenResult.class, code);
// 3. 验证token有效性
UserInfo user = getUserInfo(token.getAccessToken());
assertEquals("testuser", user.getUsername());
}
6. 性能优化建议
- Token缓存:使用Redis缓存有效token,减少数据库查询
- 连接池配置:优化OAuth2服务调用连接参数
yaml复制http:
pool:
max-total: 100
max-per-route: 50
- 异步处理:非关键日志采用异步写入
7. 生产环境部署方案
7.1 高可用架构
code复制 [负载均衡]
/ | \
[SSO实例1] [SSO实例2] [SSO实例3]
\ | /
[共享Redis集群]
7.2 监控指标
- 认证成功率
- 平均响应时间
- 并发会话数
- Token刷新频率
8. 常见问题排查
8.1 典型错误码处理
| 错误码 | 原因分析 | 解决方案 |
|---|---|---|
| 40101 | 无效client_secret | 检查yaml配置与环境变量 |
| 40302 | 过期refresh_token | 引导用户重新登录 |
| 50010 | 认证服务不可用 | 检查网络连接和服务健康状态 |
8.2 日志分析技巧
关键日志格式:
code复制[SSO] AuthCode验证失败 -
clientId={},
ip={},
error={}
分析步骤:
- 确认clientId与配置一致
- 检查请求IP是否在白名单
- 验证错误描述中的具体原因
9. 扩展开发指南
9.1 多租户适配
java复制public class TenantAwareClient {
@Value("${yudao.sso.tenant-id}")
private Long tenantId;
public OAuth2Client loadClient() {
return clientRepository.findByTenantId(tenantId);
}
}
9.2 自定义Claim
扩展Token信息:
java复制@Override
public void enhance(OAuth2AccessToken token) {
token.getAdditionalInformation()
.put("department", getCurrentDept());
}
10. 版本升级策略
- 兼容性变更:保持/v1接口稳定
- 废弃流程:
- 先标记@Deprecated
- 保留至少两个版本周期
- 提供迁移指南
- 新特性通过/v2引入
在实际项目中,我们通过这套方案成功将认证安全性提升了一个等级。特别是在金融类项目中使用时,建议额外增加以下防护:
- 关键操作二次认证
- 设备指纹识别
- 异地登录预警