将angular度redirect到login页面

我来自Asp.Net MVC世界,用户试图访问他们没有被授权的页面会自动redirect到login页面。

我正试图在Angular上重现这种行为。 我来到了@CanActivate装饰器,但它导致组件根本没有渲染,没有redirect。

我的问题如下:

  • Angular是否提供了实现这种行为的方法?
  • 如果是这样,怎么样? 这是一个很好的做法吗?
  • 如果没有,那么在Angular中处理用户授权的最佳做法是什么?

更新:我已经发布了一个完整的骨架Angular 2项目与 Github 上的OAuth2集成 ,显示下面提到的指令在行动。

一种方法是通过使用directive 。 与Angular 2 components不同,Angular 2 components基本上是新插入到页面的HTML标签(带有关联的代码),一个归属指令是一个属性,您可以将其放置在导致一些行为发生的标签中。 文档在这里 。

您的自定义属性的存在会导致事件发生在您放置该指令的组件(或HTML元素)中。请考虑用于我当前的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(); } } } 

这使得我使用我写的authentication服务来确定用户是否已经login,并且还订阅了authentication事件,以便在用户注销或超时时将用户踢出去。

你可以做同样的事情。 你会创build一个像我的指令,检查是否存在一个必要的cookie或其他状态信息,指示用户已通过身份validation。 如果他们没有你正在寻找的那些标志,redirect用户到你的主要公共页面(像我这样)或你的OAuth2服务器(或其他)。 您可以将该指令属性放在任何需要保护的组件上。 在这种情况下,可能会像上面粘贴的指令那样被称为protected

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

然后,您将要导航/redirect到您的应用程序中的login视图,并在那里处理身份validation。 您必须将当前的路线更改为您想要的路线。 因此,在这种情况下,您可以使用dependency injection来获取指令的constructor()函数中的一个路由器对象 ,然后使用navigate()方法将用户发送到您的login页面(如上面的示例中所示)。

这假设你有一系列path控制一个<router-outlet>标签,看起来像这样,也许:

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

相反,如果您需要将用户redirect到外部 URL(例如OAuth2服务器),则您的指令将执行以下操作:

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

这是一个使用Angular 4的更新示例

路由与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); 

如果用户没有login,AuthGuard将redirect到login页面

更新为将查询参数中的原始url传递到login页面

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

对于完整的例子和工作演示,你可以看看这个职位

与最终路由器一起使用

随着新路由器的引入,防护路由变得更加容易。 您必须定义一个作为服务的警卫,并将其添加到路线中。

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

现在将LoggedInGuard传递给路由,并将其添加到模块的providers数组中。

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

模块声明:

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

有关如何与最终版本一起工作的详细博客文章: https : //medium.com/@blacksonic86/angular-2-authentication-revisited-611bf7373bf9

与弃用的路由器一起使用

更强大的解决scheme是扩展RouterOutlet并在激活路由时检查用户是否已login。这样,您不必将指令复制并粘贴到每个组件。 加上基于子组件的redirect可能会产生误导。

 @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代表您的业务逻辑所在的地方,无论用户是否login。 您可以在构造函数中用DI轻松添加它。

当用户导航到您的网站上的一个新的url,激活方法调用当前的指令。 从它你可以抓住url,并决定是否允许。 如果不是只是redirect到login页面。

最后还有一件事要做,就是把它传递给我们的主要组件,而不是内置的组件。

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

此解决scheme不能与@CanActive生命周期装饰器一起使用,因为如果传递给它的函数parsing为false,则不会调用RouterOutlet的activate方法。

还写了一篇详细的博客文章: https : //medium.com/@blacksonic86/authentication-in-angular-2-958052c64492

请不要覆盖路由器sockets! 这是最新的路由器版本(3.0testing版)的噩梦。

而应使用CanActivate和CanDeactivate接口,并在路由定义中将类设置为canActivate / canDeactivate。

像那样:

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

类:

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

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

以上真棒回答我还想CanActivateChild :守护孩子的路线。 它可以用来增加对儿童路线有帮助的guard措施,比如ACL

它是这样的

src / app / auth-guard.service.ts(摘录)

 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(摘录)

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

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

参考这个代码,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] }, ];