it-swarm.com.de

Winkelumleitung zur Anmeldeseite

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

Ich versuche, dieses Verhalten auf Angular zu reproduzieren. Ich bin über den @CanActivate-Dekorator geraten, aber es führt dazu, dass die Komponente überhaupt nicht gerendert wird, keine Weiterleitung. 

Meine Frage ist folgende:

  • 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, um die Benutzerautorisierung in Angular zu handhaben?
103
Amaury

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.

Eine Möglichkeit dazu wäre die Verwendung einer directive. Im Gegensatz zu Angular 2 components, bei dem es sich im Wesentlichen um neue HTML-Tags (mit zugehörigem Code) handelt, die Sie in Ihre Seite einfügen, ist eine attributive Anweisung ein Attribut, das Sie in ein Tag einfügen, das zu bestimmten Verhaltensweisen führt. Dokumente hier .

Das Vorhandensein Ihres benutzerdefinierten Attributs bewirkt, dass der Komponente (oder dem HTML-Element), in der Sie die Direktive platziert haben, etwas passiert. Betrachten 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();
        }
    }
}

Dabei wird ein von mir geschriebener Authentifizierungsdienst verwendet, um zu bestimmen, ob der Benutzer bereits angemeldet ist und auch das Authentifizierungsereignis abonniert, sodass er einen Benutzer aus dem System werfen kann, wenn er sich abmeldet oder das Zeitlimit überschreitet.

Sie könnten dasselbe tun. Sie würden eine Direktive wie die meine erstellen, die das Vorhandensein eines erforderlichen Cookies oder anderer Statusinformationen überprüft, die darauf hinweisen, dass der Benutzer authentifiziert ist. Wenn sie diese Flaggen nicht haben, leiten Sie den Benutzer auf Ihre öffentliche Hauptseite (wie ich) oder Ihren OAuth2-Server (oder was auch immer) um. Sie würden dieses Direktionsattribut auf jede Komponente setzen, die geschützt werden muss. In diesem Fall könnte es wie in der oben eingefügten Direktive protected heißen.

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

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

Dies setzt voraus, dass Sie eine Reihe von Routen haben, die einen <router-outlet>-Tag steuern, der in etwa wie folgt 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 an eine external - URL wie Ihren OAuth2-Server weiterleiten müssen, müssen Sie mit Ihrer Anweisung etwa 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
82
Michael Oryl

Hier ist ein aktualisiertes Beispiel mit Angular 4

Routen mit durch AuthGuard geschützten Heimatrouten

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 um, wenn der Benutzer nicht angemeldet ist

Aktualisiert, um die ursprüngliche URL in 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;
    }
}

Für das vollständige Beispiel und die funktionierende Demo können Sie diesen Beitrag

96
Jason

Verwendung mit dem endgültigen Router

Mit der Einführung des neuen Routers wurde es einfacher, die Routen zu überwachen. Sie müssen einen Wächter definieren, der als Dienst fungiert, und ihn 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 die LoggedInGuard an die Route und fügen Sie sie auch dem providers-Array 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 Modulerklärung:

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

Ausführlicher Blogeintrag zur Funktionsweise der endgültigen Veröffentlichung: https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

Verwendung mit dem veralteten Router

Eine robustere Lösung besteht darin, die Variable RouterOutlet zu erweitern und beim Aktivieren einer Route zu prüfen, ob der Benutzer angemeldet ist. Auf diese Weise müssen Sie Ihre Direktive nicht kopieren und in jede Komponente einfügen. Außerdem kann das Umleiten auf Basis 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()
  }
}

Die Variable UserService steht 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 auf Ihrer Website zu einer neuen URL navigiert, wird die Aktivierungsmethode mit der aktuellen Anweisung aufgerufen. Von dort können Sie die URL abholen und entscheiden, ob sie zulässig ist oder nicht. Wenn nicht nur zur Anmeldeseite weitergeleitet werden.

Um es noch funktionieren zu lassen, müssen Sie es an unsere Hauptkomponente übergeben, anstatt an die eingebaute.

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

Diese Lösung kann nicht mit dem Lifecycle-Decorator @CanActive verwendet werden, da die Aktivierungsmethode der RouterOutlet nicht aufgerufen wird, wenn die an sie übergebene Funktion den Wert false löst.

Auch schrieb einen ausführlichen Blog-Post darüber: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492

56
Blacksonic

Überschreiben Sie bitte nicht den Router Outlet! Es ist ein Albtraum mit der neuesten Router-Version (Beta 3.0).

Verwenden Sie stattdessen die Schnittstellen CanActivate und CanDeactivate und legen Sie die Klasse in der 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

50
Nilz11

Nach den tollen Antworten oben möchte ich auch CanActivateChild: Kinderrouten bewachen. Es kann verwendet werden, um guard zu untergeordneten 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 entnommen aus __. https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard

3
Thabung

Siehe diesen Code in der 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]    },

]; 
2
sojan

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;      
    }
  }
}
0
M.Laida