尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

泛微Ecology9单点登录对接实战:Token获取与签名算法全解析

泛微Ecology9单点登录对接实战:Token获取与签名算法全解析 很多做企业信息化的朋友遇到泛微Ecology9对接第三方系统时第一个绕不开的坎就是单点登录。我当年第一次接到“从外部系统点一下直接免登进OA”的需求时也以为只是配个跳转链接的事结果深挖下去才发现这里面有几个接口路径、签名算法、token有效期和会话归属的坑不踩一遍真的写不对代码。这篇文章我把自己在Ecology9上从零实现单点登录的完整过程整理出来核心链条是应用身份认证、token获取、签名生成、页面跳转这四步把每一步的关键参数和代码片段都贴出来希望能帮后来的人少走点弯路。这篇文章适合两类人看一是公司OA管理员需要把泛微Ecology9集成到企业统一门户或者钉钉、企微这类第三方入口二是后端开发需要在自研系统里实现“跳转进OA并自动带出当前登录人身份”的功能。我会尽量用白话把原理讲清楚同时给出可以直接改改就用的代码不绕弯子。1. 整体设计与思路拆解为什么Ecology9的单点登录要分四步走单点登录这个概念本身不复杂就是用户在A系统登录后访问B系统时不希望再输一次账号密码。但落到泛微Ecology9这种老牌Java OA系统上事情就变复杂了。一是Ecology9有一套自己的用户体系和安全校验机制它要确认“你是不是你”不是随便拿个用户名拼接个URL就能冒充的二是OA系统本身有页面登录态就算接口认证通过了浏览器里也得有对应的会话才能正常打开待办、发起流程。泛微Ecology9目前比较常用的第三方单点登录方式我整理了一下大致有四种一是通过/login/SSOLogin.jsp页面传参利用系统配置的SSO密钥做校验二是通过interchangeLoginByToken.jsp接口用token换取登录态三是通过Ecology9的REST接口拿token再拼跳转链接四是通过WebService接口做身份同步和登录。前面两种本质上都是“传参服务端校验”的模式区别在于一个偏向登录页处理、一个偏向接口处理对于外部系统集成来说我最终选择了基于REST接口的方式流程上更清晰、可控性更强。为什么最终没有直接拼SSOLogin.jsp因为那个方式要求第三方页面能组织出一串符合泛微要求的加密串而且一旦参数顺序写错、时间戳过期排错特别痛苦。REST接口的方式则可以把token的获取和跳转URL的生成拆成两步哪一步出问题了日志里能直接看出来。再加上token本身带过期时间安全性也更好一些。整体流程图可以理解为外部系统先调泛微的认证接口拿到token再拼接一个带token的跳转地址浏览器访问这个地址时泛微服务器在校验token合法之后为用户创建会话并自动跳转到OA首页。这里有一个关键点需要想明白token是给“外部系统”用来证明“用户身份”的而不能取代浏览器里的会话。泛微的登录跳转接口拿到token验证后会在响应里带上会话Cookie浏览器只有跟随这个响应才能建立起真正的登录态。所以在设计上不能让后端偷偷去调接口拿token而不把浏览器导向泛微的跳转地址否则就会出现“接口认证成功了但浏览器打开OA还是要登录”的怪异现象。2. 核心细节解析token获取接口与签名算法的完整拆解2.1 应用身份认证appid和secretkey从哪来在调泛微token接口之前需要先在Ecology9后端管理里创建一套“外部应用”的凭据。系统管理里通常有“接口集成”“外部应用管理”之类的菜单新增应用之后会生成一个appid和secretkey这两个值在后端请求签名和token申请里都会用到。我把这个动作类比成给第三方系统办一张门禁卡appid是卡号secretkey是卡密码后续的所有请求都需要证明“我持有这张卡”。有一点一定要提醒secretkey不要在浏览器端代码里出现否则任何人打开前端代码就能拿到你的门禁密码。正确做法是把它放到后端服务环境变量或配置中心里由后端代第三方系统完成签名。实际项目中我就遇到过把secretkey硬编码在JS里、最后被安全扫描报告揪出来的情况相当尴尬。2.2 token申请接口的URL与请求参数泛微Ecology9不同小版本的token接口路径略有差异但常见的是通过/api/ec/dev/auth/applytoken来申请。这个接口需要同时带上appid、secretkey、时间戳、随机数、签名等参数不同版本对参数命名可能有区别最稳妥的方式是直接查看对应接口的API文档或登录Ecology9后打开接口列表页看标准示例。需要注意同一个请求参数必须按参数名的字母顺序排列后拼接再做MD5摘要顺序错了签名校验必然失败。我当时在联调环境里调这个接口时参数少了一个timestamp结果返回的是“sign error”而不是“缺少参数”排查了很久。后来翻源码才发现泛微的通用签名校验有一个固定顺序它会把收到的参数先排序、拼接、加密再和自己算出来的结果比对任何参数缺失、顺序变化都会算成签名错误不会提示具体原因。2.3 签名算法的Java实现排序、拼接、MD5一个都不能少签名算法并没有想象中那么玄乎核心就是把所有参与签名的参数按key的首字母排序然后拼成一个key1value1key2value2的字符串再拼接上secretkey最后做MD5。我用Java写了一个工具方法简单直接public class EcologySignUtil { public static String buildSign(MapString, String params, String secretKey) { // 1. 按key字母排序 TreeMapString, String sortedParams new TreeMap(params); // 2. 拼接 key1value1key2value2 StringBuilder sb new StringBuilder(); for (Map.EntryString, String entry : sortedParams.entrySet()) { if (sb.length() 0) { sb.append(); } sb.append(entry.getKey()).append().append(entry.getValue()); } // 3. 拼接secretkey sb.append(secretKey); // 4. MD5加密转小写 return md5(sb.toString()); } private static String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(StandardCharsets.UTF_8)); StringBuilder hexString new StringBuilder(); for (byte b : digest) { String hex Integer.toHexString(0xff b); if (hex.length() 1) hexString.append(0); hexString.append(hex); } return hexString.toString(); } catch (Exception e) { throw new RuntimeException(MD5加密失败, e); } } }这里面最容易出错的细节有四个一是参数值必须先做URL解码再做拼接如果原始值里带了%这种字符直接拼接会算错二是排序用的是Java字符串默认的字典序也就是ASCII码顺序不是中文拼音顺序三是MD5结果要统一转成小写泛微服务端校验时也是小写四是一次性参数如timestamp、nonce在两次请求之间不能复用我见过有人为了调试方便把时间戳写死结果第二天所有请求全部签名失败因为时间戳和随机数在服务端有缓存防重放机制。2.4 返回结果里除了token还要关注expires_in和refresh_token调用token申请接口正常情况下会返回一个JSON结构里面至少包含access_token和expires_in字段。有些版本还会返回refresh_token用于在access_token过期后免密换新。这里要注意泛微的token有效期一般是2小时甚至更短第三方系统不应该把token静态存在配置文件里而应该设计一个“过期自动重取”的机制。我一般是封装一个TokenManager用内存缓存token判断过期前预留60秒提前刷新Component public class TokenManager { private String accessToken; private long expireAt; Resource private EcologyTokenClient tokenClient; public synchronized String getToken() { // 有效期剩余不足60秒时重新获取 if (accessToken null || System.currentTimeMillis() expireAt - 60_000) { TokenResponse resp tokenClient.applyToken(); this.accessToken resp.getAccessToken(); this.expireAt System.currentTimeMillis() resp.getExpiresIn() * 1000L; } return accessToken; } }这种设计的好处是单点登录入口一旦被多人访问不会每次请求都去调token接口避免把泛微的认证服务打爆。我做压测的时候发现如果不加缓存100个并发用户在登录瞬间就会产生100次token请求有些机器性能一般的话响应会明显变慢甚至触发泛微的接口限流。2.5 如果拿不到access_token先查这三个原因接入过程中我最常遇到的三类token申请失败情况基本能覆盖八成问题。第一类是返回invalid appid说明appid写错了或者在泛微那边应用没启用第二类是返回invalid signature签名算法有问题建议用泛微文档里的示例参数手动算一遍MD5对比一下自己代码里算出来的是不是一样第三类是返回timestamp expired或invalid timestamp说明服务器时间不同步第三方服务器和泛微服务器时间差超过允许范围问题通常不在代码而在运维建议先跑一下ntpdate同步时间。还有一个冷门问题容易被忽略泛微Ecology9服务器如果部署在负载均衡后面token接口的请求分发到了多个节点而签名校验依赖的secretkey是以配置文件形式存在各节点本地一旦某台节点的配置文件不是最新的就会出现“时好时坏”的诡异现象。这个问题当时费了我不少时间后来在泛微技术支持的提醒下检查了各节点的ecology/WEB-INF/prop目录配置发现其中一台机器的secretkey确实没更新替换之后就好了。3. 页面跳转完整实现让浏览器带着token安全进入OA3.1 方案对比重定向跳转与请求头携带token拿到token之后怎么让用户进入OA有两种主流方案。第一种是后端302重定向把用户浏览器导向泛微的/login/SSOLoginByToken.jsp?tokenxxx之类的地址token放在URL上跳到泛微后泛微解析token、建会话、再跳首页。第二种是前端页面拿到token后通过AJAX请求泛微的某个接口在请求头里带上token字段让接口返回一个可访问的地址或直接返回页面信息。整体来说302重定向最简单稳定因为用户浏览器地址栏能看到最终跳转地址是OA域名后续的Cookie写入完全由浏览器按正常流程处理。使用302方案时有一个需要提前和网络团队确认的点外部系统域名和OA域名是否同域。如果两个域名完全不同浏览器会在跳转时带上Referer头等信息泛微某些版本的登录接口会做来源校验虽然在默认配置下不会强制校验但建议在对接前就确认好访问来源的限制策略避免上线后调整。我在一个客户现场就遇到过这个问题他们的安全策略是只允许特定域名引用OA链接结果集成环境测试正常生产环境一跳转就403最后是网络组在WAF上加了白名单才解决。3.2 后端生成完整跳转URL的最小实现以Java后端为例假设我们已经通过TokenManager拿到了合法token接下来只需要拼一个URL然后response.sendRedirect即可GetMapping(/sso/ecology) public void ssoToEcology(HttpServletResponse response) throws IOException { String token tokenManager.getToken(); String redirectUrl https://oa.example.com/login/SSOLoginByToken.jsp?token URLEncoder.encode(token, StandardCharsets.UTF_8.toString()) redirectUrl URLEncoder.encode(/wui/main.jsp, StandardCharsets.UTF_8.toString()); response.sendRedirect(redirectUrl); }这里的redirectUrl参数是否支持要看泛微对应版本部分版本不支持外部传入跳转目标固定跳到登录后的默认首页。如果传了不支持会怎样通常不会报错泛微会忽略它用户落到默认首页。但如果非要跳到某个指定页面建议先在浏览器里手动访问一下接口文档里写的跳转效果免得联调时发现参数名拼错了。很多朋友会纠结既然token在URL上第三方服务器日志、浏览器历史记录、CDN日志里都可能留下痕迹这是不是不安全这个担心有道理。所以实际生产环境里token的过期时间一定要短并且只能用于单次登录跳转。如果泛微支持一次性token用过即失效强烈建议在申请token时就按一次性token来设计避免token泄露后被人重放。3.3 泛微服务端拿到token之后做了什么这一步是理解整个跳转原理的关键。当浏览器访问SSOLoginByToken.jsp时泛微服务端大概会做四件事从请求参数里取出token校验token是否有效、是否过期、签名是否合法根据token中绑定的用户信息在系统内部创建一个用户会话Session将带有会话ID的Cookie写入响应头然后302跳转到OA首页。也就是说跳到这个接口的那一刻浏览器才真正变成了“已登录”的浏览器。这也是为什么外部系统不能只拿token去调OA的接口而不引导浏览器跳转——token不能替代Cookie。在需要深度集成待办消息的场景里你可能既要浏览器进OA登录又要后端服务器能调用待办查询接口这时建议分开设计浏览器用“页面跳转token”后端用“API调用token”。两个token不要混用否则API token一旦泄露别人可以直接拿它查你全公司所有人的待办这个风险等级非常高。3.4 前端页面的实现把入口做成一个安静的按钮如果只是给用户一个“进入OA”的按钮前端代码不需要做什么复杂处理一个普通链接就行a href/sso/ecology target_blank进入OA/a这里的/sso/ecology指向我们自己的后端接口后端再通过302把用户带到泛微。一般不建议在前端直接拼接泛微token地址原因是token的申请逻辑和签名逻辑不能暴露在浏览器端。如果确实是纯静态页面场景没有后端参与那就只能让后端提供一个“获取跳转地址”的接口前端拿到地址后window.location.href跳转。需要补充的是目标_blank的细节。如果用户第一次点击是弹新标签而单点登录成功后续关闭标签页再点大概率还会走一遍完整的SSO流程因为新标签页是全新上下文。如果你希望一次登录后后续点击不再经历跳转中转那可以把按钮指向泛微OA首页本身等待泛微自身的Cookie把用户带进系统。但这个方案有个前置条件浏览器里必须已经存在有效的泛微会话Cookie否则又会跳回登录页。4. 常见问题排查与避坑实录4.1 打开跳转地址后一直转圈或者报404我遇到过一个比较典型的案例跳转地址能正常打开但页面一直停留在加载状态最后报404。排查后发现问题出在redirectUrl参数编码上。泛微要求redirectUrl参数值必须URL编码但旧系统里有一段代码用了jsessionid拼接导致会话ID被当成目录的一部分传了进去整个地址结构乱了。另一个常见原因是泛微的登录接口不允许跨域来源来源页面域名如果不被信任接口直接不处理。遇到这类问题我建议先用浏览器的开发者工具看跳转链。打开Network面板勾选Preserve log从头到尾看一遍请求序列。如果某一个泛微接口返回302且Location指向了不是预期的地方那就是参数处理或来源校验出了问题。用这种排查方式10次里能定位8次。4.2 登录成功但用户没有待办权限跳转没问题、会话也建立了但打开首页后发现当前用户不是预期那个人或者没有对应菜单权限。这种通常不是单点登录本身的问题而是用户在泛微里的账号没有同步或无权访问相关应用。泛微Ecology9的登录校验只负责识别“你是谁”至于“你能看到什么”是由账号配置决定的。所以对接前一定要先确认泛微侧的账号存在、且登录名与第三方系统的唯一标识能对应上。很多企业喜欢用工号做登录名但第三方系统用的是邮箱前缀两边对不上结果微信企业号跳过来的人全变成了系统默认访客。当时我们解决的办法是让泛微管理员开放一个“用户同步接口”从主数据系统定时批量同步工号和邮箱映射关系一次性解决了账号匹配问题。4.3 token过期时间对用户体验的影响泛微token的有效期通常在2小时左右但对于长期打开着一个页面的用户来说过了半天再点击某个功能可能单点登录已经失效了。现象是页面刷新一下要重新登录或者跳转后提示“token失效”。这个问题的排查方向不是改泛微token时长而是看泛微的会话超时时间配置。因为单点登录只是建立了会话会话超时属于OA自身的运维配置。如果有“长期不要掉线”的诉求需要调整的是泛微的Session超时时间而不是每次去延长token有效期。另外一个细节是用户电脑的系统时间不准导致浏览器在TLS握手或签名校验时出现偏差。泛微服务端校验token时会同时校验时间戳偏差如果用户电脑时间比服务器时间快了好几分钟跳转后就会提示token无效。这类问题在办公电脑常年不校时的环境中挺常见让IT运维统一下发NTP校时策略才是根治之道。4.4 单点登录过程中出现HTTP 500错误HTTP 500的排查面比较广但根据我的经验最常见的是后端代码里远程调用泛微接口时超时导致拿不到token然后拼了一个空token的跳转地址泛微处理时抛异常。建议在代码里对token获取增加try-catch和降级逻辑如果token获取失败至少要给用户一个明确的错误提示页而不是直接空指针或跳到一个错误地址。泛微的接口响应时间通常和服务器负载有关如果在上午9点全员打卡高峰期调接口耗时可能比平时长很多。所以TokenManager里的“提前60秒刷新”策略比较保守如果业务允许也可以加一个“超时重试一次”的机制能有效降低高峰期偶发的500问题。5. 从单点登录到统一的身份治理这套方案还能怎么延伸单点登录做完只是第一步真正让我觉得这套方案有价值的是它顺带解决了身份数据的同步问题。前面提到用户登录名不一致的坑实际上在做完SSO之后企业内部往往还需要统一的账号生命周期管理包括入离职自动开通、禁用账号、调整部门权限。泛微Ecology9的认证接口虽然是面向登录的但它背后依赖的是一套用户数据模型如果主数据建设不完善光靠SSO只是把登录入口打通了权限和身份数据依然会在多个系统之间各管各的。如果公司在做统一的身份认证平台比如基于OAuth2或OIDC协议的身份中心那么泛微Ecology9也可以被改造成一个OAuth2的Client。用户在身份中心登录后由身份中心颁发code泛微再用code换取本地会话。不过这种改造的工作量明显大于直接用token接口要有泛微定制开发能力的团队才建议尝试。还有一个小建议企业里如果同时有多个系统要接入OA不要每个系统各写一套token申请和签名逻辑。做一个独立的SSO认证服务由它统一封装泛微的token接口、签名算法、TokenManager和跳转地址生成逻辑其他系统通过HTTP调用这个服务。这样泛微侧只需要对这一个服务签发secretkey后续如果泛微升级接口或者更换签名算法只需要改这一个服务比在N个系统里重复改要省心得多。我在一个集团项目里就是这么干的后来泛微从Ecology9的小版本升级到新补丁时认证接口有个细微的变化我们只改了一个公共包就完成了适配其他业务系统完全没感知。另外一个延伸场景是把泛微待办集成到企业微信或钉钉的工作台。用户在微信里点开待办卡片时后端先把用户的免登态转换成泛微token再跳转到OA对应页面这样用户从移动端也能直接处理OA流程不需要在手机上再输一次账号密码。这个场景下要注意的是移动端的URL Scheme和微信内置浏览器的Cookie策略部分环境会拦截跨域跳转需要提前在泛微后台配置可信域名。写在最后的几点实操感想这套基于Token的单点登录方案我在不同项目里反复用过整体稳定性很好但有几个容易被低估的坑想再强调一下。签名算法里的参数顺序一定不能靠记忆要以接口文档的示例代码为准每次泛微升级后都要重新验证一次token是有时效的凭证不要设计成永久有效生产环境里的secretkey一定要有独立的权限管理和轮换机制最好由安全团队统一保管密钥泄露的影响面远大于一次弱口令泄露。泛微Ecology9的单点登录对接本身并不算复杂真正的复杂度在身份数据的准确性和跨系统的约定上。把账号映射关系梳理清楚把token获取和跳转流程拆成独立模块后续的维护成本就会低很多。希望这篇实战记录能帮你少踩几个坑也欢迎在实际对接中遇到了不一样的报错回头一起交流解法。
返回列表