Redirection angulaire vers la page de connexion
je viens de la Asp.Net MVC world où les utilisateurs qui tentent d'accéder à une page qu'ils ne sont pas autorisés sont automatiquement redirigés vers la page de connexion.
j'essaie de reproduire ce comportement en angle. Je suis venu à travers le décorateur @CanActivate, mais il en résulte que le composant ne rend pas du tout, pas de redirection.
ma question Est la suivante:
- Ne Angulaire de fournir un moyen d'atteindre cet le comportement?
- Si oui, comment? Est-ce une bonne pratique?
- dans la négative, quelle serait la meilleure pratique pour traiter L'autorisation de l'utilisateur en angle?
7 réponses
mise à Jour: j'ai publié un squelette entier Angulaire 2 projet avec OAuth2 intégration sur Github qui montre la directive mentionnée ci-dessous dans l'action.
une façon de le faire serait d'utiliser un directive
. Contrairement à Angular 2 components
, qui sont essentiellement de nouvelles balises HTML (avec le code associé) que vous insérez dans votre page, une directive attributive est un attribut que vous mettez dans une balise qui provoque un certain comportement à se produire. Docs ici .
la présence de votre attribut personnalisé fait que des choses arrivent au composant (ou à L'élément HTML) dans lequel vous avez placé la directive. Considérez cette directive que j'utilise pour mon application actuelle 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();
}
}
}
utilise un service D'authentification que j'ai écrit pour déterminer si l'utilisateur est déjà connecté ou non et souscrit également à l'événement d'authentification afin qu'il puisse virer un utilisateur s'il se déconnecte ou s'il se déconnecte.
vous pourriez faire la même chose. Vous pouvez ainsi créer une directive comme la mienne qui vérifie la présence de cookies ou d'autres informations d'état qui indique que l'utilisateur est authentifié. S'ils n'ont pas les options que vous recherchez, redirigez l'utilisateur vers votre page publique principale (comme je le fais) ou votre serveur OAuth2 (ou autre). Vous mettez que attribut de directive sur tout élément qui doit être protégé. Dans ce cas, on pourrait l'appeler protected
comme dans la directive que j'ai collée ci-dessus.
<members-only-info [protected]></members-only-info>
alors vous voudriez naviguer/rediriger l'utilisateur vers une vue de connexion dans votre application, et gérer l'authentification là. Tu devrais changer la route actuelle pour celle que tu voulais faire. Dans ce cas, vous utiliseriez l'injection de dépendances pour obtenir un objet Routeur dans votre la fonction constructor()
de la directive et ensuite utiliser la méthode navigate()
pour envoyer l'utilisateur à votre page de connexion (comme dans mon exemple ci-dessus).
cela suppose que vous avez une série de routes quelque part contrôlant une étiquette <router-outlet>
qui ressemble à quelque chose comme ceci, peut-être:
@RouteConfig([
{path: '/loggedout', name: 'LoggedoutPage', component: LoggedoutPageComponent, useAsDefault: true},
{path: '/public', name: 'PublicPage', component: PublicPageComponent},
{path: '/protected', name: 'ProtectedPage', component: ProtectedPageComponent}
])
si, à la place, vous aviez besoin de rediriger l'utilisateur vers une URL externe , comme votre serveur OAuth2, alors vous auriez votre directive do quelque chose comme ce qui suit:
window.location.href="https://myserver.com/oauth2/authorize?redirect_uri=http://myAppServer.com/myAngular2App/callback&response_type=code&client_id=clientId&scope=my_scope
voici un exemple mis à jour utilisant L'angle 4
Routes avec route d'origine protégée par 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 redirige vers la page de connexion si l'utilisateur n'est pas connecté dans
mise à Jour pour passer d'origine de l'url dans la requête de paramètres à la page de connexion
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;
}
}
pour l'exemple complet et le travail démo vous pouvez consulter cet article
utilisation avec le routeur final
Avec l'introduction du nouveau routeur, il est devenu plus facile pour garder les routes. Vous devez définir un garde, qui agit comme un service, et l'ajouter à l'itinéraire.
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();
}
}
passe maintenant le LoggedInGuard
à la route et l'ajoute également au tableau providers
du module.
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 déclaration du module:
@NgModule({
declarations: [AppComponent, HomeComponent, LoginComponent]
imports: [HttpModule, BrowserModule, RouterModule.forRoot(routes)],
providers: [UserService, LoggedInGuard],
bootstrap: [AppComponent]
})
class AppModule {}
blog détaillé sur la façon dont il fonctionne avec la version finale: https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9
utilisation avec le routeur déprécié
Une solution plus robuste consiste à étendre le RouterOutlet
et lors de l'activation d'un itinéraire de vérifier si l'utilisateur est connecté. De cette façon, vous n'avez pas à copier et coller votre directive sur chaque composant. Majoré la redirection basée sur un sous-composant peut être trompeuse.
@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()
}
}
le UserService
signifie l'endroit où se trouve votre logique commerciale, que l'utilisateur soit connecté ou non. Vous pouvez ajouter facilement avec DI dans le constructeur.
lorsque l'utilisateur navigue vers une nouvelle url sur votre site web, la méthode activate est appelée avec L'Instruction courante. De là, vous pouvez saisir l'url et décider si elle est autorisée ou non. Si ce n'est pas simplement rediriger vers la page de connexion.
Une dernière chose reste à le faire fonctionner, est à transmettre à notre principal composant au lieu de construit dans.
@Component({
selector: 'app',
directives: [LoggedInRouterOutlet],
template: template
})
@RouteConfig(...)
export class AppComponent { }
Cette solution ne peut pas être utilisée avec le décorateur de cycle de vie @CanActive
, parce que si la fonction passe à false, la méthode activate du RouterOutlet
ne sera pas appelée.
a également écrit un billet de blog détaillé à ce sujet: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492
s'il vous plaît, n'annulez pas la sortie du routeur! C'est un cauchemar avec la dernière version de routeur (3.0 beta).
utilisez plutôt les interfaces CanActivate et CanDeactivate et définissez la classe comme canActivate / canDeactivate dans votre définition de route.
comme ça:
{ 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;
}
}
voir aussi: https://angular.io/docs/ts/latest/guide/router.html#!#peut-activer-garde
suite aux réponses géniales ci-dessus, je voudrais aussi CanActivateChild
: garder les routes des enfants. Il peut être utilisé pour ajouter guard
aux itinéraires pour enfants utiles pour les cas comme ACLs
ça se passe comme ça
src/app / auth-guard.service.ts (extrait)
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 (extrait)
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 {}
ceci est tiré de https://angular.io/docs/ts/latest/guide/router.html#!#peut-activer-garde
référez ce code, auth.fichier 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. 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;
}
}
}