一、前期准备
1.1 系统要求
-
iOS 13.0 及以上版本
-
UniApp HBuilderX 3.0+
-
有效的Apple Developer账号
1.2 Apple政策要求
根据Apple App Store审核指南,如果您的应用使用第三方登录服务(如微信、QQ等),则必须同时提供Apple登录选项。

二、Apple Developer配置
2.1 创建App ID
-
登录 Apple Developer Center
-
进入 Certificates, Identifiers & Profiles
-
点击 Identifiers → App IDs
-
创建新的App ID或编辑现有的App ID
-
在 Capabilities 中启用 Sign In with Apple
2.2 配置Services ID(可选)
如果需要在Web端使用Apple登录:
-
在 Identifiers 中选择 Services IDs
-
创建新的Services ID
-
配置域名和重定向URL
2.3 创建Key文件
-
在 Keys 部分创建新的密钥
-
启用 Sign In with Apple 服务
-
下载
.p8文件并妥善保存

三、UniApp项目配置
3.1 manifest.json配置
在 manifest.json 文件中添加Apple登录配置:
{
"app-plus": {
"oauth": {
"apple": {
"bundleId": "your.bundle.id"
}
}
},
"mp-weixin": {
"plugins": {
"login-plugin": {
"version": "latest",
"provider": "apple"
}
}
}
}
3.2 iOS平台配置
在 App模块配置 中:
-
勾选 OAuth(登录鉴权)
-
在OAuth配置中勾选 Apple登录
-
填写Bundle ID
3.3 权限配置
在 manifest.json 的 app-plus → distribute → ios 中添加:
{
"ios": {
"capabilities": {
"signin-with-apple": {
"enable": true
}
}
}
}
四、代码实现
4.1 基础登录方法
// Apple登录主方法
exportfunction appleLogin() {
returnnewPromise((resolve, reject) => {
// 检查平台支持
if (!isAppleLoginSupported()) {
reject(newError('当前平台不支持Apple登录'))
return
}
uni.login({
provider: 'apple',
success: (loginRes) => {
console.log('Apple登录成功:', loginRes)
// 获取用户信息
uni.getUserInfo({
provider: 'apple',
success: (userRes) => {
const result = {
code: loginRes.code,
authorizationCode: loginRes.authorizationCode,
identityToken: loginRes.identityToken,
email: userRes.userInfo.email,
fullName: userRes.userInfo.fullName,
user: loginRes.user
}
resolve(result)
},
fail: (error) => {
console.error('获取用户信息失败:', error)
reject(error)
}
})
},
fail: (error) => {
console.error('Apple登录失败:', error)
reject(error)
}
})
})
}
// 检查Apple登录支持
function isAppleLoginSupported() {
// #ifdef APP-PLUS
const platform = uni.getSystemInfoSync().platform
if (platform === 'ios') {
const version = uni.getSystemInfoSync().system
const iosVersion = parseFloat(version.replace('iOS ', ''))
return iosVersion >= 13.0
}
returnfalse
// #endif
// #ifdef MP-WEIXIN
returnfalse// 微信小程序不支持Apple登录
// #endif
// #ifdef H5
returntrue// Web端可以支持
// #endif
}
4.2 完整的登录组件
<template>
<view class="apple-login-container">
<button
v-if="showAppleLogin"
class="apple-login-btn"
@click="handleAppleLogin"
:loading="isLoading"
>
<image class="apple-icon" src="/static/apple-icon.png" mode="aspectFit"></image>
<text class="login-text">使用Apple账号登录</text>
</button>
</view>
</template>
<script>
export default {
data() {
return {
isLoading: false,
showAppleLogin: false
}
},
mounted() {
this.checkAppleLoginSupport()
},
methods: {
// 检查Apple登录支持
checkAppleLoginSupport() {
// #ifdef APP-PLUS
const systemInfo = uni.getSystemInfoSync()
if (systemInfo.platform === 'ios') {
const iosVersion = parseFloat(systemInfo.system.replace('iOS ', ''))
this.showAppleLogin = iosVersion >= 13.0
}
// #endif
// #ifdef H5
this.showAppleLogin = true
// #endif
},
// 处理Apple登录
async handleAppleLogin() {
if (this.isLoading) return
this.isLoading = true
try {
const result = await this.performAppleLogin()
await this.sendToServer(result)
uni.showToast({
title: '登录成功',
icon: 'success'
})
// 登录成功后的处理
this.$emit('loginSuccess', result)
} catch (error) {
console.error('Apple登录失败:', error)
uni.showToast({
title: error.message || '登录失败',
icon: 'none'
})
} finally {
this.isLoading = false
}
},
// 执行Apple登录
performAppleLogin() {
return new Promise((resolve, reject) => {
uni.login({
provider: 'apple',
success: (loginRes) => {
console.log('登录响应:', loginRes)
// 获取用户信息
uni.getUserInfo({
provider: 'apple',
success: (userRes) => {
const result = {
// 授权码,用于服务器验证
authorizationCode: loginRes.authorizationCode,
// 身份令牌
identityToken: loginRes.identityToken,
// 用户唯一标识
user: loginRes.user,
// 用户信息(可能为空,只在首次授权时提供)
email: userRes.userInfo.email,
fullName: userRes.userInfo.fullName
}
resolve(result)
},
fail: (userError) => {
// 即使获取用户信息失败,登录可能已成功
const result = {
authorizationCode: loginRes.authorizationCode,
identityToken: loginRes.identityToken,
user: loginRes.user
}
resolve(result)
}
})
},
fail: (error) => {
reject(new Error(this.getErrorMessage(error)))
}
})
})
},
// 发送到服务器验证
async sendToServer(appleData) {
return new Promise((resolve, reject) => {
uni.request({
url: 'https://your-server.com/api/auth/apple',
method: 'POST',
data: {
authorizationCode: appleData.authorizationCode,
identityToken: appleData.identityToken,
user: appleData.user,
email: appleData.email,
fullName: appleData.fullName
},
success: (res) => {
if (res.data.success) {
// 保存登录状态
uni.setStorageSync('userToken', res.data.token)
uni.setStorageSync('userInfo', res.data.userInfo)
resolve(res.data)
} else {
reject(new Error(res.data.message || '服务器验证失败'))
}
},
fail: (error) => {
reject(new Error('网络请求失败'))
}
})
})
},
// 错误信息处理
getErrorMessage(error) {
const errorMap = {
'1001': '用户取消授权',
'1002': '网络错误',
'1003': '系统错误',
'1004': '用户未登录Apple ID',
'1005': 'Apple ID不可用',
default: '登录失败,请重试'
}
return errorMap[error.code] || errorMap.default
}
}
}
</script>
<style scoped>
.apple-login-container {
margin: 20rpx 0;
}
.apple-login-btn {
display: flex;
align-items: center;
justify-content: center;
background-color: #000000;
color: #ffffff;
border-radius: 8rpx;
padding: 24rpx 40rpx;
border: none;
font-size: 32rpx;
font-weight: 500;
}
.apple-login-btn:active {
background-color: #333333;
}
.apple-icon {
width: 36rpx;
height: 36rpx;
margin-right: 16rpx;
}
.login-text {
color: #ffffff;
}
</style>
4.3 登录状态管理
// store/auth.js - 使用Vuex管理登录状态
exportdefault {
namespaced: true,
state: {
isLoggedIn: false,
userToken: '',
userInfo: null,
loginProvider: ''// 记录登录方式
},
mutations: {
SET_LOGIN_STATE(state, { token, userInfo, provider }) {
state.isLoggedIn = true
state.userToken = token
state.userInfo = userInfo
state.loginProvider = provider
// 持久化存储
uni.setStorageSync('userToken', token)
uni.setStorageSync('userInfo', userInfo)
uni.setStorageSync('loginProvider', provider)
},
CLEAR_LOGIN_STATE(state) {
state.isLoggedIn = false
state.userToken = ''
state.userInfo = null
state.loginProvider = ''
// 清除存储
uni.removeStorageSync('userToken')
uni.removeStorageSync('userInfo')
uni.removeStorageSync('loginProvider')
}
},
actions: {
// Apple登录
async appleLogin({ commit }) {
try {
const appleData = await performAppleLogin()
const serverResponse = await verifyWithServer(appleData)
commit('SET_LOGIN_STATE', {
token: serverResponse.token,
userInfo: serverResponse.userInfo,
provider: 'apple'
})
return serverResponse
} catch (error) {
throw error
}
},
// 退出登录
logout({ commit, state }) {
// 如果是Apple登录,可以选择性地撤销授权
if (state.loginProvider === 'apple') {
// Apple登录撤销授权需要在服务器端处理
this.dispatch('auth/revokeAppleAuth')
}
commit('CLEAR_LOGIN_STATE')
},
// 撤销Apple授权(可选)
async revokeAppleAuth({ state }) {
try {
await uni.request({
url: 'https://your-server.com/api/auth/apple/revoke',
method: 'POST',
data: {
token: state.userToken
}
})
} catch (error) {
console.error('撤销Apple授权失败:', error)
}
}
}
}
五、服务器端验证(Node.js示例)
5.1 验证Apple Identity Token
const jwt = require('jsonwebtoken')
const axios = require('axios')
// 验证Apple Identity Token
asyncfunction verifyAppleToken(identityToken) {
try {
// 获取Apple公钥
const appleKeys = await getApplePublicKeys()
// 解码JWT头部获取kid
const decodedHeader = jwt.decode(identityToken, { complete: true }).header
const kid = decodedHeader.kid
// 找到对应的公钥
const key = appleKeys.find(k => k.kid === kid)
if (!key) {
thrownewError('无法找到对应的Apple公钥')
}
// 验证JWT
const publicKey = `-----BEGIN RSA PUBLIC KEY-----\n${key.n}\n-----END RSA PUBLIC KEY-----`
const decoded = jwt.verify(identityToken, publicKey, {
algorithms: ['RS256'],
audience: 'your.bundle.id', // 你的Bundle ID
issuer: 'https://appleid.apple.com'
})
return decoded
} catch (error) {
thrownewError('Apple token验证失败: ' + error.message)
}
}
// 获取Apple公钥
asyncfunction getApplePublicKeys() {
try {
const response = await axios.get('https://appleid.apple.com/auth/keys')
return response.data.keys
} catch (error) {
thrownewError('获取Apple公钥失败')
}
}
// API路由示例
app.post('/api/auth/apple', async (req, res) => {
try {
const { identityToken, authorizationCode, user, email, fullName } = req.body
// 验证Identity Token
const tokenData = await verifyAppleToken(identityToken)
// 检查用户是否存在
let userRecord = await User.findOne({ appleId: tokenData.sub })
if (!userRecord) {
// 创建新用户
userRecord = await User.create({
appleId: tokenData.sub,
email: email || tokenData.email,
fullName: fullName,
provider: 'apple'
})
}
// 生成JWT token
const token = jwt.sign(
{ userId: userRecord._id, provider: 'apple' },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
)
res.json({
success: true,
token: token,
userInfo: {
id: userRecord._id,
email: userRecord.email,
fullName: userRecord.fullName
}
})
} catch (error) {
res.status(400).json({
success: false,
message: error.message
})
}
})
六、测试和调试
6.1 真机调试
Apple登录只能在真机上测试,模拟器无法使用此功能。
6.2 测试账号
-
使用已登录Apple ID的设备进行测试
-
确保设备iOS版本 ≥ 13.0
-
在设置中确认Apple ID已登录
6.3 调试技巧
// 添加详细日志
function debugAppleLogin() {
console.log('系统信息:', uni.getSystemInfoSync())
uni.login({
provider: 'apple',
success: (res) => {
console.log('登录成功完整响应:', JSON.stringify(res, null, 2))
},
fail: (err) => {
console.log('登录失败详细信息:', JSON.stringify(err, null, 2))
}
})
}
七、常见问题解决
7.1 登录按钮不显示
问题: Apple登录按钮不显示解决方案:
-
检查iOS版本是否 ≥ 13.0
-
确认manifest.json配置正确
-
验证Bundle ID配置
7.2 登录失败常见错误
错误1001 - 用户取消
// 用户主动取消,正常流程,无需特殊处理
错误1004 - 未登录Apple ID
// 提示用户在设置中登录Apple ID
uni.showModal({
title: '提示',
content: '请先在设备设置中登录Apple ID',
showCancel: false
})
错误1005 - Apple ID不可用
// Apple ID被禁用或受限
uni.showModal({
title: '提示',
content: 'Apple ID暂时不可用,请稍后重试',
showCancel: false
})
7.3 获取不到用户信息
Apple只在用户首次授权时提供email和姓名信息,后续登录可能返回空值。需要在服务器端保存首次获取的信息。
7.4 生产环境配置
-
确保App Store Connect中的Bundle ID与代码中一致
-
上传到App Store前测试Apple登录功能
-
确保服务器端Token验证正确配置
八、最佳实践
8.1 用户体验优化
-
提供清晰的登录选项
-
处理登录失败的友好提示
-
支持登录状态持久化
8.2 安全考虑
-
在服务器端验证所有Apple返回的数据
-
使用HTTPS传输敏感信息
-
定期更新JWT密钥
8.3 隐私保护
-
遵循Apple隐私政策
-
明确告知用户数据使用方式
-
支持用户删除账号功能
通过以上完整指南,您应该能够成功在UniApp中集成Apple登录功能。记住在每个步骤都进行充分测试,确保功能在不同设备和场景下都能正常工作。
1万+

被折叠的 条评论
为什么被折叠?



