当前位置: 首页 > news >正文

单点登录进阶:基于芋道(yudao)授权码模式的单点登录流程、代码实现与安全设计

最近遇到需要单点登录的场景,我使用的是芋道框架,正好它手动实现了OAuth2的功能,可以为单点登录提供一些帮助,结合授权码的模式,在改动最小的情况下实现了单点登录。关键业务数据已经隐藏,后续将以以主认证系统业务子系统的场景为例


一、主要流程

授权码模式(Authorization Code Grant)是OAuth 2.0标准中安全性最高的认证方式,主要通过“一次性授权码”避免敏感信息(如client_secret)暴露在前端。以下是主认证系统与业务子系统的单点登录全流程:


1. 用户触发跳转:从主系统到子系统的入口

用户在主认证系统的页面中点击“业务子系统入口”(例如“数据报表系统入口”),触发单点登录流程。这一步的核心是用户主动选择需要访问的子系统。


2. 主系统生成并返回授权码

前端调用主认证系统的授权接口/system/oauth2/authorize,并传递业务子系统的标识(如client_id=report-system)。主认证系统完成两项关键操作:

  • 验证用户状态:确认当前用户已登录主认证系统,且有权限访问目标子系统;
  • 生成一次性授权码:生成仅单次有效的随机字符串(如code=c36e714f324a43cfb9a75f24e14406c6),并拼接成跳转URL返回给前端。

返回的JSON示例如下:

{"code": 0,"data": "https://subsystem.example.com/login?code=c36e714f324a43cfb9a75f24e14406c6&state=1","msg": "成功"
}

关键设计:授权码仅单次有效,防止重放攻击;state参数用于防止CSRF攻击(本文示例简化为固定值,实际需动态生成)。


3. 前端重定向至子系统

