• Noser.com
facebook
linkedin
twitter
youtube
  • NOSERmobile
    • Android
    • HTML 5
    • Hybrid Apps
    • iOS
    • Windows Phone
  • NOSERembedded
    • Medizintechnik
  • NOSERprojektmanagement
  • NOSERtesting
  • NOSERlifecycle
    • .NET Allgemein
    • Application Lifecylce Management
    • Apps
    • Architektur
    • ASP.NET
    • Azure
    • Cleancode
    • Cloud
    • Silverlight
    • Visual Studio / Team Foundation Server
    • Windows 8
    • Windows Presentation Foundation
  • NOSERinnovation
    • Big Data
    • Cloud
    • IoT
    • Operations Research
    • Augmented Reality
    • RFID, NFC, Bluetooth LE

ZeroMQ / NetMQ mit Protocobuf

04. März 2022
Erik Stroeken
0
NetMQ, Protcol Buffer, protobuf, ZeroMQ

Im Blog «Tutorial: Protocol Buffers in ASP.NET Core» wird die Anwendung von Protocol Buffers in .NET und Visual Studio erklärt. Protocol Buffers (Protobuf) ist eine Methode zur Serialisierung strukturierter Daten. Sie ist die Schnittstellebeschreibungssprache für gRPC. gRPC ist Google’s Alternative für Microsoft’s WCF und wurde in einer siebenteiligen Blog-Serie auf dieser Plattform ausführlich erklärt.

Wie gRPC ist auch ZeroMQ eine gute Alternative für WCF. ZeroMQ (auch ØMQ, 0MQ oder ZMQ genannt) ist eine leichtgewichtige asynchrone Messaging-Bibliothek, die für den Einsatz in verteilten oder concurrent Anwendungen gedacht ist. Wie die meisten Frameworks arbeitet auch ZeroMQ mit Message Queues (ZeroMQ), verzichtet aber auf einen Message Broker, deshalb der Namen ‘Zero’ (ZeroMQ).

Im ersten Teil dieses Blogs wird kurz ZeroMQ erklärt und die .NET Variante NetMQ. Im zweiten Teil wird eine Client-/Server-Applikation gebaut, wobei Protobuf für die (de-)Serialisierung verwendet wird. Das Beispiel demonstriert, wie der Client Werte im Server schreiben oder lesen kann und wie der Server seine Clients über das Publisch-subscribe Muster informieren kann.

Der Beispielcode kann hier als ZIP-Datei heruntergeladen werden.

ZeroMQ

ZeroMQ wurde entwickelt zwischen 2007 und 2010 und gibt es mittlerweile für fast jede Sprache als Bibliothek. ZeroMQ implementiert verschiedene Message Patterns wovon im Beispiel dieses Blogs nur zwei verwendet werden: Request-reply um zu lesen und zu schreiben und Publish-subscribe damit der Server seine Clients informieren kann, wenn ein Ereignis auftritt.

Der Transport von Daten wird mit einem Connection String definiert. Es gibt folgende Möglichkeiten:

  • Inproc – thread zu thread im gleichen Prozess
  • Ipc – in-Memory, Interprozess Kommunikation (Linux only)
  • Tcp
  • Epgm, pgm – multicast Protokoll (out of scope)

Im Gegensatz zu WCF ist es mit ZeroMQ unter Windows nicht möglich «named pipes» zu verwenden.

Für Interprozess Kommunikation muss ausgewichen werden auf TCP mit Localhost.

NetMQ

Die meisten Sprachimplementationen von ZeroMQ kapseln die Basisbibliothek Libzmq. Da Libzmq in C++ geschrieben ist, kann man die C# Implementation von ZeroMQ nicht in .NET Core oder .NET 5/6+ verwenden. Zum Glück gibt es die pure C# Implementation NetMQ welche auch in .NET Core und .NET 5/6+ verwendet werden kann.

Beispiel

Im Beispiel generiert der Server nach einer einstellbaren Intervallzeit (NotificationIntervalMs) einen Event mit der Serverzeit als Payload. Die Intervallzeit wird mit SetNotificationIntervalMessage gesetzt und mit GetNotificationIntervalMessage gelesen.

Protobuf Verträge

