You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular SSR中CanActivate Auth Guard的使用方法及兼容实现方案

嘿,这个问题确实是Angular SSR开发中常见的坑——传统的CanActivate守卫因为依赖浏览器环境或者异步处理的差异,在SSR模式下容易失效。不过别担心,我们可以通过适配SSR的特性来改造守卫,让它在服务器端和客户端都能正常工作。下面是一步步的实现方案:

1. 先搞懂SSR中CanActivate的限制

服务器端渲染时,代码运行在Node.js环境,没有浏览器的window、localStorage等API;同时服务器需要等待守卫的异步逻辑完成后再渲染页面,否则可能输出未授权的内容。普通的CanActivate守卫如果没考虑这些差异,就会在SSR中出问题。

2. 创建支持SSR的AuthGuard守卫

我们需要结合Angular的PlatformId(判断环境)和TransferState(服务器-客户端状态传递)来改造守卫,确保两端的认证逻辑都能正常执行。

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { isPlatformServer } from '@angular/common';
import { TransferState, makeStateKey } from '@angular/platform-browser';
import { AuthService } from './auth.service';
import { Observable, of } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';

// 定义TransferState的key,用来传递认证状态
const AUTH_STATE_KEY = makeStateKey<boolean>('isAuthenticated');

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    private transferState: TransferState,
    private router: Router,
    private authService: AuthService
  ) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    // 先检查TransferState:如果服务器已经处理过认证,直接用传递过来的状态
    if (this.transferState.hasKey(AUTH_STATE_KEY)) {
      const isAuthenticated = this.transferState.get(AUTH_STATE_KEY, false);
      this.transferState.remove(AUTH_STATE_KEY);
      return this.handleAuthResult(isAuthenticated, state);
    }

    // 服务器端逻辑:从请求中获取认证信息(比如Cookie)
    if (isPlatformServer(this.platformId)) {
      return this.authService.isAuthenticatedOnServer().pipe(
        // 将认证状态存入TransferState,传递给客户端
        tap(isAuthenticated => this.transferState.set(AUTH_STATE_KEY, isAuthenticated)),
        tap(isAuthenticated => this.handleAuthResult(isAuthenticated, state)),
        catchError(() => {
          this.transferState.set(AUTH_STATE_KEY, false);
          this.router.navigate(['/login']);
          return of(false);
        })
      );
    }

    // 客户端逻辑:从本地存储获取认证信息
    return this.authService.isAuthenticated().pipe(
      tap(isAuthenticated => this.handleAuthResult(isAuthenticated, state)),
      catchError(() => {
        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
        return of(false);
      })
    );
  }

  // 统一处理认证结果
  private handleAuthResult(isAuthenticated: boolean, state: RouterStateSnapshot): boolean {
    if (!isAuthenticated) {
      this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
      return false;
    }
    return true;
  }
}
3. 实现适配SSR的AuthService

认证服务需要区分服务器端和客户端的逻辑,服务器端从请求对象获取认证信息(比如Cookie),客户端从本地存储获取:

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformServer } from '@angular/common';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { Request } from 'express';
import { Observable, of } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class AuthService {
  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    @Inject(REQUEST) private request: Request // 服务器端注入Express请求对象
  ) {}

  // 服务器端检查认证:从Cookie/请求头获取token并验证
  isAuthenticatedOnServer(): Observable<boolean> {
    if (!isPlatformServer(this.platformId)) return of(false);
    // 示例:从Cookie获取token,实际项目中需要调用后端接口验证token有效性
    const authToken = this.request.cookies['auth_token'];
    return of(!!authToken);
  }

  // 客户端检查认证:从localStorage获取token
  isAuthenticated(): Observable<boolean> {
    if (isPlatformServer(this.platformId)) return of(false);
    const authToken = localStorage.getItem('auth_token');
    return of(!!authToken);
  }
}
4. 在路由模块中正常使用守卫

和普通CanActivate守卫的用法完全一致,直接在路由配置中引用:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { LoginComponent } from './login/login.component';
import { AuthGuard } from './auth.guard';

const routes: Routes = [
  { 
    path: 'dashboard', 
    component: DashboardComponent, 
    canActivate: [AuthGuard] // 直接使用改造后的守卫
  },
  { path: 'login', component: LoginComponent },
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
5. 关键注意事项
  • 绝对不要在守卫中直接使用window、localStorage等浏览器API,必须通过PlatformId判断环境后再执行对应逻辑。
  • 用TransferState传递认证状态可以避免客户端重复执行认证逻辑,提升渲染性能。
  • 服务器端的认证必须依赖请求对象(REQUEST)获取信息,因为服务器端没有本地存储。
  • 确保守卫中的异步逻辑在服务器端渲染完成前完成,避免出现未授权内容被输出。

内容的提问来源于stack exchange,提问作者Kuldeep Sharma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 19:52:36