Winkelumleitung zur Anmeldeseite

122

Ich komme aus der Asp.Net MVC-Welt, in der Benutzer, die versuchen, auf eine nicht autorisierte Seite zuzugreifen, automatisch auf die Anmeldeseite umgeleitet werden.

Ich versuche, dieses Verhalten auf Angular zu reproduzieren. Ich bin auf den @ CanActivate-Dekorator gestoßen, aber dies führt dazu, dass die Komponente überhaupt nicht gerendert wird und keine Umleitung erfolgt.

Meine Frage lautet wie folgt:

  • Bietet Angular eine Möglichkeit, dieses Verhalten zu erreichen?
  • Wenn das so ist, wie? Ist es eine gute Praxis?
  • Wenn nicht, was wäre die beste Vorgehensweise für den Umgang mit der Benutzerautorisierung in Angular?
Amaury
quelle
Ich habe eine aktuelle Direktive hinzugefügt, die zeigt, wie man das Auth-Zeug macht, wenn man es sich ansieht.
Michael Oryl
Diese Antwort kann nützlich sein: stackoverflow.com/a/59008239/7059557
AmirReza-Farahlagha

Antworten:

86

Update: Ich habe ein vollständiges Skelett- Angular 2-Projekt mit OAuth2-Integration auf Github veröffentlicht, das die unten genannte Direktive in Aktion zeigt.

Ein Weg, dies zu tun, wäre die Verwendung von a directive. Im Gegensatz zu Angular 2 components, bei dem es sich im Grunde genommen um neue HTML-Tags (mit zugehörigem Code) handelt, die Sie in Ihre Seite einfügen, ist eine attributive Direktive ein Attribut, das Sie in ein Tag einfügen, wodurch ein gewisses Verhalten auftritt. Docs hier .

Das Vorhandensein Ihres benutzerdefinierten Attributs führt dazu, dass Dinge mit der Komponente (oder dem HTML-Element) geschehen, in die Sie die Direktive eingefügt haben. Beachten Sie diese Direktive, die ich für meine aktuelle Angular2 / OAuth2-Anwendung verwende:

import {Directive, OnDestroy} from 'angular2/core';
import {AuthService} from '../services/auth.service';
import {ROUTER_DIRECTIVES, Router, Location} from "angular2/router";

@Directive({
    selector: '[protected]'
})
export class ProtectedDirective implements OnDestroy {
    private sub:any = null;

    constructor(private authService:AuthService, private router:Router, private location:Location) {
        if (!authService.isAuthenticated()) {
            this.location.replaceState('/'); // clears browser history so they can't navigate with back button
            this.router.navigate(['PublicPage']);
        }

        this.sub = this.authService.subscribe((val) => {
            if (!val.authenticated) {
                this.location.replaceState('/'); // clears browser history so they can't navigate with back button
                this.router.navigate(['LoggedoutPage']); // tells them they've been logged out (somehow)
            }
        });
    }

    ngOnDestroy() {
        if (this.sub != null) {
            this.sub.unsubscribe();
        }
    }
}

Hierbei wird ein von mir geschriebener Authentifizierungsdienst verwendet, um festzustellen, ob der Benutzer bereits angemeldet ist, und um das Authentifizierungsereignis zu abonnieren , damit ein Benutzer ausgeschlossen werden kann, wenn er sich abmeldet oder eine Zeitüberschreitung aufweist.

Sie könnten das Gleiche tun. Sie würden eine Anweisung wie meine erstellen, die prüft, ob ein erforderliches Cookie oder andere Statusinformationen vorhanden sind, die darauf hinweisen, dass der Benutzer authentifiziert ist. Wenn sie nicht die Flags haben, nach denen Sie suchen, leiten Sie den Benutzer auf Ihre öffentliche Hauptseite (wie ich) oder Ihren OAuth2-Server (oder was auch immer) weiter. Sie würden dieses Direktivenattribut auf jede Komponente setzen, die geschützt werden muss. In diesem Fall könnte es protectedwie in der oben eingefügten Direktive aufgerufen werden .

