NESTJS 网关/Websocket - 如何通过 socket.emit 发送 jwt access_token

Posted

技术标签:

【中文标题】NESTJS 网关/Websocket - 如何通过 socket.emit 发送 jwt access_token【英文标题】:NESTJS Gateway / Websocket - how to send jwt access_token through socket.emit 【发布时间】:2020-02-28 10:03:48 【问题描述】:

我正在为我的项目使用默认护照 jwt AuthGuard。这适用于我的帖子并在设置身份验证标头时获得路由。

现在我想在客户端使用 Nestjs 网关和 socket.io,但我不知道如何将 access_token 发送到网关?

这基本上是我的网关:

@WebSocketGateway()
export class UserGateway 

  entityManager = getManager();

  @UseGuards(AuthGuard('jwt'))
  @SubscribeMessage('getUserList')
  async handleMessage(client: any, payload: any) 
    const results = await this.entityManager.find(UserEntity);
    console.log(results);
    return this.entityToClientUser(results);
  

在客户端我这样发送:

   this.socket.emit('getUserList', users => 
          console.log(users);
          this.userListSub.next(users);
        );

如何以及在何处添加 jwt access_token?对于 Websockets,nestjs 的文档完全忽略了这一点。他们只是说,Guards 对 websockets 的工作与他们对 post/get 等的工作完全相同。见here

【问题讨论】:

【参考方案1】:

对于任何寻求解决方案的人。这里是:

  @UseGuards(WsGuard)
  @SubscribeMessage('yourRoute')
  async saveUser(socket: Socket, data: any) 
    let auth_token = socket.handshake.headers.authorization;
    // get the token itself without "Bearer"
    auth_token = auth_token.split(' ')[1];
  

在客户端,您可以像这样添加授权标头:

this.socketOptions = 
   transportOptions: 
     polling: 
       extraHeaders: 
         Authorization: 'your token', //'Bearer h93t4293t49jt34j9rferek...'
       
     
   
;
...
this.socket = io.connect('http://localhost:4200/', this.socketOptions);
...

之后,您可以访问每个请求服务器端的令牌,如示例中所示。

这里也是我实现的WsGuard

@Injectable()
export class WsGuard implements CanActivate 

  constructor(private userService: UserService) 
  

  canActivate(
    context: any,
  ): boolean | any | Promise<boolean | any> | Observable<boolean | any> 
    const bearerToken = context.args[0].handshake.headers.authorization.split(' ')[1];
    try 
      const decoded = jwt.verify(bearerToken, jwtConstants.secret) as any;
      return new Promise((resolve, reject) => 
        return this.userService.findByUsername(decoded.username).then(user => 
          if (user) 
            resolve(user);
           else 
            reject(false);
          
        );

      );
     catch (ex) 
      console.log(ex);
      return false;
    
  


我只是检查我是否可以使用我的用户服务从我的数据库中的解码令牌中找到具有用户名的用户。我相信你可以让这个实现更简洁,但它确实有效。

【讨论】:

您好,我也遇到了同样的问题,您能再添加一些代码吗? 在初始化套接字本身时,您必须将令牌添加到客户端的套接字连接中。在那里你可以设置标题。我为客户端编辑了答案。 谢谢!我现在遇到了守卫问题,你自己实现了 WsGuard 吗? 嗨,没问题! WsGuard 是我用CanActivate 实现的。我也在答案中添加了我的守卫实现:) 如果token解码正确,说明已经被服务器签名。重新检查用户是否真的在数据库中是不是有点矫枉过正?由于服务器首先对其进行了签名。【参考方案2】:

虽然问题已得到解答,但我想指出 Guard 无法用于防止未经授权的用户建立连接。

它只用于保护特定事件。

使用@WebSocketGateway 注释的类的handleConnection 方法在您的Guard 的canActivate 之前调用。

我最终在我的网关类中使用了类似的东西:

  async handleConnection(client: Socket) 
    const payload = this.authService.verify(
      client.handshake.headers.authorization,
    );
    const user = await this.usersService.findOne(payload.userId);
    
    !user && client.disconnect();
  

【讨论】:

【参考方案3】:

谢谢!最后,我实现了一个类似于 jwt 守卫的守卫将用户放入请求中。最后我使用来自套接字客户端的查询字符串方法来传递身份验证令牌这是我的实现:

import  CanActivate, ExecutionContext, Injectable, Logger  from '@nestjs/common';
import  WsException  from '@nestjs/websockets';
import  Socket  from 'socket.io';
import  AuthService  from '../auth/auth.service';
import  User  from '../auth/entity/user.entity';

@Injectable()
export class WsJwtGuard implements CanActivate 
  private logger: Logger = new Logger(WsJwtGuard.name);

  constructor(private authService: AuthService)  

  async canActivate(context: ExecutionContext): Promise<boolean> 

    try 
      const client: Socket = context.switchToWs().getClient<Socket>();
      const authToken: string = client.handshake?.query?.token;
      const user: User = await this.authService.verifyUser(authToken);
      client.join(`house_$user?.house?.id`);
      context.switchToHttp().getRequest().user = user

      return Boolean(user);
     catch (err) 
      throw new WsException(err.message);
    
  

【讨论】:

在查询字符串(= URL)中传递令牌不是很糟糕吗?这很可能会泄漏。

以上是关于NESTJS 网关/Websocket - 如何通过 socket.emit 发送 jwt access_token的主要内容,如果未能解决你的问题,请参考以下文章

NestJs Websocket,如何测试和调试“Socket hang up”?

Nestjs如何同时使用http请求和Websocket

如何在 NestJS 中热重载联邦网关

将 NestJS 连接到 websocket 服务器

如何将响应放在 websocket api 网关后面的单独 lambda 中?

Nest.js WebSocket