Reindirizzamento angolare alla pagina di accesso


122

Vengo dal mondo Asp.Net MVC dove gli utenti che tentano di accedere a una pagina non autorizzati vengono reindirizzati automaticamente alla pagina di login.

Sto cercando di riprodurre questo comportamento su Angular. Mi sono imbattuto nel decoratore @CanActivate, ma risulta che il componente non viene visualizzato affatto, nessun reindirizzamento.

La mia domanda è la seguente:

  • Angular fornisce un modo per ottenere questo comportamento?
  • Se é cosi, come? È una buona pratica?
  • In caso contrario, quale sarebbe la migliore pratica per la gestione dell'autorizzazione dell'utente in Angular?

Ho aggiunto una direttiva effettiva che mostra come fare le cose di autenticazione, se ti interessa guardare.
Michael Oryl

Risposte:


86

Aggiornamento: ho pubblicato uno scheletro progetto Angular 2 completo con integrazione OAuth2 su Github che mostra la direttiva menzionata di seguito in azione.

Un modo per farlo sarebbe attraverso l'uso di un file directive. A differenza di Angular 2 components, che sono fondamentalmente nuovi tag HTML (con codice associato) che inserisci nella tua pagina, una direttiva attributiva è un attributo che inserisci in un tag che provoca il verificarsi di un comportamento. Documenti qui .

La presenza del tuo attributo personalizzato fa sì che accadano cose al componente (o elemento HTML) in cui hai inserito la direttiva. Considera questa direttiva che uso per la mia attuale applicazione 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();
        }
    }
}

Questo fa uso di un servizio di autenticazione che ho scritto per determinare se l'utente è già connesso e si iscrive anche all'evento di autenticazione in modo che possa cacciare un utente se si disconnette o scade.

Potresti fare la stessa cosa. Creeresti una direttiva come la mia che controlla la presenza di un cookie necessario o altre informazioni di stato che indicano che l'utente è autenticato. Se non hanno quei flag che stai cercando, reindirizza l'utente alla tua pagina pubblica principale (come faccio io) o al tuo server OAuth2 (o qualsiasi altra cosa). Metteresti quell'attributo di direttiva su qualsiasi componente che deve essere protetto. In questo caso, potrebbe essere chiamato protectedcome nella direttiva che ho incollato sopra.

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

Quindi vorresti navigare / reindirizzare l'utente a una vista di accesso all'interno della tua app e gestire l'autenticazione lì. Dovresti cambiare il percorso corrente con quello che volevi fare. Quindi in quel caso useresti l'inserimento delle dipendenze per ottenere un oggetto Router nella constructor()funzione della tua direttiva e quindi utilizzerai il navigate()metodo per inviare l'utente alla tua pagina di accesso (come nel mio esempio sopra).

Questo presume che tu abbia una serie di rotte da qualche parte che controllano un <router-outlet>tag che assomiglia a questo, forse:

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

Se, invece, avessi bisogno di reindirizzare l'utente a un URL esterno , come il tuo server OAuth2, allora dovresti fare in modo che la tua direttiva faccia qualcosa di simile:

window.location.href="https://myserver.com/oauth2/authorize?redirect_uri=http://myAppServer.com/myAngular2App/callback&response_type=code&client_id=clientId&scope=my_scope

4
Funziona! Grazie! Ho anche trovato un altro metodo qui: github.com/auth0/angular2-authentication-sample/blob/master/src/… Non posso dire quale metodo sia migliore, ma forse qualcuno lo troverà utile anche.
Sergey

3
Grazie ! Ho anche aggiunto una nuova rotta contenente un parametro / protected /: returnUrl, dove returnUrl è la location.path () intercettata in ngOnInit della direttiva. Ciò consente di navigare l'utente dopo il login all'URL richiesto originariamente.
Amaury

1
Vedere le risposte di seguito per una soluzione semplice. Tutto ciò che è comune (reindirizzamento se non autenticato) dovrebbe avere una soluzione semplice con una singola frase.
Rick O'Shea

7
Nota: questa risposta riguarda una versione beta o release candidate di Angular 2 e non è più applicabile per Angular 2 final.
jbandi

1
C'è una soluzione molto migliore a questo ora usando le guardie angolari
mwilson

116

Ecco un esempio aggiornato utilizzando Angular 4 (compatibile anche con Angular 5-8)

Percorsi con home route protetto da 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 reindirizza alla pagina di accesso se l'utente non ha effettuato l'accesso

Aggiornato per passare l'URL originale nei parametri della query alla pagina di accesso

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

