mirror of
https://gitee.com/ThingsGateway/ThingsGateway.git
synced 2025-10-20 18:51:28 +08:00
添加OPCUAServer插件,修复个别bug
This commit is contained in:
@@ -75,7 +75,7 @@ public class OPCUAClient : DisposableObject
|
||||
ApplicationName = OPCUAName,
|
||||
ApplicationType = ApplicationType.Client,
|
||||
CertificateValidator = certificateValidator,
|
||||
ApplicationUri = "urn:localhost:ThingsGateway:OPCUAClient",
|
||||
ApplicationUri = Utils.Format(@"urn:{0}:thingsgatewayopcuaclient", System.Net.Dns.GetHostName()),
|
||||
ProductUri = "https://diego2098.gitee.io/thingsgateway/",
|
||||
|
||||
ServerConfiguration = new ServerConfiguration
|
||||
@@ -85,7 +85,6 @@ public class OPCUAClient : DisposableObject
|
||||
MaxNotificationQueueSize = 1000000,
|
||||
MaxPublishRequestCount = 10000000,
|
||||
|
||||
|
||||
},
|
||||
|
||||
SecurityConfiguration = new SecurityConfiguration
|
||||
@@ -99,7 +98,7 @@ public class OPCUAClient : DisposableObject
|
||||
{
|
||||
StoreType = CertificateStoreType.X509Store,
|
||||
StorePath = "CurrentUser\\UA_ThingsGateway",
|
||||
SubjectName = "CN=ThingsGateway OPCUAClient, C=US, S=Arizona, O=ThingsGateway, DC=localhost",
|
||||
SubjectName = "CN=ThingsGateway OPCUAClient, C=CN, S=GUANGZHOU, O=ThingsGateway, DC=" + System.Net.Dns.GetHostName(),
|
||||
},
|
||||
TrustedIssuerCertificates = new CertificateTrustList
|
||||
{
|
||||
@@ -1408,7 +1407,7 @@ public class OPCUAClient : DisposableObject
|
||||
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
|
||||
|
||||
ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
|
||||
await m_application.CheckApplicationInstanceCertificate(false, 0);
|
||||
var d= await m_application.CheckApplicationInstanceCertificate(true, 0);
|
||||
//var x509 = await m_configuration.SecurityConfiguration.ApplicationCertificate.Find(true);
|
||||
m_session = await Opc.Ua.Client.Session.Create(
|
||||
m_configuration,
|
@@ -274,7 +274,7 @@ namespace ThingsGateway.Mqtt
|
||||
MqttRpcResult mqttRpcResult = new();
|
||||
try
|
||||
{
|
||||
var result = await _rpcCore.InvokeDeviceMethod(ToString() + "-" + arg.ClientId, rpcData.Adapt<NameVaue>());
|
||||
var result = await _rpcCore.InvokeDeviceMethod(ToString() + "-" + arg.ClientId, rpcData.Adapt<NameValue>());
|
||||
|
||||
mqttRpcResult = new() { Message = result.Message, RpcId = rpcData.RpcId, Success = result.IsSuccess };
|
||||
|
||||
|
@@ -177,7 +177,7 @@ namespace ThingsGateway.Mqtt
|
||||
MqttRpcResult mqttRpcResult = new();
|
||||
try
|
||||
{
|
||||
var result = await _rpcCore.InvokeDeviceMethod(ToString() + "-" + IdWithName[arg.ClientId], rpcData.Adapt<NameVaue>());
|
||||
var result = await _rpcCore.InvokeDeviceMethod(ToString() + "-" + IdWithName[arg.ClientId], rpcData.Adapt<NameValue>());
|
||||
|
||||
mqttRpcResult = new() { Message = result.Message, RpcId = rpcData.RpcId, Success = result.IsSuccess };
|
||||
|
||||
|
@@ -12,39 +12,84 @@ using TouchSocket.Core;
|
||||
|
||||
namespace ThingsGateway.OPCUA
|
||||
{
|
||||
/// <summary>
|
||||
/// OPCUA客户端
|
||||
/// </summary>
|
||||
public class OPCUAClient : DriverBase
|
||||
{
|
||||
internal Foundation.Adapter.OPCUA.OPCUAClient PLC = null;
|
||||
|
||||
internal CollectDeviceRunTime Device;
|
||||
|
||||
internal Foundation.Adapter.OPCUA.OPCUAClient PLC = null;
|
||||
private List<CollectVariableRunTime> _deviceVariables = new();
|
||||
|
||||
/// <inheritdoc cref="OPCUAClient"/>
|
||||
public OPCUAClient(IServiceScopeFactory scopeFactory) : base(scopeFactory)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接Url
|
||||
/// </summary>
|
||||
[DeviceProperty("连接Url", "")] public string OPCURL { get; set; } = "opc.tcp://127.0.0.1:49320";
|
||||
[DeviceProperty("登录账号", "为空时将采用匿名方式登录")] public string UserName { get; set; }
|
||||
[DeviceProperty("登录密码", "")] public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 激活订阅
|
||||
/// </summary>
|
||||
[DeviceProperty("激活订阅", "")] public bool ActiveSubscribe { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 死区
|
||||
/// </summary>
|
||||
[DeviceProperty("死区", "")] public float DeadBand { get; set; } = 0;
|
||||
public override Type DriverImportUI => typeof(ImportVariable);
|
||||
/// <summary>
|
||||
/// 自动分组大小
|
||||
/// </summary>
|
||||
[DeviceProperty("自动分组大小", "")] public int GroupSize { get; set; } = 500;
|
||||
public override ThingsGatewayBitConverter ThingsGatewayBitConverter { get; } = new(EndianType.Little);
|
||||
[DeviceProperty("重连频率", "")] public int ReconnectPeriod { get; set; } = 5000;
|
||||
[DeviceProperty("更新频率", "")] public int UpdateRate { get; set; } = 1000;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Type DriverImportUI => typeof(ImportVariable);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 登录账号
|
||||
/// </summary>
|
||||
[DeviceProperty("登录账号", "为空时将采用匿名方式登录")] public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录密码
|
||||
/// </summary>
|
||||
[DeviceProperty("登录密码", "")] public string Password { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 安全策略
|
||||
/// </summary>
|
||||
[DeviceProperty("安全策略", "True为使用安全策略,False为无")] public bool IsUseSecurity { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 重连频率
|
||||
/// </summary>
|
||||
[DeviceProperty("重连频率", "")] public int ReconnectPeriod { get; set; } = 5000;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ThingsGatewayBitConverter ThingsGatewayBitConverter { get; } = new(EndianType.Little);
|
||||
|
||||
/// <summary>
|
||||
/// 更新频率
|
||||
/// </summary>
|
||||
[DeviceProperty("更新频率", "")] public int UpdateRate { get; set; } = 1000;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void AfterStop()
|
||||
{
|
||||
PLC?.Disconnect();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task BeforStart()
|
||||
{
|
||||
await PLC?.ConnectServer();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
if (PLC != null)
|
||||
@@ -57,11 +102,19 @@ namespace ThingsGateway.OPCUA
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsConnected()
|
||||
{
|
||||
return PLC.Connected;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool IsSupportAddressRequest()
|
||||
{
|
||||
return !ActiveSubscribe;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult<List<DeviceVariableSourceRead>> LoadSourceRead(List<CollectVariableRunTime> deviceVariables)
|
||||
{
|
||||
_deviceVariables = deviceVariables;
|
||||
@@ -85,6 +138,7 @@ namespace ThingsGateway.OPCUA
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult<byte[]>> ReadSourceAsync(DeviceVariableSourceRead deviceVariableSourceRead, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
@@ -100,6 +154,7 @@ namespace ThingsGateway.OPCUA
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<OperResult> WriteValueAsync(CollectVariableRunTime deviceVariable, string value)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
@@ -107,6 +162,7 @@ namespace ThingsGateway.OPCUA
|
||||
return result ? OperResult.CreateSuccessResult() : new OperResult();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Init(CollectDeviceRunTime device, object client = null)
|
||||
{
|
||||
Device = device;
|
||||
@@ -134,15 +190,12 @@ namespace ThingsGateway.OPCUA
|
||||
PLC.OPCNode = oPCNode;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Task<OperResult<byte[]>> ReadAsync(string address, int length, CancellationToken cancellationToken)
|
||||
{
|
||||
//不走ReadAsync
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override bool IsConnected()
|
||||
{
|
||||
return PLC.Connected;
|
||||
}
|
||||
private void dataChangedHandler(List<(MonitoredItem monitoredItem, MonitoredItemNotification monitoredItemNotification)> values)
|
||||
{
|
||||
try
|
257
src/Plugins/ThingsGateway.OPCUA/OPCUAServer.cs
Normal file
257
src/Plugins/ThingsGateway.OPCUA/OPCUAServer.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Configuration;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
using ThingsGateway.Foundation;
|
||||
using ThingsGateway.Foundation.Extension;
|
||||
using ThingsGateway.Web.Foundation;
|
||||
|
||||
namespace ThingsGateway.OPCUA;
|
||||
|
||||
/// <summary>
|
||||
/// OPCUA服务端
|
||||
/// </summary>
|
||||
public partial class OPCUAServer : UpLoadBase
|
||||
{
|
||||
private ApplicationInstance m_application;
|
||||
private ApplicationConfiguration m_configuration;
|
||||
private ThingsGatewayServer m_server;
|
||||
/// <inheritdoc cref="OPCUAServer"/>
|
||||
public OPCUAServer(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// 服务地址
|
||||
/// </summary>
|
||||
[DeviceProperty("服务地址", "")]
|
||||
public string OpcUaStringUrl { get; set; } = "opc.tcp://127.0.0.1:49321";
|
||||
/// <summary>
|
||||
/// 安全策略
|
||||
/// </summary>
|
||||
[DeviceProperty("安全策略", "")]
|
||||
public bool SecurityPolicy { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task BeforStart()
|
||||
{
|
||||
// 启动服务器。
|
||||
await m_application.CheckApplicationInstanceCertificate(true, 0);
|
||||
await m_application.Start(m_server);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose()
|
||||
{
|
||||
m_server.Stop();
|
||||
m_server.Dispose();
|
||||
m_application.Stop();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override OperResult IsConnected()
|
||||
{
|
||||
var result = m_server.GetStatus();
|
||||
|
||||
return OperResult.CreateSuccessResult(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
////变化推送
|
||||
var varList = CollectVariableRunTimes.ToListWithDequeue();
|
||||
if (varList?.Count != 0)
|
||||
{
|
||||
foreach (var item in varList)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_server.NodeManager.UpVariable(item);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ToString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ToString());
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
private ConcurrentQueue<VariableData> CollectVariableRunTimes { get; set; } = new();
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Init(UploadDevice device)
|
||||
{
|
||||
m_application = new ApplicationInstance();
|
||||
m_configuration = GetDefaultConfiguration();
|
||||
m_configuration.Validate(ApplicationType.Server).GetAwaiter().GetResult();
|
||||
m_application.ApplicationConfiguration = m_configuration;
|
||||
if (m_configuration.SecurityConfiguration.AutoAcceptUntrustedCertificates)
|
||||
{
|
||||
m_configuration.CertificateValidator.CertificateValidation += (s, e) =>
|
||||
{
|
||||
e.Accept = (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted);
|
||||
};
|
||||
}
|
||||
m_server = new(_logger, _scopeFactory.CreateScope());
|
||||
|
||||
using var serviceScope = _scopeFactory.CreateScope();
|
||||
var _globalCollectDeviceData = serviceScope.ServiceProvider.GetService<GlobalCollectDeviceData>();
|
||||
|
||||
|
||||
_globalCollectDeviceData.CollectVariables.ForEach(a =>
|
||||
{
|
||||
VariableValueChange(a);
|
||||
a.VariableValueChange += VariableValueChange;
|
||||
});
|
||||
}
|
||||
|
||||
private void VariableValueChange(CollectVariableRunTime collectVariableRunTime)
|
||||
{
|
||||
CollectVariableRunTimes.Enqueue(collectVariableRunTime.Adapt<VariableData>());
|
||||
}
|
||||
|
||||
|
||||
private ApplicationConfiguration GetDefaultConfiguration()
|
||||
{
|
||||
ApplicationConfiguration config = new ApplicationConfiguration();
|
||||
string url = OpcUaStringUrl;
|
||||
// 签名及加密验证
|
||||
ServerSecurityPolicyCollection policies = new ServerSecurityPolicyCollection();
|
||||
if (SecurityPolicy)
|
||||
{
|
||||
policies.Add(new ServerSecurityPolicy()
|
||||
{
|
||||
SecurityMode = MessageSecurityMode.Sign,
|
||||
SecurityPolicyUri = SecurityPolicies.Basic128Rsa15
|
||||
});
|
||||
policies.Add(new ServerSecurityPolicy()
|
||||
{
|
||||
SecurityMode = MessageSecurityMode.SignAndEncrypt,
|
||||
SecurityPolicyUri = SecurityPolicies.Basic128Rsa15
|
||||
});
|
||||
policies.Add(new ServerSecurityPolicy()
|
||||
{
|
||||
SecurityMode = MessageSecurityMode.Sign,
|
||||
SecurityPolicyUri = SecurityPolicies.Basic256
|
||||
});
|
||||
policies.Add(new ServerSecurityPolicy()
|
||||
{
|
||||
SecurityMode = MessageSecurityMode.SignAndEncrypt,
|
||||
SecurityPolicyUri = SecurityPolicies.Basic256
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
policies.Add(new ServerSecurityPolicy()
|
||||
{
|
||||
SecurityMode = MessageSecurityMode.None,
|
||||
SecurityPolicyUri = SecurityPolicies.None
|
||||
});
|
||||
}
|
||||
|
||||
config.ApplicationName = "ThingsGateway OPCUAServer";
|
||||
config.ApplicationType = ApplicationType.Server;
|
||||
config.ApplicationUri = Utils.Format(@"urn:{0}:thingsgatewayopcuaserver", System.Net.Dns.GetHostName());
|
||||
|
||||
var userTokens = new UserTokenPolicyCollection();
|
||||
userTokens.Add(new UserTokenPolicy(UserTokenType.UserName));
|
||||
|
||||
config.ServerConfiguration = new ServerConfiguration()
|
||||
{
|
||||
// 配置登录的地址
|
||||
BaseAddresses = new string[] { url },
|
||||
SecurityPolicies = policies,
|
||||
UserTokenPolicies = userTokens,
|
||||
ShutdownDelay = 1,
|
||||
|
||||
DiagnosticsEnabled = false, // 是否启用诊断
|
||||
MaxSessionCount = 1000, // 最大打开会话数
|
||||
MinSessionTimeout = 10000, // 允许该会话在与客户端断开时(单位毫秒)仍然保持连接的最小时间
|
||||
MaxSessionTimeout = 60000, // 允许该会话在与客户端断开时(单位毫秒)仍然保持连接的最大时间
|
||||
MaxBrowseContinuationPoints = 1000, // 用于Browse / BrowseNext操作的连续点的最大数量。
|
||||
MaxQueryContinuationPoints = 1000, // 用于Query / QueryNext操作的连续点的最大数量
|
||||
MaxHistoryContinuationPoints = 500, // 用于HistoryRead操作的最大连续点数。
|
||||
MaxRequestAge = 1000000, // 传入请求的最大年龄(旧请求被拒绝)。
|
||||
MinPublishingInterval = 100, // 服务器支持的最小发布间隔(以毫秒为单位)
|
||||
MaxPublishingInterval = 3600000, // 服务器支持的最大发布间隔(以毫秒为单位)1小时
|
||||
PublishingResolution = 50, // 支持的发布间隔(以毫秒为单位)的最小差异
|
||||
MaxSubscriptionLifetime = 3600000, // 订阅将在没有客户端发布的情况下保持打开多长时间 1小时
|
||||
MaxMessageQueueSize = 100, // 每个订阅队列中保存的最大消息数
|
||||
MaxNotificationQueueSize = 100, // 为每个被监视项目保存在队列中的最大证书数
|
||||
MaxNotificationsPerPublish = 1000, // 每次发布的最大通知数
|
||||
MinMetadataSamplingInterval = 1000, // 元数据的最小采样间隔
|
||||
MaxRegistrationInterval = 30000, // 两次注册尝试之间的最大时间(以毫秒为单位)
|
||||
|
||||
};
|
||||
config.SecurityConfiguration = new SecurityConfiguration()
|
||||
{
|
||||
AddAppCertToTrustedStore = true,
|
||||
AutoAcceptUntrustedCertificates = true,
|
||||
RejectSHA1SignedCertificates = false,
|
||||
MinimumCertificateKeySize = 1024,
|
||||
SuppressNonceValidationErrors = true,
|
||||
ApplicationCertificate = new CertificateIdentifier()
|
||||
{
|
||||
StoreType = CertificateStoreType.X509Store,
|
||||
StorePath = "CurrentUser\\UAServer_ThingsGateway",
|
||||
SubjectName = "CN=ThingsGateway OPCUAServer, C=CN, S=GUANGZHOU, O=ThingsGateway, DC=" + System.Net.Dns.GetHostName(),
|
||||
},
|
||||
|
||||
TrustedPeerCertificates = new CertificateTrustList()
|
||||
{
|
||||
StoreType = CertificateStoreType.Directory,
|
||||
StorePath = "%CommonApplicationData%\\ThingsGateway\\pki\\issuer",
|
||||
},
|
||||
|
||||
TrustedIssuerCertificates = new CertificateTrustList()
|
||||
{
|
||||
StoreType = CertificateStoreType.Directory,
|
||||
StorePath = "%CommonApplicationData%\\ThingsGateway\\pki\\issuer",
|
||||
},
|
||||
|
||||
RejectedCertificateStore = new CertificateStoreIdentifier()
|
||||
{
|
||||
StoreType = CertificateStoreType.Directory,
|
||||
StorePath = "%CommonApplicationData%\\ThingsGateway\\pki\\rejected",
|
||||
},
|
||||
UserIssuerCertificates = new CertificateTrustList
|
||||
{
|
||||
StoreType = CertificateStoreType.Directory,
|
||||
StorePath = "%CommonApplicationData%\\ThingsGateway\\pki\\issuerUser",
|
||||
|
||||
},
|
||||
TrustedUserCertificates = new CertificateTrustList
|
||||
{
|
||||
StoreType = CertificateStoreType.Directory,
|
||||
StorePath = "%CommonApplicationData%\\ThingsGateway\\pki\\trustedUser",
|
||||
}
|
||||
};
|
||||
|
||||
config.TransportConfigurations = new TransportConfigurationCollection();
|
||||
config.TransportQuotas = new TransportQuotas { OperationTimeout = 15000 };
|
||||
config.ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 };
|
||||
config.TraceConfiguration = new TraceConfiguration();
|
||||
|
||||
|
||||
config.CertificateValidator = new CertificateValidator();
|
||||
config.CertificateValidator.Update(config);
|
||||
config.Extensions = new XmlElementCollection();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
13
src/Plugins/ThingsGateway.OPCUA/OPCUAServer/OPCUATag.cs
Normal file
13
src/Plugins/ThingsGateway.OPCUA/OPCUAServer/OPCUATag.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Opc.Ua;
|
||||
namespace ThingsGateway.OPCUA;
|
||||
internal class OPCUATag : BaseDataVariableState
|
||||
{
|
||||
public OPCUATag(NodeState parent) : base(parent)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 变量Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
}
|
@@ -0,0 +1,377 @@
|
||||
using Mapster;
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Server;
|
||||
|
||||
using ThingsGateway.Web.Foundation;
|
||||
|
||||
namespace ThingsGateway.OPCUA;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 数据节点
|
||||
/// </summary>
|
||||
public class ThingsGatewayNodeManager : CustomNodeManager2
|
||||
{
|
||||
private const string ReferenceServer = "https://diego2098.gitee.io/thingsgateway/";
|
||||
|
||||
private GlobalCollectDeviceData _globalCollectDeviceData;
|
||||
|
||||
/// <summary>
|
||||
/// OPC和网关对应表
|
||||
/// </summary>
|
||||
private Dictionary<NodeId, OPCUATag> _idTags = new Dictionary<NodeId, OPCUATag>();
|
||||
private RpcCore _rpcCore;
|
||||
private IServiceScope _serviceScope;
|
||||
/// <inheritdoc cref="ThingsGatewayNodeManager"/>
|
||||
public ThingsGatewayNodeManager(IServiceScope serviceScope, IServerInternal server, ApplicationConfiguration configuration) : base(server, configuration, ReferenceServer)
|
||||
{
|
||||
_serviceScope = serviceScope;
|
||||
_rpcCore = serviceScope.ServiceProvider.GetService<RpcCore>();
|
||||
_globalCollectDeviceData = serviceScope.ServiceProvider.GetService<GlobalCollectDeviceData>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 创建服务目录结构
|
||||
/// </summary>
|
||||
/// <param name="externalReferences"></param>
|
||||
public override void CreateAddressSpace(IDictionary<NodeId, IList<IReference>> externalReferences)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
IList<IReference> references = null;
|
||||
if (!externalReferences.TryGetValue(ObjectIds.ObjectsFolder, out references))
|
||||
{
|
||||
externalReferences[ObjectIds.ObjectsFolder] = references = new List<IReference>();
|
||||
}
|
||||
//首节点
|
||||
FolderState rootFolder = CreateFolder(null, "ThingsGateway", "ThingsGateway");
|
||||
rootFolder.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
|
||||
references.Add(new NodeStateReference(ReferenceTypes.Organizes, false, rootFolder.NodeId));
|
||||
rootFolder.EventNotifier = EventNotifiers.SubscribeToEvents;
|
||||
AddRootNotifier(rootFolder);
|
||||
|
||||
//创建设备树
|
||||
var _geviceGroup = CollectDeviceService.GetTree(_globalCollectDeviceData.CollectDevices.ToList().Adapt<List<CollectDevice>>());
|
||||
// 开始寻找设备信息,并计算一些节点信息
|
||||
foreach (var item in _geviceGroup)
|
||||
{
|
||||
//设备树会有两层
|
||||
FolderState fs = CreateFolder(rootFolder, item.Name, item.Name);
|
||||
fs.AddReference(ReferenceTypes.Organizes, true, ObjectIds.ObjectsFolder);
|
||||
fs.EventNotifier = EventNotifiers.SubscribeToEvents;
|
||||
if (item.Childrens?.Count > 0)
|
||||
{
|
||||
foreach (var item2 in item.Childrens)
|
||||
{
|
||||
AddTagNode(fs, item2.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddTagNode(fs, item.Name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AddPredefinedNode(SystemContext, rootFolder);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取历史数据
|
||||
/// </summary>
|
||||
public override void HistoryRead(OperationContext context, HistoryReadDetails details, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, IList<HistoryReadValueId> nodesToRead, IList<HistoryReadResult> results, IList<ServiceResult> errors)
|
||||
{
|
||||
ReadProcessedDetails readDetail = details as ReadProcessedDetails;
|
||||
//必须带有时间范围
|
||||
if (readDetail == null || readDetail.StartTime == DateTime.MinValue || readDetail.EndTime == DateTime.MinValue)
|
||||
{
|
||||
errors[0] = StatusCodes.BadHistoryOperationUnsupported;
|
||||
return;
|
||||
}
|
||||
var service = _serviceScope.GetBackgroundService<ValueHisHostService>();
|
||||
if (!service.StatuString.IsSuccess)
|
||||
{
|
||||
errors[0] = StatusCodes.BadHistoryOperationUnsupported;
|
||||
return;
|
||||
}
|
||||
|
||||
var db = service.HisConfig().GetAwaiter().GetResult();
|
||||
if (!db.IsSuccess)
|
||||
{
|
||||
errors[0] = StatusCodes.BadHistoryOperationUnsupported;
|
||||
return;
|
||||
}
|
||||
var startTime = readDetail.StartTime;
|
||||
var endTime = readDetail.EndTime;
|
||||
|
||||
for (int i = 0; i < nodesToRead.Count; i++)
|
||||
{
|
||||
var historyRead = nodesToRead[i];
|
||||
if (_idTags.TryGetValue(historyRead.NodeId, out OPCUATag tag))
|
||||
{
|
||||
var data = db.Content.Queryable<ValueHis>()
|
||||
.Where(a => a.Name == tag.SymbolicName)
|
||||
.Where(a => a.CollectTime >= startTime)
|
||||
.Where(a => a.CollectTime <= endTime)
|
||||
.ToList();
|
||||
|
||||
if (data.Count > 0)
|
||||
{
|
||||
results[i] = new HistoryReadResult()
|
||||
{
|
||||
StatusCode = StatusCodes.Good,
|
||||
HistoryData = new ExtensionObject(data)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
results[i] = new HistoryReadResult()
|
||||
{
|
||||
StatusCode = StatusCodes.GoodNoData
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
results[i] = new HistoryReadResult()
|
||||
{
|
||||
StatusCode = StatusCodes.BadNotFound
|
||||
};
|
||||
}
|
||||
}
|
||||
base.HistoryRead(context, details, timestampsToReturn, releaseContinuationPoints, nodesToRead, results, errors);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override NodeId New(ISystemContext context, NodeState node)
|
||||
{
|
||||
BaseInstanceState instance = node as BaseInstanceState;
|
||||
if (instance != null && instance.Parent != null)
|
||||
{
|
||||
string id = instance.Parent.NodeId.Identifier?.ToString();
|
||||
if (id != null)
|
||||
{
|
||||
//用下划线分割
|
||||
return new NodeId(id + "_" + instance.SymbolicName, instance.Parent.NodeId.NamespaceIndex);
|
||||
}
|
||||
}
|
||||
return node.NodeId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新变量
|
||||
/// </summary>
|
||||
/// <param name="variable"></param>
|
||||
public void UpVariable(VariableData variable)
|
||||
{
|
||||
var uaTag = _idTags.Values.FirstOrDefault(it => it.SymbolicName == variable.name);
|
||||
if (uaTag == null) return;
|
||||
object initialItemValue = null;
|
||||
initialItemValue = variable.value;
|
||||
if (initialItemValue != null)
|
||||
{
|
||||
var code = variable.quality == 192 ? StatusCodes.Good : StatusCodes.Bad;
|
||||
if (uaTag.Value != initialItemValue)
|
||||
ChangeNodeData(uaTag.NodeId.ToString(), initialItemValue, variable.changeTime);
|
||||
if (uaTag.StatusCode != code)
|
||||
uaTag.SetStatusCode(SystemContext, code, variable.changeTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加变量节点
|
||||
/// </summary>
|
||||
/// <param name="fs">设备组节点</param>
|
||||
/// <param name="name">设备名称</param>
|
||||
private void AddTagNode(FolderState fs, string name)
|
||||
{
|
||||
var device = _globalCollectDeviceData.CollectDevices.Where(a => a.Name == name).FirstOrDefault();
|
||||
if (device != null)
|
||||
{
|
||||
foreach (var item in device.DeviceVariableRunTimes)
|
||||
{
|
||||
CreateVariable(fs, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在服务器端直接更改对应数据节点的值,并通知客户端
|
||||
/// </summary>
|
||||
private void ChangeNodeData(string nodeId, object value, DateTime dateTime)
|
||||
{
|
||||
if (_idTags.ContainsKey(nodeId))
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
_idTags[nodeId].Value = value;
|
||||
_idTags[nodeId].Timestamp = dateTime;
|
||||
_idTags[nodeId].ClearChangeMasks(SystemContext, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建文件夹
|
||||
/// </summary>
|
||||
private FolderState CreateFolder(NodeState parent, string name, string description)
|
||||
{
|
||||
FolderState folder = new FolderState(parent);
|
||||
|
||||
folder.SymbolicName = name;
|
||||
folder.ReferenceTypeId = ReferenceTypes.Organizes;
|
||||
folder.TypeDefinitionId = ObjectTypeIds.FolderType;
|
||||
folder.Description = description;
|
||||
folder.NodeId = new NodeId(name, NamespaceIndex);
|
||||
folder.BrowseName = new QualifiedName(name, NamespaceIndex);
|
||||
folder.DisplayName = new LocalizedText(name);
|
||||
folder.WriteMask = AttributeWriteMask.None;
|
||||
folder.UserWriteMask = AttributeWriteMask.None;
|
||||
folder.EventNotifier = EventNotifiers.None;
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
parent.AddChild(folder);
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个值节点,类型需要在创建的时候指定
|
||||
/// </summary>
|
||||
private OPCUATag CreateVariable(NodeState parent, CollectVariableRunTime variableRunTime)
|
||||
{
|
||||
OPCUATag variable = new OPCUATag(parent);
|
||||
|
||||
variable.SymbolicName = variableRunTime.Name;
|
||||
variable.ReferenceTypeId = ReferenceTypes.Organizes;
|
||||
variable.TypeDefinitionId = VariableTypeIds.BaseDataVariableType;
|
||||
variable.NodeId = new NodeId(variableRunTime.Name, NamespaceIndex);
|
||||
variable.Description = variableRunTime.Description;
|
||||
variable.BrowseName = new QualifiedName(variableRunTime.Name, NamespaceIndex);
|
||||
variable.DisplayName = new LocalizedText(variableRunTime.Name);
|
||||
variable.WriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description;
|
||||
variable.UserWriteMask = AttributeWriteMask.DisplayName | AttributeWriteMask.Description;
|
||||
variable.ValueRank = ValueRanks.Scalar;
|
||||
|
||||
|
||||
variable.Id = variableRunTime.Id;
|
||||
variable.DataType = DataNodeType(variableRunTime);
|
||||
var level = ProtectTypeTrans(variableRunTime.ProtectTypeEnum);
|
||||
variable.AccessLevel = level;
|
||||
variable.UserAccessLevel = level;
|
||||
|
||||
variable.Historizing = false;
|
||||
variable.StatusCode = StatusCodes.Good;
|
||||
variable.Timestamp = DateTime.Now;
|
||||
variable.Value = Opc.Ua.TypeInfo.GetDefaultValue(variable.DataType, ValueRanks.Scalar, Server.TypeTree);
|
||||
variable.OnWriteValue = OnWriteDataValue;
|
||||
if (parent != null)
|
||||
{
|
||||
parent.AddChild(variable);
|
||||
}
|
||||
_idTags.Add(variable.NodeId, variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 网关转OPC数据类型
|
||||
/// </summary>
|
||||
/// <param name="variableRunTime"></param>
|
||||
/// <returns></returns>
|
||||
private NodeId DataNodeType(CollectVariableRunTime variableRunTime)
|
||||
{
|
||||
var tp = variableRunTime.DataType;
|
||||
if (tp == typeof(bool))
|
||||
return DataTypeIds.Boolean;
|
||||
if (tp == typeof(byte))
|
||||
return DataTypeIds.Byte;
|
||||
if (tp == typeof(sbyte))
|
||||
return DataTypeIds.SByte;
|
||||
if (tp == typeof(Int16))
|
||||
return DataTypeIds.Int16;
|
||||
if (tp == typeof(UInt16))
|
||||
return DataTypeIds.UInt16;
|
||||
if (tp == typeof(Int32))
|
||||
return DataTypeIds.Int32;
|
||||
if (tp == typeof(UInt32))
|
||||
return DataTypeIds.UInt32;
|
||||
if (tp == typeof(Int64))
|
||||
return DataTypeIds.Int64;
|
||||
if (tp == typeof(UInt64))
|
||||
return DataTypeIds.UInt64;
|
||||
if (tp == typeof(float))
|
||||
return DataTypeIds.Float;
|
||||
if (tp == typeof(Double))
|
||||
return DataTypeIds.Double;
|
||||
if (tp == typeof(String))
|
||||
return DataTypeIds.String;
|
||||
if (tp == typeof(DateTime))
|
||||
return DataTypeIds.TimeString;
|
||||
return DataTypeIds.ObjectNode;
|
||||
}
|
||||
|
||||
private ServiceResult OnWriteDataValue(ISystemContext context, NodeState node, NumericRange indexRange, QualifiedName dataEncoding, ref object value, ref StatusCode statusCode, ref DateTime timestamp)
|
||||
{
|
||||
try
|
||||
{
|
||||
OPCUATag variable = node as OPCUATag;
|
||||
// 验证数据类型。
|
||||
Opc.Ua.TypeInfo typeInfo = Opc.Ua.TypeInfo.IsInstanceOfDataType(
|
||||
value,
|
||||
variable.DataType,
|
||||
variable.ValueRank,
|
||||
context.NamespaceUris,
|
||||
context.TypeTable);
|
||||
|
||||
if (typeInfo == null || typeInfo == Opc.Ua.TypeInfo.Unknown)
|
||||
{
|
||||
return StatusCodes.BadTypeMismatch;
|
||||
}
|
||||
// 检查索引范围。
|
||||
if (_idTags.TryGetValue(variable.NodeId, out OPCUATag tag))
|
||||
{
|
||||
if (StatusCode.IsGood(variable.StatusCode))
|
||||
{
|
||||
//仅当指定了值时才将值写入
|
||||
if (variable.Value != null)
|
||||
{
|
||||
var nv = new NameValue() { Name = variable.SymbolicName, Value = value?.ToString() };
|
||||
var result = _rpcCore.InvokeDeviceMethod("OPCUASERVER", nv).GetAwaiter().GetResult();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return StatusCodes.Good;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return StatusCodes.BadWaitingForResponse;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return StatusCodes.BadTypeMismatch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private byte ProtectTypeTrans(ProtectTypeEnum protectTypeEnum)
|
||||
{
|
||||
switch (protectTypeEnum)
|
||||
{
|
||||
case ProtectTypeEnum.ReadOnly: return AccessLevels.CurrentRead;
|
||||
case ProtectTypeEnum.ReadWrite:
|
||||
return AccessLevels.CurrentReadOrWrite;
|
||||
default:
|
||||
return AccessLevels.CurrentRead;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,313 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Server;
|
||||
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
using ThingsGateway.Application;
|
||||
|
||||
namespace ThingsGateway.OPCUA;
|
||||
|
||||
/// <summary>
|
||||
/// UAServer核心实现
|
||||
/// </summary>
|
||||
public partial class ThingsGatewayServer : StandardServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义节点
|
||||
/// </summary>
|
||||
public ThingsGatewayNodeManager NodeManager;
|
||||
private ILogger _logger;
|
||||
private IServiceScope _serviceScope;
|
||||
private ICertificateValidator m_userCertificateValidator;
|
||||
/// <inheritdoc cref="ThingsGatewayServer"/>
|
||||
public ThingsGatewayServer(ILogger logger, IServiceScope serviceScope)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceScope = serviceScope;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override UserTokenPolicyCollection GetUserTokenPolicies(ApplicationConfiguration configuration, EndpointDescription description)
|
||||
{
|
||||
var policies = base.GetUserTokenPolicies(configuration, description);
|
||||
|
||||
// 样品如何修改默认用户令牌的政策
|
||||
if (description.SecurityPolicyUri == SecurityPolicies.Aes256_Sha256_RsaPss &&
|
||||
description.SecurityMode == MessageSecurityMode.SignAndEncrypt)
|
||||
{
|
||||
policies = new UserTokenPolicyCollection(policies.Where(u => u.TokenType != UserTokenType.Certificate));
|
||||
}
|
||||
else if (description.SecurityPolicyUri == SecurityPolicies.Aes128_Sha256_RsaOaep &&
|
||||
description.SecurityMode == MessageSecurityMode.Sign)
|
||||
{
|
||||
policies = new UserTokenPolicyCollection(policies.Where(u => u.TokenType != UserTokenType.Anonymous));
|
||||
}
|
||||
else if (description.SecurityPolicyUri == SecurityPolicies.Aes128_Sha256_RsaOaep &&
|
||||
description.SecurityMode == MessageSecurityMode.SignAndEncrypt)
|
||||
{
|
||||
policies = new UserTokenPolicyCollection(policies.Where(u => u.TokenType != UserTokenType.UserName));
|
||||
}
|
||||
return policies;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override MasterNodeManager CreateMasterNodeManager(IServerInternal server, ApplicationConfiguration configuration)
|
||||
{
|
||||
IList<INodeManager> nodeManagers = new List<INodeManager>();
|
||||
// 创建自定义节点管理器.
|
||||
NodeManager = new ThingsGatewayNodeManager(_serviceScope, server, configuration);
|
||||
nodeManagers.Add(NodeManager);
|
||||
// 创建主节点管理器.
|
||||
var masterNodeManager = new MasterNodeManager(server, configuration, null, nodeManagers.ToArray());
|
||||
return masterNodeManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override ResourceManager CreateResourceManager(IServerInternal server, ApplicationConfiguration configuration)
|
||||
{
|
||||
ResourceManager resourceManager = new ResourceManager(server, configuration);
|
||||
|
||||
System.Reflection.FieldInfo[] fields = typeof(StatusCodes).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
|
||||
|
||||
foreach (System.Reflection.FieldInfo field in fields)
|
||||
{
|
||||
uint? id = field.GetValue(typeof(StatusCodes)) as uint?;
|
||||
|
||||
if (id != null)
|
||||
{
|
||||
resourceManager.Add(id.Value, "en-US", field.Name);
|
||||
}
|
||||
}
|
||||
|
||||
resourceManager.Add("InvalidPassword", "zh-cn", "密码验证失败,'{0}'.");
|
||||
resourceManager.Add("UnexpectedUserTokenError", "zh-cn", "错误的用户令牌。");
|
||||
resourceManager.Add("BadUserAccessDenied", "zh-cn", "当前用户名不存在。");
|
||||
|
||||
return resourceManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override ServerProperties LoadServerProperties()
|
||||
{
|
||||
ServerProperties properties = new ServerProperties
|
||||
{
|
||||
ManufacturerName = "Diego",
|
||||
ProductName = "ThingsGateway OPCUAServer",
|
||||
ProductUri = "https://diego2098.gitee.io/thingsgateway",
|
||||
SoftwareVersion = Utils.GetAssemblySoftwareVersion(),
|
||||
BuildNumber = Utils.GetAssemblyBuildNumber(),
|
||||
BuildDate = Utils.GetAssemblyTimestamp()
|
||||
};
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnServerStarted(IServerInternal server)
|
||||
{
|
||||
// 当用户身份改变时请求。
|
||||
server.SessionManager.ImpersonateUser += SessionManager_ImpersonateUser;
|
||||
base.OnServerStarted(server);
|
||||
_logger.LogInformation("OPCUAServer启动成功");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnServerStarting(ApplicationConfiguration configuration)
|
||||
{
|
||||
_logger.LogInformation("OPCUAServer启动中......");
|
||||
base.OnServerStarting(configuration);
|
||||
|
||||
// 由应用程序决定如何验证用户身份令牌。
|
||||
// 此函数为 X509 身份令牌创建验证器。
|
||||
CreateUserIdentityValidators(configuration);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnServerStopping()
|
||||
{
|
||||
_logger.LogInformation("OPCUAServer停止中......");
|
||||
base.OnServerStopping();
|
||||
}
|
||||
|
||||
private void CreateUserIdentityValidators(ApplicationConfiguration configuration)
|
||||
{
|
||||
for (int ii = 0; ii < configuration.ServerConfiguration.UserTokenPolicies.Count; ii++)
|
||||
{
|
||||
UserTokenPolicy policy = configuration.ServerConfiguration.UserTokenPolicies[ii];
|
||||
|
||||
// 为证书令牌策略创建验证器。
|
||||
if (policy.TokenType == UserTokenType.Certificate)
|
||||
{
|
||||
// check if user certificate trust lists are specified in configuration.
|
||||
if (configuration.SecurityConfiguration.TrustedUserCertificates != null &&
|
||||
configuration.SecurityConfiguration.UserIssuerCertificates != null)
|
||||
{
|
||||
CertificateValidator certificateValidator = new CertificateValidator();
|
||||
certificateValidator.Update(configuration.SecurityConfiguration).Wait();
|
||||
certificateValidator.Update(configuration.SecurityConfiguration.UserIssuerCertificates,
|
||||
configuration.SecurityConfiguration.TrustedUserCertificates,
|
||||
configuration.SecurityConfiguration.RejectedCertificateStore);
|
||||
|
||||
// set custom validator for user certificates.
|
||||
m_userCertificateValidator = certificateValidator.GetChannelValidator();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArgs args)
|
||||
{
|
||||
// check for a user name token.
|
||||
UserNameIdentityToken userNameToken = args.NewIdentity as UserNameIdentityToken;
|
||||
|
||||
if (userNameToken != null)
|
||||
{
|
||||
args.Identity = VerifyPassword(userNameToken);
|
||||
|
||||
|
||||
// set AuthenticatedUser role for accepted user/password authentication
|
||||
args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_AuthenticatedUser);
|
||||
|
||||
if (args.Identity is SystemConfigurationIdentity)
|
||||
{
|
||||
// set ConfigureAdmin role for user with permission to configure server
|
||||
args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_ConfigureAdmin);
|
||||
args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_SecurityAdmin);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// check for x509 user token.
|
||||
X509IdentityToken x509Token = args.NewIdentity as X509IdentityToken;
|
||||
|
||||
if (x509Token != null)
|
||||
{
|
||||
VerifyUserTokenCertificate(x509Token.Certificate);
|
||||
args.Identity = new UserIdentity(x509Token);
|
||||
Utils.LogInfo(Utils.TraceMasks.Security, "X509 Token Accepted: {0}", args.Identity?.DisplayName);
|
||||
|
||||
// set AuthenticatedUser role for accepted certificate authentication
|
||||
args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_AuthenticatedUser);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// check for anonymous token.
|
||||
if (args.NewIdentity is AnonymousIdentityToken || args.NewIdentity == null)
|
||||
{
|
||||
// allow anonymous authentication and set Anonymous role for this authentication
|
||||
args.Identity = new UserIdentity();
|
||||
args.Identity.GrantedRoleIds.Add(ObjectIds.WellKnownRole_Anonymous);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// unsuported identity token type.
|
||||
throw ServiceResultException.Create(StatusCodes.BadIdentityTokenInvalid,
|
||||
"Not supported user token type: {0}.", args.NewIdentity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从第三方用户中校验
|
||||
/// </summary>
|
||||
/// <param name="userNameToken"></param>
|
||||
/// <returns></returns>
|
||||
private IUserIdentity VerifyPassword(UserNameIdentityToken userNameToken)
|
||||
{
|
||||
var userName = userNameToken.UserName;
|
||||
var password = userNameToken.DecryptedPassword;
|
||||
if (string.IsNullOrEmpty(userName))
|
||||
{
|
||||
// an empty username is not accepted.
|
||||
throw ServiceResultException.Create(StatusCodes.BadIdentityTokenInvalid,
|
||||
"Security token is not a valid username token. An empty username is not accepted.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
{
|
||||
// an empty password is not accepted.
|
||||
throw ServiceResultException.Create(StatusCodes.BadIdentityTokenRejected,
|
||||
"Security token is not a valid username token. An empty password is not accepted.");
|
||||
}
|
||||
var _openApiUserService = _serviceScope.ServiceProvider.GetService<IOpenApiUserService>();
|
||||
var userInfo = _openApiUserService.GetUserByAccount(userName).GetAwaiter().GetResult();//获取用户信息
|
||||
if (userInfo == null)
|
||||
{
|
||||
// construct translation object with default text.
|
||||
TranslationInfo info = new TranslationInfo(
|
||||
"InvalidPassword",
|
||||
"en-US",
|
||||
"Invalid username or password.",
|
||||
userName);
|
||||
|
||||
// create an exception with a vendor defined sub-code.
|
||||
throw new ServiceResultException(new ServiceResult(
|
||||
StatusCodes.BadUserAccessDenied,
|
||||
"InvalidPassword",
|
||||
LoadServerProperties().ProductUri,
|
||||
new LocalizedText(info)));
|
||||
}
|
||||
// 有权配置服务器的用户
|
||||
if (userName == userInfo.Account && password == userInfo.Password)
|
||||
{
|
||||
return new SystemConfigurationIdentity(new UserIdentity(userNameToken));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new UserIdentity(userNameToken);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
private void VerifyUserTokenCertificate(X509Certificate2 certificate)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_userCertificateValidator != null)
|
||||
{
|
||||
m_userCertificateValidator.Validate(certificate);
|
||||
}
|
||||
else
|
||||
{
|
||||
CertificateValidator.Validate(certificate);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
TranslationInfo info;
|
||||
StatusCode result = StatusCodes.BadIdentityTokenRejected;
|
||||
ServiceResultException se = e as ServiceResultException;
|
||||
if (se != null && se.StatusCode == StatusCodes.BadCertificateUseNotAllowed)
|
||||
{
|
||||
info = new TranslationInfo(
|
||||
"InvalidCertificate",
|
||||
"en-US",
|
||||
"'{0}' is an invalid user certificate.",
|
||||
certificate.Subject);
|
||||
|
||||
result = StatusCodes.BadIdentityTokenInvalid;
|
||||
}
|
||||
else
|
||||
{
|
||||
// construct translation object with default text.
|
||||
info = new TranslationInfo(
|
||||
"UntrustedCertificate",
|
||||
"en-US",
|
||||
"'{0}' is not a trusted user certificate.",
|
||||
certificate.Subject);
|
||||
}
|
||||
|
||||
// create an exception with a vendor defined sub-code.
|
||||
throw new ServiceResultException(new ServiceResult(
|
||||
result,
|
||||
info.Key,
|
||||
LoadServerProperties().ProductUri,
|
||||
new LocalizedText(info)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -4,7 +4,7 @@
|
||||
<LangVersion>latestMajor</LangVersion>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!--<GenerateDocumentationFile>True</GenerateDocumentationFile>-->
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<Version>1.1.0</Version>
|
||||
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
@@ -33,6 +33,7 @@
|
||||
<PackageReference Include="Masa.Blazor" Version="1.0.0-preview.10">
|
||||
<IncludeAssets>Compile</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.4.371.60" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
Reference in New Issue
Block a user