Kątowe przekierowanie na stronę logowania

Pochodzę z Asp.Net MVC world, w którym użytkownicy próbujący uzyskać dostęp do strony, do której nie są uprawnieni, są automatycznie przekierowywani na stronę logowania.

Próbuję odtworzyć to zachowanie na Angular. Trafiłem na dekoratora @ CanActivate, ale powoduje to, że komponent w ogóle nie renderuje, nie ma przekierowania.

Moje pytanie jest następujące:

  • czy Angular zapewnia sposób na osiągnięcie tego zachowania?
  • Jeśli tak, to w jaki sposób? Czy to dobra praktyka?
  • Jeśli Nie, jaka byłaby najlepsza praktyka obsługi autoryzacji użytkowników w Angular?
Author: ishandutta2007, 2015-12-17

7 answers

Update: opublikowałem pełny szkielet projektu Angular 2 z integracją OAuth2 na Githubie, który pokazuje wspomnianą poniżej dyrektywę w akcji.

Jednym ze sposobów, by to zrobić, byłoby użycie directive. W przeciwieństwie do Angular 2 components, które są zasadniczo nowymi znacznikami HTML (z powiązanym kodem), które wstawiasz na stronę, dyrektywa atrybutywna jest atrybutem umieszczanym w znaczniku, który powoduje pewne zachowanie. dokumenty tutaj .

The obecność atrybutu niestandardowego powoduje, że komponent (lub element HTML), w którym umieściłeś dyrektywę, może się zdarzyć. Rozważmy tę dyrektywę, której używam dla mojej obecnej aplikacji Angular2/OAuth2:

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();
        }
    }
}

To korzysta z usługi uwierzytelniania, którą napisałem, aby ustalić, czy użytkownik jest już zalogowany, a subskrybuje również do zdarzenia uwierzytelniania, aby móc wykopać użytkownika, jeśli wyloguje się lub przekroczy czas.

You could do the same rzecz. Stworzyłbyś dyrektywę taką jak moja, która sprawdza obecność niezbędnego pliku cookie lub innych informacji o stanie, które wskazują, że użytkownik jest uwierzytelniony. Jeśli nie mają tych flag, których szukasz, przekieruj użytkownika na główną stronę publiczną (tak jak ja) lub serwer OAuth2 (lub cokolwiek innego). Można umieścić ten atrybut dyrektywy na każdym komponencie, który wymaga ochrony. W tym przypadku może się ona nazywać protected jak w dyrektywie, którą wkleiłem powyżej.

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

Następnie chcesz nawigować / przekierowywać użytkownika do widoku logowania w aplikacji i obsługiwać tam uwierzytelnianie. Musisz zmienić bieżącą trasę na tę, którą chcesz to zrobić. Tak więc w takim przypadku należy użyć dependency injection, aby uzyskać obiekt routera w funkcji constructor() dyrektywy, a następnie użyć metody navigate(), aby wysłać użytkownika na stronę logowania (jak w moim przykładzie powyżej).

To zakłada, że masz szereg tras gdzieś kontrolując znacznik <router-outlet>, który wygląda mniej więcej tak, być może:

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

Jeśli zamiast tego musisz przekierować użytkownika do zewnętrznego adresu URL , takiego jak serwer OAuth2, twoja dyrektywa zrobi coś takiego:

window.location.href="https://myserver.com/oauth2/authorize?redirect_uri=http://myAppServer.com/myAngular2App/callback&response_type=code&client_id=clientId&scope=my_scope
 80
Author: Michael Oryl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-31 03:40:03

Oto zaktualizowany przykład użycia Angular 4

Trasy z route home chronione przez AuthGuard

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 przekierowuje na stronę logowania jeśli użytkownik nie jest zalogowany

zaktualizowano, aby przekazać oryginalny adres url w params zapytań do strony logowania

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;
    }
}

Dla pełnego przykładu i pracy demo można sprawdzić ten post

 88
Author: Jason,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-05 22:11:40

Użycie z końcowym routerem

Wraz z wprowadzeniem nowego routera stało się łatwiejsze strzeżenie tras. Musisz zdefiniować strażnika, który działa jako usługa, i dodać go do trasy.

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();
  }
}

Teraz przekaż LoggedInGuard do trasy i dodaj ją do tablicy providers modułu.

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

Deklaracja modułu:

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

Szczegółowy wpis na blogu o tym, jak to działa z finalnym wydaniem: https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

Użycie z przestarzałym routerem

Bardziej solidnym rozwiązaniem jest rozszerzenie RouterOutlet i podczas aktywacji trasy sprawdź, czy użytkownik jest zalogowany. W ten sposób nie musisz kopiować i wklejać dyrektywy do każdego komponentu. Dodatkowo przekierowanie oparte na podkomponencie może być mylące.

@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()
  }
}

UserService oznacza miejsce, w którym znajduje się twoja logika biznesowa niezależnie od tego, czy użytkownik jest zalogowany, czy nie. Możesz go łatwo dodać za pomocą DI w konstruktorze.

Gdy użytkownik przejdzie do nowego adresu url na swojej stronie internetowej, metoda aktywacji jest wywoływana z bieżącą instrukcją. Z niego możesz pobrać adres url i zdecydować, czy jest dozwolony, czy nie. Jeśli nie tylko przekierowanie do strony logowania.

Ostatnią rzeczą, aby to zadziałało, jest przekazanie go do naszego głównego komponentu zamiast wbudowanego.

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

Tego roztworu nie można stosować z dekorator @CanActive lifecycle, ponieważ jeśli przekazana do niego funkcja rozwiąże false, metoda aktywacji RouterOutlet nie zostanie wywołana.

Napisał również szczegółowy wpis na blogu: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492

 54
Author: Blacksonic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-28 10:38:34

Proszę nie nadpisywać gniazda routera! To koszmar z najnowszym wydaniem routera (3.0 beta).

Zamiast tego użyj interfejsów CanActivate i CanDeactivate i ustaw klasę jako canActivate / canDeactivate w definicji trasy.

TAK:

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

Klasa:

@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;
    }
}

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

 48
Author: Nilz11,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-14 09:04:58

Podążając za niesamowitymi odpowiedziami powyżej chciałbym również CanActivateChild: pilnować tras dla dzieci. Może być używany do dodawania guard do tras dla dzieci pomocnych w przypadkach takich jak ACLs

It goes like this

Src / app / auth-guard.obsługa.ts (fragment)

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.moduł.ts (fragment)

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 {}

To jest wzięte z https://angular.io/docs/ts/latest/guide/router.html#! # can-activate-guard

 3
Author: Thabung,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-10 12:41:35

Poleć ten kod, auth.plik ts

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
Author: sojan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-20 11:35:23

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
Author: M.Laida,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-28 05:42:11