开发者

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

目录
  • 一、前言
  • 二、准备工作
  • 三、匿名方式连接
    • 3.1 匿名方式简介
    • 3.2 示例代码
  • 四、用户名密码方式连接
    • 4.1 用户名密码方式简介
    • 4.2 示例代码
  • 五、证书加密方式连接
    • 5.1 证书加密方式简介
    • 5.2 证书和私钥获取
    • 5.3 示例代码
  • 六、常见问题与注意事项

    一、前言

    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(500编程客栈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.devze.comicateManagerCertificateValidator 以及 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内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