NestJS 入门(3):Guard 如何挡住未登录请求?
上一篇NestJS 入门2依赖注入到底解决了什么问题 讲完了「对象怎么接到一起」。接下来更贴近业务的问题是有些接口谁都能调有些接口必须先登录。Nest 把这道门放在哪里答案是Guard守卫。实战里最常见的门是JWT Passport这一套。本文会先讲清 Guard 在链路中的位置再介绍相关插件各自干什么最后用一个最小可跑例子把「登录发 token → 带 token 访问受保护接口」串起来。1. Guard 在请求链路里的位置一个请求进 Nest 后大致顺序是请求进来 → 中间件Middleware → Guard能不能进 → 管道Pipe参数校验/转换 → Controller 方法 → Service 业务逻辑Guard 回答的是一个很单纯的问题这个请求允不允许继续往下走允许 → 进入 Controller不允许 → 直接返回 401 / 403业务代码根本不会执行这和 Service 里的业务判断不一样位置典型问题Guard你是谁有没有登录有没有权限Service密码对不对这个项目属不属于你数据怎么改鉴权门槛尽量放在 Guard业务规则放在 Service。2. JWT 相关插件各自干什么Nest 官方没有「只装一个包就搞定登录」。常见组合是下面几个职责要分清npmi nestjs/jwt nestjs/passport passport passport-jwtnpmi-Dtypes/passport-jwt包角色你平时会碰到它的地方nestjs/jwtNest 封装的 JWT 工具JwtModule.register(...)、注入JwtService去sign/verifynestjs/passport把 Passport 接到 NestPassportModule、AuthGuard(jwt)、PassportStrategypassportNode 生态里的认证框架一般不直接写很多代码但是底层依赖passport-jwtPassport 的 JWT 策略实现Strategy、ExtractJwt.fromAuthHeaderAsBearerToken()types/passport-jwtTypeScript 类型开发期类型提示可以记成一句话nestjs/jwt负责签发/校验 tokenpassportpassport-jwt负责「从请求里取出 token 并按策略认证」nestjs/passport把这套策略接到 Nest 的 Guard 上。和登录常一起出现、但不属于 JWT 本体的包作用bcrypt密码哈希与比对存库用哈希登录时compare它们在 Module 里通常这样装配import{Module}fromnestjs/common;import{JwtModule}fromnestjs/jwt;import{PassportModule}fromnestjs/passport;Module({imports:[PassportModule,JwtModule.register({secret:process.env.JWT_SECRET||your-secret-key,signOptions:{expiresIn:30m},}),],// controllers / providers ...})exportclassAuthModule{}PassportModule启用 Passport 集成JwtModule.register({ secret, signOptions })配置签名密钥和默认过期时间并提供可注入的JwtService3. 最直观的用法UseGuards登录接口本身不需要登录「我是谁」接口必须登录Controller(api/auth)exportclassAuthController{constructor(privatereadonlyauthService:AuthService){}Post(login)login(Body()body:{email:string;password:string}){returnthis.authService.login(body.email,body.password);}UseGuards(JwtAuthGuard)Get(me)me(Headers(authorization)auth:string){consttokenauth?.replace(Bearer ,);returnthis.authService.validateToken(token);}}对比一下POST /api/auth/login没有 Guard任何人都能尝试登录GET /api/auth/me挂了JwtAuthGuard没带有效 token 进不来项目列表这类业务接口也一样UseGuards(JwtAuthGuard)Get()findAll(Request()req:AuthenticatedRequest){constuserIdreq.user?.userId;returnthis.projectsService.findAll(userId);}先过 Guard再拿当前用户去查「属于我的项目」。4. 方法级 vs 控制器级Guard 可以挂在单个方法上也可以挂在整个 Controller上。方法级上面那些例子只保护某一个接口。控制器级这个 Controller 里所有接口默认都要登录Controller()UseGuards(JwtAuthGuard)exportclassTaskPromptsController{constructor(privatereadonlyservice:TaskPromptsService){}Get(api/projects/:projectId/task-prompts)list(Param(projectId)projectId:string){returnthis.service.listByProject(projectId);}Put(api/projects/:projectId/task-prompts/:templateKey)saveDraft(/* ... */){// ...}}怎么选只有少数接口要登录 → 方法级更清晰整个模块几乎都要登录 → 控制器级少写很多重复装饰器5.JwtAuthGuard为什么看起来这么短真实代码里Guard 本身可能只有一行import{Injectable}fromnestjs/common;import{AuthGuard}fromnestjs/passport;Injectable()exportclassJwtAuthGuardextendsAuthGuard(jwt){}这里用的是nestjs/passport提供的AuthGuard。jwt这个名字对应的是passport-jwt策略。真正的校验逻辑在Strategy里import{Injectable}fromnestjs/common;import{PassportStrategy}fromnestjs/passport;import{ExtractJwt,Strategy}frompassport-jwt;Injectable()exportclassJwtStrategyextendsPassportStrategy(Strategy){constructor(){super({jwtFromRequest:ExtractJwt.fromAuthHeaderAsBearerToken(),ignoreExpiration:false,secretOrKey:process.env.JWT_SECRET||your-secret-key,});}asyncvalidate(payload:any){return{userId:payload.sub,email:payload.email,name:payload.name};}}对照插件代码来自哪个包AuthGuard(jwt)nestjs/passportPassportStrategy(...)nestjs/passportStrategy/ExtractJwtpassport-jwt职责拆开角色干什么JwtAuthGuard声明这里用名为jwt的认证策略守门JwtStrategy从 Header 取 Bearer token、验签、解析 payloadvalidate()验签通过后把用户信息整理出来挂到请求上AuthGuard(jwt)里的jwt必须和策略名对得上。Nest passport-jwt的默认约定里策略名就是jwt。另外secretOrKey要和JwtModule.register({ secret })、登录时JwtService.sign用的密钥一致否则「能签发、验不过」。6. 通过 Guard 之后用户信息在哪里Strategy 的validate()返回值会被放到request.user上。所以受保护接口可以这样取当前用户interfaceAuthenticatedRequestextendsExpressRequest{user?:{userId:string;email:string;name:string};}UseGuards(JwtAuthGuard)Get(generation-preferences)getGenerationPreferences(Request()req:AuthenticatedRequest){constuserIdreq.user?.userId;if(!userId){thrownewUnauthorizedException(Unauthorized);}returnthis.authService.getGenerationPreferences(userId);}链路可以记成Authorization: Bearer token → JwtStrategy 取出并校验 tokenpassport-jwt → validate(payload) 返回 { userId, email, name } → 写入 req.user → Controller 用 Request() 读取Guard 负责「放行」Controller / Service 负责「用这个身份继续办事」。7. 签发 tokenJwtService用在 Service 里Guard / Strategy 管「验票」登录成功后「出票」通常在 Service注入nestjs/jwt的JwtServiceInjectable()exportclassAuthService{constructor(privatereadonlyjwtService:JwtService){}privateasyncgenerateTokens(user:{id:string;email:string;name:string}){constpayload{sub:user.id,email:user.email,name:user.name};constaccessTokenthis.jwtService.sign(payload,{expiresIn:30m,});constrefreshTokenthis.jwtService.sign(payload,{expiresIn:7d,});return{accessToken,refreshToken};}}常见分工动作常用 API出现位置登录成功发 tokenjwtService.sign(payload)AuthService手动校验 tokenjwtService.verify(token)少数自定义逻辑请求自动验票Strategy Guard受保护接口入口生产里 access token 短、refresh token 长是很常见的组合细节可以后续再展开入门先抓住「谁签发、谁校验」。8. 还要在 Module 里注册和上一篇 DI 一样Guard、Strategy 也是 Provider要登记。Module({imports:[PassportModule,JwtModule.register({secret:process.env.JWT_SECRET||your-secret-key,signOptions:{expiresIn:30m},}),],controllers:[AuthController],providers:[AuthService,JwtStrategy,JwtAuthGuard],exports:[AuthService,JwtAuthGuard],})exportclassAuthModule{}实用提醒JwtStrategy必须进providers否则 Guard 找不到策略其他模块要用UseGuards(JwtAuthGuard)通常需要 Auth 模块exports这个 GuardJwtModule的secret与JwtStrategy的secretOrKey必须一致nestjs/jwt、nestjs/passport、passport-jwt缺一个链路就接不上9. 可选登录OptionalJwtAuthGuard有些接口「登录了更好不登录也能看」。这时可以做可选 GuardInjectable()exportclassOptionalJwtAuthGuardextendsAuthGuard(jwt){handleRequest(err:any,user:any){returnuser;}}和强制 Guard 的差别在于校验失败时不直接把请求打死而是可能让user为空继续往下走。业务代码再判断有req.user就按登录态处理没有就按游客处理。入门阶段先掌握强制登录的JwtAuthGuard即可。10. 对照一张完整链路图1) POST /api/auth/login → AuthService 校验账号密码可用 bcrypt → JwtService.sign(...) 签发 accessToken / refreshToken 2) GET /api/projects Header: Authorization: Bearer accessToken → JwtAuthGuardnestjs/passport → JwtStrategypassport-jwt验签 → validate() 写入 req.user → Controller / Service 使用 userId如果第 2 步没有 token、token 过期、或签名不对Guard / Strategy 拦下请求Controller 根本不会执行11. 实战最小可跑的 JWT 登录 受保护接口下面用最少文件搭一条能跑通的链路。目标只有两个接口POST /auth/login账号密码正确 → 返回 tokenGET /auth/profile必须带Authorization: Bearer token11.1 安装依赖npmi nestjs/jwt nestjs/passport passport passport-jwtnpmi-Dtypes/passport-jwt11.2auth.module.ts装配插件import{Module}fromnestjs/common;import{JwtModule}fromnestjs/jwt;import{PassportModule}fromnestjs/passport;import{AuthController}from./auth.controller;import{AuthService}from./auth.service;import{JwtStrategy}from./jwt.strategy;import{JwtAuthGuard}from./jwt-auth.guard;Module({imports:[PassportModule,JwtModule.register({secret:dev-secret,// 实战请改成环境变量signOptions:{expiresIn:30m},}),],controllers:[AuthController],providers:[AuthService,JwtStrategy,JwtAuthGuard],})exportclassAuthModule{}记得在AppModule里imports: [AuthModule]。11.3auth.service.ts校验账号并签发 tokenimport{Injectable,UnauthorizedException}fromnestjs/common;import{JwtService}fromnestjs/jwt;Injectable()exportclassAuthService{// 演示用内存用户真实项目换成数据库privatereadonlyusers[{id:1,email:demoexample.com,password:password123,name:Demo},];constructor(privatereadonlyjwtService:JwtService){}login(email:string,password:string){constuserthis.users.find((u)u.emailemail);if(!user||user.password!password){thrownewUnauthorizedException(Invalid credentials);}constpayload{sub:user.id,email:user.email,name:user.name};constaccessTokenthis.jwtService.sign(payload);return{accessToken,user:{id:user.id,email:user.email,name:user.name},};}}说明这里为了最短路径用了明文密码。真实项目请用bcrypt.hash/bcrypt.compare。11.4jwt.strategy.ts从 Header 取票并验票import{Injectable}fromnestjs/common;import{PassportStrategy}fromnestjs/passport;import{ExtractJwt,Strategy}frompassport-jwt;Injectable()exportclassJwtStrategyextendsPassportStrategy(Strategy){constructor(){super({jwtFromRequest:ExtractJwt.fromAuthHeaderAsBearerToken(),ignoreExpiration:false,secretOrKey:dev-secret,// 必须和 JwtModule.register 的 secret 一致});}validate(payload:{sub:string;email:string;name:string}){return{userId:payload.sub,email:payload.email,name:payload.name};}}11.5jwt-auth.guard.ts一道门import{Injectable}fromnestjs/common;import{AuthGuard}fromnestjs/passport;Injectable()exportclassJwtAuthGuardextendsAuthGuard(jwt){}11.6auth.controller.ts公开登录 受保护资料import{Body,Controller,Get,Post,Request,UseGuards}fromnestjs/common;import{AuthService}from./auth.service;import{JwtAuthGuard}from./jwt-auth.guard;Controller(auth)exportclassAuthController{constructor(privatereadonlyauthService:AuthService){}Post(login)login(Body()body:{email:string;password:string}){returnthis.authService.login(body.email,body.password);}UseGuards(JwtAuthGuard)Get(profile)profile(Request()req:{user:{userId:string;email:string;name:string}}){returnreq.user;}}11.7 用 curl 验证登录curl-XPOST http://localhost:3000/auth/login\-HContent-Type: application/json\-d{\email\:\demoexample.com\,\password\:\password123\}把返回里的accessToken拷出来再访问受保护接口curlhttp://localhost:3000/auth/profile\-HAuthorization: Bearer 粘贴你的accessToken预期带正确 token → 返回{ userId, email, name }不带 token / token 错误 → 401进不了profile方法如果这一步跑通了你就已经同时练到了nestjs/jwt签发passport-jwt Strategy 校验nestjs/passport的 Guard 守门Controller 从req.user取当前用户12. 小结Guard 决定请求能不能进入ControllerJWT 常见插件分工nestjs/jwt出票/验票工具passport-jwt策略实现nestjs/passport接到 Nest GuardUseGuards(JwtAuthGuard)可挂在方法或整个 ControllerJwtModule.secret与JwtStrategy.secretOrKey必须一致登录用JwtService.sign访问受保护接口靠 Strategy Guard 自动验票并写入req.user对照前两篇请求相关的问题可以再多一句哪个 Controller 接请求哪个 Service 做业务哪个 Module 组装它们依赖从哪里注入这个接口有没有 GuardJWT 相关插件在这条链路上各负责哪一段下一篇可以讲统一响应与异常处理——为什么业务代码抛UnauthorizedException前端却总能收到结构一致的错误包。系列导航上一篇NestJS 入门2依赖注入到底解决了什么问题第一篇NestJS 入门1先搞懂 Module、Controller、Service

相关新闻

5个技巧掌握RVC语音转换:10分钟打造专属AI音色

5个技巧掌握RVC语音转换:10分钟打造专属AI音色

5个技巧掌握RVC语音转换&#xff1a;10分钟打造专属AI音色 【免费下载链接】Retrieval-based-Voice-Conversion-WebUI Easily train a good VC model with voice data < 10 mins! 项目地址: https://gitcode.com/GitHub_Trending/re/Retrieval-based-Voice-Conversion-Web…

2026/10/27 7:27:41 阅读更多 →
gpt-bird配置文件详解:config.json中的tokenizer与游戏引擎参数解析

gpt-bird配置文件详解:config.json中的tokenizer与游戏引擎参数解析

gpt-bird配置文件详解&#xff1a;config.json中的tokenizer与游戏引擎参数解析 【免费下载链接】gpt-bird 项目地址: https://ai.gitcode.com/hf_mirrors/gmmeyer/gpt-bird gpt-bird是一款结合AI与游戏引擎的创新项目&#xff0c;其核心配置文件config.json包含了toke…

2026/10/28 4:43:48 阅读更多 →
系统级守护进程配置:systemd/launchd/NSSM实现Docuum自动运行

系统级守护进程配置:systemd/launchd/NSSM实现Docuum自动运行

系统级守护进程配置&#xff1a;systemd/launchd/NSSM实现Docuum自动运行 【免费下载链接】docuum Docuum performs least recently used (LRU) eviction of Docker images. &#x1f5d1;️ 项目地址: https://gitcode.com/gh_mirrors/do/docuum Docuum是一款轻量级Doc…

2026/10/25 22:26:55 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/10/28 23:48:14 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/10/28 7:28:00 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/28 9:02:11 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/10/28 22:10:30 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/10/28 9:00:54 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/10/28 7:29:02 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/28 16:58:40 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/28 9:01:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/28 19:25:09 阅读更多 →