<members-only-info [protected]></members-only-info>

Anschließend möchten Sie den Benutzer zu einer Anmeldeansicht in Ihrer App navigieren / umleiten und dort die Authentifizierung durchführen. Sie müssten die aktuelle Route auf die Route ändern, für die Sie dies tun möchten. In diesem Fall würden Sie die Abhängigkeitsinjektion verwenden, um ein Router-Objekt in der constructor()Funktion Ihrer Direktive abzurufen, und dann die navigate()Methode verwenden, um den Benutzer an Ihre Anmeldeseite zu senden (wie in meinem obigen Beispiel).

Dies setzt voraus, dass Sie irgendwo eine Reihe von Routen haben, die ein <router-outlet>Tag steuern , das ungefähr so ​​aussieht:

@RouteConfig([
    {path: '/loggedout', name: 'LoggedoutPage', component: LoggedoutPageComponent, useAsDefault: true},
    {path: '/public', name: 'PublicPage', component: PublicPageComponent},
    {path: '/protected', name: 'ProtectedPage', component: ProtectedPageComponent}
])

Wenn Sie stattdessen den Benutzer zu einer externen URL wie Ihrem OAuth2-Server umleiten müssten, müsste Ihre Direktive Folgendes tun:

window.location.href="https://myserver.com/oauth2/authorize?redirect_uri=http://myAppServer.com/myAngular2App/callback&response_type=code&client_id=clientId&scope=my_scope
Michael Oryl
quelle
4
Es klappt! Vielen Dank! Ich habe hier auch eine andere Methode gefunden - github.com/auth0/angular2-authentication-sample/blob/master/src/… Ich kann nicht sagen, welche Methode besser ist, aber vielleicht findet es auch jemand nützlich.
Sergey
3
Danke ! Ich habe auch eine neue Route hinzugefügt, die einen Parameter / protected /: returnUrl enthält, wobei returnUrl der location.path () ist, der bei ngOnInit der Direktive abgefangen wird. Dies ermöglicht es dem Benutzer, nach der Anmeldung zur ursprünglich angeforderten URL zu navigieren.
Amaury
1
In den Antworten unten finden Sie eine einfache Lösung. Alles, was so häufig vorkommt (umleiten, wenn nicht authentifiziert), sollte eine einfache Lösung mit einer Antwort aus einem einzigen Satz haben.
Rick O'Shea
7
Hinweis: Diese Antwort bezieht sich auf eine Beta- oder Release-Candidate-Version von Angular 2 und gilt nicht mehr für Angular 2 final.
Jbandi
1
Es gibt jetzt eine viel bessere Lösung dafür mit Angular Guards
mwilson
116

Hier ist ein aktualisiertes Beispiel mit Angular 4 (auch kompatibel mit Angular 5 - 8).

Routen mit durch AuthGuard geschützter Heimroute

import { Routes, RouterModule } from '@angular/router';

import { LoginComponent } from './login/index';
import { HomeComponent } from './home/index';
import { AuthGuard } from './_guards/index';

const appRoutes: Routes = [
    { path: 'login', component: LoginComponent },

    // home route protected by auth guard
    { path: '', component: HomeComponent, canActivate: [AuthGuard] },

    // otherwise redirect to home
    { path: '**', redirectTo: '' }
];

export const routing = RouterModule.forRoot(appRoutes);

AuthGuard leitet zur Anmeldeseite weiter, wenn der Benutzer nicht angemeldet ist

Aktualisiert, um die ursprüngliche URL in den Abfrageparametern an die Anmeldeseite zu übergeben

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private router: Router) { }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        if (localStorage.getItem('currentUser')) {
            // logged in so return true
            return true;
        }

        // not logged in so redirect to login page with the return url
        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
        return false;
    }
}

Das vollständige Beispiel und die funktionierende Demo finden Sie in diesem Beitrag

