Einführung
Eines meiner unterstützten Projekte stand kürzlich vor der Aufgabe, die Möglichkeit einer Migration von .NET Framework 4.5 auf .Net Core zu analysieren, falls eine große Menge akkumulierter technischer Schulden umgestaltet und aufgebraucht werden muss. Die Wahl fiel auf die Zielplattform .NET Core 3.0, da laut den Entwicklern von Microsoft mit der Veröffentlichung von Version 3.0 die erforderlichen Schritte für die Migration von Legacy-Code um ein Vielfaches verringert werden. Besonders die Exit-Pläne für EntityFramework 6.3 für .Net Core, d.h. Der größte Teil des auf EF 6.2 basierenden Codes kann in einem migrierten Projekt auf Net Core unverändert gelassen werden.
Bei der Datenebene wurde anscheinend klar, dass ein weiterer wichtiger Teil der Code-Portierung die Sicherheitsstufe war, die Sie nach einer schnellen Prüfung leider fast vollständig wegwerfen und von Grund auf neu schreiben müssen. Glücklicherweise verwendete das Projekt bereits einen Teil von ASP NET Identity in Form der Aufbewahrung von Benutzern und anderen seitlich angebrachten „Fahrrädern“.
Dies wirft die logische Frage auf: Wenn der Sicherheitsteil viele Änderungen vornehmen muss, warum nicht sofort die in Form von Industriestandards empfohlenen Ansätze implementieren, nämlich: Bringen Sie die Anwendung dazu, Open Id Connect und OAuth über das IdentityServer4- Framework zu verwenden.
Probleme und Lösungen
Wir haben also Folgendes erhalten: Es gibt eine JavaScript-Anwendung in Angular (Client in IS4-Begriffen), sie verwendet eine bestimmte Teilmenge von WebAPI (Ressourcen), es gibt auch eine Datenbank mit veralteter ASP NET-Identität mit Benutzeranmeldungen, die nach dem Update wiederverwendet werden müssen (um nicht alle anderen zu starten) In einigen Fällen muss die Möglichkeit bestehen, sich über die Windows-Authentifizierung auf der Seite von IdentityServer4 beim System anzumelden. Das heißt, Es gibt Zeiten, in denen Benutzer über ein lokales Netzwerk in einer ActiveDirectory-Domäne arbeiten.
Die Hauptlösung für die Migration von Benutzerdaten besteht darin, manuell (oder mithilfe automatisierter Tools) ein Migrationsskript zwischen dem alten und dem neuen Identitätsdatenschema zu schreiben. Wir haben wiederum die Anwendung zum automatischen Vergleich von Datenschemata verwendet und ein SQL-Skript generiert. Abhängig von der Identitätsversion enthält das Zielmigrationsskript unterschiedliche Anweisungen zum Aktualisieren. Die Hauptsache hier ist, nicht zu vergessen, die EFMigrationsHistory-Tabelle zu koordinieren, wenn EF zuvor verwendet wurde und es in Zukunft geplant ist, beispielsweise die IdentityUser-Entität auf zusätzliche Felder zu erweitern.
Im Folgenden wird jedoch beschrieben, wie Sie IdentityServer4 richtig konfigurieren und zusammen mit Windows-Konten konfigurieren.
Umsetzungsplan
Aus NDA-Gründen werde ich nicht beschreiben, wie wir IS4 in unserem Projekt implementiert haben. In diesem Artikel zeige ich Ihnen jedoch auf einer einfachen ASP.NET Core-Site, die von Grund auf neu erstellt wurde, welche Schritte Sie ausführen müssen, um eine vollständig konfigurierte und funktionsfähige Anwendung zu erhalten das IdentityServer4 für Autorisierungs- und Authentifizierungszwecke verwendet.
Um das gewünschte Verhalten zu realisieren, müssen wir die folgenden Schritte ausführen:
- Erstellen Sie ein leeres ASP.Net Core-Projekt und konfigurieren Sie die Verwendung von IdentityServer4.
- Fügen Sie einen Client als Angular-Anwendung hinzu.
- Melden Sie sich über Open-ID-Connect Google an
- Windows-Authentifizierungsoption hinzufügen
Aus Gründen der Kürze befinden sich alle drei Komponenten (IdentityServer, WebAPI, Angular Client) im selben Projekt. Die ausgewählte Art der Interaktion zwischen dem Client und IdentityServer (GrantType) ist der implizite Fluss, wenn das access_token im Browser an die Anwendungsseite übergeben und dann für die Interaktion mit der WebAPI verwendet wird. Nach den Änderungen im ASP.NET Core-Repository wird der implizite Datenfluss durch den Autorisierungscode + PKCE ersetzt.)
Beim Erstellen und Ändern der Anwendung wird die .NET Core-Befehlszeilenschnittstelle häufig verwendet. Sie muss auf dem System an der Stelle mit der neuesten Version von Preview Core 3.0 installiert sein (zum Zeitpunkt des Schreibens von Artikel 3.0.100-Preview7-012821).
Erstellung und Konfiguration eines Webprojekts
Die Veröffentlichung von IdentityServer Version 4 war durch das vollständige Ausschneiden der Benutzeroberfläche aus diesem Framework gekennzeichnet. Jetzt haben Entwickler das volle Recht, die Hauptschnittstelle des Autorisierungsservers selbst zu bestimmen. Es gibt verschiedene Möglichkeiten. Eine der beliebtesten ist die Verwendung der Benutzeroberfläche aus dem QuickStart-Benutzeroberflächenpaket, das sich im offiziellen Github- Repository befindet.
Eine andere, nicht weniger bequeme Möglichkeit ist die Integration in die Benutzeroberfläche von ASP NET Core Identity. In diesem Fall muss der Entwickler die entsprechende Middleware im Projekt korrekt konfigurieren. Diese Methode wird später beschrieben.
Beginnen wir mit der Erstellung eines einfachen Webprojekts. Führen Sie dazu die folgende Anweisung in der Befehlszeile aus:
dotnet new webapp -n IdentityServer4WebApp
Nach der Ausführung wird die Ausgabe ein Webanwendungsframework sein, das schrittweise in den von uns benötigten Zustand gebracht wird. Hier müssen Sie reservieren, dass .Net Core 3.0 for Identity im Gegensatz zur schwergewichtigen MVC leichtere RazorPages verwendet.
Jetzt müssen Sie unserem Projekt IdentityServer-Unterstützung hinzufügen. Installieren Sie dazu die erforderlichen Pakete:
dotnet add package Microsoft.AspNetCore.ApiAuthorization.IdentityServer -v 3.0.0-preview7.19365.7 dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore -v 3.0.0-preview7.19365.7 dotnet add package Microsoft.EntityFrameworkCore.Tools -v 3.0.0-preview7.19362.6 dotnet add package Microsoft.EntityFrameworkCore.Sqlite -v 3.0.0-preview7.19362.6
Zusätzlich zu Links zu Autorisierungsserverpaketen haben wir hier die Entity Framework-Unterstützung zum Speichern von Benutzerinformationen im Identity-Ökosystem hinzugefügt. Der Einfachheit halber verwenden wir die SQLite-Datenbank.
Um die Datenbank zu initialisieren, erstellen Sie unser Benutzermodell und unseren Datenbankkontext. Dazu deklarieren wir zwei ApplicationUser-Klassen, die von IdentityUser im Ordner Models und ApplicationDbContext geerbt wurden, geerbt von: ApiAuthorizationDbContext im Ordner Data.
Als Nächstes müssen Sie die Verwendung des EntityFramework-Kontexts konfigurieren und die Datenbank erstellen. Dazu schreiben wir den Kontext in die ConfigureServices- Methode der Startup-Klasse:
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options =>options.UseSqlite(Configuration.GetConnectionString("DefaultConnection"))); services.AddRazorPages(); }
Fügen Sie die Verbindungszeichenfolge zu appsettings.json hinzu
"ConnectionStrings": { "DefaultConnection": "Data Source=data.db" },
Jetzt können Sie die anfängliche Migration erstellen und das Datenbankschema initialisieren. Es ist zu beachten, dass das installierte Tool für ef core erforderlich ist (für die betreffende Vorschau wird Version 3.0.0-Preview7.19362.6 benötigt).
dotnet ef migrations add Init dotnet ef database update
Wenn alle vorherigen Schritte fehlerfrei ausgeführt wurden, sollte die SQLite-Datendatei data.db in Ihrem Projekt angezeigt werden.
In dieser Phase können wir die vollwertige Fähigkeit zur Verwendung von Asp.Net Core Identity vollständig konfigurieren und testen. Nehmen Sie dazu Änderungen an den Startmethoden vor . Configure and Startup.ConfigureServices .
Mit diesen Zeilen integrieren wir die Möglichkeit der Authentifizierung und Autorisierung in die Anforderungsverarbeitungspipeline. Fügen Sie außerdem die Standardbenutzeroberfläche für Identität hinzu.
Es bleibt nur, um die Benutzeroberfläche zu reparieren und den Seiten \ Shared eine neue Razor-Ansicht mit dem Namen _LoginPartial.cshtml und den folgenden Inhalten hinzuzufügen :
@using IdentityServer4WebApp.Models @using Microsoft.AspNetCore.Identity @inject SignInManager<ApplicationUser> SignInManager @inject UserManager<ApplicationUser> UserManager <ul class="navbar-nav"> @if (SignInManager.IsSignedIn(User)) { <li class="nav-item"> <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a> </li> <li class="nav-item"> <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/"> <button type="submit" class="nav-link btn btn-link text-dark">Logout</button> </form> </li> } else { <li class="nav-item"> <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a> </li> } </ul>
Der obige Präsentationscode sollte Links zum Identitätsschnittstellenbereich mit integrierten Benutzersteuerelementen im Navigationsbereich hinzufügen (Login und Passwort, Registrierung usw.).
Um das Rendern neuer Menüelemente zu erreichen, ändern wir einfach die Datei _Layout.cshtml , indem wir das Rendern dieser Teilansicht hinzufügen.
<ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a> </li> </ul> </div> <partial name="_LoginPartial" /> <!––––> </div>
Und jetzt versuchen wir, unsere Anwendung auszuführen und auf die Links zu klicken, die im Kopf erscheinen
Menü sollte der Benutzer eine Seite mit einer Begrüßung und Anfrage sehen
Login und Passwort eingeben. In diesem Fall können Sie sich registrieren und anmelden - alles
sollte funktionieren.

Die Entwickler von IdentityServer4 haben die Integration von ASP.NET Identity und dem Server-Framework selbst hervorragend verbessert. Um die Möglichkeit zur Verwendung von OAuth2-Token hinzuzufügen, müssen Sie unser Projekt durch einige neue Anweisungen im Code ergänzen.
Fügen Sie in der vorletzten Zeile der Startup.ConfigureServices- Methode die Konfiguration von IS4-Konventionen über ASP.NET Core Identity hinzu:
services.AddIdentityServer() .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
Die AddApiAuthorization-Methode weist das Framework an, eine bestimmte unterstützte Konfiguration zu verwenden, hauptsächlich über die Datei appsettings.json. Derzeit sind die integrierten IS4-Verwaltungsfunktionen nicht so flexibel und sollten als Ausgangspunkt für die Erstellung Ihrer Anwendungen angesehen werden. In jedem Fall können Sie die überladene Version dieser Methode verwenden und die Parameter durch Rückruf detaillierter konfigurieren.
Als Nächstes rufen wir die Hilfsmethode auf, mit der die Anwendung so konfiguriert wird, dass die vom Framework ausgegebenen JWT-Token überprüft werden.
services.AddAuthentication() .AddIdentityServerJwt();
Fügen Sie schließlich in der Startup.Configure- Methode Middleware für hinzu
Bereitstellen von Open ID Connect-Endpunkten
app.UseAuthentication(); app.UseAuthorization(); app.UseIdentityServer();
Wie oben erwähnt, lesen die verwendeten Hilfsmethoden die Konfiguration in
Anwendungseinstellungsdatei appsettings.json , in der wir eine neue hinzufügen müssen
Abschnitt IdentityServer .
"IdentityServer": { "Clients": { "TestIdentityAngular": { "Profile": "IdentityServerSPA" } } }
In diesem Abschnitt wird ein Client mit dem Namen TestIdentityAngular definiert, den wir dem zukünftigen Browser-Client und einem bestimmten Konfigurationsprofil zuweisen.
Application Profiles ist ein neues IdentityServer-Konfigurationstool, das mehrere vordefinierte Konfigurationen mit der Möglichkeit bietet, bestimmte Parameter zu verfeinern. Wir werden das IdentityServerSPA- Profil verwenden, das für Fälle entwickelt wurde, in denen sich der Browser-Client und das Framework im selben Projekt befinden und die folgenden Parameter haben:
- Die Ressource redirect_uri ist auf / authentication / login-callback festgelegt .
- Ressource post_logout_redirect_uri, gesetzt auf / authentication / logout-callback.
- Der Bereichssatz enthält openid, profile für jede Anwendungs-API-Ressource.
- Satz zulässiger OIDC-Antworttypen - id_token-Token
- GrantType für den Client - Implizit
Andere mögliche Profile sind SPA (Anwendung ohne IS4), IdentityServerJwt (mit IS4 gemeinsam genutzte API ), API (separate API).
Darüber hinaus registriert die Konfiguration Ressourcen:
- ApiResources: Eine API-Ressource mit dem Namen << appname >> API mit Eigenschaften für alle Clients (*).
- IdentityServerResources: IdentityResources.OpenId () und IdentityResources.Profile ()
Wie Sie wissen, verwendet IdentityServer Zertifikate zum Signieren von Token. Ihre Parameter können auch in der Konfigurationsdatei festgelegt werden, sodass wir sie zum Zeitpunkt des Tests verwenden können
x509-Testzertifikat, dazu müssen Sie es im Abschnitt "Schlüssel" der Datei appsettings.Development.json angeben .
"IdentityServer": { "Key": { "Type": "Development" } }
Jetzt können wir sagen, dass das Backend, mit dem Sie IdentityServer verwenden können, bereit ist und Sie mit der Implementierung der Browseranwendung beginnen können.
Angular Client-Implementierung
Unser browserbasiertes SPA wird auf der Angular-Plattform geschrieben. Die Anwendung enthält zwei Seiten, eine für nicht autorisierte Benutzer und eine für authentifizierte Benutzer. Die Beispiele verwenden Version 8.1.2
Erstellen Sie zunächst den zukünftigen Rahmen:
ng new ClientApp
Beim Erstellen müssen Sie den Vorschlag zur Verwendung des Routings mit "Ja" beantworten. Und ein wenig stilisieren Sie die Seite durch die Bootstrap-Bibliothek:
cd ClientApp ng add bootstrap
Als Nächstes müssen Sie unserer Hauptanwendung SPA-Hosting-Unterstützung hinzufügen. Zuerst müssen Sie das csproj-Projekt reparieren - fügen Sie Informationen zu unserer Browseranwendung hinzu.
<PropertyGroup> <TargetFramework>netcoreapp3.0</TargetFramework> <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked> <TypeScriptToolsVersion>Latest</TypeScriptToolsVersion> <IsPackable>false</IsPackable> <SpaRoot>ClientApp\</SpaRoot> <DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes> <BuildServerSideRenderer>false</BuildServerSideRenderer> </PropertyGroup> … <ItemGroup> <Content Remove="$(SpaRoot)**" /> <None Remove="$(SpaRoot)**" /> <None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" /> </ItemGroup> <Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') "> <Exec Command="node --version" ContinueOnError="true"> <Output TaskParameter="ExitCode" PropertyName="ErrorCode" /> </Exec> <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." /> <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." /> <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> </Target>
Installieren Sie anschließend das spezielle Nuget-Paket, um Browseranwendungen zu unterstützen.
dotnet add package Microsoft.AspNetCore.SpaServices.Extensions -v 3.0.0-preview7.19365.7
Und wir verwenden seine Hilfsmethoden:
Zusätzlich zum Aufrufen neuer Methoden müssen Sie die Seiten Razor Index.chtml und _ViewStart.chtml löschen, damit die SPA-Dienste jetzt den Inhalt bereitstellen.
Wenn alles gemäß den Anweisungen ausgeführt wurde, wird beim Start der Anwendung die Standardseite auf dem Bildschirm angezeigt.

Jetzt müssen Sie das Routing konfigurieren, dazu fügen wir dem Projekt 2 hinzu
Komponente:
ng generate component Home -t=true -s=true --skipTests=true ng generate component Data -t=true -s=true --skipTests=true
Wir schreiben sie in die Routing-Tabelle:
const routes: Routes = [ { path: '', component: HomeComponent, pathMatch: 'full' }, { path: 'data', component: DataComponent } ];
Und wir ändern die Datei app.component.html, um die Menüelemente korrekt anzuzeigen.
<header> <nav class='navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3'> <div class="container"> <a class="navbar-brand" [routerLink]='["/"]'>Client App</a> <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse" [ngClass]='{"show": isExpanded}'> <ul class="navbar-nav flex-grow"> <li class="nav-item" [routerLinkActive]='["link-active"]' [routerLinkActiveOptions]='{ exact: true }'> <a class="nav-link text-dark" [routerLink]='["/"]'>Home</a> </li> <li class="nav-item" [routerLinkActive]='["link-active"]'> <a class="nav-link text-dark" [routerLink]='["/data"]'>Web api data</a> </li> </ul> </div> </div> </nav> </header> <div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> </div> <div class="router-outlet"> <router-outlet></router-outlet> </div>
In diesem Schritt können Sie die grundlegende Vorbereitung des Anwendungsframeworks für die Implementierung der Interaktion über von IdentityServer ausgegebene Token abschließen.
Die aktuelle Phase der Vorbereitung des Frameworks unseres SPA kann als abgeschlossen bezeichnet werden. Jetzt sollten wir mit der Implementierung des Moduls beginnen, das für die Interaktion mit dem Serverteil mithilfe der OpenID Connect- und OAuth-Protokolle verantwortlich ist. Glücklicherweise haben die Entwickler von Microsoft diesen Code bereits implementiert, und jetzt können Sie dieses Modul einfach von ihnen ausleihen. Da mein Artikel auf ASP.NET Core 3.0 Pre-Release 7 basiert, nehmen wir den gesamten Code mit dem Release-Tag „v3.0.0-Preview7.19365.7“ auf Github .
Vor dem Importieren des Codes müssen Sie die oidc-client- Bibliothek installieren
bietet viele Schnittstellen für Browser-Anwendungen sowie
unterstützt die Verwaltung von Benutzersitzungen und Zugriffstoken. Für
Um damit arbeiten zu können, müssen Sie das entsprechende Paket installieren.
npm install oidc-client@1.8.0
Jetzt ist es in unserem SPA erforderlich, ein Modul zu implementieren, das die vollständige Interaktion gemäß den erforderlichen Protokollen kapselt. Dazu müssen Sie das gesamte ApiAuthorizationModule-Modul aus der obigen ASP.NET Core-Repository-Bezeichnung entnehmen und alle zugehörigen Dateien zur Anwendung hinzufügen.
Außerdem müssen Sie es in das Hauptmodul der AppModule-Anwendung importieren:
@NgModule({ declarations: [ AppComponent, HomeComponent, DataComponent ], imports: [ BrowserModule, HttpClientModule, ApiAuthorizationModule,//<- AppRoutingModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Um neue Menüelemente im importierten Modul anzuzeigen, gibt es eine App-Login-Menükomponente :
es kann komplett geändert werden, um Ihren Bedürfnissen zu entsprechen und hinzuzufügen
einen Link dazu im Navigationsbereich der Ansicht app.component.html .
Das API-Autorisierungsmodul zum Konfigurieren der OpenID-Verbindung des SPA-Clients muss für die Implementierung einen speziellen Endpunkt im Anwendungs-Backend verwenden
muss diese Schritte befolgen:
- Korrigieren Sie die Client-ID gemäß den Angaben in der Konfigurationsdatei appsettings.json im Abschnitt IdentityServer: Clients. In unserem Fall handelt es sich um TestIdentityAngular. Sie wird in die erste Zeile des Konstantensatzes api-authorisation.constants.ts geschrieben.
- Fügen Sie einen OidcConfigurationController-Controller hinzu, der die Konfiguration direkt an die Browseranwendung zurückgibt
Der Code des erstellten Controllers wird unten dargestellt:
[ApiController] public class OidcConfigurationController: ControllerBase { private readonly IClientRequestParametersProvider _clientRequestParametersProvider; public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider) { _clientRequestParametersProvider = clientRequestParametersProvider; } [HttpGet("_configuration/{clientId}")] public IActionResult GetClientRequestParameters([FromRoute]string clientId) { var parameters = _clientRequestParametersProvider.GetClientParameters(HttpContext, clientId); return Ok(parameters); } }
Sie müssen auch die Punkt-API-Unterstützung für die Backend-Anwendung konfigurieren.
Jetzt ist es Zeit, die Anwendung zu starten. Auf der Hauptseite im oberen Menü sollten zwei Elemente angezeigt werden: Anmelden und Registrieren . Außerdem fordert das importierte Autorisierungsmodul beim Start von der Serverseite die Client-Konfiguration an, die anschließend im Protokoll berücksichtigt wird. Eine beispielhafte Konfigurationsausgabe ist unten dargestellt:
{ "authority": "https://localhost:44367", "client_id": "TestIdentityAngular", "redirect_uri": "https://localhost:44367/authentication/login-callback", "post_logout_redirect_uri": "https://localhost:44367/authentication/logout-callback", "response_type": "id_token token", "scope": "IdentityServer4WebAppAPI openid profile" }
Wie Sie sehen können, erwartet der Client während der Interaktion, dass er das ID-Token und das Zugriffstoken erhält, und es ist auch für den Zugriffsbereich auf unsere API konfiguriert.
Wenn wir nun den Menüpunkt Anmelden auswählen, sollten wir zur Seite unseres IdentityServers4 weitergeleitet werden. Hier können wir den Login und das Passwort eingeben. Wenn diese korrekt sind, werden wir sofort zurück zur Browseranwendung weitergeleitet, die wiederum id_token und access_token erhält . Wie Sie unten sehen können, hat die App-Login-Menü-Komponente selbst festgestellt, dass die Autorisierung erfolgreich abgeschlossen wurde, und eine „Begrüßung“ sowie eine Schaltfläche zum Abmelden angezeigt.

Wenn Sie die "Entwicklertools" im Browser öffnen, können Sie im Hintergrund die gesamte Interaktion mithilfe des OIDC / OAuth-Protokolls anzeigen. Dadurch werden Informationen zum Autorisierungsserver abgerufen
über den Endpunkt .well-bekannte / openid-Konfiguration und Pooling-Sitzungsaktivität über den Connect / Checksession-Zugangspunkt. Darüber hinaus ist das Autorisierungsmodul für den Mechanismus "Stille Aktualisierung von Token" konfiguriert. Wenn das Zugriffstoken abläuft, übergibt das System die Autorisierungsschritte unabhängig in einem versteckten Iframe. Sie können die automatische Tokenaktualisierung deaktivieren, indem Sie den Wert des Parameters includeIdTokenInSilentRenew in der Datei authorize.service.ts auf "false" setzen.
Jetzt können Sie den Zugriff auf nicht autorisierte Benutzer über die Komponenten der SPA-Anwendung sowie einige API-Controller auf der Rückseite einschränken. Um einige APIs zu demonstrieren, erstellen wir eine ExchangeRateItem- Klasse im Ordner "Models" sowie einen Controller im Ordner "Controller", der einige zufällige Daten zurückgibt.
Erstellen Sie als Nächstes auf der Front-End-Seite eine neue Komponente, die empfangen wird
und Daten zu Wechselkursen des gerade erstellten Controllers anzeigen.
ng generate component ExchangeRate -t=true -s=true --skipTests=true
Der Inhalt der Komponente sollte folgendermaßen aussehen:
Code import { Component, OnInit, Input } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, of, Subject } from 'rxjs'; import { catchError } from 'rxjs/operators'; @Component({ selector: 'app-exchange-rate', template: ` <div class="alert alert-danger" *ngIf="errorMessage | async as msg"> {{msg}} </div> <table class='table table-striped'> <thead> <tr> <th>From currency</th> <th>To currency</th> <th>Rate</th> </tr> </thead> <tbody> <tr *ngFor="let rate of rates | async"> <td>{{ rate.fromCurrency }} </td> <td>{{ rate.toCurrency }}</td> <td>{{ rate.value }}</td> </tr> </tbody> </table> `, styles: [] }) export class ExchangeRateComponent implements OnInit { public rates: Observable<ExchangeRateItem[]>; public errorMessage: Subject<string>; @Input() public apiUrl: string; constructor(private http: HttpClient) { this.errorMessage = new Subject<string>(); } ngOnInit() { this.rates = this.http.get<ExchangeRateItem[]>("/api/"+this.apiUrl).pipe(catchError(this.handleError(this.errorMessage)) ); } private handleError(subject: Subject<string>): (te:any) => Observable<ExchangeRateItem[]> { return (error) => { let message = ''; if (error.error instanceof ErrorEvent) { message = `Error: ${error.error.message}`; } else { message = `Error Code: ${error.status}\nMessage: ${error.message}`; } subject.next(message); let emptyResult: ExchangeRateItem[] = []; return of(emptyResult); } } } interface ExchangeRateItem { fromCurrency: string; toCurrency: string; value: number; }
Jetzt muss es auf der App-Datenseite einfach in der Vorlage verwendet werden, indem die Zeile <app-exchange-rate apiUrl = "Rates"> </ app-EXchange-Rate> geschrieben wird, und Sie können das Projekt erneut starten. Wenn wir entlang des Zielpfads navigieren, sehen wir, dass die Komponente die Daten empfangen und in Form einer Tabelle angezeigt hat.
Als Nächstes werden wir versuchen, eine Anforderung zur Autorisierung des Zugriffs auf die API des Controllers hinzuzufügen. Fügen Sie dazu das Attribut [Authorize] in der ExchangeRateController- Klasse hinzu und führen Sie SPA erneut aus. Nachdem wir jedoch zu der Komponente zurückgekehrt sind, die unsere API aufruft, wird ein Fehler angezeigt Autorisierungsheader.

Sie können ausgehenden Anforderungen ein Autorisierungstoken korrekt hinzufügen
Aktivieren Sie den Angular Interceptors Interceptor-Mechanismus. Glücklicherweise enthält das importierte Modul bereits den erforderlichen Typ, wir müssen ihn nur im Basismodul der Anwendung registrieren.
providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthorizeInterceptor, multi: true } ],
Nach diesen Schritten sollte alles richtig funktionieren. Wenn Sie sich die Entwicklertools noch einmal ansehen, wird im Browser der neue Berechtigungsheader für Bearer access_token angezeigt. Im Backend wird dieses Token von IdentityServer überprüft und es wird auch die Berechtigung zum Aufrufen eines sicheren API-Punkts erteilt.
Am Ende des Beispiels für die Integration mit dem Autorisierungsserver können Sie den Aktivierungsschutz mit den Wechselkursdaten im SPA auf die Route setzen. Dadurch wird verhindert, dass Benutzer zur Seite wechseln, wenn sie derzeit nicht autorisiert sind. Dieser Protektor wird auch im zuvor importierten Modul vorgestellt. Sie müssen ihn nur an die Zielroute hängen.
{ path: 'data', component: DataComponent, canActivate: [AuthorizeGuard] }
Wenn sich der Benutzer nicht bei der Anwendung angemeldet und einen Link zu unserer geschützten Komponente ausgewählt hat, wird er sofort mit der Aufforderung zur Eingabe eines Logins und eines Passworts auf die Autorisierungsseite weitergeleitet. Der resultierende Code ist auf github verfügbar.
Verbinden eines externen Logins über einen Google-Anbieter
Es gibt ein separates Microsoft.AspNetCore.Authentication.Google Nuget-Paket für die Verbindung der Anmeldung über Google-Konten für ASP.NET Core 1.1 / 2.0 +. Aufgrund von Änderungen in den Richtlinien des Unternehmens selbst hat Microsoft jedoch Pläne für ASP.NET Core 3.0+ erkenne es als veraltet . Und jetzt wird empfohlen, eine Verbindung über die Hilfsmethoden OpenIdConnectExtensions und AddOpenIdConnect herzustellen , die wir in diesem Artikel verwenden werden.
Installieren Sie die OpenIdConnect-Erweiterung:
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect -v 3.0.0-preview7.19365.7
Um zu beginnen, müssen wir zwei Schlüsselwerte von Google abrufen - Id Client und Client Secret. Dazu wird vorgeschlagen, die folgenden Schritte auszuführen:
Secret Manager . , .
dotnet user-secrets init dotnet user-secrets set "Authentication:Google:ClientId" " ClientID" dotnet user-secrets set "Authentication:Google:ClientSecret" " ClientSecret"
Google.
services.AddAuthentication() .AddOpenIdConnect("Google", "Google", o => { IConfigurationSection googleAuthNSection = Configuration.GetSection("Authentication:Google"); o.ClientId = googleAuthNSection["ClientId"]; o.ClientSecret = googleAuthNSection["ClientSecret"]; o.Authority = "https://accounts.google.com"; o.ResponseType = OpenIdConnectResponseType.Code; o.CallbackPath = "/signin-google"; }) .AddIdentityServerJwt();
,
Google. , , SPA .

