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鉴权应该是封装在全局守卫中,调整逻辑如下:
- 第一步统一校验JWT有效性,无论什么客户端、什么端点都必须先通过JWT校验,这是基础安全底线
- 第二步通过反射器读取当前路由是否加了
@RequireSSO()装饰器,如果标记了,强制走SSO校验 - 第三步如果没有标记强制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
相关产品推荐
相关产品推荐

