繁体   English   中英

令牌过期后在 signalR 更新令牌

[英]renew token in signalR after expired token

我通过 SignalR 从客户端(angular 9)和服务器(asp.net core 3.1)创建实时连接,并通过 JWT 令牌授权集线器,例如以下代码:

 private createConnection() {
      this.hubConnection = new HubConnectionBuilder().withUrl(`${this.appConfig.hubEndpoint}/Hubs`,
        { accessTokenFactory: () => jwtToken })
        .withAutomaticReconnect()
        .build();
  }

  private startConnection(): void {
    this.hubConnection
      .start()
      .then(() => {
        this.connectionIsEstablished = true;
        this.connectionEstablished.emit(true);
      })
      .catch(err => {
        console.log('Error while establishing connection, retrying...');
      });
  }

这工作正常,直到令牌过期。 根据我的研究,在收到带有刷新令牌的新令牌后,应该停止先前的连接并应该使用新令牌创建新连接。 现在我想知道我应该怎么做? 我必须经常检查令牌吗? 还是应该通过向服务器发送每个请求来解决这个问题?

当令牌到期时,服务器将断开连接,您将在服务器端遇到错误。 我相信你会得到Method now allowed

因此,您需要捕获此令牌过期错误并断开连接,以便您可以使用新令牌启动新的连接。

对我有用的是,这是一个快速而肮脏的修复,是在关闭事件时重新加载页面:

this.hubConnection.onclose(() =>{
  window.location.reload()
})

解释

我正在使用 RxJS 包装连接过程,因此对我的情况更好的解决方法是抛出错误而不是重新加载页面并使用retryWhen运算符捕获它。 但由于这是一个硬错误(需要等待 1 小时让令牌过期,并且本地模拟器不关心令牌......),我只是更喜欢 go 使用这个临时解决方案。

我想出的解决方案是通过扩展它使用的signalR.DefaultHttpClient来拦截 signalR 客户端的身份验证调用。 如果有 401 然后我刷新令牌(通过我的authService ),然后重试调用:

Typescript:

const getAuthHeaders = () => {
  return {
    Authorization: `Bearer ${authService.getToken()?.accessToken}`,
  };
};

class CustomHttpClient extends signalR.DefaultHttpClient {
  constructor() {
    super(console); // the base class wants an signalR.ILogger
  }
  public async send(
    request: signalR.HttpRequest
  ): Promise<signalR.HttpResponse> {
    const authHeaders = getAuthHeaders();
    request.headers = { ...request.headers, ...authHeaders };

    try {
      const response = await super.send(request);
      return response;
    } catch (er) {
      if (er instanceof signalR.HttpError) {
        const error = er as signalR.HttpError;
        if (error.statusCode == 401) {
          //token expired - trying a refresh via refresh token
          await authService.refresh();
          const authHeaders = getAuthHeaders();
          request.headers = { ...request.headers, ...authHeaders };
        }
      } else {
        throw er;
      }
    }
    //re try the request
    return super.send(request);
  }
}

const connection = new signalR.HubConnectionBuilder()
  .withUrl("/MyHub", {
// use the custom client
    httpClient: new CustomHttpClient(),
  })
  .configureLogging(signalR.LogLevel.Information)
  .build();

在此处查看.withUrl(..)的选项: https://docs.microsoft.com/en-us/aspnet/core/signalr/configuration?view=aspnetcore-6.0&tabs=dotnet

根据微软文档,必须在 accessToken function 中更新令牌。

“在 SignalR 发出的每个 HTTP 请求之前调用提供的访问令牌 function。如果需要更新令牌以保持连接处于活动状态,请从这个 function 中执行此操作并返回更新的令牌。可能需要更新令牌所以它在连接期间不会过期。”

如果是上面的链接,则需要在“this.loginToken”中完成。

我有同样的问题,我以这种方式解决了。 我正在使用 Angular 14 和 .NET Core 6。

首先,我在用于更新访问令牌的服务中创建一个Subject<string> ,当我更新它时我会在其中发出新的访问令牌(或者当我注销时发出一个空字符串)。

tokenRefreshed$: Subject<string> = new Subject();
public PersistTokens(accessToken?: string, refreshToken?: string) {
  this.tokenRefreshed$.next(accessToken??'');
  SetJWTAccessToken(accessToken??'');
  SetJWTRefreshToken(refreshToken??'');
}

之后,在我启动 signalr 集线器连接的服务中,我订阅了之前描述的主题......然后我在连接请求中使用新的访问令牌。

constructor(private appService: ApplicationService, private jwtTokenService:JwtTokenService) {
  // first hub connection on application start or page refresh (I want to allow anonymous siglr connection too)
  this.startConnection(GetJWTAccessToken()??'');
 
  // if user logged in restart the hub connection to handle the new token
  jwtTokenService.tokenRefreshed$.subscribe(
    token => {
      // if already connected stop existing connection
      if(this.hubConnection && this.hubConnection.state === signalR.HubConnectionState.Connected) {
        this.hubConnection.stop();
      }
      this.startConnection(token);
    }
  );
}

async startConnection(accesToken:string) {
  this.hubConnection = new signalR.HubConnectionBuilder()      
    .withUrl(`${this.appService.appConfiguration.apiEndpointUrl}/hubs/notifyHub`, {
      skipNegotiation: true,
      transport: signalR.HttpTransportType.WebSockets,
      accessTokenFactory: () => {
        return accesToken;
    }
  })
  .build();

this.hubConnection
  .start()
  .then(() => console.debug('Notify hub connected'))
  // .then(() => this.getConnectionId())
  .catch(err => console.log('Error while starting Notify hub connection: ' + err));

this.hubConnection.on('NotifyOrderCreated', (data) => {
  this.notifyOrderCreated$.emit(data);
});
// Other messages subscriptions ...

}

function GetJWTAccessToken()只是读取浏览器存储中保存的访问令牌。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM