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

Flutter集成Supabase实现Microsoft登录后无法跳转回应用求助

Flutter微软登录完成后无法跳转回应用的问题

我在Flutter应用中开发了Microsoft登录功能,调用signInWithMicrosoft()方法时,集成浏览器会打开账号登录界面,完成登录并确认「Only continue if you downloaded the app from a store or website you trust.」提示后,页面陷入循环无法跳转回应用。以下是我的相关代码配置:

登录逻辑代码

// ...
Future<AuthResponse> signInWithMicrosoft() async {
  // Just a random string
  final rawNonce = generateRandomString();
  final hashedNonce = sha256.convert(utf8.encode(rawNonce)).toString();

  /// Client ID that you registered with Microsoft Azure.
  const clientId = '1ecf76c4-433e-43f9-a30f-2c669f031229';

  /// Redirect URI that you registered with Microsoft Azure.
  const redirectUrl =
      'msauth://dev.blueflame.canvas_plus/uUyjEmnuSAzNkfi4rwzLZCKmIVM%3D';

  /// Microsoft OpenID Connect discovery URL.
  const discoveryUrl =
      'https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration';

  const appAuth = FlutterAppAuth();

  // authorize the user by opening the concent page
  final result = await appAuth.authorize(
    AuthorizationRequest(
      clientId,
      redirectUrl,
      discoveryUrl: discoveryUrl,
      nonce: hashedNonce,
      scopes: [
        'openid',
        'email',
        'profile',
      ],
    ),
  );

  if (result == null) {
    throw 'No result';
  }

  // Request the access and id token to Microsoft
  final tokenResult = await appAuth.token(
    TokenRequest(
      clientId,
      redirectUrl,
      authorizationCode: result.authorizationCode,
      discoveryUrl: discoveryUrl,
      codeVerifier: result.codeVerifier,
      nonce: result.nonce,
      scopes: [
        'openid',
        'email',
        'profile',
      ],
    ),
  );

  final idToken = tokenResult?.idToken;

  if (idToken == null) {
    throw 'No idToken';
  }

  return supabase.auth.signInWithIdToken(
    provider: Provider.azure,
    idToken: idToken,
    nonce: rawNonce,
  );
}
// ...

main.dart中Supabase初始化配置

await Supabase.initialize(
    url: 'https://hkyajejasstzlneelmwm.supabase.co',
    anonKey:
        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhreWFqZWphc3N0emxuZWVsbXdtIiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODc3NDk5OTEsImV4cCI6MjAwMzMyNTk5MX0.iRJXHt6bjaQ55GOfVzI6Nd5IPMRkUPTVpjjgdauLHoI',
    authCallbackUrlHostname: 'auth',
    debug: true,
  );

build.gradle配置

def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
    namespace "dev.blueflame.canvas_plus"
    compileSdkVersion flutter.compileSdkVersion
    ndkVersion flutter.ndkVersion

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = '1.8'
    }

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "dev.blueflame.canvas_plus"
        // You can update the following values to match your application needs.
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
        minSdkVersion 19
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
        manifestPlaceholders += [appAuthRedirectScheme: 'canvasplus://account/redirect/']
    }

    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }
}

flutter {
    source '../..'
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

AndroidManifest.xml配置

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="canvas_plus"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name="com.microsoft.identity.client.BrowserTabActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="msauth"
                    android:host="dev.blueflame.canvas_plus"
                    android:path="/uUyjEmnuSAzNkfi4rwzLZCKmIVM%3D" />
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

问题

请问无法跳转回应用的原因是什么?


问题原因及修复方案

1. Redirect Scheme不匹配

你的build.gradle中配置的appAuthRedirectScheme是canvasplus://account/redirect/,但登录代码中使用的redirectUrl是msauth://dev.blueflame.canvas_plus/uUyjEmnuSAzNkfi4rwzLZCKmIVM%3D,同时Manifest中的intent-filter使用的是msauth scheme。这里存在两处冲突:

  • appAuthRedirectScheme必须和登录代码中的redirectUrl的scheme一致,也就是msauth
  • 确保Azure后台注册的redirectUri和代码中的完全一致,包括path部分的编码

修复:修改build.gradle中的appAuthRedirectScheme为msauth:

manifestPlaceholders += [appAuthRedirectScheme: 'msauth']

2. Supabase Auth回调配置冲突

你在Supabase初始化时设置了authCallbackUrlHostname: 'auth',但当前登录流程是通过FlutterAppAuth直接处理微软登录,再将idToken传给Supabase。这个配置可能会干扰回调路由,导致跳转逻辑混乱。

修复:如果不需要Supabase处理其他OAuth回调,可以移除authCallbackUrlHostname配置:

await Supabase.initialize(
  url: 'https://hkyajejasstzlneelmwm.supabase.co',
  anonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhreWFqZWphc3N0emxuZWVsbXdtIiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODc3NDk5OTEsImV4cCI6MjAwMzMyNTk5MX0.iRJXHt6bjaQ55GOfVzI6Nd5IPMRkUPTVpjjgdauLHoI',
  debug: true,
);

3. 签名哈希一致性验证

Manifest中android:path部分的哈希值uUyjEmnuSAzNkfi4rwzLZCKmIVM%3D是应用签名的URL编码哈希。需要确认这个哈希值是否和你在Azure后台注册应用时提交的签名哈希完全一致,包括大小写和编码格式。如果使用的是debug签名,要确保生成哈希时用的是debug keystore;release版本则对应release keystore。

4. 浏览器缓存干扰

如果上述配置都正确,可能是浏览器缓存导致的循环跳转。可以尝试清除浏览器缓存,或者在登录时添加prompt: 'login'参数强制重新验证:

final result = await appAuth.authorize(
  AuthorizationRequest(
    clientId,
    redirectUrl,
    discoveryUrl: discoveryUrl,
    nonce: hashedNonce,
    scopes: [
      'openid',
      'email',
      'profile',
    ],
    promptValues: ['login'],
  ),
);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 17:25:54