You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter应用对接SignalR [ABP]时认证失败求助

Flutter SignalR 认证问题:后端接收Token但Hub连接isAuthenticated为false

问题描述

我在将Flutter应用连接到SignalR时遇到认证问题:后端服务器已正确接收Token,但Hub连接的isAuthenticated始终为false,尝试多种方法均未解决,寻求帮助。

使用的包

  • signalr_netcore(基于master分支的Git仓库)

flutter doctor -v 输出

[√] Flutter (Channel master, 3.16.0-5.0.pre.15, on Microsoft Windows [Version 10.0.22621.2283], locale en-US)
    • Flutter version 3.16.0-5.0.pre.15 on channel master at C:\src\flutter
    • Framework revision fe0275f9d5 (22 hours ago), 2023-10-05 05:57:26 -0400
    • Engine revision f8d53c5fe9
    • Dart version 3.2.0 (build 3.2.0-231.0.dev)
    • DevTools version 2.28.1

[√] Windows Version (Installed version of Windows is version 10 or higher)

[√] Android toolchain - develop for Android devices (Android SDK version 33.0.2)
    • Android SDK at C:\Users\muonroi\AppData\Local\Android\Sdk
    • Platform android-33-ext5, build-tools 33.0.2
    • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java
    • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-9505619)
    • All Android licenses accepted.

[√] Chrome - develop for the web
    • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop Windows apps (Visual Studio Enterprise 2022 17.6.5)
    • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Enterprise
    • Visual Studio Enterprise 2022 version 17.6.33829.357
    • Windows 10 SDK version 10.0.22000.0

[√] Android Studio (version 2022.1)
    • Android Studio at C:\Program Files\Android\Android Studio
    • Flutter plugin可安装
    • Dart plugin可安装
    • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-9505619)

[√] VS Code (version 1.83.0)
    • VS Code at C:\Users\muonroi\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension version 3.74.0

[√] Connected device (4 available)
    • Android SDK built for x86 (mobile) • emulator-5554 • android-x86    • Android 10 (API 29) (emulator)
    • Windows (desktop)                  • windows       • windows-x64    • Microsoft Windows [Version 10.0.22621.2283]
    • Chrome (web)                       • chrome        • web-javascript • Google Chrome 117.0.5938.134
    • Edge (web)                         • edge          • web-javascript • Microsoft Edge 117.0.2045.47

[√] Network resources
    • All expected network resources are available.

• No issues found!

Flutter Hub连接代码

// Flutter code to connect to the hub
await _initSharedPreferences();
_hubConnection = HubConnectionBuilder()
    .withUrl(
        SignalrCentral.chatStatus,
        options: HttpConnectionOptions(
            accessTokenFactory: () async {
                return _sharedPreferences
                    .getString(KeyToken.accessToken.name)!;
            },
            headers: SignalrCentral.getHeader(
                _sharedPreferences.getString(KeyToken.encToken.name)!)))
    .withAutomaticReconnect(retryDelays: [30000])
    .build();
await initHubAndListenStatus();

Future<void> initHubAndListenStatus() async {
    try {
        await _hubConnection.start();
        debugPrint('Connection established successfully');
    } catch (e) {
        print('Error starting hub connection: $e');
    }
}

后端服务器认证代码

/* This method is needed to authorize SignalR javascript client.
 * SignalR can not send authorization header. So, we are getting it from query string as an encrypted text. */
private static Task QueryStringTokenResolver(MessageReceivedContext context)
{
    if (context.HttpContext.Request.Path.Value.StartsWith("/signalr"))
    {
        var env = context.HttpContext.RequestServices.GetService<IWebHostEnvironment>();
        var config = env.GetAppConfiguration();
        var allowAnonymousSignalRConnection = bool.Parse(config["App:AllowAnonymousSignalRConnection"]);

        return SetToken(context, allowAnonymousSignalRConnection);
    }

    List<string> urlsUsingEnchAuthToken = new List<string>()
    {
        "/Chat/GetUploadedObject?",
        "/Profile/GetProfilePictureByUser?"
    };

    if (urlsUsingEnchAuthToken.Any(url => context.HttpContext.Request.GetDisplayUrl().Contains(url)))
    {
        if (context.HttpContext.Request.Headers.ContainsKey("authorization"))
        {
            return Task.CompletedTask;
        }

        return SetToken(context, false);
    }

    return Task.CompletedTask;
}


