Angular redirect to login page

匿名 (未验证) 提交于 2019-12-03 01:47:02

问题:

I come from the Asp.Net MVC world where users trying to access a page they are not authorized are automatically redirected to the login page.

I am trying to reproduce this behavior on Angular. I came accross the @CanActivate decorator, but it results in the component not rendering at all, no redirection.

My question is the following:

  • Does Angular provide a way to achieve this behaviour?
  • If so, how? Is it a good practice?
  • If not, what would be the best practice for handling user authorization in Angular?

回答1:

Update: I've published a full skeleton Angular 2 project with OAuth2 integration on Github that shows the directive mentioned below in action.

One way to do that would be through the use of a directive. Unlike Angular 2 components, which are basically new HTML tags (with associated code) that you insert into your page, an attributive directive is an attribute that you put in a tag that causes some behavior to occur. Docs here.

The presence of your custom attribute causes things to happen to the component (or HTML element) that you placed the directive in. Consider this directive I use for my current Angular2/OAuth2 application:

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

This makes use of an Authentication service I wrote to determine whether or not the user is already logged in and also subscribes to the authentication event so that it can kick a user out if he or she logs out or times out.

You could do the same thing. You'd create a directive like mine that checks for the presence of a necessary cookie or other state information that indicates that the user is authenticated. If they don't have those flags you are looking for, redirect the user to your main public page (like I do) or your OAuth2 server (or whatever). You would put that directive attribute on any component that needs to be protected. In this case, it might be called protected like in the directive I pasted above.

Then you would want to navigate/redirect the user to a login view within your app, and handle the authentication there. You'd have to change the current route to the one you wanted to do that. So in that case you'd use dependency injection to get a Router object in your directive's constructor() function and then use the navigate() method to send the user to your login page (as in my example above).

This assumes that you have a series of routes somewhere controlling a tag that looks something like this, perhaps:

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

If, instead, you needed to redirect the user to an external URL, such as your OAuth2 server, then you would have your directive do something like the following:

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


回答2:

Here's an updated example using Angular 4

Routes with home route protected by 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 redirects to login page if user isn't logged in

Updated to pass original url in query params to login page

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

For the full example and working demo you can check out this post



回答3:

Usage with the final router

With the introduction of the new router it became easier to guard the routes. You must define a guard, which acts as a service, and add it to the route.

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

Now pass the LoggedInGuard to the route and also add it to the providers array of the 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 }, ]; 

The module declaration:

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

Detailed blog post about how it works with the final release: https://medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

Usage with the deprecated router

A more robust solution is to extend the RouterOutlet and when activating a route check if the user is logged in. This way you don't have to copy and paste your directive to every component. Plus redirecting based on a subcomponent can be misleading.

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

The UserService stands for the place where your business logic resides whether the user is logged in or not. You can add it easily with DI in the constructor.

When the user navigates to a new url on your website, the activate method is called with the current Instruction. From it you can grab the url and decide whether it is allowed or not. If not just redirect to the login page.

One last thing remain to make it work, is to pass it to our main component instead of the built in one.

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

This solution can not be used with the @CanActive lifecycle decorator, because if the function passed to it resolves false, the activate method of the RouterOutlet won't be called.

Also wrote a detailed blog post about it: https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492



回答4:

Please, do not override Router Outlet! It's a nightmare with latest router release (3.0 beta).

Instead use the interfaces CanActivate and CanDeactivate and set the class as canActivate / canDeactivate in your route definition.

Like that:

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

Class:

@Injectable() export class AuthGuard implements CanActivate {      constructor(protected router: Router, protected authService: AuthService)     {      }      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | boolean {          if (state.url !== '/login' && !this.authService.isAuthenticated()) {             this.router.navigate(['/login']);             return false;         }          return true;     } } 

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



回答5:

Following the awesome answers above I would also like to CanActivateChild: guarding child routes. It can be used to add guard to children routes helpful for cases like ACLs

It goes like this

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

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

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

This is taken from https://angular.io/docs/ts/latest/guide/router.html#!#can-activate-guard



回答6:

Refer this code, auth.ts file

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


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!