|
|
|
@ -1,12 +1,12 @@ |
|
|
|
import 'dart:convert'; |
|
|
|
import 'dart:io'; |
|
|
|
import 'dart:math'; |
|
|
|
import 'package:firebase_auth/firebase_auth.dart'; |
|
|
|
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 = ''; |
|
|
|
@ -14,7 +14,12 @@ class Apple { |
|
|
|
String pictureurl = ''; |
|
|
|
String id = ''; |
|
|
|
|
|
|
|
/// 生成随机nonce字符串 |
|
|
|
// 存储原始的Apple凭证信息 |
|
|
|
String? _identityToken; |
|
|
|
String? _authorizationCode; |
|
|
|
String? _userIdentifier; |
|
|
|
|
|
|
|
/// 生成随机nonce字符串(用于安全验证) |
|
|
|
String generateNonce([int length = 32]) { |
|
|
|
const charset = |
|
|
|
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._'; |
|
|
|
@ -30,7 +35,7 @@ class Apple { |
|
|
|
return digest.toString(); |
|
|
|
} |
|
|
|
|
|
|
|
/// Apple登录主方法 |
|
|
|
/// Apple登录主方法(纯sign_in_with_apple实现) |
|
|
|
Future<bool> signInWithApple() async { |
|
|
|
try { |
|
|
|
// 检查Apple登录可用性 |
|
|
|
@ -41,11 +46,23 @@ class Apple { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
UserCredential userCredential = await loginApple(); |
|
|
|
token = await userCredential.user?.getIdToken(); |
|
|
|
if (kDebugMode) { |
|
|
|
print('开始执行Apple登录流程'); |
|
|
|
} |
|
|
|
|
|
|
|
// 执行Apple登录 |
|
|
|
final appleCredential = await _getAppleCredential(); |
|
|
|
|
|
|
|
// 提取并存储用户信息 |
|
|
|
_extractUserInfo(appleCredential); |
|
|
|
|
|
|
|
// 使用identityToken作为登录token |
|
|
|
token = appleCredential.identityToken; |
|
|
|
|
|
|
|
if (kDebugMode) { |
|
|
|
print('Apple登录响应数据token: $token'); |
|
|
|
print('Apple登录成功'); |
|
|
|
print('Token: $token'); |
|
|
|
print('用户信息: name=$name, email=$email, id=$id'); |
|
|
|
} |
|
|
|
|
|
|
|
return token != null && token!.isNotEmpty; |
|
|
|
@ -57,83 +74,102 @@ class Apple { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// 执行Apple登录流程 |
|
|
|
Future<UserCredential> loginApple() async { |
|
|
|
// 生成nonce用于安全验证 |
|
|
|
/// 获取Apple登录凭证 |
|
|
|
Future<AuthorizationCredentialAppleID> _getAppleCredential() async { |
|
|
|
// 生成nonce用于安全验证(遵循官方最佳实践) |
|
|
|
final rawNonce = generateNonce(); |
|
|
|
final nonce = sha256ofString(rawNonce); |
|
|
|
|
|
|
|
try { |
|
|
|
// 请求Apple登录凭证 |
|
|
|
// 请求Apple登录凭证(与官方示例保持一致的参数结构) |
|
|
|
final appleCredential = await SignInWithApple.getAppleIDCredential( |
|
|
|
scopes: [ |
|
|
|
AppleIDAuthorizationScopes.email, |
|
|
|
AppleIDAuthorizationScopes.fullName, |
|
|
|
], |
|
|
|
nonce: nonce, |
|
|
|
// 可以添加state参数用于防CSRF攻击(可选) |
|
|
|
state: 'voitrans-login-${DateTime.now().millisecondsSinceEpoch}', |
|
|
|
); |
|
|
|
|
|
|
|
// 提取用户信息 |
|
|
|
_extractUserInfo(appleCredential); |
|
|
|
|
|
|
|
// 创建Firebase OAuth凭证 |
|
|
|
final oauthCredential = OAuthProvider("apple.com").credential( |
|
|
|
idToken: appleCredential.identityToken, |
|
|
|
rawNonce: rawNonce, |
|
|
|
); |
|
|
|
// 存储原始凭证信息(遵循官方示例的数据提取方式) |
|
|
|
_identityToken = appleCredential.identityToken; |
|
|
|
_authorizationCode = appleCredential.authorizationCode; |
|
|
|
_userIdentifier = appleCredential.userIdentifier; |
|
|
|
|
|
|
|
if (kDebugMode) { |
|
|
|
print('Apple OAuth凭证创建成功'); |
|
|
|
print('用户信息: name=$name, email=$email, id=$id'); |
|
|
|
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}'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 使用Firebase Auth进行登录 |
|
|
|
return await FirebaseAuth.instance.signInWithCredential(oauthCredential); |
|
|
|
return appleCredential; |
|
|
|
} catch (e) { |
|
|
|
if (kDebugMode) { |
|
|
|
print('Apple登录流程异常:$e'); |
|
|
|
print('获取Apple凭证异常:$e'); |
|
|
|
} |
|
|
|
throw Exception('Apple登录失败: $e'); |
|
|
|
throw Exception('获取Apple凭证失败: $e'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// 提取Apple返回的用户信息 |
|
|
|
|
|
|
|
|
|
|
|
/// 提取Apple返回的用户信息(遵循官方示例的处理方式) |
|
|
|
void _extractUserInfo(AuthorizationCredentialAppleID credential) { |
|
|
|
// 用户ID |
|
|
|
// 用户ID(官方示例中的主要标识符) |
|
|
|
id = credential.userIdentifier ?? ''; |
|
|
|
|
|
|
|
// 邮箱(可能为空,如果用户选择隐藏邮箱) |
|
|
|
email = credential.email ?? ''; |
|
|
|
|
|
|
|
// 用户姓名(仅首次登录时提供) |
|
|
|
if (credential.givenName != null || credential.familyName != null) { |
|
|
|
final givenName = credential.givenName ?? ''; |
|
|
|
final familyName = credential.familyName ?? ''; |
|
|
|
name = '$givenName $familyName'.trim(); |
|
|
|
// 用户姓名处理(与官方示例保持一致的逻辑) |
|
|
|
// 注意:姓名信息仅在首次登录时提供 |
|
|
|
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('ID: $id'); |
|
|
|
print('Email: $email'); |
|
|
|
print('Name: $name'); |
|
|
|
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 { |
|
|
|
await FirebaseAuth.instance.signOut(); |
|
|
|
// 清空用户信息 |
|
|
|
token = ''; |
|
|
|
name = ''; |
|
|
|
email = ''; |
|
|
|
pictureurl = ''; |
|
|
|
id = ''; |
|
|
|
_clearUserInfo(); |
|
|
|
|
|
|
|
if (kDebugMode) { |
|
|
|
print('Apple登录退出成功'); |
|
|
|
@ -145,14 +181,87 @@ class Apple { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// 清空用户信息 |
|
|
|
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()}'); |
|
|
|
print('Firebase Auth实例: ${FirebaseAuth.instance.currentUser?.uid ?? "未登录"}'); |
|
|
|
|
|
|
|
// 检测设备地区 |
|
|
|
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, |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|