Per l'esempio completo e la demo funzionante puoi dare un'occhiata a questo post


6
Ho un follow-up D, non è se l'impostazione di un valore arbitrario su currentUserin localStoragesarebbe ancora in grado di accedere al percorso protetto? per esempio. localStorage.setItem('currentUser', 'dddddd')?
jsd

2
Ignorerebbe la sicurezza lato client. Ma eliminerebbe anche il token che sarebbe necessario per le transazioni lato server, quindi nessun dato utile potrebbe essere estratto dall'applicazione.
Matt Meng

55

Utilizzo con il router finale

Con l'introduzione del nuovo router è diventato più facile sorvegliare le rotte. È necessario definire una guardia, che funge da servizio, e aggiungerla al percorso.

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

Ora passa LoggedInGuardal percorso e aggiungilo anche providersall'array del modulo.

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

La dichiarazione del modulo:

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

Post di blog dettagliato su come funziona con la versione finale: https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

Utilizzo con il router deprecato

Una soluzione più robusta è quella di estendere RouterOutlete quando si attiva un percorso controllare se l'utente è loggato. In questo modo non è necessario copiare e incollare la direttiva su ogni componente. Inoltre, il reindirizzamento basato su un sottocomponente può essere fuorviante.

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

La UserServicesta per il luogo in cui la logica di business risiede se l'utente è loggato o meno. Puoi aggiungerlo facilmente con DI nel costruttore.

Quando l'utente naviga verso un nuovo URL sul tuo sito web, il metodo di attivazione viene chiamato con l'istruzione corrente. Da esso puoi prendere l'URL e decidere se è consentito o meno. In caso contrario, reindirizza alla pagina di accesso.

Un'ultima cosa che resta per farlo funzionare, è passarlo al nostro componente principale invece che a quello integrato.

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

Questa soluzione non può essere utilizzata con il @CanActivedecoratore del ciclo di vita, perché se la funzione ad esso passata si risolve in false, il metodo di attivazione di RouterOutletnon verrà chiamato.

Ha anche scritto un post dettagliato sul blog a riguardo: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492


2
Ha anche scritto un post sul blog più dettagliato a riguardo medium.com/@blacksonic86/…
Blacksonic

Ciao @Blacksonic. Ho appena iniziato a scavare in ng2. Ho seguito il tuo suggerimento ma alla fine ho ricevuto questo errore durante gulp-tslint: 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". non riuscivo a capire cosa cambiare nel costruttore ("_parentRounter") per eliminare questo messaggio. qualche idea?
leovrf

la dichiarazione viene copiata dall'oggetto integrato sottostante RouterOutlet per avere la stessa firma della classe estesa, disabiliterei la regola tslint specifica per questa riga
Blacksonic

Ho trovato un riferimento sulla guida allo stile di mgechev (cerca "Preferisci gli input al decoratore dei parametri @Attribute"). Modificata la linea in _parentRouter: Router, @Input() nameAttr: string,e tslint non genera più l'errore. Sostituito anche l'importazione "Attributo" in "Input" dal nucleo angolare. Spero che questo ti aiuti.
leovrf

1
C'è un problema con 2.0.0-rc.1 perché RouterOutlet non viene esportato e non è possibile estenderlo
mkuligowski

53

Per favore, non sovrascrivere la presa del router! È un incubo con l'ultima versione del router (3.0 beta).

Utilizza invece le interfacce CanActivate e CanDeactivate e imposta la classe come canActivate / canDeactivate nella definizione del percorso.

Come quello:

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

Classe:

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

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


2
Bello, la risposta di @ Blacksonic funzionava perfettamente con il router obsoleto. Ho dovuto rifattorizzare molto dopo l'aggiornamento al nuovo router. La tua soluzione è proprio quello di cui avevo bisogno!
evandongen

Non riesco a far funzionare canActivate nel mio app.component. Sto cercando di reindirizzare l'utente se non autenticato. Questa è la versione del router che ho (se ho bisogno di aggiornarlo come posso farlo utilizzando la riga di comando git bash?) Versione che ho: "@ angular / router": "2.0.0-rc.1"
AngularM

posso usare la stessa classe (AuthGuard) per proteggere il percorso di un altro componente?
tsiro

4

Seguendo le fantastiche risposte sopra, vorrei anche CanActivateChild: sorvegliare le rotte dei bambini. Può essere utilizzato per aggiungere guardai figli percorsi utili per casi come ACL

Va così

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

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 (estratto)

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

Questo è preso da https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard


2

Fare riferimento a questo codice, file auth.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]    },

]; 

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;      
    }
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.