Etwas speziell an dieser Lösung ist die Kapselung der Protobuf Berichte. NetMqRequestMessage kapselt alle Request-Berichte und NetMqResponseMessage fügt zu dem Response-Bericht noch einen Status und optionalen Fehlertext hinzu. Der Server informiert seine Clients mit Events, die durch die NetMqEventMessage übermittelt werden.

syntax = "proto3";
option csharp_namespace = "NetMqProtobufContracts.Protos";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";

enum NetMqResult {
  NET_MQ_RESULT_UNDEFINED = 0;
  NET_MQ_RESULT_OK = 1;
  NET_MQ_RESULT_ERROR = 2;
}

message NotificationIntervalMessage {
    int32 interval_ms = 1;
}

message GetNotificationIntervalMessage {
}

message SetNotificationIntervalMessage {
    int32 interval_ms = 1;
}

message NotificationEventMessage {
  google.protobuf.Timestamp server_time = 1;
}

message NetMqEventMessage {
  google.protobuf.Any data = 1;
}

message NetMqRequestMessage {
  google.protobuf.Any data = 1;
}

message NetMqResponseMessage {
  NetMqResult result = 1;
  string error_message = 2;
  google.protobuf.Any data = 3;
}

Der NetMQ Server

Im Beispiel kommunizieren Client und Server über eine TCP-Verbindung. Auf einem Thread wird zyklisch geprüft, ob neue Daten im Response-Socket vorhanden sind und ob es Zeit ist, eine Servernotification zu senden.

Der Server benutzt zwei Sockets:

  • Response-Socket – zum Empfangen von Requests und Senden von Responses
  • Publisher-Socket – um die Client Events zu senden.

Mit NetMQ wird ein Socket wie folgt kreiert und geöffnet:

private const string ResponseSocketAddress = "tcp://*:5555";
…
try
{
    _responseSocket = new ResponseSocket();
    _responseSocket.Bind(ResponseSocketAddress);
}
catch (Exception e)
{
    Log($"Exception while binding response socket. Exception {e}");
    return;
}

Um das Socket aufzuräumen muss Unbind und Dispose aufgerufen werden:

_responseSocket.Unbind(ResponseSocketAddress);
_responseSocket.Dispose();
_responseSocket = null;

Das Empfangen und Senden von Berichten mit NetMQ ist einfach. Leider ist die Deserialisierung des Protobuf-Inhalt etwas kompliziert. SetNotificationIntervalMessage setzt die Eigenschaft NotificationIntervalMs und GetNotificationIntervalMessage bittet den Server, einen Bericht zurückzusenden mit dem Wert der NotificationIntervalMs Eigenschaft.

