java连接opcua的常见问题及解决方法

2025-06-06 03:50

本文主要是介绍java连接opcua的常见问题及解决方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《java连接opcua的常见问题及解决方法》本文将使用EclipseMilo作为示例库,演示如何在Java中使用匿名、用户名密码以及证书加密三种方式连接到OPCUA服务器,若需要使用其他SDK,原理...

一、前言

OPC UA(Open Platform Communications Unified Architecture)是针对工业自动化领域的跨平台通信协议标准。它在 OPC 经典版本的基础上进行优化,可以在不同操作系统、设备和编程语言之间进行安全且可靠的数据交换。对于很多工业控制、设备监控以及物联网相关项目,OPC UA 是常用的数据通信方式。

Java 中,我们常用的 OPC UA 客户端开发库包括:

  • Eclipse Milo
  • Prosys OPC UA SDK for Java
  • 其他商业或开源的 Java SDK

本篇将使用 Eclipse Milo 作为示例库,演示如何在 Java 中使用匿名、用户名密码以及证书加密三种方式连接到 OPC UA 服务器。若需要使用其他 SDK,原理大同小异,API 的调用方式会有所不同。

二、准备工作

JDK
建议至少使用 JDK 8 或更高版本。

Maven 或 Gradle
便于引入 Eclipse Milo 等依赖。如果使用 Maven,请在 pom.XML 中添加以下依赖:

<dependency>
    <groupId>org.eclipse.milo</groupId>
    <artifactId>sdk-client</artifactId>
    <version>0.6.15</version> 
    <!-- 版本号可根据需要更新 -->
</dependency>
<dependency>
         <groupId>org.eclipse.milo</groupId>
         <artifactId>server-examples</artifactId>
         <version>0.6.15</version> 
     </dependency>

如果使用 Gradle,则在 build.gradle 中添加:

implementation 'org.eclipse.milo:sdk-client:0.6.15'

OPC UA 服务器
本地或远程的 OPC UA 服务器环境,用于测试连接。可以在虚拟机或本地主机上安装开源的 OPC UA 服务器,也可以使用商业软件自带的模拟服务器。

证书文件(仅在证书加密方式时需要)

若您在服务器上开启了证书加密,需要准备好客户端证书(public key)和客户端私钥(private key),也可能需要服务器的信任证书。Eclipse Milo 提供了简单的证书管理机制,或者您也可以使用标准 Java KeyStore 的方式来存储并读取证书和私钥。

三、匿名方式连接

3.1 匿名方式简介

匿名连接是最简单的方式,不需要用户名、密码或任何证书。只要服务器允许匿名访问,就可以通过匿名方式连接。适合在测试环境或对安全要求不高的场景下使用。

3.2 示例代码

以下演示最基本的匿名连接流程,包括:

  • 创建 OPC UA Client 配置
  • 初始化并连接到服务器
  • 读取或写入数据(仅作示例)

请确保替换示例中的 endpointUrlnodeId 等信息为你自己的实际配置。

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.UserTokenPolicy;
import org.eclipse.milo.opcua.stack.core.types.enumerated.UserTokenType;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class OpcUaAnonymousExample {
    public static void main(String[] args) {
        try {
            // OPC UA 服务器地址,例如 "opc.tcp://localhost:49320"
            String url= "opc.tcp://127.0.0.1:49320";
            // 创建 client
                OpcUaClient client = OpcUaClient.create(url,
                        endpoints ->
                                endpoints.stream()
                                        .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
                                        .findFirst(),
                        configBuilder ->
                                configBuilder
                                        //访问方式
                                        .setIdentityProvider(new AnonymousProvider())
                                        .setRequestTimeout(UInteger.valueOf(500China编程0))
                                        .build());
            }
            // 连接到服务器
            CompletableFuture<OpcUaClient> future = client.connect();
            future.get(); // 等待连接完成
            System.out.println("匿名连接成功!");
            // 在此处可以进行读写操作,例如读取节点的值
            // client.readValue(0, TimestampsToReturn.Both, new ReadValueId(NodeId, ...));
            // ...
            // 最后断开连接
            client.disconnect().get();
            System.out.println("客户端断开连接。");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 简单选择一个安全策略为 None 的端点(匿名方式一般使用安全策略None,具体看服务器配置)
    private static EndpointDescription chooseSecureEndpoint(List<EndpointDescription> endpoints) {
        EndpointDescription result = null;
        for (EndpointDescription e : endpoints) {
            if (e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri())) {
                result = e;
                break;
            }
        }
        return result;
    }
}

