ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java连接OPC Server报Access is denied?DCOM权限配置与排查指南

Java连接OPC Server报Access is denied?DCOM权限配置与排查指南 搞Java的人第一次去连OPC Server十有八九会撞上这个异常org.jinterop.dcom.common.JIException: Access is denied它出现的时机通常都在创建DCOM会话、调用CoCreateInstanceEx 那一步也就是程序刚尝试连接远程OPC Server或者还在网络协商阶段就抛出来了。明明代码是照着教程抄的用户名、密码也都填了可它就是要拒绝你。尤其当你在Windows防火墙已经关闭、网络能ping通的情况下还看到这个错误那种挫败感我太熟悉了。这篇文章我从问题根因说起把DCOM这套老旧的访问控制机制拆开讲清楚再给出dcomcnfg的详细配置步骤、Java侧连接代码的注意点、以及一套能直接落地的排查顺序。适合正在用Utgard、j-Interop这类库对接OPC DA的程序员也适合第一次接触OPC集成、被Windows权限折腾到怀疑人生的新手。如果你是做OPC UA的可以关掉了这个异常基本只在OPC DA DCOM这条老链路上出现。1. Access is denied 到底是谁在拒绝你1.1 整条技术链Java 是怎么够到 OPC 的Java本身跑在JVM里而OPC DAData Access是构建在Windows COM/DCOM之上的老协议。两端语言都不一样中间必须有一座桥。实际项目中最常见的链路是Java应用 → OpenSCADA Utgard → j-Interop → DCOM网络协议 → 远程Windows机器上的OPC ServerUtgard是Java生态里比较老牌的开源OPC库它封装了OPC DA 2.0/3.0的客户端逻辑底层依赖j-Interop也就是你报错信息里那个org.jinterop包用纯Java重新实现了DCOM协议所以客户端机器上不需要安装任何Windows COM组件或DLL。j-Interop的任务就是在Java和远程Windows之间模拟出一个DCOM客户端来。整个流程大致是这样你的Java程序先通过j-Interop建立一个JISession会话然后向远程机器的RPC Endpoint Mapper也就是135端口发起请求申请创建一个OPC Server组件的实例。创建成功后再通过DCOM调用OPC Server暴露出来的接口去读写点位数据。注意“创建实例”这步很关键。一个OPC Server在Windows里是一个COM组件你想远程使用它不光要网络通、账户对还要Windows的DCOM授权机制点头。任何一个环节不放行结果就是一个错误码0x80070005 E_ACCESSDENIEDj-Interop收到这个错误就包装成你看到的JIException: Access is denied。1.2 它不是登录失败而是“被门禁拦住了”很多人把Access is denied理解为用户名或密码错误这个理解方向不完全对。它更像是一个门禁系统你到了楼下有门禁卡用户名密码但系统没有给这张卡开放“进入这栋楼”的权限于是门就是不打开。DCOM的权限体系里至少有三道门访问权限Access Permissions决定谁能远程调用这个COM组件的方法。启动和激活权限Launch and Activation Permissions决定谁能远程创建、激活这个COM组件的实例。身份验证级别和模拟级别决定身份是以什么安全级别在会话中传递以及能否模拟客户端身份去访问其他资源。你代码里填的用户名密码只是让DCOM知道“你是谁”但实际问题往往是“你是谁”这个答案本身没有出现在允许列表里。所以光靠修Java代码解决不了要去Windows那一侧给这个用户授权。这也是为什么网上搜这个错误几乎所有人最后都指向同一个操作打开dcomcnfg 改权限。2. DCOM 权限模型里最容易踩的坑在哪2.1 “编辑默认值”和“编辑限制”是两回事打开组件服务之后在“我的电脑”属性里点进COM安全你会看到两组设置访问权限、启动和激活权限每组下面又有“编辑默认值”和“编辑限制”两个按钮。这两者的区别很多人搞不清。默认值是对没有单独配置安全属性的组件生效的统一授权限制则是一个强制底线优先级高于任何组件自己的配置。换句话说哪怕你在某个具体组件的属性里给了用户“允许”如果“编辑限制”里没有给最终结果还是拒绝。很多人改完默认值发现没用就是因为没动限制访问权限的两处默认值和限制都要添加用户并且勾选“远程访问”。启动和激活权限的两处默认值和限制都要添加用户并且勾选“远程启动”和“远程激活”。我第一次配置时也踩过这个坑只改了默认值重启服务后连接还是Access is denied后来把限制也改了问题消失。所以配置时要记住两侧同步缺一不可。2.2 用户选谁、加哪些账户是有讲究的在“编辑默认值”和“编辑限制”里需要添加哪些账户取决于你的实际部署环境。常规做法是添加三类Everyone这一项能保证授权覆盖范围最大适合测试环境快速验证。ANONYMOUS LOGON用于匿名远程激活如果局域网里没有域环境、工作组模式下非常常见。实际运行j-Interop的用户账户比如你代码里填的ci.user或者目标机器上的本地管理员账户。要注意的是某些Windows安全策略默认禁止匿名枚举、禁止匿名访问某些对象。如果你选择了添加ANONYMOUS LOGON还需要确认本地安全策略里“网络安全允许对SAM账户和共享的匿名枚举”这类选项没有被强行锁死。否则即使给了匿名登录权限系统也可能不认。在一个没有域控的工作组环境里我建议直接用目标机上已有的管理员账户把“Everyone”和“ANONYMOUS LOGON”都先加上把环境跑通后再按最小权限原则收紧。生产环境千万别一上来就追求“最小权限”否则你会分不清到底是权限不够还是配置没生效排查周期会拉得很长。2.3 网络访问模型和“拒绝从网络访问”的坑Windows本地安全策略里有一个位置非常隐蔽但它能让你所有权限配置都白费。在“本地策略→用户权限分配”下面有两项从网络访问此计算机拒绝从网络访问此计算机默认情况下普通管理员账号是允许从网络访问的。但在某些精简版系统、或者被安全加固过的服务器上“拒绝从网络访问这台计算机”里可能会带着Guest或者其它账户组。如果你Java连接代码用的用户正好被放在了这个拒绝列表里那DCOM请求过来时系统会直接拒绝日志里也像Access is denied。另一个坑是“网络访问本地账户的共享和安全模型”。在Windows 10/Server 2016之后这个策略有两个选项仅来宾所有网络用户都映射为Guest账户这种模式下即使你填了管理员密码远程创建DCOM实例也极大概率失败。经典本地用户以自身身份验证这才是DCOM远程访问通常需要的模式。如果你的机器是刚装好的Windows系统先确认这两处策略没有拖后腿再继续往下配置。3. dcomcnfg 完整配置步骤照着做就行3.1 先确认 OPC Server 已经以 DCOM 方式注册权限配置的前提是这个OPC Server组件已经被正确安装并注册到Windows的COM注册表里。如果你用的是KepServerEX、Matrikon OPC Simulation或者西门子、罗克韦尔等厂家的OPC Server安装完成后通常会自动注册。可以用两种方式确认打开dcomcnfg → 组件服务 → 计算机 → 我的电脑 → DCOM配置在列表里找有没有对应的OPC Server项。名字一般类似KEPware.KEPServerEX.V6、Matrikon.OPC.Simulation.1这类或者带有OPC关键字。在注册表里搜ProgId字符串开始菜单搜索regedit在HKEY_CLASSES_ROOT下搜索你OPC Server的ProgId名称能找到对应的CLSID。如果DCOM配置列表里看不到OPC Server常见原因是OPC Core Components没装或者某些绿色精简版OPC Server没有注册过COM。装一次OPC Core Components Redistributable 3.00.107问题往往就解决了。3.2 逐项配置 COM 安全这里是最核心的一步我以Windows Server 2019 / Windows 10为例完整步骤是这样按Win R输入dcomcnfg回车打开组件服务。左侧导航组件服务 → 计算机 → 我的电脑。右键“我的电脑”选择“属性”。在“默认属性”页签里确认“在此计算机上启用分布式COM”是勾选状态。默认身份验证级别选择“连接”。默认模拟级别选择“标识”。在“默认协议”页签里确认有“面向连接的TCP/IP”如果没有就添加。切到“COM安全”页签开始正式配置。访问权限的配置点击“访问权限”区域里的“编辑默认值”添加以下账户并勾选“远程访问”EveryoneANONYMOUS LOGON你实际连接用的Windows账户再点击“编辑限制”做同样的添加和“远程访问”勾选。确定保存。启动和激活权限的配置点击“启动和激活权限”区域里的“编辑默认值”添加同样几个账户并勾选“远程启动”和“远程激活”。再点击“编辑限制”重复同样的操作。确定保存。配置完“我的电脑”这层再去DCOM配置列表里找到你的OPC Server项右键组件选择“属性”。在“身份”页签里选择“交互式用户”。有些服务型OPC Server会建议“指定用户”并填入高权限账户这个可以按OPC Server的产品文档来。在“安全”页签里如果你希望针对这个组件单独设置权限可以选择“自定义”然后继续添加账户授权。实际上只要“我的电脑”层的权限都放开了这里通常保持默认即可。全部改完后最好重启一次系统或者至少重启OPC Server服务。DCOM权限的生效依赖系统服务重新加载完全不用重启的情况也不是没有但重启后确认最省心。3.3 防火墙也要管不只是放行 135 端口DCOM远程调用的第一步是访问TCP 135端口。但这个端口只负责“牵线”真正传输调用数据的端口是从系统动态端口范围里随机分配的通常是高速TCP端口。所以只放行135端口是不够的否则会出现“一开始好像连上了随后又超时或拒绝”的诡异现象。我通常用两条防火墙规则解决# 放行 RPC Endpoint Mapper netsh advfirewall firewall add rule nameDCOM TCP 135 dirin actionallow protocolTCP localport135 # 放行动态 RPC 端口范围根据系统版本动态端口可能不同一般是 49152-65535 netsh advfirewall firewall add rule nameDCOM TCP Dynamic dirin actionallow protocolTCP localport49152-65535在测试环境里我会先直接关闭Windows防火墙来确认问题是否出在防火墙侧# 临时关闭防火墙仅测试环境 netsh advfirewall set allprofiles state off如果关了防火墙就通了那基本可以确定是端口放行不完整。顺带一提某些网络环境里UDP 135、UDP 137-138、TCP/UDP 445也会被DCOM相关的发现、浏览机制用到。严格来说只要OPC Server地址和CLSID是明确的走TCP 135 动态端口就够了但如果你用主机名做hostNetBIOS和DNS解析就可能牵涉445端口。为了快速跑通测试环境把防火墙全部关闭验证一次是最高效的办法。4. Java 连接代码与 j-Interop 侧的注意事项4.1 一个能跑的连接示例依赖方面如果你用Maven核心依赖如下dependency groupIdorg.openscada.utgard/groupId artifactIdorg.openscada.utgard/artifactId version1.5.0/version /dependency dependency groupIdorg.jinterop/groupId artifactIdorg.jinterop/artifactId version2.4.7/version /dependency如果你的公司私服拉不到这些旧依赖去Maven Central直接搜索utgard和org.jinterop也能找到对应坐标。正因为这些库维护得不活跃我强烈建议锁定版本不要盲目升到最新否则API可能有变化。连接代码的骨架大致是这样import org.openscada.opc.lib.da.ConnectionInformation; import org.openscada.opc.lib.da.OPCServer; public class OpcConnectionTest { public static void main(String[] args) { // 有些容器环境需要显式设置上下文ClassLoader否则j-Interop反射时会出问题 Thread.currentThread().setContextClassLoader( OpcConnectionTest.class.getClassLoader() ); ConnectionInformation ci new ConnectionInformation(); ci.setHost(192.168.1.100); // OPC Server机器IP ci.setDomain(WORKGROUP); // 域工作组就填WORKGROUP或机器名 ci.setUser(administrator); // 有DCOM远程权限的账户 ci.setPassword(your_password); ci.setClsid(F8582CF2-88FB-11D0-B850-00A0C922A001); // OPC Server组件的CLSID // 或者用ProgId二选一 // ci.setProgId(KEPware.KEPServerEX.V6); OPCServer server new OPCServer(); try { server.connect(ci); System.out.println(连接成功当前OPC Server: server.getServerState()); // 后续就是获取group、添加item、读取值 // ... } catch (Exception e) { e.printStackTrace(); } finally { try { server.disconnect(); } catch (Exception e) { // 忽略断开异常 } } } }我建议测试时先用clsid连接而不是用progId因为progId解析额外依赖OPCEnum服务如果OPC Core Components没装好解析会失败。CLSID需要从OPC Server的产品文档或注册表里查。KepServer的CLSID不同版本不一样不要直接抄网上的。4.2 两个经常被忽略的 Java 侧问题第一个是ClassLoader。j-Interop内部某些类加载操作走的是线程上下文ClassLoader。如果你把连接代码写在Tomcat、Spring Boot或者其他类加载器隔离比较严格的容器里偶尔会报奇怪的ClassNotFoundException甚至表现为连接建立到一半就失败。提前加一行Thread.currentThread().setContextClassLoader(...)能省很多排查时间。第二个是会话生命周期。每次连接都应该使用独立的JISession用完必须销毁。如果不销毁Windows端会积累大量来自同一客户机的DCOM会话一段时间后新连接会被Windows拒绝表现仍是Access is denied。很多人查了半天权限结果发现是之前的测试会话没清理干净。在Windows端看到会话堆积的办法目标机器打开“计算机管理 → 系统工具 → 共享文件夹 → 会话”如果看到大量来自你本机的网络连接基本就是会话没释放。另外JDK版本要特别注意。j-Interop是很多年前的库在JDK 8上运行最稳定。我遇到过在JDK 11及以上版本调用时出现模块化相关的IllegalAccessError这不是业务代码问题而是JDK强封装导致的反射受限。如果你必须用JDK 11可以尝试加上--add-opens java.base/java.langALL-UNNAMED但坦白讲这只能缓解一部分问题最稳妥的还是JDK 8。5. 一套实用的排障路线五分钟定位问题5.1 排查顺序先剥开 Java再处理 Windows遇到Access is denied我建议不要急着改代码先按下面的顺序排查用现成的OPC客户端工具验证。在目标机器或同网络的另一台Windows机器上装一个Matrikon OPC Explorer尝试远程连接同一个OPC Server。如果OPC Explorer也连不上那问题百分百不在Java侧而是DCOM、防火墙或OPC Server本身的问题。这个步骤能直接帮你把方向定下来。在目标Windows机器上打开事件查看器看“应用程序”日志里有没有DCOM相关报错。最重要的两个事件ID是10005和10010它们会明确告诉你哪个用户尝试激活哪个CLSID的组件、因为什么权限失败。看到CLSID后再去注册表确认是不是你的OPC Server。检查防火墙端口。先看135端口通不通再确认动态端口范围。实在不行测试环境临时关防火墙验证。最后再看Java代码用户、域、CLSID是否和Windows那边一致ClassLoader是否设置会话是否正常销毁。这个顺序能避免你在Java代码里反复折腾最后发现是Windows权限没给。5.2 常见错误与解决方法速查错误信息可能原因处理方式JIException: Access is deniedDCOM访问权限或启动激活权限不足dcomcnfg添加用户并勾选远程访问、远程启动、远程激活JIException: Access is denied且固定发生在运行一段时间后会话没释放Windows端DCOM会话堆积确保每次连接后disconnect必要时重启OPC Server服务0x800706ba: The RPC server is unavailable135端口不通、防火墙拦截、目标机器远程服务不可用检查135端口和动态RPC端口放行0x80040154: Class not registeredCLSID错误、OPC Core Components缺失、OPC Server未注册核对CLSID安装OPC Core Components重新注册OPC Server0x80070002: The system cannot find the file specifiedProgId解析失败或注册信息损坏改用CLSID连接或修复OPC Server安装ClassNotFoundException依赖缺失或上下文ClassLoader不对补全依赖设置Thread.currentThread().setContextClassLoader这张表我是在项目里整理出来的。以前接手过一台很老的工控机对方说Java连不上OPC我远程一看和代码一点关系没有纯粹是OPC Server那个Windows账户被人从“从网络访问此计算机”里移掉了加上事件日志里10010一直刷。把用户加回允许列表立刻就好。6. 最后再分享一点实际操作中的体会DCOM这套东西设计于上世纪九十年代安全模型复杂调试手段又少和现代化的API风格完全不是一个时代的产品。我的体会是遇到Access is denied心态上要接受“大概率是Windows权限问题”而不是一直在Java代码里钻牛角尖。有几个小技巧对排查很有帮助第一次配置时可以把Everyone和ANONYMOUS LOGON都加上跑通后再按最小权限原则收缩到指定用户配置完dcomcnfg后不要舍不得重启重启一次能排除配置未生效的干扰连接代码里把日志级别调到DEBUG如果j-Interop能输出更详细的DCOM握手日志很多报错原因会更早暴露出来。另外如果你的项目是从零开始、OPC可是可选的我的建议很直接上OPC UA。OPC UA摆脱了DCOM依赖Java端有Eclipse Milo这种维护活跃、文档齐全的库连接配置比OPC DA简单太多也不用跟Windows权限纠缠。老一辈的OPC DA设备没法升级时才需要和DCOM硬碰硬。这篇配置方案也是给这些“不得不兼容老设备”的场景兜底用的希望它能让你少熬几个夜。
返回列表