Angular 通用 - Auth Guard 未决问题

Angular Universal - Auth Guard pending problem

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

@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 数据库发送 http 请求以获取用户数据。在所有其他路线上它都按预期工作。

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

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

  2. 我有多个项目 运行 AuthGuard / User / Auth,我不建议 return promise 而是 canActivate 的布尔值。

因为:

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

-> 这样你只能检查你的 canActivate 方法中是否设置了当前用户对象并且 return 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,
                canActivate: [AuthGuardService],
                data: {
                    permissions: 'Foo-canEdit'
                }
            }, {
                path: AppPagesConfig.calendarTeam.path,
                component: Pages.CalendarTeamPage,
                canActivate: [AuthGuardService],
                data: {
                    permissions: '0100'
                }
            },
        ]
    }, {
        path: 'contacts',
        children: [
            {
                path: '',
                redirectTo: 'private',
                pathMatch: 'full'
            }, {
                path: 'private',
                component: Pages.ContactsPage,
                canActivate: [AuthGuardService],
                canDeactivate: [CanDeactivateGuard],
                data: {
                    permissions: []
                }
            },
        ]
    }, {
        path: 'errors',
        children: [
            {
                path: '',
                redirectTo: '404',
                pathMatch: 'full'
            }, {
                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;
    }
}

我想我有一个简单实用的方法,只需在 auth guard 中验证,如果它是浏览器,则继续验证,如果它不只是继续,你不会有问题,因为 API 会没有发送到服务器私人信息,因为没有提供令牌,然后在浏览器中重复整个过程,并且将为私人数据提供令牌。

Auth Guard 示例:

import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

import { CanActivate, Router } from '@angular/router';

@Injectable({
    providedIn: 'root'
})
export class AuthGuard implements CanActivate {
    constructor(
        private router: Router,
        @Inject(PLATFORM_ID) private platformId: any
    ) {}

    canActivate(): boolean {
        var canContinue: boolean = false;

        if (isPlatformBrowser(this.platformId)) {
            // Browser
            // Verify here if the token exists
            canContinue = !!localStorage.getItem('token');

            if (!canContinue) this.router.navigate(['/error/401']);
        }
        if (isPlatformServer(this.platformId)) {
            // Server side
            canContinue = true;
        }

        return canContinue;
    }
}

希望对你有用