Azure SignalR 无服务器模式,通过 Azure 函数应用协商失败

Azure SignalR serverless mode, Negotiate fails through Azure function app

提问人:rebailey 提问时间:11/9/2023 最后编辑:rebailey 更新时间:11/9/2023 访问量:65

问:

我一直在尝试弄清楚是否有办法使用 Azure SignalR 无服务器模式通过不允许未经身份验证的请求的 Azure 函数应用进行协商。这里的关键问题是不允许未经身份验证的请求部分。关闭此功能可使此功能正常工作,但我目前已将此设置设置为通过我的身份提供商进行身份验证(出于安全原因)。当然,这在本地运行时是有效的,因为这不需要经过身份验证的请求。我认为这是从 Azure SignalR 服务返回的通信问题,没有正确的身份验证来与函数应用通信(以设置连接)。这也不是访问此函数应用程序的授权问题,因为我有 40 个其他函数,可以通过我的应用程序访问。

函数应用身份验证

使用类似此代码链接的示例

无服务开发

我已经在 StackOverFlow 中搜索了有关此问题的其他问题,其他评论中的人也问过这个问题,但前几年没有关于类似问题的回复答案。有谁知道有一种方法可以让它以这种方式工作?

我尝试以两种方式将 azure 标识访问角色添加到服务中,看看这是否允许特定访问。这允许我的函数应用请求访问 SignalR 实例。但这不是初始访问的问题,因为对 SignalR 实例的访问也可以正常使用访问密钥。

我试图在 Kudu 控制台中查找日志记录,我唯一能找到的是实际通过的函数调用。由于这是函数应用上的身份验证设置阻止的 401 未经授权,因此我找不到这方面的日志,或者无法通过 kudu 控制台访问它们。(通过国际空间站程序文件以及其他文件进行搜索)。

使用了很多方法,但这是利用 Microsoft.Azure.Webjobs.extensions.SignalRService 的通用方法的一个示例。

正如一般注释中要求的代码:

public class SignalRTestHub : ServerlessHub
{
  [FunctionName("negotiate")]
   public SignalRConnectionInfo 
   Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest 
    req)
{
    return Negotiate(req.Headers["x-ms-signalr-user-id"], 
    GetClaims(req.Headers["Authorization"]));
}

[FunctionName(nameof(OnConnected))]
public async Task OnConnected([SignalRTrigger]InvocationContext 
invocationContext, ILogger logger)
{
    await Clients.All.SendAsync(NewConnectionTarget, new 
NewConnection(invocationContext.ConnectionId));
    logger.LogInformation($"{invocationContext.ConnectionId} has 
connected");
}

[FunctionName(nameof(Broadcast))]
public async Task Broadcast([SignalRTrigger]InvocationContext 
invocationContext, string message, ILogger logger)
{
    await Clients.All.SendAsync(NewMessageTarget, new 
NewMessage(invocationContext, message));
    logger.LogInformation($"{invocationContext.ConnectionId} 
broadcast {message}");
}

[FunctionName(nameof(OnDisconnected))]
public void OnDisconnected([SignalRTrigger]InvocationContext 
invocationContext)
{
}

}

另一个例子:

 [FunctionName("negotiate")]
 public static SignalRConnectionInfo Negotiate(
 [HttpTrigger(AuthorizationLevel.Anonymous, Route = "negotiate")] 
 HttpRequest req,
 [SignalRConnectionInfo(HubName = AzureConstants.SignalRHub , 
 UserId = "{headers.x-ms-client-principal-name}")] 
 SignalRConnectionInfo connectionInfo,
 ILogger log
 )
 {
    return connectionInfo;
 }

当允许未经身份验证的请求时,这两种方法都适用于 Azure 函数,但当它设置为 not 时则不然。

!!!已解决:对于任何有此类问题的未来读者: 我有一些错误的假设,angular/signalr 包不像开箱即用的 HTTP 客户端那样工作,并且剥离了身份验证令牌。您需要针对 API 进行身份验证。仅使用来自 MSAL 的数据,可以通过传入带有标头的持有者令牌并设置 Access-Control-Allow-Headers: X-Auth-Token 来执行此操作。

给我的。。不确定这是否适用于其他人,但我会在这里放一些通用代码来解释这一点。我使用 angular MSAL 库通过访问 api 范围进行身份验证。然后,我从缓存中检查令牌,以查找我使用持有者令牌的令牌,这是我从登录到此特定 api 时收到的令牌。免责声明:不保证这是做到这一点的好方法。这只是一种通过提取从 Msal 接收的数据来使其工作的方法,假设你以某种方式设置了 Azure 环境。

export type MsalTokenClass =
{
cachedAt: number;
clientId: string;
credentialType: string;
environment: string;
expiresOn: number;
extendedExpiresOn: number;
homeAccountId: string;
realm: string;
secret : string;
target: string;
tokenType: string;
}

isMyType(o: any): o is MsalTokenClass {
    return "cachedAt" in o && "clientId" in o && "credentialType" 
in o && "environment" in o && "expiresOn" in o && 
"extendedExpiresOn" in o && "homeAccountId" in o && "realm" in o && 
"secret" in o && "target" in o && "tokenType" in o;
}

getAndSetApiToken()
{
    let cache :any  = this.authService.instance.getTokenCache();
    let getStorage = cache.storage.browserStorage.windowStorage;

    for(const key in getStorage)
    {
      let current = getStorage[key];
      let parsed : any; 

      try 
      {
          parsed = JSON.parse(current);

          if(this.isMyType(parsed))
          {
              console.log("Current parsed = ", parsed);
              console.log("Current parsed target = ", 
              parsed.target);
                //this is like 
               this should mirror what you setup in the 
              msalinterceptorConfigFactory protectedResourceMap 
              scope 
              i.e. 
              https://youtenant.com/myapi/api.ReadWrite
              if(parsed.target ===  
               environment.resourceIds.apiScope + api.ReadWrite
              )
              {
                  console.log("Current Bearer = ", parsed.secret);
                  this.currentBearerToken = parsed.secret;
              }
          }
          else
          {
              console.log("not the right type");
          }
      }
      catch{
          console.log("Not Json type");
          continue;
      }
   
    }
}

 public startConnection = () => 
{
    this.getAndSetApiToken();
    this.hubConnection = new signalR.HubConnectionBuilder()
    .withUrl(this.apiAddress,
    {        
        headers: 
        { 
            'Authorization': 'Bearer '+ this.currentBearerToken,
            'Access-Control-Allow-Headers':'X-Auth-Token'
        },
    })
    .configureLogging(LogLevel.Trace)
    .build();

    this.hubConnection
        .start()
        .then(() => 
        {   
        })
        .catch(err => console.log('Error while starting connection: 
        ' + err))
}
azure-functions signalr

评论

0赞 Sampath 11/10/2023
解决了吗?!在 js 中求解。
0赞 rebailey 11/23/2023
它是在 js(ts) 中解决的,是的。感谢您的检查。.不确定如何平仓?
0赞 Sampath 11/23/2023
只需在“您的答案”中发布您的答案

答: 暂无答案