29 changed files with 1304 additions and 627 deletions
@ -1,201 +0,0 @@ |
|||||
PODS: |
|
||||
- amap_service (0.0.1): |
|
||||
- AMapNavi (~> 10.0.900) |
|
||||
- Flutter |
|
||||
- AMapFoundation (1.8.2) |
|
||||
- AMapNavi (10.0.900): |
|
||||
- AMapFoundation (>= 1.8.2) |
|
||||
- audio_waveforms (0.0.1): |
|
||||
- Flutter |
|
||||
- classic_bluetooth (0.0.1): |
|
||||
- Flutter |
|
||||
- FBAEMKit (18.0.0): |
|
||||
- FBSDKCoreKit_Basics (= 18.0.0) |
|
||||
- FBSDKCoreKit (18.0.0): |
|
||||
- FBAEMKit (= 18.0.0) |
|
||||
- FBSDKCoreKit_Basics (= 18.0.0) |
|
||||
- FBSDKCoreKit_Basics (18.0.0) |
|
||||
- FBSDKLoginKit (18.0.0): |
|
||||
- FBSDKCoreKit (= 18.0.0) |
|
||||
- Flutter (1.0.0) |
|
||||
- flutter_facebook_auth (7.1.2): |
|
||||
- FBSDKLoginKit (~> 18.0.0) |
|
||||
- Flutter |
|
||||
- flutter_image_compress_common (1.0.0): |
|
||||
- Flutter |
|
||||
- Mantle |
|
||||
- SDWebImage |
|
||||
- SDWebImageWebPCoder |
|
||||
- flutter_local_notifications (0.0.1): |
|
||||
- Flutter |
|
||||
- flutter_secure_storage (6.0.0): |
|
||||
- Flutter |
|
||||
- flutter_tts (0.0.1): |
|
||||
- Flutter |
|
||||
- fluwx (0.0.1): |
|
||||
- Flutter |
|
||||
- fluwx/pay (= 0.0.1) |
|
||||
- fluwx/pay (0.0.1): |
|
||||
- Flutter |
|
||||
- WechatOpenSDK-XCFramework (~> 2.0.4) |
|
||||
- haptic_feedback (0.5.1): |
|
||||
- Flutter |
|
||||
- keyboard_height_plugin (0.0.1): |
|
||||
- Flutter |
|
||||
- libwebp (1.5.0): |
|
||||
- libwebp/demux (= 1.5.0) |
|
||||
- libwebp/mux (= 1.5.0) |
|
||||
- libwebp/sharpyuv (= 1.5.0) |
|
||||
- libwebp/webp (= 1.5.0) |
|
||||
- libwebp/demux (1.5.0): |
|
||||
- libwebp/webp |
|
||||
- libwebp/mux (1.5.0): |
|
||||
- libwebp/demux |
|
||||
- libwebp/sharpyuv (1.5.0) |
|
||||
- libwebp/webp (1.5.0): |
|
||||
- libwebp/sharpyuv |
|
||||
- Mantle (2.2.0): |
|
||||
- Mantle/extobjc (= 2.2.0) |
|
||||
- Mantle/extobjc (2.2.0) |
|
||||
- ota (0.0.1): |
|
||||
- Flutter |
|
||||
- permission_handler_apple (9.3.0): |
|
||||
- Flutter |
|
||||
- QCloudCore (6.4.7): |
|
||||
- QCloudCore/Default (= 6.4.7) |
|
||||
- QCloudCore/Default (6.4.7): |
|
||||
- QCloudTrack/Beacon (= 6.4.7) |
|
||||
- QCloudCOSXML (6.4.7): |
|
||||
- QCloudCOSXML/Default (= 6.4.7) |
|
||||
- QCloudCOSXML/Default (6.4.7): |
|
||||
- QCloudCore (= 6.4.7) |
|
||||
- QCloudTrack/Beacon (6.4.7) |
|
||||
- record_darwin (1.0.0): |
|
||||
- Flutter |
|
||||
- SDWebImage (5.21.0): |
|
||||
- SDWebImage/Core (= 5.21.0) |
|
||||
- SDWebImage/Core (5.21.0) |
|
||||
- SDWebImageWebPCoder (0.14.6): |
|
||||
- libwebp (~> 1.0) |
|
||||
- SDWebImage/Core (~> 5.17) |
|
||||
- spotify_sdk (0.0.1): |
|
||||
- Flutter |
|
||||
- tencentcloud_cos_sdk_plugin (1.2.3): |
|
||||
- Flutter |
|
||||
- QCloudCOSXML (= 6.4.7) |
|
||||
- WechatOpenSDK-XCFramework (2.0.4) |
|
||||
- wifi_iot (0.0.1): |
|
||||
- Flutter |
|
||||
|
|
||||
DEPENDENCIES: |
|
||||
- amap_service (from `.symlinks/plugins/amap_service/ios`) |
|
||||
- audio_waveforms (from `.symlinks/plugins/audio_waveforms/ios`) |
|
||||
- classic_bluetooth (from `.symlinks/plugins/classic_bluetooth/ios`) |
|
||||
- Flutter (from `Flutter`) |
|
||||
- flutter_facebook_auth (from `.symlinks/plugins/flutter_facebook_auth/ios`) |
|
||||
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`) |
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) |
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) |
|
||||
- flutter_tts (from `.symlinks/plugins/flutter_tts/ios`) |
|
||||
- fluwx (from `.symlinks/plugins/fluwx/ios`) |
|
||||
- haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`) |
|
||||
- keyboard_height_plugin (from `.symlinks/plugins/keyboard_height_plugin/ios`) |
|
||||
- ota (from `.symlinks/plugins/ota/ios`) |
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) |
|
||||
- record_darwin (from `.symlinks/plugins/record_darwin/ios`) |
|
||||
- spotify_sdk (from `.symlinks/plugins/spotify_sdk/ios`) |
|
||||
- tencentcloud_cos_sdk_plugin (from `.symlinks/plugins/tencentcloud_cos_sdk_plugin/ios`) |
|
||||
- wifi_iot (from `.symlinks/plugins/wifi_iot/ios`) |
|
||||
|
|
||||
SPEC REPOS: |
|
||||
trunk: |
|
||||
- AMapFoundation |
|
||||
- AMapNavi |
|
||||
- FBAEMKit |
|
||||
- FBSDKCoreKit |
|
||||
- FBSDKCoreKit_Basics |
|
||||
- FBSDKLoginKit |
|
||||
- libwebp |
|
||||
- Mantle |
|
||||
- QCloudCore |
|
||||
- QCloudCOSXML |
|
||||
- QCloudTrack |
|
||||
- SDWebImage |
|
||||
- SDWebImageWebPCoder |
|
||||
- WechatOpenSDK-XCFramework |
|
||||
|
|
||||
EXTERNAL SOURCES: |
|
||||
amap_service: |
|
||||
:path: ".symlinks/plugins/amap_service/ios" |
|
||||
audio_waveforms: |
|
||||
:path: ".symlinks/plugins/audio_waveforms/ios" |
|
||||
classic_bluetooth: |
|
||||
:path: ".symlinks/plugins/classic_bluetooth/ios" |
|
||||
Flutter: |
|
||||
:path: Flutter |
|
||||
flutter_facebook_auth: |
|
||||
:path: ".symlinks/plugins/flutter_facebook_auth/ios" |
|
||||
flutter_image_compress_common: |
|
||||
:path: ".symlinks/plugins/flutter_image_compress_common/ios" |
|
||||
flutter_local_notifications: |
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios" |
|
||||
flutter_secure_storage: |
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios" |
|
||||
flutter_tts: |
|
||||
:path: ".symlinks/plugins/flutter_tts/ios" |
|
||||
fluwx: |
|
||||
:path: ".symlinks/plugins/fluwx/ios" |
|
||||
haptic_feedback: |
|
||||
:path: ".symlinks/plugins/haptic_feedback/ios" |
|
||||
keyboard_height_plugin: |
|
||||
:path: ".symlinks/plugins/keyboard_height_plugin/ios" |
|
||||
ota: |
|
||||
:path: ".symlinks/plugins/ota/ios" |
|
||||
permission_handler_apple: |
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios" |
|
||||
record_darwin: |
|
||||
:path: ".symlinks/plugins/record_darwin/ios" |
|
||||
spotify_sdk: |
|
||||
:path: ".symlinks/plugins/spotify_sdk/ios" |
|
||||
tencentcloud_cos_sdk_plugin: |
|
||||
:path: ".symlinks/plugins/tencentcloud_cos_sdk_plugin/ios" |
|
||||
wifi_iot: |
|
||||
:path: ".symlinks/plugins/wifi_iot/ios" |
|
||||
|
|
||||
SPEC CHECKSUMS: |
|
||||
amap_service: 1c3e7e07f51cbbdff03719a4a079c60d015c1c0b |
|
||||
AMapFoundation: 9885c48fc3a78fdfb84a0299a2293e56ea3c9fec |
|
||||
AMapNavi: fceb4715f9a3b5a25f330facebee2aa2bdba42dd |
|
||||
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf |
|
||||
classic_bluetooth: 29f392f1ab7c52862a9ef036c94f2713b02e0c50 |
|
||||
FBAEMKit: e34530df538b8eb8aeb53c35867715ba6c63ef0c |
|
||||
FBSDKCoreKit: d3f479a69127acebb1c6aad91c1a33907bcf6c2f |
|
||||
FBSDKCoreKit_Basics: 017b6dc2a1862024815a8229e75661e627ac1e29 |
|
||||
FBSDKLoginKit: 5875762d1fe09ddcb05d03365d4f5dc34413843d |
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 |
|
||||
flutter_facebook_auth: d97dc09216aebd99d7ac52360186a630f67bdf18 |
|
||||
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1 |
|
||||
flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4 |
|
||||
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 |
|
||||
flutter_tts: b88dbc8655d3dc961bc4a796e4e16a4cc1795833 |
|
||||
fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b |
|
||||
haptic_feedback: fd1d8509833f58f164fc12122c0357fa9b2750e0 |
|
||||
keyboard_height_plugin: ef70a8181b29f27670e9e2450814ca6b6dc05b05 |
|
||||
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 |
|
||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d |
|
||||
ota: 20c3e522c57e7ba70ce118109e73650acc9fec76 |
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d |
|
||||
QCloudCore: 49e7571984a8e5fd8f0fd58c2f3b806255796223 |
|
||||
QCloudCOSXML: 7205e76aa9cf613468222615483c9d7c074e4298 |
|
||||
QCloudTrack: 3b53a7fc4fe3920e407f2aa73f2452992a61f7f3 |
|
||||
record_darwin: fb1f375f1d9603714f55b8708a903bbb91ffdb0a |
|
||||
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 |
|
||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 |
|
||||
spotify_sdk: a48400bb29f70c4fe251ebfdc9135c37097ac5ca |
|
||||
tencentcloud_cos_sdk_plugin: c3d78b2c3bae42b5a20009dc047f3f1621e6272b |
|
||||
WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f |
|
||||
wifi_iot: f645260a2be8608517b2a9bf4c39b98e97003acc |
|
||||
|
|
||||
PODFILE CHECKSUM: e69e629768e642fde97676ea149350c61d57751e |
|
||||
|
|
||||
COCOAPODS: 1.16.2 |
|
||||
@ -0,0 +1,267 @@ |
|||||
|
import 'dart:convert'; |
||||
|
import 'dart:io'; |
||||
|
import 'dart:math'; |
||||
|
import 'package:flutter/foundation.dart'; |
||||
|
import 'package:sign_in_with_apple/sign_in_with_apple.dart'; |
||||
|
import 'package:crypto/crypto.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
|
||||
|
/// Apple登录控制器(纯sign_in_with_apple实现) |
||||
|
class Apple { |
||||
|
String? token = ''; |
||||
|
String name = ''; |
||||
|
String email = ''; |
||||
|
String pictureurl = ''; |
||||
|
String id = ''; |
||||
|
|
||||
|
// 存储原始的Apple凭证信息 |
||||
|
String? _identityToken; |
||||
|
String? _authorizationCode; |
||||
|
String? _userIdentifier; |
||||
|
|
||||
|
/// 生成随机nonce字符串(用于安全验证) |
||||
|
String generateNonce([int length = 32]) { |
||||
|
const charset = |
||||
|
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._'; |
||||
|
final random = Random.secure(); |
||||
|
return List.generate(length, (_) => charset[random.nextInt(charset.length)]) |
||||
|
.join(); |
||||
|
} |
||||
|
|
||||
|
/// 返回输入字符串的sha256哈希值(十六进制表示) |
||||
|
String sha256ofString(String input) { |
||||
|
final bytes = utf8.encode(input); |
||||
|
final digest = sha256.convert(bytes); |
||||
|
return digest.toString(); |
||||
|
} |
||||
|
|
||||
|
/// Apple登录主方法(纯sign_in_with_apple实现) |
||||
|
Future<bool> signInWithApple() async { |
||||
|
try { |
||||
|
// 检查Apple登录可用性 |
||||
|
if (!await SignInWithApple.isAvailable()) { |
||||
|
if (kDebugMode) { |
||||
|
print('Apple登录不可用'); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (kDebugMode) { |
||||
|
print('开始执行Apple登录流程'); |
||||
|
} |
||||
|
|
||||
|
// 执行Apple登录 |
||||
|
final appleCredential = await _getAppleCredential(); |
||||
|
|
||||
|
// 提取并存储用户信息 |
||||
|
_extractUserInfo(appleCredential); |
||||
|
|
||||
|
// 使用identityToken作为登录token |
||||
|
token = appleCredential.identityToken; |
||||
|
|
||||
|
if (kDebugMode) { |
||||
|
print('Apple登录成功'); |
||||
|
print('Token: $token'); |
||||
|
print('用户信息: name=$name, email=$email, id=$id'); |
||||
|
} |
||||
|
|
||||
|
return token != null && token!.isNotEmpty; |
||||
|
} catch (e) { |
||||
|
if (kDebugMode) { |
||||
|
print('Apple登录异常:$e'); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 获取Apple登录凭证 |
||||
|
Future<AuthorizationCredentialAppleID> _getAppleCredential() async { |
||||
|
// 生成nonce用于安全验证(遵循官方最佳实践) |
||||
|
final rawNonce = generateNonce(); |
||||
|
final nonce = sha256ofString(rawNonce); |
||||
|
|
||||
|
try { |
||||
|
// 请求Apple登录凭证(与官方示例保持一致的参数结构) |
||||
|
final appleCredential = await SignInWithApple.getAppleIDCredential( |
||||
|
scopes: [ |
||||
|
AppleIDAuthorizationScopes.email, |
||||
|
AppleIDAuthorizationScopes.fullName, |
||||
|
], |
||||
|
nonce: nonce, |
||||
|
// 可以添加state参数用于防CSRF攻击(可选) |
||||
|
state: 'voitrans-login-${DateTime.now().millisecondsSinceEpoch}', |
||||
|
); |
||||
|
|
||||
|
// 存储原始凭证信息(遵循官方示例的数据提取方式) |
||||
|
_identityToken = appleCredential.identityToken; |
||||
|
_authorizationCode = appleCredential.authorizationCode; |
||||
|
_userIdentifier = appleCredential.userIdentifier; |
||||
|
|
||||
|
if (kDebugMode) { |
||||
|
print('Apple凭证获取成功'); |
||||
|
print('Identity Token长度: ${_identityToken?.length ?? 0}'); |
||||
|
print('Authorization Code长度: ${_authorizationCode?.length ?? 0}'); |
||||
|
print('User Identifier: $_userIdentifier'); |
||||
|
print('State: ${appleCredential.state}'); |
||||
|
|
||||
|
// 调试输出用户信息(与官方示例类似) |
||||
|
if (appleCredential.givenName != null) { |
||||
|
print('Given Name: ${appleCredential.givenName}'); |
||||
|
} |
||||
|
if (appleCredential.familyName != null) { |
||||
|
print('Family Name: ${appleCredential.familyName}'); |
||||
|
} |
||||
|
if (appleCredential.email != null) { |
||||
|
print('Email: ${appleCredential.email}'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return appleCredential; |
||||
|
} catch (e) { |
||||
|
if (kDebugMode) { |
||||
|
print('获取Apple凭证异常:$e'); |
||||
|
} |
||||
|
throw Exception('获取Apple凭证失败: $e'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
/// 提取Apple返回的用户信息(遵循官方示例的处理方式) |
||||
|
void _extractUserInfo(AuthorizationCredentialAppleID credential) { |
||||
|
// 用户ID(官方示例中的主要标识符) |
||||
|
id = credential.userIdentifier ?? ''; |
||||
|
|
||||
|
// 邮箱(可能为空,如果用户选择隐藏邮箱) |
||||
|
email = credential.email ?? ''; |
||||
|
|
||||
|
// 用户姓名处理(与官方示例保持一致的逻辑) |
||||
|
// 注意:姓名信息仅在首次登录时提供 |
||||
|
final givenName = credential.givenName; |
||||
|
final familyName = credential.familyName; |
||||
|
|
||||
|
if (givenName != null || familyName != null) { |
||||
|
// 按照官方示例的方式组合姓名 |
||||
|
final firstName = givenName ?? ''; |
||||
|
final lastName = familyName ?? ''; |
||||
|
name = '$firstName $lastName'.trim(); |
||||
|
} else { |
||||
|
// 如果没有姓名信息,保持空值(后续登录不会提供姓名) |
||||
|
name = ''; |
||||
|
} |
||||
|
|
||||
|
// Apple不提供头像,使用默认值 |
||||
|
pictureurl = ''; |
||||
|
|
||||
|
if (kDebugMode) { |
||||
|
print('=== 提取的用户信息 ==='); |
||||
|
print('User Identifier: $id'); |
||||
|
print('Email: ${email.isEmpty ? "未提供或隐藏" : email}'); |
||||
|
print('Given Name: ${givenName ?? "未提供"}'); |
||||
|
print('Family Name: ${familyName ?? "未提供"}'); |
||||
|
print('Combined Name: ${name.isEmpty ? "未提供" : name}'); |
||||
|
print('Picture URL: $pictureurl'); |
||||
|
print('========================'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 退出Apple登录 |
||||
|
Future<void> signOutApple() async { |
||||
|
try { |
||||
|
// 清空用户信息 |
||||
|
_clearUserInfo(); |
||||
|
|
||||
|
if (kDebugMode) { |
||||
|
print('Apple登录退出成功'); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
if (kDebugMode) { |
||||
|
print('Apple登录退出异常:$e'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 清空用户信息 |
||||
|
void _clearUserInfo() { |
||||
|
token = ''; |
||||
|
name = ''; |
||||
|
email = ''; |
||||
|
pictureurl = ''; |
||||
|
id = ''; |
||||
|
_identityToken = null; |
||||
|
_authorizationCode = null; |
||||
|
_userIdentifier = null; |
||||
|
} |
||||
|
|
||||
|
/// 检查Apple登录环境 |
||||
|
static Future<void> checkAppleSignInEnvironment() async { |
||||
|
if (kDebugMode) { |
||||
|
print('=== Apple登录环境检查 ==='); |
||||
|
print('平台: ${GetPlatform.isIOS ? "iOS" : "非iOS"}'); |
||||
|
print('Apple登录可用性: ${await SignInWithApple.isAvailable()}'); |
||||
|
|
||||
|
// 检测设备地区 |
||||
|
final locale = Get.deviceLocale; |
||||
|
final countryCode = locale?.countryCode?.toUpperCase(); |
||||
|
print('设备地区: $countryCode'); |
||||
|
print('认证方式: 纯Apple登录(无Firebase依赖)'); |
||||
|
|
||||
|
print('========================'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 获取认证方式名称 |
||||
|
String getAuthMethodName() { |
||||
|
return 'Apple Sign-In'; |
||||
|
} |
||||
|
|
||||
|
/// 获取详细的凭证信息(用于调试) |
||||
|
Map<String, String?> getCredentialInfo() { |
||||
|
return { |
||||
|
'identityToken': _identityToken, |
||||
|
'authorizationCode': _authorizationCode, |
||||
|
'userIdentifier': _userIdentifier, |
||||
|
'name': name, |
||||
|
'email': email, |
||||
|
'id': id, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/// 获取用于后端验证的数据(遵循官方示例的后端集成方式) |
||||
|
/// |
||||
|
/// [deviceId] 设备ID |
||||
|
/// 返回格式化的登录数据,包含Apple凭证信息 |
||||
|
Map<String, dynamic> getLoginData(String deviceId) { |
||||
|
return { |
||||
|
// 现有系统兼容的字段 |
||||
|
"mail": email, |
||||
|
"phone": "", |
||||
|
"stype": 5, // Apple登录类型 |
||||
|
"vcode": "", |
||||
|
"ttoken": token ?? '', // 主要使用identityToken |
||||
|
"phonemodel": "", |
||||
|
"phonemac": deviceId, |
||||
|
"name": name, |
||||
|
"avatar": pictureurl, |
||||
|
"auth_method": "apple_signin", // 标识使用纯Apple登录 |
||||
|
|
||||
|
// Apple特有的凭证信息(遵循官方示例) |
||||
|
"apple_credentials": { |
||||
|
"identity_token": _identityToken, |
||||
|
"authorization_code": _authorizationCode, |
||||
|
"user_identifier": _userIdentifier, |
||||
|
"email": email, |
||||
|
"name": name, |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/// 获取原始Apple凭证信息(用于高级验证) |
||||
|
Map<String, String?> getAppleCredentials() { |
||||
|
return { |
||||
|
'identityToken': _identityToken, |
||||
|
'authorizationCode': _authorizationCode, |
||||
|
'userIdentifier': _userIdentifier, |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue