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.
158 lines
4.3 KiB
158 lines
4.3 KiB
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';
|
|
|
|
class Apple {
|
|
String? token = '';
|
|
String name = '';
|
|
String email = '';
|
|
String pictureurl = '';
|
|
String id = '';
|
|
|
|
/// 生成随机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登录主方法
|
|
Future<bool> signInWithApple() async {
|
|
try {
|
|
// 检查Apple登录可用性
|
|
if (!await SignInWithApple.isAvailable()) {
|
|
if (kDebugMode) {
|
|
print('Apple登录不可用');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
UserCredential userCredential = await loginApple();
|
|
token = await userCredential.user?.getIdToken();
|
|
|
|
if (kDebugMode) {
|
|
print('Apple登录响应数据token: $token');
|
|
}
|
|
|
|
return token != null && token!.isNotEmpty;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Apple登录异常:$e');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 执行Apple登录流程
|
|
Future<UserCredential> loginApple() async {
|
|
// 生成nonce用于安全验证
|
|
final rawNonce = generateNonce();
|
|
final nonce = sha256ofString(rawNonce);
|
|
|
|
try {
|
|
// 请求Apple登录凭证
|
|
final appleCredential = await SignInWithApple.getAppleIDCredential(
|
|
scopes: [
|
|
AppleIDAuthorizationScopes.email,
|
|
AppleIDAuthorizationScopes.fullName,
|
|
],
|
|
nonce: nonce,
|
|
);
|
|
|
|
// 提取用户信息
|
|
_extractUserInfo(appleCredential);
|
|
|
|
// 创建Firebase OAuth凭证
|
|
final oauthCredential = OAuthProvider("apple.com").credential(
|
|
idToken: appleCredential.identityToken,
|
|
rawNonce: rawNonce,
|
|
);
|
|
|
|
if (kDebugMode) {
|
|
print('Apple OAuth凭证创建成功');
|
|
print('用户信息: name=$name, email=$email, id=$id');
|
|
}
|
|
|
|
// 使用Firebase Auth进行登录
|
|
return await FirebaseAuth.instance.signInWithCredential(oauthCredential);
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Apple登录流程异常:$e');
|
|
}
|
|
throw Exception('Apple登录失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 提取Apple返回的用户信息
|
|
void _extractUserInfo(AuthorizationCredentialAppleID credential) {
|
|
// 用户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();
|
|
}
|
|
|
|
// Apple不提供头像,使用默认值
|
|
pictureurl = '';
|
|
|
|
if (kDebugMode) {
|
|
print('提取的用户信息:');
|
|
print('ID: $id');
|
|
print('Email: $email');
|
|
print('Name: $name');
|
|
print('Picture URL: $pictureurl');
|
|
}
|
|
}
|
|
|
|
/// 退出Apple登录
|
|
Future<void> signOutApple() async {
|
|
try {
|
|
await FirebaseAuth.instance.signOut();
|
|
// 清空用户信息
|
|
token = '';
|
|
name = '';
|
|
email = '';
|
|
pictureurl = '';
|
|
id = '';
|
|
|
|
if (kDebugMode) {
|
|
print('Apple登录退出成功');
|
|
}
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Apple登录退出异常:$e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 检查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 ?? "未登录"}');
|
|
print('========================');
|
|
}
|
|
}
|
|
}
|
|
|