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 请求以获取用户数据。在所有其他路线上它都按预期工作。
目前没有太多时间,但有一些想法:
如果你在服务器端渲染angular页面(Angular通用),你为什么不在服务器端处理授权过程?检查用户是否在每个请求中登录并在登录页面上重定向用户 - 你需要一个独立的登录页面而不是覆盖。
我有多个项目 运行 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;
}
}
希望对你有用
我正在使用 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 请求以获取用户数据。在所有其他路线上它都按预期工作。
目前没有太多时间,但有一些想法:
如果你在服务器端渲染angular页面(Angular通用),你为什么不在服务器端处理授权过程?检查用户是否在每个请求中登录并在登录页面上重定向用户 - 你需要一个独立的登录页面而不是覆盖。
我有多个项目 运行 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;
}
}
希望对你有用