Flutter中用aad_oauth实现微软账号免重复输入登录
问题描述
我希望实现:用户退出我的Flutter应用后,点击“Sign In”按钮时,能显示已登录账号选择界面,无需重新输入微软凭据。但当前使用aad_oauth包实现的登录功能,用户退出后需从头输入凭据,而参考应用可保留已登录账号记录。以下是我的代码配置:
auth_service.dart
class AuthService extends ChangeNotifier { static final Config config = Config( tenant: '...', clientId: '...', scope: "${dotenv.env['AppScope']!} openid profile offline_access user.read", redirectUri: (Platform.isAndroid) ? dotenv.env['androidRedirectUri']! : dotenv.env['iOSRedirectUri'], navigatorKey: navigatorKey, loader: const Center(child: CircularProgressIndicator()), ); final AadOAuth oauth = AadOAuth(config); bool _isLoadingUser = true; bool get isLoadingUser => _isLoadingUser; set setIsLoadingUser(bool value) { _isLoadingUser = value; notifyListeners(); } bool _isAuthenticated = false; bool get isAuthenticated => _isAuthenticated; AuthService(); Future<void> initializeAuthService() async { try { if (await hasCachedAccountInformation()) { log('User is already authenticated'); await oauth.refreshToken(); _isAuthenticated = true; } else { log('User is not authenticated'); } } catch (e) { log('Error initializing AuthService: $e'); } finally { _isLoadingUser = false; notifyListeners(); } } Future<void> login() async { final result = await oauth.login(); result.fold( (l) => log(l.toString()), (r) => log('Logged in successfully, your access token: $r'), ); var accessToken = await oauth.getAccessToken(); if (accessToken != null) { log('Access token: $accessToken'); _isLoadingUser = false; _isAuthenticated = true; notifyListeners(); } } Future<void> logout() async { try { log('Logging out'); await oauth.logout(); _isAuthenticated = false; notifyListeners(); } catch (e) { log('Error logging out: ${e.toString()}'); } } Future<bool> hasCachedAccountInformation() async { var hasCachedAccountInformation = await oauth.hasCachedAccountInformation; log('HasCachedAccountInformation: $hasCachedAccountInformation'); return hasCachedAccountInformation; } Future<String?> getCachedAccessToken() async { var accessToken = await oauth.getAccessToken(); log('Got Access token: $accessToken'); return accessToken; } refreshToken() async { return await oauth.refreshToken(); } }
auth_gate.dart
class AuthGate extends StatelessWidget { AuthGate({super.key}); // ... 省略其他代码 @override Widget build(BuildContext context) { // ... 省略其他逻辑 // User is not authenticated return LayoutBuilder(builder: (context, constraints) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // ... 省略其他组件 ElevatedButton( onPressed: () async { await showAlertDialog( context, authService, isMobileWidth); }, child: Text( 'SIGN IN', style: isMobileWidth ? primaryButtonText : primaryButtonText.copyWith( fontSize: tabletFontSize, ), ), ), ], ), ); }); } showAlertDialog( BuildContext context, AuthService authService, bool isMobileWidth, ) { // ... 省略弹窗创建代码 // 原弹窗逻辑 } }
解决方案
1. 修改登出逻辑,保留账号缓存
默认的oauth.logout()会清除所有账号缓存,导致无法显示历史账号。改为仅清除当前会话令牌,保留账号信息:
Future<void> logout() async { try { log('Logging out'); // 仅清除当前令牌,不删除账号缓存 await oauth.clearToken(); _isAuthenticated = false; notifyListeners(); } catch (e) { log('Error logging out: ${e.toString()}'); } }
2. 添加获取缓存账号列表的方法
在AuthService中新增方法,读取aad_oauth存储的账号信息:
import 'dart:convert'; // 新增账号信息模型(根据实际存储结构调整) class AccountInfo { final String? username; final Map<String, dynamic>? idToken; AccountInfo({this.username, this.idToken}); factory AccountInfo.fromJson(Map<String, dynamic> json) { return AccountInfo( username: json['username'], idToken: json['idToken'], ); } } // 在AuthService中新增方法 Future<List<AccountInfo>?> getCachedAccounts() async { try { final storage = await oauth.storage; final accountsJson = await storage.read('msal_accounts'); if (accountsJson != null) { final List<dynamic> accountsList = jsonDecode(accountsJson); return accountsList.map((json) => AccountInfo.fromJson(json)).toList(); } return null; } catch (e) { log('Error getting cached accounts: $e'); return null; } }
3. 实现账号选择弹窗
替换原有的登录确认弹窗,改为显示缓存账号列表供用户选择:
// 在AuthGate中新增账号选择弹窗方法 Future<void> showAccountSelectionDialog(BuildContext context, AuthService authService, bool isMobileWidth) async { final accounts = await authService.getCachedAccounts(); if (accounts == null || accounts.isEmpty) { // 无缓存账号,直接启动登录流程 authService.setIsLoadingUser = true; await authService.login(); authService.setIsLoadingUser = false; return; } // 显示账号选择弹窗 showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text('选择登录账号'), content: SingleChildScrollView( child: ListBody( children: accounts.map((account) { return ListTile( title: Text(account.username ?? '未知账号'), subtitle: Text(account.idToken?['name'] ?? ''), onTap: () async { Navigator.of(context).pop(); authService.setIsLoadingUser = true; // 使用选中的账号快速登录 await authService.oauth.loginWithAccount(account); authService.setIsLoadingUser = false; // 更新认证状态 authService._isAuthenticated = await authService.hasCachedAccountInformation(); authService.notifyListeners(); }, ); }).toList(), ), ), actions: [ TextButton( child: const Text('使用其他账号'), onTap: () async { Navigator.of(context).pop(); authService.setIsLoadingUser = true; await authService.login(); authService.setIsLoadingUser = false; }, ), ], ); }, ); }
然后修改Sign In按钮的点击事件,调用新的弹窗方法:
ElevatedButton( onPressed: () async { await showAccountSelectionDialog(context, authService, isMobileWidth); }, child: Text( 'SIGN IN', style: isMobileWidth ? primaryButtonText : primaryButtonText.copyWith( fontSize: tabletFontSize, ), ), )
4. 调整初始化逻辑
确保应用启动时仅检查认证状态,不自动登录:
Future<void> initializeAuthService() async { try { // 仅检查缓存账号状态,不自动刷新令牌登录 _isAuthenticated = await hasCachedAccountInformation(); } catch (e) { log('Error initializing AuthService: $e'); _isAuthenticated = false; } finally { _isLoadingUser = false; notifyListeners(); } }
内容的提问来源于stack exchange,提问作者pl8nt
相关产品推荐
相关产品推荐

