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

 更新时间:2025年06月05日 14:30:49   作者:胡八一、  
本文将使用 Eclipse Milo 作为示例库,演示如何在Java中使用匿名、用户名密码以及证书加密三种方式连接到 OPC UA 服务器,若需要使用其他 SDK,原理大同小异,API 的调用方式会有所不同,对java连接opcua的相关知识感兴趣的朋友一起看看吧

一、前言

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

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

本篇将使用 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(5000))
                                        .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`加密标准的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.matcher(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"))
                                    .setRequestTimeout(UInteger.valueOf(5000))
                                    .build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

证书路径

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

要点说明:

  • 选择合适的安全策略(如 Basic256Sha256)。
  • 使用客户端证书和私钥(可以自签名,也可以通过权威 CA 签发)。
  • 服务端需信任此客户端证书(在服务器配置中添加到信任列表)。
  • 配置 CertificateManagerCertificateValidator 以及 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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解使用Spring Boot的AOP处理自定义注解

    详解使用Spring Boot的AOP处理自定义注解

    本篇文章主要介绍了详解使用Spring Boot的AOP处理自定义注解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-01-01
  • 关于JVM翻越内存管理的墙

    关于JVM翻越内存管理的墙

    这篇文章主要介绍了JVM翻越内存管理的墙,由虚拟机管理内存看起来一切都很美好,但也正是因为把控制内存的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,就不得不从Java虚拟机角度上去排查问题,本文给大家介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • 实现一个基于Servlet的hello world程序详解步骤

    实现一个基于Servlet的hello world程序详解步骤

    Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层
    2022-02-02
  • SpringBoot整合mybatis使用Druid做连接池的方式

    SpringBoot整合mybatis使用Druid做连接池的方式

    这篇文章主要介绍了SpringBoot整合mybatis使用Druid做连接池的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • SpringBoot调用公共模块的自定义注解失效的解决

    SpringBoot调用公共模块的自定义注解失效的解决

    这篇文章主要介绍了SpringBoot调用公共模块的自定义注解失效的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Java 位运算符>>与>>>区别案例详解

    Java 位运算符>>与>>>区别案例详解

    这篇文章主要介绍了Java 位运算符>>与>>>区别案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • SpringBoot项目部署到服务器的两种方式

    SpringBoot项目部署到服务器的两种方式

    目前,前后端分离的架构已成主流,而使用SpringBoot构建Web应用是非常快速的,项目发布到服务器上的时候,只需要打成一个jar包,然后通过命令 : java -jar jar包名称即可启动服务了,本文介绍了SpringBoot项目部署到服务器的两种方式,需要的朋友可以参考下
    2024-10-10
  • Java方法递归与输入输出深入探索

    Java方法递归与输入输出深入探索

    这篇文章主要介绍了Java方法递归与输入输出的相关资料,方法递归是一种在方法内部调用自身的技术,适用于具有递归结构的问题,输入输出是Java程序与外部世界交互的桥梁,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-04-04
  • 使用原生JDBC动态解析并获取表格列名和数据的方法

    使用原生JDBC动态解析并获取表格列名和数据的方法

    这篇文章主要介绍了使用原生JDBC动态解析并获取表格列名和数据,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08
  • JAVA生成pdf文件的实操指南

    JAVA生成pdf文件的实操指南

    最近项目需要实现PDF下载的功能,由于没有这方面的经验,从网上花了很长时间才找到相关的资料,下面这篇文章主要给大家介绍了关于JAVA生成pdf文件的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-10-10

最新评论