Sichere Kommunikation zwischen Hosting und Clientintegrationen
Dieser Artikel ist eine Fortsetzung von zwei vorherigen Artikeln, die die Erstellung von benutzerdefinierten Hostingintegrationen und benutzerdefinierten Clientintegrationenzeigen.
Einer der Hauptvorteile von .NET.NET Aspire ist, wie es die Konfigurierbarkeit von Ressourcen und die Nutzung der Clients (oder Integrationen) vereinfacht. In diesem Artikel wird veranschaulicht, wie Sie Authentifizierungsanmeldeinformationen aus einer benutzerdefinierten Ressource in einer Hostingintegration für den verbrauchenden Client in einer benutzerdefinierten Clientintegration freigeben. Die benutzerdefinierte Ressource ist ein MailDev-Container, der eingehende oder ausgehende Anmeldeinformationen zulässt. Die benutzerdefinierte Clientintegration ist ein MailKit-Client, der E-Mails sendet.
Voraussetzungen
Da dieser Artikel aus vorherigen Inhalten weitergeht, sollten Sie die resultierende Lösung bereits als Ausgangspunkt für diesen Artikel erstellt haben. Falls noch nicht geschehen, führen Sie die folgenden Artikel aus:
- Erstellen Sie benutzerdefinierte .NET.NET Aspire Hosting-Integrationen
- Erstellen von benutzerdefinierten .NET.NET Aspire Clientintegrationen
Die resultierende Lösung aus diesen vorherigen Artikeln enthält die folgenden Projekte:
- MailDev.Hosting: Enthält den benutzerdefinierte Ressourcentyp für den MailDev-Container.
- MailDevResource.AppHost-: Der App-Host, der die benutzerdefinierte Ressource verwendet und als Abhängigkeit für einen Newsletterdienst definiert.
- MailDevResource.NewsletterService: Ein ASP.NET Core Web-API-Projekt, das E-Mails mithilfe des MailDev Containers sendet.
- MailDevResource.ServiceDefaults: Enthält die Standarddienstkonfigurationen, die für die Freigabe vorgesehen sind.
-
MailKit.Client: Enthält die benutzerdefinierte Clientintegration, die das MailKit-
SmtpClient
über eine Factory verfügbar macht.
Ressource MailDev aktualisieren
Um Authentifizierungsanmeldeinformationen von der MailDev-Ressource in die MailKit-Integration fließen zu lassen, müssen Sie die MailDev-Ressource aktualisieren, um die Benutzernamen und Kennwort-Parameter einzuschließen.
Der MailDev-Container unterstützt die grundlegende Authentifizierung sowohl für eingehende als auch ausgehende Verbindungen des einfachen Mail-Übertragungsprotokolls (SMTP). Um die Anmeldeinformationen für eingehende Verbindungen zu konfigurieren, müssen Sie die Umgebungsvariablen MAILDEV_INCOMING_USER
und MAILDEV_INCOMING_PASS
festlegen. Weitere Informationen finden Sie unter MailDev: Verwendung. Aktualisieren Sie die MailDevResource.cs Datei im MailDev.Hosting
Projekt, indem Sie den Inhalt durch den folgenden C#-Code ersetzen:
// For ease of discovery, resource types should be placed in
// the Aspire.Hosting.ApplicationModel namespace. If there is
// likelihood of a conflict on the resource name consider using
// an alternative namespace.
namespace Aspire.Hosting.ApplicationModel;
public sealed class MailDevResource(
string name,
ParameterResource? username,
ParameterResource password)
: ContainerResource(name), IResourceWithConnectionString
{
// Constants used to refer to well known-endpoint names, this is specific
// for each resource type. MailDev exposes an SMTP and HTTP endpoints.
internal const string SmtpEndpointName = "smtp";
internal const string HttpEndpointName = "http";
private const string DefaultUsername = "mail-dev";
// An EndpointReference is a core .NET Aspire type used for keeping
// track of endpoint details in expressions. Simple literal values cannot
// be used because endpoints are not known until containers are launched.
private EndpointReference? _smtpReference;
/// <summary>
/// Gets the parameter that contains the MailDev SMTP server username.
/// </summary>
public ParameterResource? UsernameParameter { get; } = username;
internal ReferenceExpression UserNameReference =>
UsernameParameter is not null ?
ReferenceExpression.Create($"{UsernameParameter}") :
ReferenceExpression.Create($"{DefaultUsername}");
/// <summary>
/// Gets the parameter that contains the MailDev SMTP server password.
/// </summary>
public ParameterResource PasswordParameter { get; } = password;
public EndpointReference SmtpEndpoint =>
_smtpReference ??= new(this, SmtpEndpointName);
// Required property on IResourceWithConnectionString. Represents a connection
// string that applications can use to access the MailDev server. In this case
// the connection string is composed of the SmtpEndpoint endpoint reference.
public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create(
$"Endpoint=smtp://{SmtpEndpoint.Property(EndpointProperty.Host)}:{SmtpEndpoint.Property(EndpointProperty.Port)};Username={UserNameReference};Password={PasswordParameter}"
);
}
Diese Updates fügen eine UsernameParameter
- und PasswordParameter
-Eigenschaft hinzu. Diese Eigenschaften werden verwendet, um die Parameter für den MailDev Benutzernamen und das Kennwort zu speichern. Die ConnectionStringExpression
-Eigenschaft wird aktualisiert, um die Parameter für Benutzername und Kennwort in die Verbindungszeichenfolge einzuschließen. Aktualisieren Sie als Nächstes die MailDevResourceBuilderExtensions.cs Datei im MailDev.Hosting
Projekt mit dem folgenden C#-Code:
using Aspire.Hosting.ApplicationModel;
// Put extensions in the Aspire.Hosting namespace to ease discovery as referencing
// the .NET Aspire hosting package automatically adds this namespace.
namespace Aspire.Hosting;
public static class MailDevResourceBuilderExtensions
{
private const string UserEnvVarName = "MAILDEV_INCOMING_USER";
private const string PasswordEnvVarName = "MAILDEV_INCOMING_PASS";
/// <summary>
/// Adds the <see cref="MailDevResource"/> to the given
/// <paramref name="builder"/> instance. Uses the "2.1.0" tag.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource.</param>
/// <param name="httpPort">The HTTP port.</param>
/// <param name="smtpPort">The SMTP port.</param>
/// <returns>
/// An <see cref="IResourceBuilder{MailDevResource}"/> instance that
/// represents the added MailDev resource.
/// </returns>
public static IResourceBuilder<MailDevResource> AddMailDev(
this IDistributedApplicationBuilder builder,
string name,
int? httpPort = null,
int? smtpPort = null,
IResourceBuilder<ParameterResource>? userName = null,
IResourceBuilder<ParameterResource>? password = null)
{
var passwordParameter = password?.Resource ??
ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(
builder, $"{name}-password");
// The AddResource method is a core API within .NET Aspire and is
// used by resource developers to wrap a custom resource in an
// IResourceBuilder<T> instance. Extension methods to customize
// the resource (if any exist) target the builder interface.
var resource = new MailDevResource(
name, userName?.Resource, passwordParameter);
return builder.AddResource(resource)
.WithImage(MailDevContainerImageTags.Image)
.WithImageRegistry(MailDevContainerImageTags.Registry)
.WithImageTag(MailDevContainerImageTags.Tag)
.WithHttpEndpoint(
targetPort: 1080,
port: httpPort,
name: MailDevResource.HttpEndpointName)
.WithEndpoint(
targetPort: 1025,
port: smtpPort,
name: MailDevResource.SmtpEndpointName)
.WithEnvironment(context =>
{
context.EnvironmentVariables[UserEnvVarName] = resource.UserNameReference;
context.EnvironmentVariables[PasswordEnvVarName] = resource.PasswordParameter;
});
}
}
// This class just contains constant strings that can be updated periodically
// when new versions of the underlying container are released.
internal static class MailDevContainerImageTags
{
internal const string Registry = "docker.io";
internal const string Image = "maildev/maildev";
internal const string Tag = "2.1.0";
}
Im oben genannten Code wird die AddMailDev
-Erweiterungsmethode aktualisiert, um die Parameter userName
und password
einzuschließen. Die WithEnvironment
-Methode wird aktualisiert, um die UserEnvVarName
- und PasswordEnvVarName
Umgebungsvariablen einzuschließen. Diese Umgebungsvariablen werden verwendet, um den MailDev Benutzernamen und das Kennwort festzulegen.
Aktualisieren des App-Hosts
Nachdem die Ressource aktualisiert wurde, um die Parameter für Benutzername und Kennwort einzuschließen, müssen Sie den App-Host aktualisieren, um diese Parameter einzuschließen. Aktualisieren Sie die Program.cs Datei im MailDevResource.AppHost
Projekt mit dem folgenden C#-Code:
var builder = DistributedApplication.CreateBuilder(args);
var mailDevUsername = builder.AddParameter("maildev-username");
var mailDevPassword = builder.AddParameter("maildev-password");
var maildev = builder.AddMailDev(
name: "maildev",
userName: mailDevUsername,
password: mailDevPassword);
builder.AddProject<Projects.MailDevResource_NewsletterService>("newsletterservice")
.WithReference(maildev);
builder.Build().Run();
Der vorangehende Code fügt zwei Parameter für den MailDev Benutzernamen und das Kennwort hinzu. Sie weist diese Parameter den MAILDEV_INCOMING_USER
- und MAILDEV_INCOMING_PASS
Umgebungsvariablen zu. Die AddMailDev
-Methode verfügt über zwei verkettete Aufrufe an WithEnvironment
, die diese Umgebungsvariablen enthalten. Weitere Informationen zu Parametern finden Sie unter Externe Parameter.
Als nächsten Schritt konfigurieren Sie die Secrets für diese Parameter. Klicken Sie mit der rechten Maustaste auf das MailDevResource.AppHost
Projekt, und wählen Sie Manage User Secrets
aus. Fügen Sie der datei secrets.json die folgende JSON hinzu:
{
"Parameters:maildev-username": "@admin",
"Parameters:maildev-password": "t3st1ng"
}
Warnung
Diese Anmeldeinformationen dienen nur zu Demonstrationszwecken. MailDev ist für die lokale Entwicklung vorgesehen. Diese Anmeldeinformationen sind fiktive und sollten nicht in einer Produktionsumgebung verwendet werden.
Aktualisieren der MailKit-Integration
Es empfiehlt sich, dass Clientintegrationen davon ausgehen, dass Verbindungszeichenfolgen verschiedene Schlüssel-Wert-Paare enthalten und diese Paare in die entsprechenden Eigenschaften analysieren. Aktualisieren Sie die MailKitClientSettings.cs Datei im MailKit.Client
Projekt mit dem folgenden C#-Code:
using System.Data.Common;
using System.Net;
namespace MailKit.Client;
/// <summary>
/// Provides the client configuration settings for connecting MailKit to an SMTP server.
/// </summary>
public sealed class MailKitClientSettings
{
internal const string DefaultConfigSectionName = "MailKit:Client";
/// <summary>
/// Gets or sets the SMTP server <see cref="Uri"/>.
/// </summary>
/// <value>
/// The default value is <see langword="null"/>.
/// </value>
public Uri? Endpoint { get; set; }
/// <summary>
/// Gets or sets the network credentials that are optionally configurable for SMTP
/// server's that require authentication.
/// </summary>
/// <value>
/// The default value is <see langword="null"/>.
/// </value>
public NetworkCredential? Credentials { get; set; }
/// <summary>
/// Gets or sets a boolean value that indicates whether the database health check is disabled or not.
/// </summary>
/// <value>
/// The default value is <see langword="false"/>.
/// </value>
public bool DisableHealthChecks { get; set; }
/// <summary>
/// Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is disabled or not.
/// </summary>
/// <value>
/// The default value is <see langword="false"/>.
/// </value>
public bool DisableTracing { get; set; }
/// <summary>
/// Gets or sets a boolean value that indicates whether the OpenTelemetry metrics are disabled or not.
/// </summary>
/// <value>
/// The default value is <see langword="false"/>.
/// </value>
public bool DisableMetrics { get; set; }
internal void ParseConnectionString(string? connectionString)
{
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new InvalidOperationException($"""
ConnectionString is missing.
It should be provided in 'ConnectionStrings:<connectionName>'
or '{DefaultConfigSectionName}:Endpoint' key.'
configuration section.
""");
}
if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri))
{
Endpoint = uri;
}
else
{
var builder = new DbConnectionStringBuilder
{
ConnectionString = connectionString
};
if (builder.TryGetValue("Endpoint", out var endpoint) is false)
{
throw new InvalidOperationException($"""
The 'ConnectionStrings:<connectionName>' (or 'Endpoint' key in
'{DefaultConfigSectionName}') is missing.
""");
}
if (Uri.TryCreate(endpoint.ToString(), UriKind.Absolute, out uri) is false)
{
throw new InvalidOperationException($"""
The 'ConnectionStrings:<connectionName>' (or 'Endpoint' key in
'{DefaultConfigSectionName}') isn't a valid URI.
""");
}
Endpoint = uri;
if (builder.TryGetValue("Username", out var username) &&
builder.TryGetValue("Password", out var password))
{
Credentials = new(
username.ToString(), password.ToString());
}
}
}
}
Die vorhergehende Einstellungsklasse enthält nun eine Eigenschaft Credentials
vom Typ NetworkCredential
. Die ParseConnectionString
-Methode wird aktualisiert, um die Schlüssel Username
und Password
aus der Verbindungszeichenfolge zu analysieren. Wenn die schlüssel Username
und Password
vorhanden sind, wird ein NetworkCredential
erstellt und der eigenschaft Credentials
zugewiesen.
Wenn die Einstellungsklasse aktualisiert wurde, um die Anmeldeinformationen zu verstehen und aufzufüllen, aktualisieren Sie die Factory, um die Anmeldeinformationen bedingt zu verwenden, wenn sie konfiguriert sind. Aktualisieren Sie die MailKitClientFactory.cs Datei im MailKit.Client
Projekt mit dem folgenden C#-Code:
using System.Net;
using MailKit.Net.Smtp;
namespace MailKit.Client;
/// <summary>
/// A factory for creating <see cref="ISmtpClient"/> instances
/// given a <paramref name="smtpUri"/> (and optional <paramref name="credentials"/>).
/// </summary>
/// <param name="settings">
/// The <see cref="MailKitClientSettings"/> settings for the SMTP server
/// </param>
public sealed class MailKitClientFactory(MailKitClientSettings settings) : IDisposable
{
private readonly SemaphoreSlim _semaphore = new(1, 1);
private SmtpClient? _client;
/// <summary>
/// Gets an <see cref="ISmtpClient"/> instance in the connected state
/// (and that's been authenticated if configured).
/// </summary>
/// <param name="cancellationToken">Used to abort client creation and connection.</param>
/// <returns>A connected (and authenticated) <see cref="ISmtpClient"/> instance.</returns>
/// <remarks>
/// Since both the connection and authentication are considered expensive operations,
/// the <see cref="ISmtpClient"/> returned is intended to be used for the duration of a request
/// (registered as 'Scoped') and is automatically disposed of.
/// </remarks>
public async Task<ISmtpClient> GetSmtpClientAsync(
CancellationToken cancellationToken = default)
{
await _semaphore.WaitAsync(cancellationToken);
try
{
if (_client is null)
{
_client = new SmtpClient();
await _client.ConnectAsync(settings.Endpoint, cancellationToken)
.ConfigureAwait(false);
if (settings.Credentials is not null)
{
await _client.AuthenticateAsync(settings.Credentials, cancellationToken)
.ConfigureAwait(false);
}
}
}
finally
{
_semaphore.Release();
}
return _client;
}
public void Dispose()
{
_client?.Dispose();
_semaphore.Dispose();
}
}
Wenn die Factory bestimmt, dass Anmeldeinformationen konfiguriert wurden, authentifiziert sie sich nach dem Herstellen einer Verbindung mit dem SMTP-Server, bevor die SmtpClient
zurückgegeben wird.
Beispiel ausführen
Nachdem Sie die Ressource, die entsprechenden Integrationsprojekte und den App-Host aktualisiert haben, können Sie die Beispiel-App ausführen. Wenn Sie das Beispiel aus Ihrer IDE ausführen möchten, wählen Sie F5- aus, oder verwenden Sie dotnet run
aus dem Stammverzeichnis der Lösung, um die Anwendung zu starten. Es sollte das .NET.NET Aspire-Dashboardangezeigt werden. Navigieren Sie zur maildev
Container-Ressource und zeigen Sie die Details an. In den Ressourcendetails, im Abschnitt Umgebungsvariablen, sollten die Parameter für Benutzername und Kennwort angezeigt werden.
Ebenso sollte die Verbindungszeichenfolge in den newsletterservice
Ressourcendetails unter dem Abschnitt Umgebungsvariablen angezeigt werden:
Überprüfen Sie, ob alles wie erwartet funktioniert.
Zusammenfassung
In diesem Artikel wurde gezeigt, wie Authentifizierungsanmeldeinformationen von einer benutzerdefinierten Ressource zu einer benutzerdefinierten Clientintegration fließen. Die benutzerdefinierte Ressource ist ein MailDev-Container, der eingehende oder ausgehende Anmeldeinformationen zulässt. Die benutzerdefinierte Clientintegration ist ein MailKit-Client, der E-Mails sendet. Durch das Aktualisieren der Ressource, um die Parameter username
und password
einzuschließen, und das Aktualisieren der Integration, um diese Parameter zu analysieren und zu verwenden, fließen die Anmeldeinformationen der Authentifizierung von der Hosting-Integration zur Client-Integration.