private static Task SetToken(MessageReceivedContext context, bool allowAnonymous)
{
    var qsAuthToken = context.HttpContext.Request.Query["enc_auth_token"].FirstOrDefault() ?? context.HttpContext.Request.Headers["enc_auth_token"].FirstOrDefault();
    if (qsAuthToken == null)
    {
        if (!allowAnonymous)
        {
            throw new AbpAuthorizationException("SignalR auth token is missing.");
        }

        return Task.CompletedTask;
    }
    //Set auth token from cookie
    context.Token = SimpleStringCipher.Instance.Decrypt(qsAuthToken, AppConsts.DefaultPassPhrase);
    return Task.CompletedTask;
}

排查与解决方案

1. 匹配Token传递方式

后端QueryStringTokenResolver优先从**查询字符串enc_auth_token或请求头enc_auth_token**获取加密Token,但当前Flutter代码的传递方式不匹配:

  • accessTokenFactory返回的明文Token会被自动放入Authorization: Bearer {token}头,后端未处理该头的SignalR认证逻辑
  • 需调整Flutter代码,将加密Token放到后端期望的位置:
// 方式1:通过查询参数传递
_hubConnection = HubConnectionBuilder()
    .withUrl(
        "${SignalrCentral.chatStatus}?enc_auth_token=${Uri.encodeComponent(_sharedPreferences.getString(KeyToken.encToken.name)!)}",
        options: HttpConnectionOptions(
            // 若后端支持明文Token可保留,否则删除
            accessTokenFactory: () async {
                return _sharedPreferences.getString(KeyToken.accessToken.name)!;
            },
        ))
    .withAutomaticReconnect(retryDelays: [30000])
    .build();

// 方式2:通过请求头传递
_hubConnection = HubConnectionBuilder()
    .withUrl(
        SignalrCentral.chatStatus,
        options: HttpConnectionOptions(
            headers: {
                "enc_auth_token": _sharedPreferences.getString(KeyToken.encToken.name)!
            },
        ))
    .withAutomaticReconnect(retryDelays: [30000])
    .build();

2. 验证Token解密与身份验证流程

  • 在后端SetToken方法中添加日志,输出解密后的context.Token,确认其格式、签名、有效期均正确
  • 检查系统身份验证中间件(如JWT)是否能正确解析该Token,确保用户身份信息被正确注入
  • 确认SignalR Hub已添加[Authorize]特性,且认证方案已在AddSignalR中配置:
services.AddSignalR(options =>
{
    options.AuthorizationEnabled = true;
});

3. 检查SignalR中间件顺序

确保身份验证中间件在SignalR中间件之前注册:

app.UseAuthentication();
app.UseAuthorization();
app.UseSignalR(routes =>
{
    routes.MapHub<ChatHub>("/signalr/chat");
});

4. 启用客户端日志排查

在Flutter中添加SignalR连接日志,捕获握手阶段的错误信息:

_hubConnection = HubConnectionBuilder()
    .withUrl(...)
    .build();

// 添加状态监听
_hubConnection.onclose((error) => print("Connection closed: $error"));
_hubConnection.onreconnecting((error) => print("Reconnecting: $error"));

// 调用授权方法验证权限
try {
    await _hubConnection.start();
    debugPrint('Connection ID: ${_hubConnection.connectionId}');
    // 调用一个需要认证的Hub方法,触发权限检查
    await _hubConnection.invoke("GetUserStatus");
} catch (e) {
    print('Connection error: ${e.toString()}');
}

内容的提问来源于stack exchange,提问作者Muon Roi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 13:54:49