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

Flutter应用实现微软登录“记住邮箱仅需输密码”功能咨询

实现Microsoft认证Flutter应用的邮箱记忆优化

核心思路

  • 用持久化存储(如shared_preferences)保存首次登录成功后的用户邮箱
  • 调整Azure AD OAuth配置,利用login_hint字段预填充已保存的邮箱,同时取消强制全流程重新认证的参数
  • 登录成功后提取用户邮箱并存储,下次启动时自动加载并应用到认证流程

具体实现步骤

1. 添加依赖

在pubspec.yaml中添加shared_preferences依赖:

dependencies:
  flutter:
    sdk: flutter
  aad_oauth: ^x.x.x # 保持你当前使用的版本
  shared_preferences: ^2.2.2

2. 修改登录页面代码

import 'package:flutter/material.dart';
import 'package:aad_oauth/aad_oauth.dart';
import 'package:aad_oauth/model/config.dart';
import 'package:aad_oauth/model/token.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../provider/sign_in_provider.dart';
import '../SuccessScreen.dart';

class LoginScreen extends StatefulWidget {
  static final navigatorKey = GlobalKey<NavigatorState>();

  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  late final AadOAuth oauth;
  late SharedPreferences _prefs;
  String? _savedEmail;

  @override
  void initState() {
    super.initState();
    _initPrefsAndOAuth();
  }

  Future<void> _initPrefsAndOAuth() async {
    // 初始化SharedPreferences
    _prefs = await SharedPreferences.getInstance();
    _savedEmail = _prefs.getString('user_email');

    // 配置OAuth
    final config = Config(
      tenant: '2f5964b2-44fc-420d-8974-7afab2aaxxxxx',
      clientId: 'ed4c80e3-847b-4b0a-85a9-c9686209xxxx',
      scope: 'openid profile email offline_access',
      redirectUri: 'https://dasundola.b2clogin.com/oauth2/nativeclient',
      navigatorKey: SignInProvider.navigatorKey,
      customParameters: {
        // 动态设置prompt:有保存邮箱时优先复用会话,无则引导完整登录
        'prompt': _savedEmail != null ? 'select_account' : 'login',
        // 预填充已保存的邮箱
        'login_hint': _savedEmail ?? '',
        // 移除max_age=0,避免强制重新认证
      },
    );
    oauth = AadOAuth(config);
    setState(() {});
  }

  void _login() async {
    try {
      // 移除登录前的强制logout,保留用户会话
      // await oauth.logout();

      // 执行登录
      await oauth.login();
      final token = await oauth.getIdToken();
      final accessToken = await oauth.getAccessToken();

      if (accessToken != null && token != null) {
        // 从ID Token中提取用户邮箱
        final email = token.email;
        if (email != null) {
          // 保存邮箱到本地
          await _prefs.setString('user_email', email);
        }

        Navigator.pushReplacement(
          context,
          MaterialPageRoute(builder: (context) => SuccessScreen()),
        );
      }
    } catch (e) {
      _showLoginError(e.toString());
    }
  }

  void _showLoginError(String message) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Login Error'),
        content: Text(message),
        actions: [
          TextButton(
            child: const Text('OK'),
            onPressed: () => Navigator.of(context).pop(),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const SizedBox(height: 20),
            const Text(
              'Sign in',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _login,
              child: const Padding(
                padding: EdgeInsets.symmetric(vertical: 12),
                child: Text(
                  'Login with Microsoft',
                  style: TextStyle(fontSize: 16),
                ),
              ),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.blue[700],
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(4),
                ),
              ),
            ),
            // 可选:添加切换账号按钮,允许用户清除已保存邮箱
            if (_savedEmail != null)
              TextButton(
                onPressed: () async {
                  await _prefs.remove('user_email');
                  setState(() => _savedEmail = null);
                  await _initPrefsAndOAuth();
                },
                child: const Text('Switch Account'),
              ),
            const SizedBox(height: 16),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    // 移除自动logout,避免清除用户会话
    // oauth.logout();
    super.dispose();
  }
}

关键修改说明

  • 持久化存储:通过shared_preferences实现邮箱的跨启动保存
  • OAuth配置优化:
    • 移除max_age: '0',不再强制每次重新走完整认证流程
    • 动态设置prompt参数:已保存邮箱时用select_account(复用现有会话,仅需验证密码/MFA),无邮箱时用login(引导完整登录)
    • 利用login_hint预填充邮箱,减少用户重复输入操作
  • 登录流程调整:取消登录前的强制logout,保留用户会话;登录成功后从ID Token提取邮箱并存储
  • 可选功能:添加切换账号按钮,支持用户清除已保存邮箱并重新登录

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 02:32:07