Jason
quelle
6
Ich habe eine Folgefrage. Ist es nicht möglich, auf die geschützte Route zuzugreifen , wenn Sie einen beliebigen Wert currentUserin localStoragefestlegen. z.B. localStorage.setItem('currentUser', 'dddddd')?
jsd
2
Es würde die clientseitige Sicherheit umgehen. Es würde aber auch das Token löschen, das für serverseitige Transaktionen erforderlich wäre, sodass keine nützlichen Daten aus der Anwendung extrahiert werden könnten.
Matt Meng
55

Verwendung mit dem endgültigen Router

Mit der Einführung des neuen Routers wurde es einfacher, die Routen zu bewachen. Sie müssen eine Wache definieren, die als Dienst fungiert, und sie der Route hinzufügen.

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { UserService } from '../../auth';

@Injectable()
export class LoggedInGuard implements CanActivate {
  constructor(user: UserService) {
    this._user = user;
  }

  canActivate() {
    return this._user.isLoggedIn();
  }
}

Übergeben Sie nun das LoggedInGuardan die Route und fügen Sie es auch dem providersArray des Moduls hinzu.

import { LoginComponent } from './components/login.component';
import { HomeComponent } from './components/home.component';
import { LoggedInGuard } from './guards/loggedin.guard';

const routes = [
    { path: '', component: HomeComponent, canActivate: [LoggedInGuard] },
    { path: 'login', component: LoginComponent },
];

Die Moduldeklaration:

@NgModule({
  declarations: [AppComponent, HomeComponent, LoginComponent]
  imports: [HttpModule, BrowserModule, RouterModule.forRoot(routes)],
  providers: [UserService, LoggedInGuard],
  bootstrap: [AppComponent]
})
class AppModule {}

Ausführlicher Blog-Beitrag zur Funktionsweise der endgültigen Version: https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

Verwendung mit dem veralteten Router

Eine robustere Lösung besteht darin, die RouterOutletund beim Aktivieren einer Routenprüfung zu erweitern, ob der Benutzer angemeldet ist. Auf diese Weise müssen Sie Ihre Direktive nicht in jede Komponente kopieren und einfügen. Außerdem kann eine Umleitung basierend auf einer Unterkomponente irreführend sein.

@Directive({
  selector: 'router-outlet'
})
export class LoggedInRouterOutlet extends RouterOutlet {
  publicRoutes: Array;
  private parentRouter: Router;
  private userService: UserService;

  constructor(
    _elementRef: ElementRef, _loader: DynamicComponentLoader,
    _parentRouter: Router, @Attribute('name') nameAttr: string,
    userService: UserService
  ) {
    super(_elementRef, _loader, _parentRouter, nameAttr);

    this.parentRouter = _parentRouter;
    this.userService = userService;
    this.publicRoutes = [
      '', 'login', 'signup'
    ];
  }

  activate(instruction: ComponentInstruction) {
    if (this._canActivate(instruction.urlPath)) {
      return super.activate(instruction);
    }

    this.parentRouter.navigate(['Login']);
  }

  _canActivate(url) {
    return this.publicRoutes.indexOf(url) !== -1 || this.userService.isLoggedIn()
  }
}

Das UserServicesteht für den Ort, an dem sich Ihre Geschäftslogik befindet, unabhängig davon, ob der Benutzer angemeldet ist oder nicht. Sie können es einfach mit DI im Konstruktor hinzufügen.

Wenn der Benutzer zu einer neuen URL auf Ihrer Website navigiert, wird die Aktivierungsmethode mit der aktuellen Anweisung aufgerufen. Daraus können Sie die URL abrufen und entscheiden, ob sie zulässig ist oder nicht. Wenn nicht, leiten Sie einfach zur Anmeldeseite weiter.

Eine letzte Sache, die noch funktioniert, ist, sie an unsere Hauptkomponente anstatt an die eingebaute zu übergeben.

@Component({
  selector: 'app',
  directives: [LoggedInRouterOutlet],
  template: template
})
@RouteConfig(...)
export class AppComponent { }

