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