You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
7.9 KiB
267 lines
7.9 KiB
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,
|
|
};
|
|
}
|
|
}
|
|
|