Angular 5/6:保护路线(路线防护)而不重定向到错误路线

Iva*_*jak 11 angular-routing angular-router-guards angular5 angular6

我有一点泡菜.我正在使用Route guard(实现CanActivate接口)来检查用户是否被授予访问特定路由的权限:

const routes: Routes = [
    {
        path: '',
        component: DashboardViewComponent
    },
    {
        path: 'login',
        component: LoginViewComponent
    },
    {
        path: 'protected/foo',
        component: FooViewComponent,
        data: {allowAccessTo: ['Administrator']},
        canActivate: [RouteGuard]
    },
    {
        path: '**',
        component: ErrorNotFoundViewComponent
    }
];
Run Code Online (Sandbox Code Playgroud)

现在它可以很好地保护'/ protected/foo'路由不被激活,但我想告诉用户他试图访问的路由是禁止的(类似于你可能从服务器获得的403 Forbidden).

问题: 如何向用户显示此特殊错误视图,而不将其重定向到错误路径哪个接缝是我找到的众多来源的首选选项? 而且如何在RouteGuard没有实际加载禁止路线的情况下仍然使用我,因为如果我检查我的内部访问FooViewComponent并显示不同的视图它首先会失败点RouteGuard.

理想情况下,我想让我RouteGuard不仅在canActivate()方法中返回false ,而且还完全用say替换组件ErrorForbiddenViewComponent.但我不知道该怎么做,或者事件是否可能.任何替代品?

这就是我的护卫队现在的样子:

import {Injectable} from '@angular/core';
import {Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';
import {AuthService} from '../services/auth.service';

@Injectable()
export class RouteGuard implements CanActivate {

    constructor(
        private router: Router,
        private auth: AuthService
    ) {}

    canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        const { auth, router } = this;
        const { allowAccessTo } = next.data;
        const identity = auth.getIdentity();
        if (
            identity &&
            allowAccessTo.indexOf(identity.role)
        ) {
            // all good, proceed with activating route
            return true;
        }
        if (identity) {
            // TODO show ErrorForbiddenViewComponent instead of redirecting
            console.log('403 Forbidden >>', next);
        }
        else { 
            // not logged in: redirect to login page with the return url
            const [returnUrl, returnQueryParams] = state.url.split('?');
            console.log('401 Unauthorised >>', returnUrl, returnQueryParams, next);
            router.navigate(['/login'], {queryParams: {returnUrl, returnQueryParams}});
        }
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我只是阻止路由加载,但我没有重定向.我只将未登录的访问者重定向到登录路由.

推理:

  • 路由应该反映某种应用状态 - 访问路由URL应该重新创建该状态
  • 要有错误路由(404 Not Found除外)将意味着您的应用程序实际上可以重新创建错误状态.这没有任何意义,为什么你会将错误状态保持为应用程序的状态?出于调试目的,应该使用日志(控制台或服务器),重新访问错误页面(即页面刷新)可能会干扰它.
  • 此外,通过重定向到错误路由应用程序应该为用户提供一些错误的见解.对于这个问题,要么需要通过url传递某个参数,要么(更糟糕的是)将错误状态保留在某个错误服务中并在访问错误路由时检索它.
  • 此外,忽略RouteGuard只是加载组件并检查其中的访问权限可能会导致加载一些额外的依赖项,无论如何都不会使用(因为不允许用户),使得整个延迟加载更加困难.

有没有人有这种解决方案?我也想知道在Angular 2+出现这么长时间以后没有人遇到这种情况之后怎么样?每个人都可以重定向吗?

还要记住,虽然我目前正在使用FooViewComponent同步,但未来可能会有所改变!

pla*_*ter 5

我曾经研究过类似的问题。

分享我创建的stackblitz poc -

  • 认证组件(带保护)
  • 登录组件
  • 权限卫士
  • 路线(/auth路线设有PermissionGuardService警卫)

守卫正在评估用户类型并相应地处理重定向/错误。

用例是 -

  • 用户未登录 ( shows a toast with log in message)
  • 用户不是管理员 ( shows a toast with unauthorised message)
  • 用户是管理员 ( show a toast with success messaage)

我已将用户存储在本地存储中。

编辑 - 演示 在此处输入图片说明

如果您需要特殊处理,请告诉我,我将更新代码库。

干杯!


Iva*_*jak 3

在查看了Tarun Lalwani 在问题评论中提供的angular2 示例并深入研究了Angular 文档上的动态组件加载器文章后,我成功地将其应用到我的代码中:

RouteGuard我在指定路线时不再使用 my :

{
     path: 'protected/foo',
     component: FooViewComponent,
     data: {allowAccessTo: ['Administrator']}, // admin only
     canActivate: [RouteGuard]
},
Run Code Online (Sandbox Code Playgroud)

相反,我创建了特殊的RouteGuardComponent,以下是我如何使用它:

{
    path: 'protected/foo',
    component: RouteGuardComponent,
    data: {component: FooViewComponent, allowAccessTo: ['Administrator']}
},
Run Code Online (Sandbox Code Playgroud)

这是代码RouteGuardComponent

@Component({
    selector: 'app-route-guard',
    template: '<ng-template route-guard-bind-component></ng-template>
    // note the use of special directive ^^
})
export class RouteGuardComponent implements OnInit {

    @ViewChild(RouteGuardBindComponentDirective)
    bindComponent: RouteGuardBindComponentDirective;
    // ^^ and here we bind to that directive instance in template

    constructor(
        private auth: AuthService,
        private route: ActivatedRoute,
        private componentFactoryResolver: ComponentFactoryResolver
    ) {
    }

    ngOnInit() {
        const {auth, route, componentFactoryResolver, bindComponent} = this;
        const {component, allowAccessTo} = route.snapshot.data;
        const identity = auth.getIdentity();
        const hasAccess = identity && allowAccessTo.indexOf(identity.role);
        const componentFactory = componentFactoryResolver.resolveComponentFactory(
            hasAccess ?
               component : // render component
               ErrorForbiddenViewComponent // render Forbidden view
        );
        // finally use factory to create proper component
        routeGuardBindComponentDirective
            .viewContainerRef
            .createComponent(componentFactory);
    }

}
Run Code Online (Sandbox Code Playgroud)

另外,这需要定义特殊指令(我确信这可以通过其他方式完成,但我刚刚应用了 Angular 文档中的动态组件示例):

@Directive({
    selector: '[route-guard-bind-component]'
})
export class RouteGuardBindComponentDirective {
    constructor(public viewContainerRef: ViewContainerRef) {}
}
Run Code Online (Sandbox Code Playgroud)

它不是我自己问题的完整答案(但它是一个开始),所以如果有人提供更好的东西(即仍然使用的方法canActivate和延迟加载的能力),我将确保考虑到这一点。