角度重定向到登录页面

我来自阿斯帕。在 Net MVC 世界中,用户试图访问未经授权的页面时会自动重定向到登录页面。

我试图在 Angular 上重现这种行为。我偶然发现了@CanActivate 装饰器,但是它导致组件根本不呈现,没有重定向。

我的问题是:

  • Angular 是否提供了一种实现这种行为的方法?
  • 如果是这样,怎么做? 这是一个好的做法吗?
  • 如果没有,那么以角度处理用户授权的最佳实践是什么?
310831 次浏览

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

一种方法是使用 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();
}
}
}

这将利用我编写的一个 Authentication 服务来确定用户是否已经登录,以及身份验证事件的 也订阅,以便在用户登出或超时时将其踢出。

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

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

然后,您需要将用户导航/重定向到应用程序中的登录视图,并在那里处理身份验证。你得把当前的路线改成你想要的那条。所以在这种情况下,你可以使用依赖注入在你的指令的 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}
])

相反,如果您需要将用户重定向到 外部 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 轻松地添加它。

当用户导航到网站上的一个新网址时,当前指令将调用激活方法。从它你可以抓取网址,并决定是否允许与否。如果不只是重定向到登录页面。

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

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

此解决方案不能与 @CanActive生命周期修饰符一起使用,因为如果传递给它的函数解析为 false,则不会调用 RouterOutlet的激活方法。

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

请不要覆盖路由器插座! 最新的路由器版本(3.0测试版)是一场噩梦。

而是使用接口 CanActivate 和 CanDeactive,并在路由定义中将类设置为 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

这里是一个更新的例子,使用角度4 (也兼容角5-8)

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

对于完整的例子和工作演示,您可以查看 这篇文章

遵循以上令人敬畏的答案,我也想到 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]    },


];

创建一个如下所示的警卫。 2. 安装 ngx-cookie-service 获取外部 SSO 返回的 cookie。 3. 在 Environment. ts 中创建 ssoPath (SSO 登录重定向)。 4. 获取 state.url 并使用 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;
}
}
}