
接了个老项目的维护Java程序要从工控机上连KEPware读几个点位数据以前跑得好好的换了一台部署机之后启动直接抛了org.jinterop.dcom.common.JIException: Access is denied。这玩意儿不是偶发每次必现我第一反应是账号密码坏了结果在原来那台机器上又能跑通新机器就是不行。排查到最后才明白Java连OPC DA的本质是通过j-interop库发起远程DCOM调用Windows的DCOM安全体系在Windows层面就把这个进程拦下了和Java代码关系不大。这类问题在论坛里能找到不少帖子但大多是“去组件服务里改DCOM权限”一句话带过没人讲清楚到底要改哪些项、为什么这么改。这篇文章把整个定位过程和完整配置步骤拆开写适合两类人看一类是正被Access is denied卡住的Java开发另一类是已经有OPC基础、但没系统理解DCOM权限模型的工程师。看完你不仅能解决眼下的报错还能理解异常背后Windows到底做了什么判断。1. 先看异常细节Access is denied到底是什么拒绝了你1.1 异常栈里藏着关键信息不同版本的j-interop打出来的异常栈不完全一样但核心内容类似org.jinterop.dcom.common.JIException: Access is denied at org.jinterop.dcom.core.JIComServer.getInterfaceObject(Unknown Source) at org.jinterop.dcom.core.JIComServer.newInstanceByName(Unknown Source) at com.example.opc.OPCConnection.connect(OPCConnection.java:45) ... Caused by: org.jinterop.dcom.common.JIException: Access is denied [0xD] at jinterop.ji.JIComServer$JIComServer... at org.jinterop.dcom.core.JIANSIconverter...异常末尾的[0xD]是COM错误码十进制13对应Windows RPC的RPC_S_ACCESS_DENIED翻译过来就是“RPC访问被拒绝”。大部分搜到Access is denied的同学基本都在这一步因为栈里没有任何Java代码的业务逻辑很容易误判成是网络不通或者j-interop库的问题。实际上错误是在DCOM协议握手阶段就返回了也就是说服务器收到了客户端的RPC连接请求但在权限校验阶段决定不给你继续往下走。这里先记住一个结论**如果异常是[0xD]或0x80070005E_ACCESSDENIED问题几乎100%出在Windows DCOM安全配置上而不是你的Java代码。**如果异常是0x800706BA服务器不可用才是网络或服务未启动问题。1.2 常见COM错误码和实际含义对照我把实际项目里遇到过的一些COM/DCOM相关错误码整理成了表方便你对号入座错误码含义常见场景0xD (RPC_S_ACCESS_DENIED)RPC层拒绝访问DCOM访问权限、启动权限未包含当前用户0x80070005拒绝访问COM安全限制、文件/注册表权限0x800706BARPC服务器不可用OPC Server服务未启动、防火墙阻挡、135端口不通0x80070776未找到指定的源路径ProgID不存在程序集未注册0x80040154没有注册类OPC Server没有注册为COM组件看到[0xD]就直接往DCOM权限方向查别浪费时间去抓包看端口。实际上即使DCOM服务存在但当前Java进程所用的Windows账户没有权限服务器也会返回一样的结果。后面我们做的事情就是把Java进程的Windows身份加入OPC Server DCOM组件的许可名单。2. 为什么Java远程调用OPC会被Windows拒绝DCOM权限模型三层过滤2.1 DCOM安全权限的三个层面OPC DAData Access老协议基于COM/DCOM当Java程序要从远程机器访问OPC Server时j-interop充当了一个“DCOM客户端”。DCOM的安全模型比简单socket复杂得多它在建立连接时至少要经过三层校验计算机级别的“COM安全”限制管理所有COM组件的全局默认值如果这里限制得死后续组件级配置完全不会生效。组件级别的“启动/激活权限”控制谁可以远程启动或激活这个COM对象。组件级别的“访问权限”控制谁可以连接已经运行起来的COM对象并调用其接口。多数报错的根本原因是后两个Java进程当前运行所用的Windows用户既不在访问权限列表里也不在启动权限列表里。j-interop发起的请求在DCOM服务器端会被视为一个“匿名或未授权调用”RPC层直接返回[0xD]。为什么原来那台机器不报错因为原来那台机器的OPC Server DCOM配置很早前被修改过管理员把Everyone或NETWORK SERVICE放进了访问列表新机器用的是默认配置默认只允许Administrators和Interactive Users正好把Java部署服务使用的固定账号排除了。2.2 核心逻辑服务器不会验证你是谁而是验证谁派你来的很多开发对DCOM权限会有个误解以为只要Java代码里传了和OPC Server一样的用户名密码就天然有权限。实际上DCOM的权限认证是这样的你的Java进程运行在机器A上通过j-interop发的用户名密码会被机器A的Windows某个服务用来创建一个安全令牌然后这个令牌会被发送到机器BOPC Server。机器B校验这个令牌的权限而不是校验这个令牌对应的人在不在机器B上。这里最常出问题的是用户名、密码、域名三个参数只要有一个不对DCOM就会拒绝有的场景下返回的是“密码错误”有的就直接返回Access is denied尤其是在工作组环境下特别容易因为域名参数填错导致权限令牌不合法。另外还有一层是“模拟级别”Impersonation Level。j-interop默认使用IMPERSONATE但如果Java进程所在机器不支持模拟或者OPC Server侧配置不允许模拟也可能出现权限异常。这一层相对少见但网上很多解决方案都提到了“打开组件服务的标识选项”其实就是在处理这个模拟身份问题。3. 一步一步配置Windows DCOM从组件服务到OPC Server的所有权限点3.1 先找到目标OPC Server的DCOM组件在OPC Server所在的Windows机器上按Win R输入dcomcnfg打开组件服务。依次展开“组件服务 - 计算机 - 我的电脑 - DCOM配置”。这里你会看到一长串已注册的COM组件目标就是找到你自己的OPC Server。不同OPC Server的注册名称不一样常见的有OPC Server软件DCOM配置中显示的名称KEPware KEPServerEXKEPServerEX RuntimeMatrikon OPC SimulationMatrikon.OPC.SimulationSIEMENS SIMATICNETSIMATIC.NET OPC Server可能多个自研OPC Server项目注册的 ProgID 或应用名如果列表里看不到先确认OPC Server到底有没有安装并注册成功。尤其要注意32位和64位的问题dcomcnfg默认打开的是当前系统位数的界面如果OPC Server是32位组件在64位系统的默认DCOM配置里可能看不到它。这时需要打开32位组件服务管理单元Win R输入mmc然后添加组件服务管理单元或者直接运行C:\Windows\SysWOW64\Dcomcnfg.exe。3.2 修改组件级安全属性找到目标组件后右键点击属性切到安全页签。这里有三个区域启动和激活权限、访问权限、配置权限。我们主要改前两个。统一按下面操作启动和激活权限选“自定义”点击编辑添加运行Java进程的Windows账户。如果Java进程以Windows服务形式跑一般是NT AUTHORITY\SYSTEM或NT AUTHORITY\NETWORK SERVICE如果是直接命令行启动则添加你的具体用户名。为了避免反复追加遗漏新手阶段可以临时添加Everyone等验证通过了再收窄。访问权限选“自定义”同样添加该账户并保证下方的“本地访问”和“远程访问”两个复选框都被勾选。配置权限一般不用动保持默认即可。这里特别注意如果你选了“自定义”等于彻底清空默认继承的权限只以你添加的内容为准。所以一定要确保把真正需要的账户加进启动权限和访问权限否则连本机OPC客户端也会失败。3.3 设置“标识”决定OPC Server以谁的身份运行刚才那段是操作“谁可以来连”的现在“标识”是“OPC Server组件以什么身份运行并与客户端交互”。在组件属性窗口切到标识页签有四种选项交互式用户只有当有人登录服务器桌面时组件才能正常运行对Java这种无人值守服务非常不可靠。启动用户每次由发起启动的客户端身份来运行多客户端场景容易出现偶发无权限。此用户指定一个固定账户运行最稳定。下列用户不同Windows版本措辞不同本质同上。实际生产环境里OPC Server很多是以Windows服务形式启动的KEPware就是典型此时组件标识不用过分折腾因为服务已经是独立进程在跑了。但如果你用Java程序去访问的OPC Server是以“组件免注册/服务方式”运行就要留意这个标识字段。如果标识选的是“交互式用户”而当前工控机上没有人登录桌面OPC Server就没有实际进程去处理请求日志可能不是Access is denied而是服务器不可用或启动失败。只有一种情况特别诡异OPC Server进程启动了但它的权限令牌无法模拟给Java客户端于是返回[0xD]。我遇到过一次后来把标识改成“此用户”并指定了一个有管理员权限的本地账户问题消失。不过要提醒一句标识账户必须设置为“密码永不过期”否则几个月后到期DCOM会突然全灭而且做得隐蔽的话Windows事件日志里只有一条“组件X消耗了超出其配额的成本”之类的提示很难联想到是密码过期。3.4 不要忽略计算机级别的COM安全限制改完组件属性后还要回到“我的电脑”上。右键点我的电脑选择属性切到COM安全页签。这里有“访问限制”“启动限制”“配置限制”三个区域。默认情况下“访问限制”和“启动限制”里是有系统默认账户组的一般不需要额外添加。但当你把OPC Server的访问权限收得很紧之后可能会遇到“远程访问限制”拦住了Java客户端。稳妥起见可以在“启动限制”里添加运行Java进程的账户或者临时添加Everyone验证是否是这里兜底拦截。这不是常规步骤但确实是很多隐藏问题的来源组件级放开了计算机级又把你拦回去。我没有夸张曾经在一个Win10系统上修改OPC Server组件属性怎么都无效最后发现是“COM安全”里默认的ANONYMOUS LOGON被删除了重新添加ANONYMOUS LOGON并勾选远程访问后问题解决。原因是DCOM协议第一阶段会用匿名令牌查询注册表信息匿名登录被禁止就直接拒绝。4. Java侧和网络侧一定要同步做对的几件事4.1 初始化j-interop的参数不是随便填的如果你是直接在Java代码里手动创建j-interop连接典型代码如下import org.jinterop.dcom.common.JISystem; import org.jinterop.dcom.core.*; import java.net.InetAddress; public class OPCDAConnector { public static void main(String[] args) throws Exception { String host 192.168.1.100; // OPC Server所在IP String domain OPC_HOST; // 注意这里的domain String userName admin; String password password; JISystem.setAutoRegisteration(true); JISession session JISession.createSession(domain, userName, password); InetAddress address InetAddress.getByName(host); JIComServer server new JIComServer(KEPware.KEPServerEX.V6, address, session); JIComObject comObject server.getJIComObject(); } }这段代码里最重要的就是domain参数。如果你把domain直接填成OPC_HOST在一个非域环境下这是可能的但在域环境里domain必须填你的Windows域名称。如果你不确定域是什么可以在OPC Server机器上运行set命令查看USERDOMAIN环境变量或者直接填一个点号.来表示本机。实际踩坑记录一个项目里Java服务跑在域账户下domain填了本机名DCOM权限怎么配置都不对最后把domain改成域名称一行代码没改就通了。另外JISystem.setAutoRegisteration(true)只会影响本地注册的组件远程DCOM连接实际上还需要j-interop在客户端机器上生成几个临时文件。如果Java进程的运行账户对临时目录C:\Users\用户\AppData\Local\Temp没有写权限也会出现无法初始化DCOM环境的问题表现形式类似于Access is denied但不是权限问题。遇到这种去查看系统临时目录写入是否正常。4.2 Windows防火墙和DCOM端口范围DCOM的动态端口设计是另一个容易踩的坑。客户端要连接远程DCOM首先必须能访问DC域控如果有和RPC服务通常要通TCP 135端口。建立连接初阶段的话题之后DCOM会协商后续数据传输使用Windows动态端口范围一般是49152-65535Windows Server 2008及以后版本旧系统是1024-65535。如果OPC Server机器开着防火墙不把动态端口范围放行Java客户端会出现的情况是偶尔能连接成功程序跑一段时间后突然异常或者第一次连接卡住超时然后返回一般的网络错误但有时候也会报Access is denied因为DCOM回调端口连不上客户端重新发起的RPC请求被服务器拒绝。解决方法是在防火墙中放行C:\Windows\System32\dllhost.exe程序COM Surrogate进程。或放行TCP 135端口并放行TCP 49152-65535端口动态RPC端口。如果你不想开一大段端口也可以在OPC Server机器上通过regedit手动固定RPC动态端口范围然后只放行这个范围。固定DCOM端口的注册表项为HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Rpc\Internet在该键下新建DWORD值Ports值数据填写端口范围例如5000-5100再新建值PortsInternetAvailable为Y最后重启RPC服务。4.3 常见OPC Server各自的注意事项KEPware、Matrikon、西门子SIMATIC NET虽然都遵循OPC DA标准但各自的默认配置有微小差异KEPware KEPServerEX安装后自带的OPC UA Server是独立的OPC DA和OPC UA需要分别看日志。连接KEPware的OPC DA时要确保“OPC”许可和通道设备已正常激活。如果服务器装了防火墙KEPware安装目录下的Kepware.OPCClient工具能连通但Java连不上往往还是防火墙端口问题。Matrikon OPC Simulation这款常用于开发测试环境网上很多资料。它有个“安全模式”选项在Matrikon服务器管理器中安全设置如果勾选了“OPC Security”相关选项会导致Java客户端无法匿名访问。如果你的机器只是内部调试直接把它安全等级调低。西门子SIMATIC NET需要特别注意OPC Server服务名称是Siemens OPC Server在DCOM配置里可能有多个条目。必须在组件服务里对实际使用的条目做权限修改否则会遇到“ProgID能连但读取点位时Access is denied”的诡异现象。5. 改完配置还是不通排查链路和几个隐蔽坑5.1 完整验证路径从简单到复杂按顺序来每次改完DCOM配置不推荐直接重启Windows正确的做法是按下面顺序验证重启OPC Server服务在服务管理器中找到对应的OPC Server服务右键重启。DCOM权限配置在服务进程启动时读取不重启服务新权限可能不生效。在OPC Server机器本地验证用自带的客户端工具比如KEPware的OPCQuickClient连接一次本地点位确认OPC Server进程本身没问题。在Java部署机器上ping通确认网络层面通。如果跨网段优先检查是否通TCP 135端口telnet 192.168.1.100 135如果telnet没反应说明RPC端点是直接被防火墙或网络策略阻断了。用j-interop自带的示例代码连一次用官方JUnit示例里的连接测试排除你自己业务代码的影响。查看Windows事件日志OPC Server机器上的“事件查看器 - Windows日志 - 系统”如果DCOM权限问题通常会记录事件ID 10016事件文本里会明确说出无权限的账户和应用ID。这是个非常高效的定位手段。5.2 我踩过的三个隐蔽坑拿出来说说第一个改了DCOM权限但忘了把Java进程运行账户加进本地“性能日志用户”组。有些OPC Server在定位点和读取项目的时候会调用性能计数器如果账户没有权限访问性能库部分客户端会有Access is denied但这跟DCOM配置没有关系。解决办法是把运行Java的账户添加到该机器的“Performance Log Users”或“Performance Monitor Users”用户组。第二个Java进程跑在Tomcat服务或者Windows服务里导致实际身份不是当前登录用户。很多开发在IDE里能连上部署到服务器上就报错就是因为IDE里用的是你自己登录的域名管理员账户而服务配置里用的可能是LocalSystem或NetworkService。LocalSystem在DCOM分组里属于“管理员”一般没问题NetworkService则容易出现权限不够。务必确认Windows服务登录身份然后去DCOM配置中把对应账户加进去。第三个同一台机器同时装32位和64位OPC Server。这时候DCOM配置里会出现两个相似的组件名称一个对应32位一个对应64位。我遇到过一次配置了其中一个Java依然Access is denied后来发现Java程序连的ProgID是另一个版本但DCOM配置界面显示的名称极其相似不仔细看很容易忽略。这时可以通过OPC Server安装路径、ProgID注册表来确认到底注册的是哪一个版本的组件再去修改对应的DCOM项。5.3 最后给一个自查清单为了方便你将来快速排查我把这类问题涉及的所有开关整理成清单[ ] Java进程运行账户已加入OPC Server DCOM组件的“启动和激活权限”[ ] Java进程运行账户已加入该组件的“访问权限”[ ] “我的电脑”COM安全中的启动/访问限制没有拒绝该账户[ ] OPC Server组件标识选择了“此用户”且密码未过期[ ] 防火墙放行TCP 135和动态RPC端口或固定端口范围已配置[ ] Java侧j-interop的domain参数与OPC Server所属域一致或为.[ ] 如果Java跑在服务中确认Windows服务账户确实是上面加入权限的账户[ ] 查看过OPC Server机器系统日志确认没有10016等DCOM错误[ ] 修改配置后重启了OPC Server服务按照这个清单走一遍绝大多数org.jinterop.dcom.common.JIException: Access is denied都能解决。如果还有问题基本就是Windows安全组策略或者杀毒软件在中间捣乱了。我自己后来在工控机上调试都喜欢先把DCOM安全相关的所有其他权限项打开可以先把业务跑通再逐步收紧。这也算是个小技巧别一上来就追求“最小权限”先把问题验证过去再慢慢做收敛否则你很难判断是哪个策略把连接掐断的。