Angular重定向到登录页面

Ama*_*ury 113 login typescript angular2-routing angular

我来自Asp.Net MVC世界,用户试图访问他们未授权的页面会自动重定向到登录页面.

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

我的问题如下:

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

Jas*_*son 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);
Run Code Online (Sandbox Code Playgroud)

如果用户未登录,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;
    }
}
Run Code Online (Sandbox Code Playgroud)

有关完整示例和工作演示,您可以查看此帖子

  • 我有一个跟进Q,是不是如果在`localStorage`中设置一个任意值为'currentUser`仍然能够访问受保护的路由?例如.`localStorage.setItem('currentUser','dddddd')`? (6认同)
  • 它会绕过客户端安全性.但它也会清除服务器端事务所必需的令牌,因此无法从应用程序中提取有用的数据. (2认同)

Mic*_*ryl 84

更新:我已经在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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这使用我编写的身份验证服务来确定用户是否已经登录并且还订阅了身份验证事件,以便在用户注销或超时时可以将用户踢出.

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

<members-only-info [protected]></members-only-info>
Run Code Online (Sandbox Code Playgroud)

然后,您可能希望将用户导航/重定向到应用程序中的登录视图,并在那里处理身份验证.您必须将当前路线更改为您想要的路线.因此,在这种情况下,您将使用依赖注入来获取指令函数中的Router对象constructor(),然后使用该navigate()方法将用户发送到您的登录页面(如上例所示).

这假设您有一系列路径控制着<router-outlet>看起来像这样的标签,或许:

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

相反,如果您需要将用户重定向到外部 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
Run Code Online (Sandbox Code Playgroud)

  • 注意:此答案涉及Angular 2的beta版或候选版本,不再适用于Angular 2 final. (7认同)
  • 有用!谢谢!我还在这里找到了另一种方法 - https://github.com/auth0/angular2-authentication-sample/blob/master/src/app/LoggedInOutlet.ts我不能说哪种方法更好,但也许有人会觉得它也很有用. (4认同)
  • 谢谢 !我还添加了一个包含参数/ protected /:returnUrl的新路由,returnUrl是在指令的ngOnInit截获的location.path().这允许在登录到最初提示的URL之后导航用户. (3认同)
  • 请参阅下面的答案以获取简单的解决方案。任何这种常见的(如果未验证则重定向)都应该有一个简单的解决方案,只需一句话回答。 (2认同)
  • 现在使用 Angular Guards 有一个更好的解决方案 (2认同)

Bla*_*nic 56

用于最终路由器

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

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

现在传递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 },
];
Run Code Online (Sandbox Code Playgroud)

模块声明:

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

关于它如何与最终版本一起使用的详细博客文章: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()
  }
}
Run Code Online (Sandbox Code Playgroud)

UserService看台上为您的业务逻辑所在的用户是否登录或不到位.您可以使用构造函数中的DI轻松添加它.

当用户导航到您网站上的新网址时,将使用当前指令调用activate方法.从中你可以抓住网址并决定是否允许.如果不只是重定向到登录页面.

让它工作的最后一件事就是将它传递给我们的主要组件而不是内置组件.

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

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

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

  • 还写了一篇关于它的更详细的博客文章https://medium.com/@blacksonic86/authentication-in-angular-2-958052c64492 (2认同)

Nil*_*z11 50

请不要覆盖Router Outlet!这是最新路由器版本(3.0测试版)的噩梦.

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

像那样:

{ path: '', component: Component, canActivate: [AuthGuard] },
Run Code Online (Sandbox Code Playgroud)

类:

@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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 不错的一个,@ Blacksonic的回答对我来说非常适合不推荐的路由器.升级到新路由器后,我不得不重构一遍.您的解决方案正是我所需要的! (2认同)

Tha*_*ung 5

根据上面的精彩答案,我还想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);
  }

/* . . . */
}
Run Code Online (Sandbox Code Playgroud)

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 {}
Run Code Online (Sandbox Code Playgroud)

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