if (_responseSocket.TryReceiveFrameBytes(_receiveTimeout, out byte[] bytes))
{
    NetMqRequestMessage requestMsg = new NetMqRequestMessage();
    requestMsg.MergeFrom(bytes);
    string typeName = Any.GetTypeName(requestMsg.Data.TypeUrl);
    NetMqResponseMessage response = new NetMqResponseMessage();
    if (typeName == nameof(SetNotificationIntervalMessage))
    {
        SetNotificationIntervalMessage msg = requestMsg.Data.Unpack<SetNotificationIntervalMessage>();
        NotificationIntervalMs = msg.IntervalMs;
        Log($"SetNotificationIntervalMessage request with IntervalMs={NotificationIntervalMs}
              received");
        response.Result = NetMqResult.Ok;
    }
    else if (typeName == nameof(GetNotificationIntervalMessage))
    {
        Log("GetNotificationIntervalMessage request received");
        NotificationIntervalMessage notificationIntervalMessage = new NotificationIntervalMessage();
        notificationIntervalMessage.IntervalMs = NotificationIntervalMs;
        response.Result = NetMqResult.Ok;
        response.Data = Any.Pack(notificationIntervalMessage);
    }
    else
    {
        response.Result = NetMqResult.Error;
        response.ErrorMessage = $"Unknown message type '{typeName}' received";
    }
    if (_responseSocket.TrySendFrame(_sendTimeout, response.ToByteArray()))
    {
        IsClientConnected = true;
    }
}

Das Senden von Events geht gleich wie das Senden von Responses, nur über das Publisher-Socket:

if (DateTime.UtcNow > nextNotificationTime)
{
    nextNotificationTime = DateTime.UtcNow.AddMilliseconds(NotificationIntervalMs);
    NotificationEventMessage notificationEventMessage = new NotificationEventMessage()
    {
        ServerTime = Timestamp.FromDateTime(DateTime.UtcNow)
    };
    NetMqEventMessage eventMessage = new NetMqEventMessage();
    eventMessage.Data = Any.Pack(notificationEventMessage);
    _publisherSocket.TrySendFrame(_sendTimeout, eventMessage.ToByteArray());
}

Der NetMQ Client

Der Client verwendet ein Request-Socket und Subscriber-Socket. Die SendRequest Methode im Beispiel öffnet jedes Mal einen neuen Socket, welcher mit dem Server über TCP verbunden wird. Nach TrySendFrame folgt TryReceiveFrame, welcher die Antwort in einer NetMqResponseMessage Instanz verpackt.

public NetMqResponseMessage SendRequest(IMessage message)
{
    NetMqResponseMessage netMqResponseMessage = new NetMqResponseMessage();
    try
    {
        if (_requestSocket == null)
        {
            _requestSocket = new RequestSocket();
            _requestSocket.Connect(RequestSocketAddress);
        }
        if (_requestSocket.TrySendFrame(_sendTimeout, message.ToByteArray()))
        {
            if (_requestSocket.TryReceiveFrameBytes(_responseTimeout, out byte[] bytes))
            {
                netMqResponseMessage.MergeFrom(bytes);
                IsConnected = true;
                return netMqResponseMessage;
            }
            Log($"NetMQ server didn't send response after '{_responseTimeout}'.");
        }
    }
    catch (Exception ex)
    {
        Log(ex.ToString());
    }
    _requestSocket?.Disconnect(RequestSocketAddress);
    _requestSocket?.Dispose();
    _requestSocket = null;
    IsConnected = false;
    return null;
}

Die Methode, um die NotificationInterval Zeit auf dem Server zu setzen sieht dann so aus:

public bool SendSetNotificationIntervalMessage(int intervalMs)
{
    SetNotificationIntervalMessage setNotificationIntervalMessage = 
      new SetNotificationIntervalMessage()
    {
        IntervalMs = intervalMs
    };
    NetMqRequestMessage msg = new NetMqRequestMessage();
    msg.Data = Any.Pack(setNotificationIntervalMessage);
    NetMqResponseMessage response = SendRequest(msg);
    return response?.Result == NetMqResult.Ok;
}

Um Events vom Server zu empfangen, wird ein Thread gestartet, der ein Subscriber-Socket zyklisch auf Daten prüft. Bevor Events empfangen werden können, muss man dem Server erst noch mitteilen, an welcher Kategorie der Client interessiert ist. Das Beispiel abonniert sich auf alle Kategorien (SubscribeToAnyTopic).

private void DoWork()
{
    try
    {
        _subscriberSocket = new SubscriberSocket();
        _subscriberSocket.Connect(SubSocketAddress);
        _subscriberSocket.SubscribeToAnyTopic();
    }
    catch (Exception e)
    {
        Log($"Exception while binding publisher socket. Exception {e}");
        return;
    }

    try
    {
        do
        {
            _cancellationToken.ThrowIfCancellationRequested();
            NetMqEventMessage eventMessage = new NetMqEventMessage();
            if(_subscriberSocket.TryReceiveFrameBytes(_sendTimeout, out byte[] bytes))
            {
                IsConnected = true;
                eventMessage.MergeFrom(bytes);
                string typeName = Any.GetTypeName(eventMessage.Data.TypeUrl);
                if (typeName == nameof(NotificationEventMessage))
                {
                    NotificationEventMessage v = eventMessage.Data.Unpack<NotificationEventMessage>();
                    ServerDateTimeReceivedEvent?.Invoke(this,
                        new EventArgs<DateTime>(v.ServerTime.ToDateTime()));
                }
            }

        } while (true); // Setup listener again and wait for client to connect
    }
    catch (ThreadInterruptedException)
    {
        // Expected exception: triggered by client code
    }
    catch (OperationCanceledException)
    {
        // Expected exception: triggered by client code
    }
    catch (Exception ex)
    {
        Log(ex.ToString());
    }
    finally
    {
        _subscriberSocket.Unsubscribe(string.Empty);
        _subscriberSocket.Disconnect(SubSocketAddress);
        _subscriberSocket?.Dispose();
        _subscriberSocket = null;
    }
}

Fazit

NetMQ ist eine gute Alternative zu WCF. Im Vergleich mit gRPC muss man im Bereich der (De-)Serialisierung ein wenig mehr Handarbeit machen, welche bei gRPC vom Codegenerator übernommen wird. Ein weiterer Nachteil ist, dass NetMQ auf Windows keine Interprocess-Communication unterstützt. WCF verwendet dafür named-pipes und gRPC benutzt dafür Unix Domain Sockets, welche in moderneren Versionen von Windows unterstützt werden.

gRPC Tutorial Teil 7: JWT Client Credentials mit Rollen und IdentityServer4

28. Februar 2020
Erik Stroeken
1
C#, gRPC, identityserver4

Im letzten Blog wurde der einfachste OAuth2-Flow, der Client Credential Flow, anhand eines Beispiels unter Verwendung eines gRPC-Clients / -Servers mit einem minimalistischen Token-Server erläutert. In diesem Blog wird der Token Server implementiert mit IdentityServer4. Es wird das Public-Key-Verschlüsselungsverfahren verwendet und rollenbasierte Autorisierung.

Der Beispielcode kann hier als ZIP-Datei heruntergeladen werden.

Ablauf

Das Konstrukt involviert drei Teilnehmer: den Client, den Identity Server und den Ressourcen-Server.

Der Ablauf ist wie folgt:

  1. Der Client holt sich beim Discovery Endpoint des Identity Server den Token Endpoint.
  2. Mit seiner ClientId, dem Secret und dem Namen des gewünschten APIs fragt der Client ein JWT Token beim Token Endpoint des Identity Servers an.
  3. Der Identity Server prüft die ClientId, das Client Secret und ob der Client mit der gewünschten API kommunizieren darf. Der Identity Server definiert die Autorisierungsrolle des Clients und packt diese in das Token.
  4. Der Client speichert das Token und sendet es bei jeder Anfrage am Ressourcen-Server mit (im Header).
  5. Der Ressourcen-Server validiert das Token und kontrolliert die Signatur. Wenn das Token gültig ist, wird die Rolle aus dem Token gelesen und kontrolliert, ob der Client berechtigt ist, die spezifische Anfrage zu machen.

Die ganze Kommunikation verläuft über eine gesicherte Verbindung, weil der Besitzer des Tokens direkt auf die Ressource zugreifen kann.

Identity Server 4

Identity Server 4 ist ein OpenID Connect- und OAuth 2.0-Framework für .NET, .NET Core und ASP.NET Core, um JWT Bearer Tokens zu generieren. Identity Server 4 ist eine Implementierung der OAuth 2.0-Spezifikation und unterstützt die Standard OAuth 2.0 ‘Flows’ (siehe https://blog.noser.com/grpc-tutorial-teil-6-jwt-client-credentials). Die Webseiten des Identity Server 4 sind sehr ausführlich und beinhalten einfache Beispiele, unter anderen vom Client Credential Flow (https://identityserver4.readthedocs.io/en/latest/quickstarts/1_client_credentials.html).

Beispiel: Identity-Server

Das Identity-Server Projekt im Beispiel heisst RpcJwtClientCredentialsRolesIdentityServer und referenziert das NuGet Paket IdentityServer4 (version 3.0.1).

Das Projekt benutzt den Kestrel-Server mit gesicherter Verbindung wie beschrieben in ‘gRPC Tutorial Teil 4: HTTP/2 über HTTPS’.

Die relevanten Parameter in appsettings.json sehen wie folgt aus:

…
"ApiResource": {
    "Name": "DemoService",
    "DisplayName": "gRPC DemoService API"
},
"ClientUser": {
    "ClientId": "ClientUser",
    "ClientSecret": "D9B31A4E97C40EC47D758490C801FFCDD39BCE3EF5E9C978E4FF9D34FA0F1967",
    "AllowedScopes": "DemoService",
    "Role": "User"
},
"ClientAdmin": {
    "ClientId": "ClientAdmin",
    "ClientSecret": "V4YH2Q8C968XC52S5EFWD7VDX3TBEU9ZNY8EE6GE2F35ATM5XSS3PD6C6PEA9DZM",
    "AllowedScopes": "DemoService",
    "Role": "Admin"
},
"Certificate": {
    "File": "server.pfx",
    "Password": "P@ssw0rd"
}
…

In der Methode ConfigureServices() in Startup.cs wird der Identity-Server konfiguriert.

Als erstes wird ein Array erstellt mit den APIs, hier nur DemoService:

IEnumerable<ApiResource> apis = new ApiResource[]
{
    new ApiResource(Configuration["ApiResource:Name"],
        Configuration["ApiResource:DisplayName"])
};

Dann wird ein Array mit zwei Clients definiert, einen mit User- und einen mit Admin-Rechten:

IEnumerable<Client> clients = new Client[]
{
    // Client User
    new Client
    {
        ClientId = Configuration["ClientUser:ClientId"],
        AllowedGrantTypes = GrantTypes.ClientCredentials,
        ClientSecrets =
        {
            new Secret(Configuration["ClientUser:ClientSecret"].Sha256())
        },
        AllowedScopes = { Configuration["ClientUser:AllowedScopes"] },
        Claims = new List<Claim>
        {
            new Claim(JwtClaimTypes.Role, Configuration["ClientUser:Role"])
        }
        ,ClientClaimsPrefix = null
    },

    // Client Admin
    new Client
    {
        ClientId = Configuration["ClientAdmin:ClientId"],
        AllowedGrantTypes = GrantTypes.ClientCredentials,
        ClientSecrets =
        {
            new Secret(Configuration["ClientAdmin:ClientSecret"].Sha256())
        },
        AllowedScopes = { Configuration["ClientAdmin:AllowedScopes"] },
        Claims = new List<Claim>
        {
            new Claim(JwtClaimTypes.Role, Configuration["ClientAdmin:Role"])
        }
        ,ClientClaimsPrefix = null
    }
};

Wenn ein Client ein Token anfragt, dann sendet dieser seine ClientId, Secret und gewünschte API mit. Die ClientId wird benutzt, um in den ‘clients’-Array die Daten aufzusuchen. Das ClientSecret wird verglichen und es wird kontrolliert, ob der Client auf die gewünschte API zugreifen darf. Die definierte Rolle wird im Token verpackt.

Der Identity-Server hört in diesem Projekt auf https://localhost:5001 (eingestellt in ‘launchSettings.json’). Man kann unter der URL https://localhost:5001/.well-known/openid-configuration ein ‘discovery’-Dokument beim Server abfragen, worin sich die URL des Token-Endpoints befindet (token_endpoint: “https://localhost:5001/connect/token“).

{
issuer: "https://localhost:5001",
jwks_uri: "https://localhost:5001/.well-known/openid-configuration/jwks",
authorization_endpoint: "https://localhost:5001/connect/authorize",
token_endpoint: "https://localhost:5001/connect/token",
userinfo_endpoint: "https://localhost:5001/connect/userinfo",
end_session_endpoint: "https://localhost:5001/connect/endsession",
check_session_iframe: "https://localhost:5001/connect/checksession",
revocation_endpoint: "https://localhost:5001/connect/revocation",
introspection_endpoint: "https://localhost:5001/connect/introspect",
device_authorization_endpoint: "https://localhost:5001/connect/deviceauthorization",
frontchannel_logout_supported: true,
frontchannel_logout_session_supported: true,
backchannel_logout_supported: true,
backchannel_logout_session_supported: true,
scopes_supported: 
[
"DemoService",
"offline_access"
],
claims_supported: [ ],
grant_types_supported: 
[
"authorization_code",
"client_credentials",
…

Beispiel: Client

Das Client-Projekt im Beispiel heisst RpcJwtClientCredentialsRolesClient. Die relevanten Parameter in appsettings.json sehen wie folgt aus:

"IdentityServer": {
    "Authority": "https://localhost:5001"
},
"ClientUser": {
    "ClientId": "ClientUser",
    "ClientSecret": "D9B31A4E97C40EC47D758490C801FFCDD39BCE3EF5E9C978E4FF9D34FA0F1967",
    "Audience": "DemoService"
},
"ClientAdmin": {
    "ClientId": "ClientAdmin",
    "ClientSecret": "V4YH2Q8C968XC52S5EFWD7VDX3TBEU9ZNY8EE6GE2F35ATM5XSS3PD6C6PEA9DZM",
    "Audience": "DemoService"
},
"Service": {
    "CustomerId": 1,
    "DelayInterval": 1000,
    "ServiceUrl": "https://localhost:5002"
}

In Worker.cs ist eine Methode ‘GetToken()’ definiert. Als erstes holt sich diese Methode das ‘Discovery’-Dokument beim Identity-Server:

using var identityClient = new HttpClient();
var discover = await identityClient.GetDiscoveryDocumentAsync(_config["IdentityServer:Authority"]).ConfigureAwait(false);
if (discover.IsError)
{
    _logger.LogError(discover.Error);
    return false;
}

Wenn das gelungen ist, extrahiert der Client den Token-Endpoint und sendet einen Request für ein Token ab:

ClientCredentialsTokenRequest tokenRequest = new ClientCredentialsTokenRequest()
{
    Address = discover.TokenEndpoint,
    ClientId = _config["ClientAdmin:ClientId"],
    ClientSecret = _config["ClientAdmin:ClientSecret"],
    Scope = _config["ClientAdmin:Audience"]
};
var tokenResponse = await identityClient.RequestClientCredentialsTokenAsync(tokenRequest).ConfigureAwait(false);
if (tokenResponse.IsError)
{
    _logger.LogError(tokenResponse.Error);
    return false;
}
_logger.LogInformation(tokenResponse.Json.ToString());
_token = tokenResponse.AccessToken;

Das JWT-Token wird gespeichert und sieht für ClientAdmin wie folgt aus:

Noser Engineering AG - gRPC with IdentityServer4 JWT-Token

gRPC with IdentityServer4 JWT-Token

gRPC with IdentityServer4 JWT-TokenDer gRPC-Client wird wie üblich erstellt, nur wird bei jeder Anfrage das Token im Header mitgesendet:

NameMessage nameMessage = new NameMessage
{
    Name = $"{Assembly.GetExecutingAssembly().FullName}"
};
string tokenValue = $"Bearer {_token}";
var metadata = new Metadata()
{
    { "Authorization", tokenValue }
};
CallOptions callOptions = new CallOptions(metadata);
var result = await _client.ExchangeNamesAsync(nameMessage, callOptions);

Beispiel: Ressourcen-Server

Das Client-Projekt im Beispiel heisst RpcJwtClientCredentialsRolesServer. Das Projekt benutzt ebenfalls den Kestrel-Server mit gesicherter Verbindung wie beschrieben in ‘gRPC Tutorial Teil 4: HTTP/2 über HTTPS’.

Die relevanten Parameter in appsettings.json sehen wie folgt aus:

"JwtBearerAuth": {
    "Audience": "DemoService",
    "Authority": "https://localhost:5001"
},
"Certificate": {
    "File": "server.pfx",
    "Password": "P@ssw0rd"
}

Die JwtBearer Authentication wird in Startup.cs wie folgt konfiguriert. Der Code ist genau gleich für gRpc wie für einen REST-Server.

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(cfg =>
        {
            cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; // "Bearer"
            cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(options =>
        {
            options.Authority = Configuration["JwtBearerAuth:Authority"];
            options.Audience = Configuration["JwtBearerAuth:Audience"];
            options.Events = new JwtBearerEvents()
            {
                OnAuthenticationFailed = c =>
                {
                    _logger.LogDebug($"Exception: {c.Exception}");
                    return Task.CompletedTask;
                }
            };
        });
    services.AddAuthorization();
    services.AddControllers();
    services.AddGrpc(opt => { opt.EnableDetailedErrors = true; });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
    _logger = loggerFactory.CreateLogger("JwtBearerAuthentication");
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGrpcService<DemoServiceRpc>();
        endpoints.MapControllers();
    });
}

Nun kann man wie üblich die Methoden mit dem Authorize-Attribute schützen.

public class DemoServiceRpc : DemoService.DemoServiceBase
{
    [Authorize(Roles = "User,Admin")]
    public override Task<NameMessage> ExchangeNames(NameMessage request, ServerCallContext context)
    {
        NameMessage result = new NameMessage
        {
            Name = "Hello from Server"
        };
        return Task.FromResult(result);
    }

    [Authorize(Roles = "Admin")]
    public override Task<ResultMessage> ChangeSettings(SettingsMessage request, ServerCallContext context)
    {
        ResultMessage result = new ResultMessage()
        {
            Success = true
        };
        return Task.FromResult(result);
    }
}

Wenn der Client ein JWT-Token für ClientUser gefragt hat und damit auf die Methode ‘ChangeSettings()’ zugreift, dann gibt es folgende Ausnahme:

Noser Engineering AG - gRPC permission denied

gRPC permission denied

Fazit

Dieser Blog hat gezeigt, wie Client Credential Flow mit asymmetrischer Verschlüsselung, Rollen und IdentityServer4 verwendet werden kann. Der IdentityServer4 implementiert ebenfalls die kompliziertere OAuth2 Flows, welche ausserhalb den Scope dieser Blog-Serie fallen.

gRPC bietet noch ein paar weitere fortgeschrittenen Funktionen wie Server Side Reflection, um die Schnittstelle des Servers abzufragen (WSDL wird nicht unterstützt) oder Custom Attributes, welche sowieso schon in der C#-Sprache existieren. Bis jetzt ist es den Autor nicht gelungen, einen Client zu implementieren, welcher erfolgreich die gesamte Schnittstelle mit Server Side Reflection abfragen konnte. Der gRPC-Community Support hat bis jetzt keine Lösungen für diese Probleme gebracht.

Damit endet diese Blog-Serie. Meine persönliche Schlussfolgerung ist, dass gRPC WCF nicht vollständig ersetzt (keine Named-Pipes, Server Notifications nur über Streams möglich) und bin enttäuscht, dass Microsoft WCF in .NET Core nicht mehr weiterführt.

← Voriger Post
1234Next ›Last »

Tag Cloud

.NET android Angular AngularJs app Arduino ASP.Net automated testing Azure Big Data C# C++ Cloud continuous integration Elm Embedded gRPC Internet of Things IoT Java Javascript M2M OWASP Projektmanagement protobuf Python Raspberry Pi Reactive Programming REST Scrum Security Softwarequalität SPA Testen testing Testmanagement Teststrategie Visual Studio WebAPI windows WPF Xamarin Xamarin.Android Xamarin.Forms Xamarin.iOS

Archive

Current Posts

  • Virtual pty/tty uart listener: Pitfalls on linux socat
  • Akzente setzen mit der Android Splash Screen API unter .NET MAUI
  • Do You have Your Personal Space?
  • Automated provisioning with ARM Templates
  • Asynchrone Beobachtungen und Versprechungen in Angular

Last Comments

  • Hans Reinsch bei Der Safety-Plan: Die wichtigsten Antworten mit Checkliste
  • George H. Barbehenn bei Modeling Optocouplers with Spice
  • Noser Blog Touch-Actions in Xamarin.Forms - Noser Blog bei Mach mehr aus Animationen in Xamarin.Forms mit SkiaSharp
  • Noser Blog Focus on the Secure Storage service of Trusted Firmware (TFM) - Noser Blog bei First run of the Trusted Firmware (TFM) application
  • Noser Blog First run of the Trusted Firmware (TFM) application - Noser Blog bei Focus on the Secure Storage service of Trusted Firmware (TFM)

Popular Posts

Xamarin.Android Code Obfuscation

6 Comments

ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 1 - Aufbruch

5 Comments

ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 2 - Kein Picknick

4 Comments

Contact us

  1. Name *
    * Please enter your name
  2. Email *
    * Please enter a valid email address
  3. Message *
    * Please enter message
© 2013 NOSER ENGINEERING AG. All rights reserved. Datenschutz | Cookie-Richtlinie