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

xDr*_*ago 8 websocket socket.io nestjs

我正在为我的项目使用默认的护照 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);
  }
Run Code Online (Sandbox Code Playgroud)

在客户端我发送这样的:

   this.socket.emit('getUserList', users => {
          console.log(users);
          this.userListSub.next(users);
        });
Run Code Online (Sandbox Code Playgroud)

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

xDr*_*ago 13

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

  @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];
  }
Run Code Online (Sandbox Code Playgroud)

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

this.socketOptions = {
   transportOptions: {
     polling: {
       extraHeaders: {
         Authorization: 'your token', //'Bearer h93t4293t49jt34j9rferek...'
       }
     }
   }
};
...
this.socket = io.connect('http://localhost:4200/', this.socketOptions);
...
Run Code Online (Sandbox Code Playgroud)

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

这里也是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;
    }
  }
}

Run Code Online (Sandbox Code Playgroud)

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

  • 如果令牌被正确解码,则意味着它已经被服务器签名。重新检查用户是否确实在数据库中是不是太过分了?由于服务器首先对其进行了签名。 (2认同)

arm*_*6er 9

在回答问题时,我想指出 Guard 不能用于防止未经授权的用户建立连接。

它仅可用于保护特定事件。

handleConnection带有注释的类的方法@WebSocketGatewaycanActivate您的 Guard之前调用。

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

  async handleConnection(client: Socket) {
    const payload = this.authService.verify(
      client.handshake.headers.authorization,
    );
    const user = await this.usersService.findOne(payload.userId);
    
    !user && client.disconnect();
  }
Run Code Online (Sandbox Code Playgroud)

  • 我只想补充一点,如果您只验证handleConnection上的令牌,如果令牌无效,则客户端会在很短的时间内连接并能够在验证失败断开连接之前发送其他事件。 (8认同)

Fel*_*ati 7

谢谢!最后,我实现了一个 Guard,就像 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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 在查询字符串(= URL)中传递令牌不是很糟糕吗?这个很有可能会泄露。 (3认同)