AuthGuard返回true但HTTP返回401 Unauthorized的NestJS认证问题
NestJS会话认证异常:AuthGuard验证通过但接口返回401
我使用NestJS、Express Sessions和Passport实现会话认证(非JWT令牌认证),尽管自定义AuthGuard的日志明确输出User authenticated: true,但调用标注了@UseGuards(AuthGuard)的API接口时,仍会返回{"statusCode": 401, "message": "Unauthorized"}。
自定义AuthGuard代码
@Injectable() export class AuthGuard implements CanActivate { private readonly logger = new Logger(AuthGuard.name); canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); const isAuthenticated = request.isAuthenticated(); this.logger.log(`User authenticated: ${isAuthenticated}`); this.logger.log(`Request headers: ${JSON.stringify(request.headers)}`); this.logger.log(`Request user: ${JSON.stringify(request.user)}`); this.logger.log(`Session: ${JSON.stringify(request.session)}`); return isAuthenticated; } }
控制台输出示例:
LOG [AuthGuard] User authenticated: true
LocalAuthGuard代码
@Injectable() export class LocalAuthGuard extends AuthGuard('local') { async canActivate(context: ExecutionContext): Promise<boolean> { const result = (await super.canActivate(context)) as boolean; const request = context.switchToHttp().getRequest(); await super.logIn(request); return result; } }
AuthService代码
@Injectable() export class AuthService { constructor( private readonly userService: UsersService, private jwtService: JwtService, ) {} async validateUser(username: string, password: string): Promise<User> { const user: User = await this.userService.validateUser(username, password); if (!user) { throw new UnauthorizedException('Invalid credentials'); } return user; } }
LocalStrategy代码
@Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { constructor(@Inject('AUTH_SERVICE') private authService: AuthService) { super(); } async validate(username: string, password: string): Promise<User> { return this.authService.validateUser(username, password); } }
AuthController代码
@Controller('auth') export class AuthController { /** * This is a POST request to /auth/login that logs in a user * and returns the user object * * @param {e.Request} req - the request object * @returns {Promise<Express.User>} - the user object */ @Throttle({ default: { limit: 5, ttl: 300000 } }) // 5 requests per 5 minutes @UseGuards(LocalAuthGuard) @Post('login') async login(@Request() req: ExpressRequest): Promise<Express.User> { // Passport automatically attaches user to the request object return req.user; } /** * This is a POST request to /auth/logout that logs out a user * and returns a message * * @param {e.Request} req - the request object * @param {e.Response} res - the response object * @param {e.NextFunction} next - the next function * @returns {Promise<{message: string}>} - a message */ @UseGuards(AuthGuard) @Throttle({ default: { limit: 5, ttl: 60000 } }) // 5 requests per minute @Post('logout') async logout( @Request() req: ExpressRequest, @Res() res: Response, @Next() next: NextFunction, ): Promise<{ message: string }> { req.logout(function (err) { if (err) { return next(err); } // Clear the session cookie req.session = null; res.redirect('/'); }); return { message: 'Logged out successfully' }; } /** * This is a GET request to /auth/session that returns the session * and the session id. * * @param {e.Request} req - the request object * @returns {Promise<{session: session.Session, sessionId: string}>} - the session and the session id */ @Get('session') @UseGuards(AuthGuard) async getAuthSession( @Request() req: ExpressRequest, ): Promise<{ session: Session; sessionId: string }> { const session: Session = req.session; return { session: session, sessionId: session.id, }; } }
SessionSerializer代码
@Injectable() export class SessionSerializer extends PassportSerializer { constructor(private readonly usersService: UsersService) { super(); } serializeUser(user: User, done: (err: Error, user: User) => void) { done(null, user); } async deserializeUser( sessionUser: User, done: (err: Error, user: User) => void, ) { const user = await this.usersService.findUserById(sessionUser.id); return user ? done(null, user) : done(null, null); } }
Main.ts代码
require('mysql2'); async function bootstrap(): Promise<void> { const app = await NestFactory.create(AppModule); // CORS ... const mySqlStore = require('express-mysql-session')(session); const APP_PORT: string = process.env.APP_PORT || '3000'; const IN_PROD: boolean = process.env.NODE_ENV === 'production'; const TEN_MINUTES: number = 1000 * 60 * 10; const options = { connectionLimit: 10, connectTimeout: 10000, host: process.env.HOST, port: parseInt(process.env.PORT), // db port user: process.env.USER, password: process.env.PASSWORD, database: process.env.DATABASE, createDatabaseTable: true, }; const pool: mysql.Pool = mysql.createPool(options); const sessionStore = new mySqlStore(options, pool); // Set up the session middleware app.use( session({ name: process.env.SESS_NAME, genid: function () { return uuidV4(); }, secret: process.env.SESS_SECRET, resave: false, saveUninitialized: false, store: sessionStore, cookie: { httpOnly: true, secure: IN_PROD, maxAge: TEN_MINUTES, sameSite: 'lax', // 'lax' for local testing }, rolling: true, // Reset the maxAge on every request }), ); // Initialize and set up the Passport session middleware app.use(passport.initialize()); app.use(passport.session()); await app.listen(APP_PORT); } bootstrap().catch((err) => console.error(err));
关键异常现象
- 即使接口返回401未授权,已登录用户的实际操作仍会执行(例如点赞帖子,错误响应返回但点赞记录已生成)
- 问题近期才出现,疑似版本更新引入的bug,但未找到同类讨论案例
内容的提问来源于stack exchange,提问作者CairoMisr
相关产品推荐
相关产品推荐