Diese Lösung kann nicht mit dem @CanActiveLifecycle Decorator verwendet werden, da die Aktivierungsmethode von RouterOutletnicht aufgerufen wird , wenn die an sie übergebene Funktion false auflöst .

Schrieb auch einen ausführlichen Blog-Beitrag darüber: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492

Blacksonic
quelle
2
Schrieb auch einen ausführlicheren Blog-Beitrag darüber medium.com/@blacksonic86/…
Blacksonic
Hallo @Blacksonic. Ich habe gerade angefangen, mich mit ng2 zu beschäftigen. Ich bin Ihrem Vorschlag gefolgt, habe aber während gulp-tslint diesen Fehler erhalten: Ich Failed to lint <classname>.router-outlet.ts[15,28]. In the constructor of class "LoggedInRouterOutlet", the parameter "nameAttr" uses the @Attribute decorator, which is considered as a bad practice. Please, consider construction of type "@Input() nameAttr: string". konnte nicht herausfinden, was im Konstruktor ("_parentRounter") geändert werden muss, um diese Nachricht zu entfernen. Irgendwelche Gedanken?
Leovrf
Die Deklaration wird aus dem zugrunde liegenden eingebauten Objekt RouterOutlet kopiert, um dieselbe Signatur wie die erweiterte Klasse zu haben. Ich würde eine bestimmte tslint-Regel für diese Zeile deaktivieren
Blacksonic
Ich habe einen Verweis auf mgechev style-guide gefunden (siehe "Eingaben gegenüber dem @Attribute-Parameterdekorator bevorzugen"). Das _parentRouter: Router, @Input() nameAttr: string,Ändern der Zeile in und tslint löst den Fehler nicht mehr aus. Ersetzte auch den Import "Attribut" in "Eingabe" vom Winkelkern. Hoffe das hilft.
Leovrf
1
Es gibt ein Problem mit 2.0.0-rc.1, weil RouterOutlet nicht exportiert wird und es keine Möglichkeit gibt, es zu erweitern
mkuligowski
53

Bitte Router Outlet nicht überschreiben! Es ist ein Albtraum mit der neuesten Router-Version (3.0 Beta).

Verwenden Sie stattdessen die Schnittstellen CanActivate und CanDeactivate und legen Sie die Klasse in Ihrer Routendefinition als canActivate / canDeactivate fest.

So wie das:

{ path: '', component: Component, canActivate: [AuthGuard] },

Klasse:

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(protected router: Router, protected authService: AuthService)
    {

    }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {

        if (state.url !== '/login' && !this.authService.isAuthenticated()) {
            this.router.navigate(['/login']);
            return false;
        }

        return true;
    }
}

Siehe auch: https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard

Nilz11
quelle
2
Schön, @ Blacksonics Antwort funktionierte perfekt für mich mit dem veralteten Router. Nach dem Upgrade auf den neuen Router musste ich viel umgestalten. Ihre Lösung ist genau das, was ich brauchte!
evandongen
Ich kann canActivate nicht dazu bringen, in meiner app.component zu arbeiten. Ich möchte den Benutzer umleiten, wenn er nicht authentifiziert ist. Dies ist die Version des Routers, den ich habe (Wenn ich ihn aktualisieren muss, wie mache ich das mit der git bash-Befehlszeile?) Version, die ich habe: "@ angle / router": "2.0.0-rc.1"
AngularM
Kann ich dieselbe Klasse (AuthGuard) verwenden, um eine andere Komponentenroute zu schützen?
Tsiro
4

Nach den fantastischen Antworten oben möchte ich auch CanActivateChild: Kinderrouten bewachen. Es kann verwendet werden, um guarduntergeordneten Routen hinzuzufügen , die für Fälle wie ACLs hilfreich sind

Es geht so

src / app / auth-guard.service.ts (Auszug)