前端通过浏览器重定向(302 Redirect)跳转到步骤2返回的URL(如https://subsystem.example.com/login?code=...)。此时,业务子系统的前端页面将接收到URL中的code参数,进入验证流程。


4. 子系统验证授权码并获取用户信息

业务子系统的核心任务是通过授权码向主认证系统验证其有效性,并获取用户身份信息。这一步必须由子系统后端完成(避免client_secret暴露在前端),具体流程如下:

4.1 子系统前端传递code至后端

前端从URL中提取code参数,调用子系统后端接口/login/callbackLogin,将code传递给后端。

4.2 后端调用主系统验证接口

子系统后端通过HTTP请求调用主认证系统的/system/oauth2/token接口,传递以下参数:

  • client_id:子系统标识(如report-system);
  • client_secret:子系统密钥(需保密,仅后端持有);
  • grant_type=authorization_code:标识使用授权码模式;
  • code:步骤2生成的授权码;
  • redirect_uri:登录成功后的跳转地址(需与主认证系统预先配置一致)。

主认证系统验证通过后,返回用户信息及访问令牌(access_token),示例如下:

{"code": 0,"data": {"scope": "all","userId": 194,"subsystemCode": "report-system","subsystemName": "数据报表系统","access_token": "f963b902248646ffa71d27cdc48fd37d","refresh_token": "8d4dfc224e724ceca296c40b2087f7c7","token_type": "bearer","expires_in": 1799},"msg": "成功"
}
4.3 子系统完成用户登录

子系统后端通过返回的userIdsubsystemCode查询本地用户信息(若用户不存在需提前同步或注册),生成子系统的登录令牌(如JWT),并记录登录日志。

关键代码示例(子系统后端)

public AuthLoginRespVO callbackLogin(String code) throws IOException {// 1. 调用主认证系统,用code换取用户标识(如subsystemCode)String userIdentifier = oAuth2TokenClient.getUserIdByAuthCode(code);// 2. 根据用户标识查询本地用户(需提前维护主系统与子系统的用户映射)AdminUserDO localUser = userService.getUserByIdentifier(userIdentifier);if (localUser == null) {throw ServiceExceptionUtil.exception(USER_NOT_EXISTS, "用户未同步至子系统");}// 3. 生成子系统登录令牌,记录日志return createTokenAfterLoginSuccess(localUser.getId(), localUser.getUsername(), LoginLogTypeEnum.LOGIN_SSO);
}

5. 子系统生成令牌并跳转首页

子系统后端将生成的登录令牌(如token=abc123)返回给前端,前端携带该令牌跳转到子系统首页,完成单点登录。


二、子系统后端的HTTP客户端实现

子系统后端需要通过HTTP客户端与主认证系统交互,以下是核心实现类(已简化):

@Component
public class OAuth2TokenClient {// 从配置文件读取主认证系统信息(敏感信息需加密存储)@Value("${sso.base_url}")private String baseUrl; // 主认证系统基础URL(如http://sso.main-system.com)@Value("${sso.token_url}")private String tokenUrl; // 令牌接口路径(如/system/oauth2/token)@Value("${sso.client_id}")private String clientId; // 子系统标识@Value("${sso.client_secret}")private String clientSecret; // 子系统密钥(需保密)@Value("${sso.redirect_url}")private String redirectUri; // 登录成功跳转地址private static final ObjectMapper objectMapper = new ObjectMapper();public String getUserIdByAuthCode(String authCode) throws IOException {// 构造POST请求HttpPost httpPost = new HttpPost(baseUrl + tokenUrl);httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");// 组装请求参数(严格遵循OAuth 2.0规范)List<NameValuePair> params = new ArrayList<>();params.add(new BasicNameValuePair("client_id", clientId));params.add(new BasicNameValuePair("client_secret", clientSecret));params.add(new BasicNameValuePair("grant_type", "authorization_code"));params.add(new BasicNameValuePair("code", authCode));params.add(new BasicNameValuePair("redirect_uri", redirectUri));try (CloseableHttpClient client = HttpClients.createDefault();CloseableHttpResponse response = client.execute(httpPost)) {String responseBody = EntityUtils.toString(response.getEntity());JsonNode root = objectMapper.readTree(responseBody);// 校验主认证系统返回状态if (root.path("code").asInt() != 0) {throw ServiceExceptionUtil.exception(new ErrorCode(root.path("code").asInt(), root.path("msg").asText()));}// 提取用户标识(根据主认证系统返回结构调整)return root.path("data").path("subsystemCode").asText();} catch (ParseException e) {throw new RuntimeException("响应解析失败", e);}}
}
查看全部

三、配置示例

sso:base_url: "http://sso.main-system.com" # 主认证系统基础URL(替换为实际地址)token_url: "/system/oauth2/token" # 授权码验证接口路径client_id: "report-system" # 子系统标识(需主认证系统预先注册)client_secret: "subsystem-secret-123" # 子系统密钥(需加密存储,避免明文)redirect_url: "http://subsystem.example.com/auto-login" # 登录成功跳转地址(需与主认证系统配置一致)

四、子系统前后端协作流程总结

阶段前端操作后端操作
接收code从URL参数中提取code-
传递code调用/login/callbackLogin接口,传递code接收code,调用主认证系统验证
完成登录接收后端返回的token生成子系统token,返回前端
跳转首页携带token跳转到首页-

五、注意事项

  1. 授权码的安全性
    • 授权码仅单次有效,主认证系统需严格校验其使用状态,防止重放攻击;
    • 避免在前端暴露client_secret,所有与主认证系统的交互必须由后端完成。
  2. 用户映射与同步
    • 主认证系统与子系统需维护用户关联关系(如主系统userId=194对应子系统userId=1001),建议通过定时任务或事件通知同步用户信息;
    • 若用户未同步至子系统,需明确提示“用户无权限”或触发自动注册流程(需评估安全风险)。
  3. 错误处理
    • 主认证系统返回错误(如code无效)时,子系统需捕获异常并返回友好提示(如“登录失败,请重新操作”);
    • 记录详细的日志(如code、请求时间、错误码),便于排查问题。
  4. 参数校验
    • 子系统后端需校验code的格式(如长度、字符类型),防止非法请求;
    • state参数需动态生成并校验(本文示例简化,实际需实现),防止CSRF攻击。
http://www.lryc.cn/news/572666.html

相关文章:

  • SAP-ABAP:LOOP ... ASSIGNING高效处理内表数据详解
  • pandas polars 数据类型转换
  • 【pdf】Java代码生成PDF
  • lingma(阿里云Ai)结合idea使用
  • uni-app-配合iOS App项目开发apple watch app
  • Python按钮点击事件快速入门
  • vue3 reactive重新赋值
  • VSCode1.101.1Win多语言语言编辑器便携版安装教程
  • 【Dify精讲】第14章:部署架构与DevOps实践
  • 字符编码(UTF-8,16,32 和GBK和ASCLL码)
  • 三维视频融合平台:如何构建动态感知的数字空间
  • 配置Fiori应用时报错
  • 从语音到字幕,视频剪辑效率翻倍方案
  • vtk和opencv和opengl直接的区别是什么?
  • Web Splats
  • 每天一个前端小知识 Day 7 - 现代前端工程化与构建工具体系
  • 设计模式实战指南:从源码解析到Java后端架构的艺术
  • mysql查询使用`_rowid` 虚拟列
  • Apipost 签约锐捷网络:AI赋能,共推 ICT 领域 API 生态智能化升级
  • (链表:哈希表 + 双向链表)146.LRU 缓存
  • 性能测试-jmeter实战3
  • 二十二章 stable diffusion SDXL1.0模型 介绍
  • 期货反向跟单-终止盘手合作原则(二)
  • 原点安全入选 Gartner®“数据安全平台”中国市场指南代表厂商
  • Mac电脑-SSH客户端-Termius
  • JetBrains IDE v2025.1 升级,AI 智能+语言支持齐飞
  • Kafka协议开发总踩坑?3步拆解二进制协议核心
  • OpenGL和OpenGL ES区别
  • 可编辑64页PPT | 基于DeepSeek的数据治理方案
  • SaaS+AI架构实战,