在上述示例中,最关键的步骤是将身份认证方式设为 new AnonymousProvider() 并选择一个 SecurityPolicy 为 None 的 endpoint。这样即可使用匿名方式成功连接。

四、用户名密码方式连接

4.1 用户名密码方式简介

在实际生产环境中,常常需要使用账号密码进行身份验证,以限制访问权限、保护关键信息。与匿名方式相比,多了用户名密码的配置,但整体流程类似。

4.2 示例代码

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfigBuilder;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.UserTokenPolicy;
import org.eclipse.milo.opcua.stack.core.types.enumerated.UserTokenType;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class OpcUaUsernamePasswordExample {
    public static void main(String[] args) {
        try {
            String endpointUrl = "opc.tcp://127.0.0.1:4840";
            List<EndpointDescription> endpoints = OpcUaClient
                    .getEndpoints(endpointUrl).get();
            EndpointDescription endpoint = chooseUserNameEndpoint(endpoints);
            OpcUaClientConfigBuilder configBuilder = new OpcUaClientConfigBuilder();
            configBuilder.setEndpoint(endpoint);
            // 假设用户名为 "user", 密码为 "password"
            configBuilder.setIdentityProvider(new UsernameProvider("user", "password"));
            OpcUaClient client = OpcUaClient.create(configBuilder.build());
            CompletableFuture<OpcUaClient> future = client.connect();
            future.get();
            System.out.println("用户名密码方式连接成功!");
            // 进行后续读写操作
            // ...
            client.disconnect().get();
            System.out.println("客户端断开连接。");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static EndpointDescription chooseUserNameEndpoint(List<EndpointDescription> endpoints) {
        // 通常 OPC UA 服务器也支持 SecurityPolicy.None + UserName 方式
        // 也可能是 Basic128Rsa15, Basic256, etc. 具体看服务端配置
        for (EndpointDescription e : endpoints) {
            if (e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri())) {
                // 确保端点支持 UserName 类型的认证
                for (UserTokenPolicy tokenPolicy : e.getUserIdentityTokens()) {
                    if (tokenPolicy.getTokenType() == UserTokenType.UserName) {
                        return e;
                    }
                }
            }
        }
        return null;
    }
}

要点说明:

  • 将 IdentityProvider 切换为 new UsernameProvider("username", "password")。
  • 根据服务端提供的用户名、密码进行配置。
  • 需要注意端点是否支持 UserName 类型认证。如果端点仅支持 Anonymous 或 Certificate,则无法使用用户名密码方式。

五、证书加密方式连接

5.1 证书加密方式简介

在实际工业环境中,安全性要求更高时通常会启用证书加密(基于 Public Key Infrastructure)。

  • 每个客户端都会持有一份证书(公钥)和对应的私钥,服务器端也有自己的证书。
  • 当客户端与服务器通信时,会先验证双方的证书签名并进行加密传输,从而保证安全性与完整性。

在这种方式下,服务端可能要求:

  • 客户端必须提供已经被服务器信任(或在服务器端手动信任)的证书。
  • 采用特定的安全策略(例如 Basic256Sha256)并通过相应端点连接。

5.2 证书和私钥获取

  • 可以通过第三方工具(例如 openssl、keytool 或 Eclipse Milo 提供的证书工具脚本)生成自签名证书。
  • 生成后的证书和私钥,可以存储在 Java KeyStore 中,或者存储为 .der.pem 等格式并让应用程序读取。

下方示例假设已经拥有 ClientCert.der(客户端公钥)和 ClientKey.der(客户端私钥),并且服务器端配置了对应的信任或信任链。

OPC UA访问证书类

import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.regex.Pattern;
class KeyStoreLoader {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private static final Pattern IP_ADDR_PATTERN = Pattern.compile(
        "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
    // 证书别名
    private static final String CLIENT_ALIAS = "client-ai";
    // 获取私钥的密码
    private static final char[] PASSWORD = "password".toCharArray();
    // 证书对象
    private X509Certificate clientCertificate;
    // 密钥对对象
    private KeyPair clientKeyPair;
    KeyStoreLoader load(Path baseDir) throws Exception {
        // 创建一个使用`PKCS12`加密标准android的KeyStore。KeyStore在后面将作为读取和生成证书的对象。
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        // PKCS12的加密标准的文件后缀是.pfx,其中包含了公钥和私钥。
        // 而其他如.der等的格式只包含公钥,私钥在另外的文件中。
        Path serverKeyStore = baseDir.resolve("example-client.pfx");
        logger.info("Loading KeyStore at {}", serverKeyStore);
        // 如果文件不存在则创建.pfx证书文件。
        if (!Files.exists(serverKeyStore)) {
            keyStore.load(null, PASSWORD);
            // 用2048位的RAS算法。`SelfSignedCertificateGenerator`为Milo库的对象。
            KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);
            // `SelfSignedCertificateBuilder`也是Milo库的对象,用来生成证书。
            // 中间所设置的证书属性可以自行修改。
            SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair)
                .setCommonName("Eclipse Milo Example Client")
                .setOrganization("digitalpetri")
                .setOrganizationalUnit("dev")
                .setLocalityName("Folsom")
                .setStateName("CA")
                .setCountryCode("US")
                .setApplicationUri("urn:eclipse:milo:examples:client")
                .addDnsName("localhost")
                .addIpAddress("127.0.0.1");
            // Get as many hostnames and IP addresses as we can listed in the certificate.
            for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) {
                if (IP_ADDR_PATTERN.matcandroidher(hostname).matches()) {
                    builder.addIpAddress(hostname);
                } else {
                    builder.addDnsName(hostname);
                }
            }
            // 创建证书
            X509Certificate certificate = builder.build();
            // 设置对应私钥的别名,密码,证书链
            keyStore.setKeyEntry(CLIENT_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[]{certificate});
            try (OutputStream out = Files.newOutputStream(serverKeyStore)) {
                // 保存证书到输出流
                keyStore.store(out, PASSWORD);
            }
        } else {
            try (InputStream in = Files.newInputStream(serverKeyStore)) {
                // 如果文件存在则读取
                keyStore.load(in, PASSWORD);
            }
        }
        // 用密码获取对应别名的私钥。
        Key serverPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD);
        if (serverPrivateKey instanceof PrivateKey) {
            // 获取对应别名的证书对象。
            clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);
            // 获取公钥
            PublicKey serverPublicKey = clientCertificate.getPublicKey();
            // 创建Keypair对象。
            clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) serverPrivateKey);
        }
        return this;
    }
    // 返回证书
    X509Certificate getClientCertificate() {
        return clientCertificate;
    }
    // 返回密钥对
    KeyPair getClientKeyPair() {
        return clientKeyPair;
    }
}