import { Injectable }       from '@angular/core';
import {
  CanActivate, Router,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  CanActivateChild
}                           from '@angular/router';
import { AuthService }      from './auth.service';

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild {
  constructor(private authService: AuthService, private router:     Router) {}

  canActivate(route: ActivatedRouteSnapshot, state:    RouterStateSnapshot): boolean {
    let url: string = state.url;
    return this.checkLogin(url);
  }

  canActivateChild(route: ActivatedRouteSnapshot, state:  RouterStateSnapshot): boolean {
    return this.canActivate(route, state);
  }

/* . . . */
}

src / app / admin / admin-routing.module.ts (Auszug)

const adminRoutes: Routes = [
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [AuthGuard],
    children: [
      {
        path: '',
        canActivateChild: [AuthGuard],
        children: [
          { path: 'crises', component: ManageCrisesComponent },
          { path: 'heroes', component: ManageHeroesComponent },
          { path: '', component: AdminDashboardComponent }
        ]
      }
    ]
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(adminRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class AdminRoutingModule {}

Dies ist https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard entnommen

Thabung
quelle
2

Verweisen Sie auf diesen Code, auth.ts-Datei

import { CanActivate } from '@angular/router';
import { Injectable } from '@angular/core';
import {  } from 'angular-2-local-storage';
import { Router } from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate {
constructor(public localStorageService:LocalStorageService, private router: Router){}
canActivate() {
// Imaginary method that is supposed to validate an auth token
// and return a boolean
var logInStatus         =   this.localStorageService.get('logInStatus');
if(logInStatus == 1){
    console.log('****** log in status 1*****')
    return true;
}else{
    console.log('****** log in status not 1 *****')
    this.router.navigate(['/']);
    return false;
}


}

}
// *****And the app.routes.ts file is as follow ******//
      import {  Routes  } from '@angular/router';
      import {  HomePageComponent   } from './home-page/home- page.component';
      import {  WatchComponent  } from './watch/watch.component';
      import {  TeachersPageComponent   } from './teachers-page/teachers-page.component';
      import {  UserDashboardComponent  } from './user-dashboard/user- dashboard.component';
      import {  FormOneComponent    } from './form-one/form-one.component';
      import {  FormTwoComponent    } from './form-two/form-two.component';
      import {  AuthGuard   } from './authguard';
      import {  LoginDetailsComponent } from './login-details/login-details.component';
      import {  TransactionResolver } from './trans.resolver'
      export const routes:Routes    =   [
    { path:'',              component:HomePageComponent                                                 },
    { path:'watch',         component:WatchComponent                                                },
    { path:'teachers',      component:TeachersPageComponent                                         },
    { path:'dashboard',     component:UserDashboardComponent,       canActivate: [AuthGuard],   resolve: { dashboardData:TransactionResolver } },
    { path:'formone',       component:FormOneComponent,                 canActivate: [AuthGuard],   resolve: { dashboardData:TransactionResolver } },
    { path:'formtwo',       component:FormTwoComponent,                 canActivate: [AuthGuard],   resolve: { dashboardData:TransactionResolver } },
    { path:'login-details', component:LoginDetailsComponent,            canActivate: [AuthGuard]    },

]; 
Sojan
quelle
1

1. Create a guard as seen below. 2. Install ngx-cookie-service to get cookies returned by external SSO. 3. Create ssoPath in environment.ts (SSO Login redirection). 4. Get the state.url and use encodeURIComponent.

import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from 
  '@angular/router';
import { CookieService } from 'ngx-cookie-service';
import { environment } from '../../../environments/environment.prod';

@Injectable()
export class AuthGuardService implements CanActivate {
  private returnUrl: string;
  constructor(private _router: Router, private cookie: CookieService) {}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if (this.cookie.get('MasterSignOn')) {
      return true;
    } else {
      let uri = window.location.origin + '/#' + state.url;
      this.returnUrl = encodeURIComponent(uri);      
      window.location.href = environment.ssoPath +  this.returnUrl ;   
      return false;      
    }
  }
}
M. Laida
quelle