☰
NC65单点登录落地实践:票据校验、过滤器与踩坑记录
2026/10/13 12:12:13 网站建设 项目流程

简介:面向第三方系统与用友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 票据从生成到落地的完整过程

我用最直白的方式描述一下这个包设计的流程:

  1. 用户在门户系统登录成功,门户生成一次性票据 ticket。票据内容一般包含 userId、timestamp、随机数和签名。
  2. 门户把浏览器重定向到 NC65 的 SSO 入口地址,例如/nc/ssoLogin.jsp?ticket=xxxx&returnUrl=/index.jsp。
  3. NC65 的 SSOFilter 拦截到这个请求,取出 ticket 参数,先用配置好的密钥校验签名,再判断时间戳是否在有效期内。
  4. 校验通过后,过滤器从票据中解析出 userId,去 NC65 用户表查出对应的本地用户编码。
  5. 过滤器调用 Session 创建逻辑,把用户信息写入 Session,然后 redirect 到 returnUrl。
  6. 校验失败则跳回门户登录页,并带上错误码。

这个流程里,票据是一次性的这个点特别重要。虽然包里没有强制做一次性消费的记录,但我强烈建议加上。否则票据在 URL 里被别人拿到,有效期以内都能用,风险较大。可以理解成电影院的一次性电影票:检票后必须作废,才能防止黄牛循环卖票。

2.3 为什么选择“票据+重定向”而不是共享Session

很多初次接触的人会问:直接把认证中心的 Session 状态存到 Redis,NC65 每次请求都去查一下,是不是更“现代”?理论上是,但实际落地很难。NC65 作为老牌企业套件,它的 Session 管理有自己的逻辑,牵扯到权限缓存、模块开关、客户端类型识别,强行引入外部 Session 方案,改造量大,而且稍有不慎会影响原有功能。

票据方案的好处在于把改动收敛到入口这一层,NC65 内部的东西基本不动。认证中心只负责发票据、验票据,NC65 只负责收票据、建会话,两边耦合度非常低。对实施团队来说,这种方案的可控性和回滚能力都要好得多。

3. 在NC65侧落地:拦截器、参数配置与用户映射

3.1 拦截器的实现逻辑与关键代码

这个方案的核心就是过滤器。我按包里的思路重写了一个简化版本,重点是让新接触的人能看懂判断结构:

public class NCSSOFilter implements Filter { private String secretKey; private int expireSeconds; private List<String> 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; } // 校验签名与有效期 Map<String, 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?error=ticket_invalid"); } }

这里最需要注意三个判断顺序:先查本地会话,再查白名单,最后才处理票据。顺序反了,会出现登录后反复校验、静态资源无法加载的问题。包的说明文档里没写这个顺序,我第一次就踩了。

3.2 过滤器注册和配置文件

NC65 的 Web 应用都部署在中间件上,过滤器注册还是在 web.xml 里做。需要在 NC65 对应 webapp 的 web.xml 里加这段:

<filter> <filter-name>NCSSOFilter</filter-name> <filter-class>com.example.sso.NCSSOFilter</filter-class> <init-param> <param-name>secretKey</param-name> <param-value>your-32-bytes-secret</param-value> </init-param> <init-param> <param-name>expireSeconds</param-name> <param-value>60</param-value> </init-param> </filter> <filter-mapping> <filter-name>NCSSOFilter</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 很可能不在一个域名下。门户域名的 Cookie,NC65 域名读不到,反过来也一样。很多人一开始想通过设置 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-8,NC65 这边老过滤器默认按 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。这些配置里任何一个不对,都会让你在联调阶段反复来回拉锯。

最后分享一个经验:拿到这种集成需求,第一步不是埋头看代码,而是先把域名、编码、负载均衡策略、用户数据同步方式这四件事和两边团队确认清楚。技术方案反而简单,业务边界和运行环境不确定,后面才会真正让人崩溃。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询