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:
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
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
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
Ü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
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
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] },
];
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;
}
}
}