UniApp集成Apple登录完整指南

一、前期准备

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

  1. 登录 Apple Developer Center

  2. 进入 Certificates, Identifiers & Profiles

  3. 点击 Identifiers → App IDs

  4. 创建新的App ID或编辑现有的App ID

  5. 在 Capabilities 中启用 Sign In with Apple

2.2 配置Services ID(可选)

如果需要在Web端使用Apple登录:

  1. 在 Identifiers 中选择 Services IDs

  2. 创建新的Services ID

  3. 配置域名和重定向URL

2.3 创建Key文件

  1. 在 Keys 部分创建新的密钥

  2. 启用 Sign In with Apple 服务

  3. 下载 .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模块配置 中:

  1. 勾选 OAuth(登录鉴权)

  2. 在OAuth配置中勾选 Apple登录

  3. 填写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 生产环境配置

  1. 确保App Store Connect中的Bundle ID与代码中一致

  2. 上传到App Store前测试Apple登录功能

  3. 确保服务器端Token验证正确配置

八、最佳实践

8.1 用户体验优化

  • 提供清晰的登录选项

  • 处理登录失败的友好提示

  • 支持登录状态持久化

8.2 安全考虑

  • 在服务器端验证所有Apple返回的数据

  • 使用HTTPS传输敏感信息

  • 定期更新JWT密钥

8.3 隐私保护

  • 遵循Apple隐私政策

  • 明确告知用户数据使用方式

  • 支持用户删除账号功能

通过以上完整指南,您应该能够成功在UniApp中集成Apple登录功能。记住在每个步骤都进行充分测试,确保功能在不同设备和场景下都能正常工作。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端组件开发

你的钟意将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值