5.3 示例代码

public static OpcUaClient initClient(String url,SecurityPolicy securityPolicy) {
        try {
            if (securityPolicy.equals(SecurityPolicy.None)){
                return OpcUaClient.create(url,
                        endpoints ->
                                endpoints.stream()
                                        .filter(e -> e.getSecurityPolicyUri().equals(securityPolicy.getUri()))
                                        .findFirst(),
                        configBuilder ->
                                configBuilder
                                        //访问方式
                                        .setIdentityProvider(new AnonymousProvider())
                                        .setRequestTimeout(UInteger.valueOf(5000))
                                        .build());
            }
            Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
            Files.createDirectories(securityTempDir);
            if (!Files.exists(securityTempDir)) {
                throw new Exception("unable to create security dir: " + securityTempDir);
            }
            KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);
            File pkiDir = securityTempDir.resolve("pki").toFile();
            DefaultTrustListManager trustListManager = new DefaultTrustListManager(pkiDir);
            DefaultClientCertificateValidator certificateValidator =
                    new DefaultClientCertificateValidator(trustListManager);
            String hostName = InetAddress.getLocalHost().getHostName();
            return OpcUaClient.create(url,
                    endpoints ->
                            endpoints.stream()
                                    .map(endpoint -> {
                                        // 构建一个新的 EndpointDescription(可选修改某些字段)
                                        return new EndpointDescription(
                                                url,
                                                endpoint.getServer(),
                                                endpoint.getServerCertificate(),
                                                endpoint.getSecurityMode(), // 或者强制改为某种模式
                                                endpoint.getSecurityPolicyUri(),
                                                endpoint.getUserIdentityTokens(),
                                                endpoint.getTransportProfileUri(),
                                                endpoint.getSecurityLevel()
                                        );
                                    })
                                    .filter(e -> e.getSecurityPolicyUri().equals(securityPolicy.getUri()))
                                    .findFirst(),
                    configBuilder ->
                            configBuilder
                                    //访问方式
                                    .setApplicationName(LocalizedText.english("datacollector-driver"))
                                    .setApplicationUri(String.format("urn:%s:opcua-client", hostName))  // 必须与证书中的URI一致
                                    .setKeyPair(loader.getClientKeyPair())
                                    .setCertificate(loader.getClientCertificate())
                                    .setCertificateChain(loader.getClientCertificateChain())
                                    .setCertificateValidator(certificateValidator)
                                    .setIdentityProvider(new UsernameProvider("admin", "123456"))
             python                       .setRequestTimeout(UInteger.valueOf(5000))
                                    .build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

证书路径

java连接opcua的常见问题及解决方法

我们需要把服务器证书放在pki\trusted\certs目录下:

java连接opcua的常见问题及解决方法

要点说明:

  • 选择合适的安全策略(如 Basic256Sha256)。
  • 使用客户端证书和私钥(可以自签名,也可以通过权威 CA 签发)。
  • 服务端需信任此客户端证书(在服务器配置中添加到信任列表)。
  • 配置 Certifwww.chinasem.cnicateManagerCertificateValidator 以及 X509IdentityProvider

六、常见问题与注意事项

端点选择

  • 不同 OPC UA 服务器可能同时暴露多个端点,包含不同的安全模式(Security Mode)和安全策略(Security Policy)。
  • 在匿名或用户名密码方式时,如果选择了需要证书的端点,就会出现认证失败或连接被拒的情况。
  • 在证书加密方式时,如果选择了安全策略为 None 的端点,则证书不会被使用,同样也会连接异常或者导致安全策略不匹配。

服务器信任客户端证书

  • 大多数 OPC UA 服务器在默认情况下不信任任何客户端的证书,需要在服务端管理界面或配置文件中手动将客户端证书加入白名单。
  • 记得查看服务器日志,若提示「Untrusted Certificate」,就需要在服务器端操作信任列表。

安全策略与性能

  • 加密等级越高(如 Basic256Sha256),对 CPU 资源消耗越大,通信速度会相对降低,但数据安全性更强。
  • 在测试环境或低安全需求的场景下可以先使用 SecurityPolicy.None ;正式项目上线时再切换到更高的安全策略。

兼容性

  • 不同版本的 OPC UA SDK、服务器或 Java 版本之间可能存在兼容性问题;如果连接失败,可以尝试升级或降低 Milo 版本、换用不同的 JDK 版本等。
  • OPC UA 服务器上若启用特定的加密算法(例如 AES-256),客户端也需要对应的加密套件。

断线重连

  • 工业现场环境中网络抖动常见,客户端需要实现断线重连或重试机制,以确保数据采集的连续性与稳定性。

到此这篇关于java连接opcua的文章就介绍到这了,更多相关java连接opcua内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!

这篇关于java连接opcua的常见问题及解决方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154929

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1