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

NestJS中RESTful API适配Web/桌面端按需跳过SSO保留JWT鉴权方案咨询

实现方案

核心结论

完全可以通过自定义装饰器(即你提到的条件注解)+ 鉴权守卫的组合实现需求,相比中间件方案粒度更可控、适配性更强,可同时满足Web/桌面端的安全要求。

具体实现步骤

1. 定义强制SSO校验的自定义装饰器

首先新增用于标记特殊端点的装饰器,凡是加了该装饰器的接口,无论请求来自Web端还是桌面端,都必须走SSO校验:

// src/common/decorators/require-sso.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const REQUIRE_SSO_KEY = 'requireSso';
export const RequireSSO = () => SetMetadata(REQUIRE_SSO_KEY, true);

2. 调整原有全局鉴权守卫逻辑

你原本的SSO+JWT鉴权应该是封装在全局守卫中,调整逻辑如下:

  1. 第一步统一校验JWT有效性,无论什么客户端、什么端点都必须先通过JWT校验,这是基础安全底线
  2. 第二步通过反射器读取当前路由是否加了@RequireSSO()装饰器,如果标记了,强制走SSO校验
  3. 第三步如果没有标记强制SSO,调用你已经实现的客户端环境识别逻辑判断请求来源:
    • Web端请求:执行SSO校验
    • 桌面端请求:跳过SSO校验,直接放行

守卫代码参考:

// src/common/guards/auth.guard.ts
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import { REQUIRE_SSO_KEY } from '../decorators/require-sso.decorator';
// 你已经实现的客户端识别工具
import { ClientType, getClientType } from '../utils/client-recognition.util';
// 你原有SSO校验逻辑
import { validateSSOToken } from '../utils/sso.util';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private jwtService: JwtService,
    private reflector: Reflector
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    
    // 1. 统一校验JWT
    const token = request.headers.authorization?.split(' ')[1];
    if (!token) throw new UnauthorizedException('未携带JWT');
    try {
      const payload = await this.jwtService.verifyAsync(token);
      request.user = payload;
    } catch {
      throw new UnauthorizedException('JWT无效');
    }

    // 2. 检查是否强制要求SSO
    const requireSso = this.reflector.getAllAndOverride<boolean>(REQUIRE_SSO_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (requireSso) {
      const ssoValid = await validateSSOToken(request.headers['x-sso-token']);
      if (!ssoValid) throw new UnauthorizedException('SSO校验失败');
      return true;
    }

    // 3. 非强制SSO场景按客户端类型判断
    const clientType = getClientType(request);
    if (clientType === ClientType.WEB) {
      const ssoValid = await validateSSOToken(request.headers['x-sso-token']);
      if (!ssoValid) throw new UnauthorizedException('SSO校验失败');
    }

    // 桌面端直接放行
    return true;
  }
}

3. 端点配置

  • 普通业务接口不需要额外加装饰器,自动适配两端规则
  • 版本更新等需要强制走SSO的接口,加@RequireSSO()装饰器即可:
// src/module/app/app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { RequireSSO } from '../../common/decorators/require-sso.decorator';

@Controller('app')
export class AppController {
  // 版本检查接口强制走SSO
  @RequireSSO()
  @Get('version/check')
  checkVersion() {
    // 业务逻辑
  }

  // 普通业务接口自动适配
  @Get('data/list')
  getDataList() {
    // 业务逻辑
  }
}

安全加固要求

为了避免攻击者伪造桌面端请求绕过SSO校验,必须做以下加固:

  • 桌面端的客户端标识不能仅依赖User-Agent,需要加签名校验:NW.js端发送请求时携带X-Client-Type: desktop + X-Client-Sign: [签名],签名规则为sha256(时间戳 + 桌面端内置的保密salt),后端校验签名有效性和时间戳误差不超过5分钟,防止签名被复用
  • JWT生成时需要在Payload中加入client_type字段,标识该JWT是Web端还是桌面端登录生成的,守卫中需要校验请求头的客户端类型与JWT中的client_type一致,避免Web端的JWT被用来冒充桌面端请求绕过SSO
  • 定期轮换桌面端内置的签名salt,每次版本更新同步更新后端校验salt
  • 敏感操作接口(如付费、权限变更、数据删除)统一加@RequireSSO()装饰器,强制校验SSO

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 15:06:02