
简介面向第三方系统与用友NC65的单点登录集成需求这套方案提供了一份轻量级配置资料帮助用户在第三方系统完成认证后免密登录NC65并自动拉起UClient客户端直达首页减少重复登录操作。压缩包共3个文件由xml配置样例、txt使用说明和docx详细配置文档组成整体大小仅174KB结构紧凑适合NC65实施人员、系统集成工程师及二次开发人员快速上手。xml样例为单点登录对接提供关键参数模板txt说明便于快速了解接入流程docx文档则进一步梳理UClient端打开首页的配置步骤与注意事项三者配合可有效降低集成验证和排错成本。目前已有1073人学习或下载该资源对正在处理统一身份认证、门户集成或NC65单点登录需求的技术人员而言这份成套配置资料可直接借鉴能够帮助节省自行摸索的时间。 前阵子接了个用友 NC65 的改造项目需求听起来很常见干起来却一点也不省心把 NC65 接进公司的统一登录平台实现单点登录。网上的资料大多是零散帖子真正能落地的方案最后是从朋友那里拿到的一个压缩包名字就叫 NC65单点登陆.rar。解压后里面有过滤器源码、两个 JSP 页面、一个 properties 配置文件和一篇很简略的说明文档。我照着改也踩了不少坑。这篇文章我把整个落地过程、核心代码和排查记录整理出来给准备做 NC65 集成的人做个参考。1. NC65的本地登录链路单点登录到底要改哪一段1.1 NC65的登录态是怎么建立的NC65 本质上是跑在 J2EE 中间件上的 Web 应用跟大多数老牌管理系统一样本地登录流程非常传统用户打开登录页 login.jsp输入账号密码系统拿到后交给后端的认证逻辑做校验。校验通过服务端生成一个 Session同时通过 Cookie 把会话标识写回浏览器后续用户点菜单、查单据都是靠这个会话标识维持状态。很多第一次改 NC65 的人会习惯性去翻它的登录 Action想在原来校验密码的地方打补丁。这个思路不能说错但很容易陷入细节。NC65 的登录动作里除了密码校验还有验证码、密码策略、登录锁定、操作日志这些逻辑你逐个去改相当于在人家核心链路上动刀出问题的影响面会非常大。关键要认清一点NC65 的 Session 是它自己内存里的会话对象跟外部门户系统的会话完全隔离。1.2 单点登录要替换的是哪一段单点登录的本质是让用户在一个系统登录后访问另一个系统不用再输密码。外部认证中心已经证明了用户身份NC65 需要做的不是再校验一次密码而是信任认证中心给出的证明并把这份信任转换成自己内部的登录态。所以实际要改的只是在登录链路前面加一道“信任翻译层”认证中心把用户身份信息包装成一个票据跳转带到 NC65 的一个入口NC65 这边的过滤器或者 Servlet 接收票据用约定好的密钥验证票据真伪验过之后直接创建本地 Session然后跳回业务页面。原来的密码校验、验证码流程全部绕过。一句话总结把“用户名密码校验”替换成“票据校验”这就是 NC65 单点登录的核心改造点。2. 解开NC65单点登陆.rar里面的方案是这样流转的2.1 解压后的核心文件与整体设计那个 rar 包里的文件不算多我整理了一下大概是这么个结构SSOFilter.java核心过滤器负责拦截客户端请求处理票据校验和 Session 建立。NCSSOUtil.java工具类封装签名生成、签名校验、票据时效校验。ssoLogin.jsp票据入口页展示校验结果并做页面跳转。ssoLogout.jsp退出接口处理本地 Session 清理和门户退出回调。sso.properties参数配置文件包括认证中心地址、密钥、过期时间、白名单。nc-sso-guide.docx一份很简略的说明文档。整个方案属于比较典型的“URL 票据 本地会话”模型。外部系统认证通过后把 userId、时间戳、随机数拼在一起用密钥做签名拼成 ticket 参数重定向到 NC65 的 SSO 入口。NC65 拿到票据后做三件事验签名、验有效期、映射本地用户然后建立会话。2.2 票据从生成到落地的完整过程我用最直白的方式描述一下这个包设计的流程用户在门户系统登录成功门户生成一次性票据 ticket。票据内容一般包含 userId、timestamp、随机数和签名。门户把浏览器重定向到 NC65 的 SSO 入口地址例如/nc/ssoLogin.jsp?ticketxxxxreturnUrl/index.jsp。NC65 的 SSOFilter 拦截到这个请求取出 ticket 参数先用配置好的密钥校验签名再判断时间戳是否在有效期内。校验通过后过滤器从票据中解析出 userId去 NC65 用户表查出对应的本地用户编码。过滤器调用 Session 创建逻辑把用户信息写入 Session然后 redirect 到 returnUrl。校验失败则跳回门户登录页并带上错误码。这个流程里票据是一次性的这个点特别重要。虽然包里没有强制做一次性消费的记录但我强烈建议加上。否则票据在 URL 里被别人拿到有效期以内都能用风险较大。可以理解成电影院的一次性电影票检票后必须作废才能防止黄牛循环卖票。2.3 为什么选择“票据重定向”而不是共享Session很多初次接触的人会问直接把认证中心的 Session 状态存到 RedisNC65 每次请求都去查一下是不是更“现代”理论上是但实际落地很难。NC65 作为老牌企业套件它的 Session 管理有自己的逻辑牵扯到权限缓存、模块开关、客户端类型识别强行引入外部 Session 方案改造量大而且稍有不慎会影响原有功能。票据方案的好处在于把改动收敛到入口这一层NC65 内部的东西基本不动。认证中心只负责发票据、验票据NC65 只负责收票据、建会话两边耦合度非常低。对实施团队来说这种方案的可控性和回滚能力都要好得多。3. 在NC65侧落地拦截器、参数配置与用户映射3.1 拦截器的实现逻辑与关键代码这个方案的核心就是过滤器。我按包里的思路重写了一个简化版本重点是让新接触的人能看懂判断结构public class NCSSOFilter implements Filter { private String secretKey; private int expireSeconds; private ListString excludeUrls; Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) servletRequest; HttpServletResponse response (HttpServletResponse) servletResponse; String uri request.getRequestURI(); // 已登录用户直接放行 if (request.getSession().getAttribute(nc_user) ! null) { chain.doFilter(request, response); return; } // 白名单路径放行避免登录页、静态资源被拦截 for (String suffix : excludeUrls) { if (uri.endsWith(suffix)) { chain.doFilter(request, response); return; } } String ticket request.getParameter(ticket); if (ticket null || ticket.isEmpty()) { // 没有票据跳转统一认证中心 String returnUrl request.getRequestURL().toString(); String redirectUrl ssoServer /login?redirect URLEncoder.encode(returnUrl, UTF-8); response.sendRedirect(redirectUrl); return; } // 校验签名与有效期 MapString, String result SSOUtil.verifyTicket(ticket, secretKey, expireSeconds); if (ok.equals(result.get(status))) { String userId result.get(userId); String ncUserCode NCUserMapper.getNcUserCode(userId); if (ncUserCode ! null) { request.getSession().setAttribute(nc_user, ncUserCode); response.sendRedirect(request.getParameter(returnUrl) null ? /nc/index.jsp : request.getParameter(returnUrl)); return; } } // 校验失败回到认证中心并携带错误信息 response.sendRedirect(ssoServer /login?errorticket_invalid); } }这里最需要注意三个判断顺序先查本地会话再查白名单最后才处理票据。顺序反了会出现登录后反复校验、静态资源无法加载的问题。包的说明文档里没写这个顺序我第一次就踩了。3.2 过滤器注册和配置文件NC65 的 Web 应用都部署在中间件上过滤器注册还是在 web.xml 里做。需要在 NC65 对应 webapp 的 web.xml 里加这段filter filter-nameNCSSOFilter/filter-name filter-classcom.example.sso.NCSSOFilter/filter-class init-param param-namesecretKey/param-name param-valueyour-32-bytes-secret/param-value /init-param init-param param-nameexpireSeconds/param-name param-value60/param-value /init-param /filter filter-mapping filter-nameNCSSOFilter/filter-name url-pattern/nc/*/url-pattern /filter-mapping这里要留意过滤器的映射路径看你的实际访问前缀多数 NC65 应用会挂在/nc上下文下。改完 web.xml 必须重启中间件很多配置不生效的原因就是只替换了 class 没重启排查半天最后发现是缓存。3.3 用户映射与初始权限票据校验通过只代表“认证中心承认这个人”不代表 NC65 里有这个用户。两块系统的用户主数据如果不一致常见的表现就是跳转过来能进 NC65 首页但菜单空白或者直接提示“用户不存在”。所以落地方案里必须有用户映射逻辑。最简单的方式是把认证中心的 userId 和 NC65 的用户编码做成映射表放在配置或数据库里。更完善的方式是写一个自动开号接口票据校验通过后如果本地查不到用户就调用 NC65 的账号创建接口按规则赋予默认角色。我的建议是生产环境不要全自动开号第一次对接用映射表跑通等确认角色权限模型没问题再考虑自动开号。4. 实测最磨人的五个坑重定向、乱码、集群掉线4.1 登录页面被过滤器反复拦截形成死循环这是所有 SSO 对接里出现频率最高的坑。原因很简单过滤器把/login.jsp本身也拦截了用户没登录访问登录页被踢到门户门户又带着票据跳回来回来后还是没建立会话又被踢走页面一直刷新。解决方式就是白名单要配全。除了登录页还要把静态资源、CSS、JS、图片、以及认证中心回调地址一并放行。我和同事总结过一个经验凡是你在浏览器地址栏看到的独立请求路径只要不在需要鉴权的业务范围里都先加进 excludeUrls等验证完再一个一个收紧。4.2 跨域环境下 Cookie 丢失会话建了也存不住门户和 NC65 很可能不在一个域名下。门户域名的 CookieNC65 域名读不到反过来也一样。很多人一开始想通过设置 Cookie 的 domain 来解决比如统一成.company.com但老系统中间件处理不优雅后来我们干脆走 URL 传输票据彻底绕开跨域 Cookie 问题。另外还有个隐蔽点NC65 前面如果有 Nginx 做反向代理并且代理路径和实际情况不一致服务端写回的 Cookie path 会对不上浏览器不认导致每次请求都重新建 session。排查这类问题直接用浏览器开发者工具看 Cookie 的 Domain 和 Path基本能定位。4.3 集群环境下用户刷新就掉线NC65 生产环境经常是多节点集群。第一次请求落在 A 节点A 节点创建了 Session用户一点刷新负载均衡把请求分到 B 节点B 节点本地没有这个 Session于是又跳去认证中心体验非常差。这个问题的根源是 Session 存在了单个服务器内存里。最省事的解法是在负载均衡层做会话保持Nginx 里配置ip_hash或 sticky session让同一用户的请求固定落在同一个节点。优先在接入层解决别一上来就去改造 NC65 的 Session 存储。改 Redis 共享 Session 听起来很高级但老系统的兼容性问题会无穷无尽。4.4 中文用户名乱码导致票据验签失败票据里如果包含中文 userId最容易出编码问题。认证中心生成票据时用的是 UTF-8NC65 这边老过滤器默认按 ISO-8859-1 解码结果就是中文乱码签名对不上白白浪费一上午查逻辑。解决方式很粗暴但有效在过滤器最前面加request.setCharacterEncoding(UTF-8)同时所有 URL 参数在生成票据时统一用URLEncoder.encode编码。更重要的是签名的时候一定要用原始字符串不能拿编码后的值去签不然两边永远不一致。4.5 退出登录两边不同步用户被“自动登录”回来这是个特别容易被忽略的点。用户从 NC65 点退出系统把 NC65 的 Session 清了但门户的 Session 还在。用户以为退出了实际上回到门户一点 NC65 入口又自动登录进来了这对合规要求严格的系统来说是个事故。解决方案是在 NC65 的退出接口里同时调用门户的 logout 地址把两边会话都销毁或者由门户统一跳转退出逻辑让两边都收到通知。包里的 ssoLogout.jsp 本身有这个设计但很多人部署时没把门户的 logout 地址配对导致功能失效。4.6 用户认证成功但进 NC65 后没菜单、没权限最后一个问题不是技术是数据。认证中心里用户状态正常票据也验过了但进入 NC65 后看到的是空菜单或者点任何模块都提示无权限。翻日志往往是“用户没有分配任何角色”。原因是 NC65 的用户表和角色分配表里根本没有这个人的数据。SSO 只解决“你是谁”的问题不解决“你能干什么”的问题。对接清单里一定要加一步确认用户映射、角色分配、默认密码策略在两边系统是一致且同步的否则上线后会被业务部门疯狂投诉。5. 这套方案能复用到哪些场景一个配置自查清单做完这个项目之后最大的体会是NC65 单点登录这套东西完全能抽象成一种通用接入模式。无论是 NC65、NC57还是其他老 J2EE 系统只要你能在系统入口加一道过滤器能做到“收到票据、验签、建会话、跳转”就能接进统一认证门户。我给自己整理了一个自查清单后来做类似项目都会过一遍认证中心和业务系统的密钥是否一致、票据有效期是否合理、过滤器白名单是否覆盖登录页和静态资源、用户映射表是否完整、退出接口是否双向通知、集群是否配了会话保持、负载均衡代理路径和 Cookie Path 是否对齐、编码统一用 UTF-8。这些配置里任何一个不对都会让你在联调阶段反复来回拉锯。最后分享一个经验拿到这种集成需求第一步不是埋头看代码而是先把域名、编码、负载均衡策略、用户数据同步方式这四件事和两边团队确认清楚。技术方案反而简单业务边界和运行环境不确定后面才会真正让人崩溃。本文还有配套的精品资源点击获取