我来自Asp.Net MVC世界,用户试图访问他们未授权的页面会自动重定向到登录页面.
我试图在Angular上重现这种行为.我来到@CanActivate装饰器,但它导致组件根本没有渲染,没有重定向.
我的问题如下:
Angular是否提供了实现此行为的方法?
如果是这样,怎么样?这是一个好习惯吗?
如果没有,在Angular中处理用户授权的最佳做法是什么?
Jason.. 102
这是使用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);
如果用户未登录,AuthGuard会重定向到登录页面
更新以将查询参数中的原始URL传递到登录页面
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; } }
有关完整示例和工作演示,您可以查看此帖子
这是使用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);
如果用户未登录,AuthGuard会重定向到登录页面
更新以将查询参数中的原始URL传递到登录页面
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; } }
有关完整示例和工作演示,您可以查看此帖子
更新:我已经在Github上发布了一个完整的骨架Angular 2项目与OAuth2集成,该项目显示了下面提到的指令.
一种方法是通过使用a directive
.与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(); } } }
这使用我编写的身份验证服务来确定用户是否已经登录并且还订阅了身份验证事件,以便在用户注销或超时时可以将用户踢出.
你可以做同样的事情.您将创建一个像我的指令,检查是否存在必要的cookie或其他状态信息,指示用户已通过身份验证.如果他们没有您正在寻找的那些标志,请将用户重定向到您的主公共页面(就像我一样)或您的OAuth2服务器(或其他).您可以将该指令属性放在需要保护的任何组件上.在这种情况下,可能会protected
像我上面粘贴的指令一样调用它.
然后,您可能希望将用户导航/重定向到应用程序中的登录视图,并在那里处理身份验证.您必须将当前路线更改为您想要的路线.因此,在这种情况下,您将使用依赖注入来获取指令函数中的Router对象constructor()
,然后使用该navigate()
方法将用户发送到您的登录页面(如上例所示).
这假设您有一系列路径控制着
看起来像这样的标签,或许:
@RouteConfig([ {path: '/loggedout', name: 'LoggedoutPage', component: LoggedoutPageComponent, useAsDefault: true}, {path: '/public', name: 'PublicPage', component: PublicPageComponent}, {path: '/protected', name: 'ProtectedPage', component: ProtectedPageComponent} ])
相反,如果您需要将用户重定向到外部 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
用于最终路由器
随着新路由器的引入,保护路线变得更加容易.您必须定义一个作为服务的防护,并将其添加到路由中.
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
使用不推荐的路由器
更强大的解决方案是RouterOutlet
在用户登录时扩展和激活路由检查.这样您就不必将指令复制并粘贴到每个组件.加上基于子组件的重定向可能会产生误导.
@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
看台上为您的业务逻辑所在的用户是否登录或不到位.您可以使用构造函数中的DI轻松添加它.
当用户导航到您网站上的新网址时,将使用当前指令调用activate方法.从中你可以抓住网址并决定是否允许.如果不只是重定向到登录页面.
让它工作的最后一件事就是将它传递给我们的主要组件而不是内置组件.
@Component({ selector: 'app', directives: [LoggedInRouterOutlet], template: template }) @RouteConfig(...) export class AppComponent { }
此解决方案不能与@CanActive
生命周期装饰器一起使用,因为如果传递给它的函数解析为false,RouterOutlet
则不会调用该方法的activate方法.
还写了一篇关于它的详细博客文章:https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492
请不要覆盖Router Outlet!这是最新路由器版本(3.0测试版)的噩梦.
而是使用接口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 { 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