, , OAuth , . Nuget .
Windows
, SPA Microsoft, ActiveDirectory. , Html ASP.NET, WebForms .., Windows WindowsIdentity, , . Identity Server, Windows, claims id_token access_token . , IS4 , ,
github . , ASP.NET Core Identity 3.0.
Identity, Razor Login ExternalLogin ( CLI aspnet-codegenerator ):
dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design dotnet aspnet-codegenerator identity -dc IdentityServer4WebApp.Data.ApplicationDbContext --files "Account.Login;Account.ExternalLogin"
, Area Identity , .
, . , Identity I AuthenticationSchemeProvider. GetAllSchemesAsync() DisplayName != null, Windows DisplayName = null. LoginModel OnGetAsync :
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
private readonly AuthenticationSchemeProvider _schemeProvider . View Login.cshtml :
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> << >> <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @(provider.DisplayName ??provider.Name) account">@(provider.DisplayName ??provider.Name)</button>
, windows launchSettings.json
( IIS, web.config ).
"iisSettings": { "windowsAuthentication": true, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:15479", "sslPort": 44301 } },
«Windows» .

SPA IdentityServer . «» [AllowAnonymous] LoginModel [Authorize(AuthenticationSchemes = "Windows")] , , WindowsIdentity.
ExternalLogin , Identity Windows . ProcessWindowsLoginAsync .
private async Task<IActionResult> ProcessWindowsLoginAsync(string returnUrl) { var result = await HttpContext.AuthenticateAsync(IISDefaults.AuthenticationScheme); if (result?.Principal is WindowsPrincipal wp) { var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl }); var props = _signInManager.ConfigureExternalAuthenticationProperties(IISDefaults.AuthenticationScheme, redirectUrl); props.Items["scheme"] = IISDefaults.AuthenticationScheme; var id = new ClaimsIdentity(IISDefaults.AuthenticationScheme); id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name)); id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name)); id.AddClaim(new Claim(ClaimTypes.NameIdentifier, wp.Identity.Name)); var wi = wp.Identity as WindowsIdentity; var groups = wi.Groups.Translate(typeof(NTAccount)); var hasUsersGroup = groups.Any(i => i.Value.Contains(@"BUILTIN\Users", StringComparison.OrdinalIgnoreCase)); id.AddClaim(new Claim("hasUsersGroup", hasUsersGroup.ToString())); await HttpContext.SignInAsync(IdentityConstants.ExternalScheme, new ClaimsPrincipal(id), props); return Redirect(props.RedirectUri); } return Challenge(IISDefaults.AuthenticationScheme); }
, .
ExternalLoginModel.OnPost :
if (IISDefaults.AuthenticationScheme == provider) { return await ProcessWindowsLoginAsync(returnUrl); }
Claim Windows Claim «hasUsersGroup», ID access, . ASP.NET Identity UserClaims. ExternalLoginModel .
private async Task UpdateClaims(ExternalLoginInfo info, ApplicationUser user, params string[] claimTypes) { if (claimTypes == null) { return; } var claimTypesHash = new HashSet<string>(claimTypes); var claims = (await _userManager.GetClaimsAsync(user)).Where(c => claimTypesHash.Contains(c.Type)).ToList(); await _userManager.RemoveClaimsAsync(user, claims); foreach (var claimType in claimTypes) { if (info.Principal.HasClaim(c => c.Type == claimType)) { claims = info.Principal.FindAll(claimType).ToList(); await _userManager.AddClaimsAsync(user, claims); } } }
OnPostConfirmationAsync (
).
result = await _userManager.AddLoginAsync(user, info); if (result.Succeeded) { await _signInManager.SignInAsync(user, isPersistent: false); _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider); await UpdateClaims(info, user, "hasUsersGroup");
OnGetCallbackAsync , .
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor : true); if (result.Succeeded) { var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey); await UpdateClaims(info, user, "hasUsersGroup");
, WebAPI
«hasUsersGroup». «ShouldHasUsersGroup»
services.AddAuthorization(options => { options.AddPolicy("ShouldHasUsersGroup", policy => { policy.RequireClaim("hasUsersGroup");}); });
ExchangeRateController
Policy.
[Authorize(Policy = "ShouldHasUsersGroup")] [HttpGet("api/internalrates")] public IEnumerable<ExchangeRateItem> GetInternalRates() { return Get().Select(i=>{i.Value=Math.Round(i.Value-0.02,2);return i;}); }
view .
ng generate component InternalData -t=true -s=true --skipTests=true
template .
//internal-data.component.ts: template: `<app-exchange-rate apiUrl="internalrates"></app-exchange-rate> `, //app-routing.module.ts: { path: ' internaldata', component: InternalDataComponent, canActivate: [AuthorizeGuard] } //app.component.html: <li class="nav-item" [routerLinkActive]='["link-active"]'> <a class="nav-link text-dark" [routerLink]='["/internaldata"]'>Internal api data</a> </li>
, , . , accsee_token
claim hasUsersGroup ,
ApiResources . , , appsettings.json , Startup. ConfigureServices .
services.AddIdentityServer() .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => { var apiResource = options.ApiResources.First(); apiResource.UserClaims = new[] { "hasUsersGroup" }; });
, , windows , .
, – Guard claim «hasUsersGroup» « ». Guard :
ng generate guard AuthorizeWindowsGroupGuard --skipTests=true
:
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { map,tap} from 'rxjs/operators'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router'; import { AuthorizeService } from "./authorize.service"; import { ApplicationPaths, QueryParameterNames } from './api-authorization.constants'; @Injectable({ providedIn: 'root' }) export class AuthorizeWindowsGroupGuardGuard implements CanActivate{ constructor(private authorize: AuthorizeService, private router: Router) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { return this.authorize.getUser().pipe(map((u: any) => !!u && !!u.hasUsersGroup)).pipe(tap((isAuthorized:boolean) => this.handleAuthorization(isAuthorized, state)));; } private handleAuthorization(isAuthenticated: boolean, state: RouterStateSnapshot) { if (!isAuthenticated) { window.location.href = "/Identity/Account/Login?" + QueryParameterNames.ReturnUrl + "=/"; } } }
, , .
{ path: 'internaldata', component: InternalDataComponent, canActivate: [AuthorizeWindowsGroupGuardGuard]
IdentityServer, claims ( sub , profile ), «hasUsersGroup». IdentityResource, - IdentityResources Startup.ConfigureServices .
var identityResource = new IdentityResource { Name = "customprofile", DisplayName = "Custom profile", UserClaims = new[] { "hasUsersGroup" }, }; identityResource.Properties.Add(ApplicationProfilesPropertyNames.Clients, "*"); options.IdentityResources.Add(identityResource);
- , windows « » SPA – , , Guard .
Fazit
, ASP.NET Core 3.0 IdentityServer4, . preview , . , github .