Angular Universal - Auth Guard 待处理问题

问题描述

我正在使用带有 Angular Material 的弹出式登录,当我添加 Angular Universal 时,身份验证保护是问题所在。 如果某些路由受 auth 保护,则页面只是开始挂起并且永远不会完成。没有 Angular Universal 的行为是在页面重新加载时它只是打开弹出窗口进行登录

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(readonly auth: AuthService,public router: Router,private dialog: MatDialog,private store: Store<fromAuth.State>) {}

    /** Performs the user authentication prompting the user when neeed or resolving to the current authenticated user otherwise */

    public authenticate(action: loginAction = 'signIn') {
      return this.store.pipe(
        select(fromAuth.selectAuthState),take(1),switchMap(user => !user.user ? this.prompt(action) : of(user.user))
      ).toPromise();
    }

  public prompt(data: loginAction = 'signIn'): Promise<any> {

    return this.dialog.open<LogInComponent,loginAction>(LogInComponent,{ data }).afterClosed().toPromise();
  }

  canActivate(route: ActivatedRouteSnapshot,state: RouterStateSnapshot) {
    // Gets the authorization mode when specified
    // const mode = route.queryParamMap.get('authMode') || 'signIn';
    // Prompts the user for authentication
     return this.authenticate()
      .then(user => !!user);
  }
}

如果我直接访问 canActivate 中的 ngrx 商店,它可以工作,但我想使用 .toPromise()

我正在使用 httponly cookie,并且在每次重新加载时,Angular 都会向 nodejs db 发送 http 请求以获取用户数据。在其他所有路线上它都按预期工作。

解决方法

目前没有太多时间,但有一些想法:

  1. 如果你是在服务器端(Angular Universal)渲染 angular 页面,你为什么不在服务器上处理 auth 过程?检查用户是否在每个请求中登录并在登录页面上重定向用户 - 您需要一个独立的登录页面而不是覆盖。

  2. 我有多个使用 AuthGuard / User / Auth 运行的项目,我不建议返回一个 promise,而是返回一个用于 canActivate 的布尔值。

因为:

  • 实际上,您不需要检查每个请求的登录状态,因为您的会话通常在一段时间内有效。
  • 我通常会在登录过程完成后存储一份有关用户的副本或一些紧凑的信息。
  • 每个例如。 1 分钟后会调用某个身份验证端点“whoami”或“loggedin”,返回用户会话是否仍然有效。
  • 如果不是,则用户已注销。
  • 如果您想在浏览器或选项卡关闭时保持会话处于活动状态,您可以将用户对象存储在本地存储中一段时间​​。

-> 这样你就只能检查你的 canActivate 方法中是否设置了当前的用户对象并返回 true 或 false。

所以在我看来: 要么完全使用服务器端渲染,这意味着还要检查后端用户的身份验证状态。 或者使用 angular 作为真正的前端项目并在那里处理身份验证过程。混合这两个世界会导致一些令人讨厌的问题,并使维护变得不必要地复杂。

示例路由

import { NgModule } from '@angular/core';
import { Routes,RouterModule } from '@angular/router';
import { AuthGuardService,CanDeactivateGuard } from '@app/core/services';
import { AppPagesConfig } from '@app/config';
import * as Pages from '@app/pages';

const routes: Routes = [
    {
        path: 'dashboard',component: Pages.DashboardPage,canActivate: [AuthGuardService],data: {
            permissions: []
        }
    },{
        path: 'calendar',children: [
            {
                path: '',redirectTo: AppPagesConfig.calendarPersonal.path,pathMatch: 'full'
            },{
                path: AppPagesConfig.calendarPersonal.path,component: Pages.CalendarPersonalPage,data: {
                    permissions: 'Foo-canEdit'
                }
            },{
                path: AppPagesConfig.calendarTeam.path,component: Pages.CalendarTeamPage,data: {
                    permissions: '0100'
                }
            },]
    },{
        path: 'contacts',redirectTo: 'private',{
                path: 'private',component: Pages.ContactsPage,canDeactivate: [CanDeactivateGuard],data: {
                    permissions: []
                }
            },{
        path: 'errors',redirectTo: '404',{
                path: '404',component: Pages.ErrorNotFoundPage
            },{
                path: '403',component: Pages.ErrorNoPermissionsPage
            },{
        path: 'login',component: Pages.LoginPage
    },{
        path: '**',component: Pages.ErrorNotFoundPage
    }
];

@NgModule({
    imports: [
        RouterModule.forRoot(routes,{
            useHash: true
        })
    ],exports: [
        RouterModule
    ]
})
export class AppRoutingModule { }

用户模型示例

import { uniq,union } from 'lodash';
import { UserBaseModel } from '@app/models/user-base';
import { Deserializable } from './deserializable.model';

export class User implements Deserializable {
    public permissions: string[];

    /**
     * Call this function to fill the model with data.
     */
    public deserialize(input: UserBaseModel): this {
        Object.assign(this,input);
        this.updateUserPermissions();

        return this;
    }

    /**
     * Checks if the user has all required permissions.
     */
    public hasPermissions(requiredPermissions: string[]): boolean {
        // If there where no required permissions given it is valid.
        if (!requiredPermissions || !requiredPermissions.length) {
            return true;
        }

        // If there are required permissions given but the user has no permissions at all it is always invalid.
        if (requiredPermissions.length && !this.permissions.length) {
            return false;
        }

        // Check the users permissions to contain all required permissions.
        for (const permission of requiredPermissions) {
            if (!this.permissions.includes(permission)) {
                return false;
            }

        }

        return true;
    }
}

AuthGuard 示例

import { isEmpty } from 'lodash';
import { Injectable } from '@angular/core';
import { Router,CanActivate,ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router';
import { SessionService } from './session.service';

@Injectable({
    providedIn: 'root'
})
export class AuthGuardService implements CanActivate {
    constructor(
        private readonly _router: Router,private readonly sessionService: SessionService
    ) { }

    /**
     * Check if user is allowed to navigate to the new state.
     */
    canActivate(route: ActivatedRouteSnapshot,state: RouterStateSnapshot): boolean {
        const currentUser = this.sessionService.getCurrentUser();

        // Not logged in so redirect to login page.
        if (!currentUser) {
            this.sessionService.logoutUser();
            this._router.navigate(['/login']);

            return false;
        }

        // Route is not protected so continue (for routes without auth permission needed).
        if (isEmpty(route.data) || !route.data.permissions || !route.data.permissions.length) {
            return true;
        }

        // If the permissions do not match redirect.
        if (currentUser && !currentUser.hasPermissions(route.data.permissions)) {
            this._router.navigate(['/errors/403'],{
                queryParams: {
                    referrerUrl: state.url
                }
            });

            return false;
        }

        // If the permissions do match continue.
        if (currentUser && currentUser.hasPermissions(route.data.permissions)) {
            return true;
        }

        // If nothing matches log out the user.
        this.sessionService.logoutUser();
        this._router.navigate(['/login']);

        return false;
    }
}