需用到 Composer 库:firebase/php-jwt,直接安装即可 composer require firebase/php-jwt

资源

生成 Apple 登录按钮图标:https://appleid.apple.com/signinwithapple/button

官方文档(使用 Apple 登录):https://developer.apple.com/documentation/sign_in_with_apple

生成并验证 Token:https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens

校验 Identity Token:https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/verifying_a_user#3383769

这个简单说下,苹果建议使用 5 个校验

  1. 使用公钥校验 JWS E256 签名
  2. 验证 nonce 随机数
  3. 验证 iss 字段包含 https://appleid.apple.com
  4. 验证 aud 字段为开发者的 client_id(即 Bundle ID)
  5. 验证当前时间早于 exp 字段时间

JWKSet:https://developer.apple.com/documentation/sign_in_with_apple/jwkset

获取 access_token/identityToken

简单示例下 UniApp 中通过 uni.login() 方法获取 JWT

let type = 'apple'

uni.getProvider({
    service: 'oauth',
    success: (res) => {
        if (res.provider.includes(type)) {
            uni.login({
                provider: type,
                success: (authed) => {
                    console.log('三方登录获取用户信息成功', authed)

                    // Apple 登录这儿可用 authed.authResult 或 authed.appleInfo 得到授权数据
                    // authResult.access_token 与 appleInfo.identityToken 相同;authResult.openid 与 appleInfo.user 相同
                    // 但 Authorization Code 存在于 appleInfo,即 appleInfo.authorizationCode
                    // TODO: 登录请求
                },
                fail: (err) => {
                    console.log('三方登录获取登录信息失败', err)

                    if (err.errCode === 1001) {
                        // 登录已取消
                    } else {
                        // 其它错误情况
                    }
                }
            })
        } else {
            // 当前环境不支持该登录方式
        }
    },
    fail: (err) => {
        console.log('获取三方登录信息异常', err)
    }
})

获取 Apple 公钥

公钥说明:https://developer.apple.com/documentation/sign_in_with_apple/fetch_apple_s_public_key_for_verifying_token_signature

需要注意的是这段说明:

The endpoint can return multiple keys, and the count of keys can vary over time. From this set of keys, select the key with the matching key identifier (kid) to verify the signature of any JSON Web Token (JWT) issued by Apple. For more information, see the JSON Web Signature specification.
该接口会返回多个密钥,并且密钥的数量会随着时间发生变化。在该组密钥中选择与标识符(kid)匹配的密钥来验证由 Apple 颁布的 JWT 签名。有关更多信息,请参阅 JSON Web 签名规范。

公钥接口:https://appleid.apple.com/auth/keys

因为该密钥集会产生变化,所以在不了解更新周期的情况下 不建议缓存 该内容

获取后需进行解码 $keys = json_decode($keys, true);

解析 $keys(公钥)

这里就需要使用安装的 Composer 库了:

try {
    $parsedKeys = \Firebase\JWT\JWK::parseKeySet($keys);
} catch (Exception $e) {
    // 一定要捕获异常并自行处理解析 Key 失败的情况
}

解码获取 OpenID

一定要捕获异常decode 方法会抛出 7 种不同的异常,有个简单且友好的做法是单独判断过期异常并响应友好提示

try {
    $decoded = \Firebase\JWT\JWT::decode($token, $parsedKeys);
} catch (\Exception $e) {
    if ($e instanceof \Firebase\JWT\ExpiredException) {
        // 返回友好提示告知用户授权过期
    }

    // 可直接响应登录异常或参数异常
}

// JWT 中 sub 即为 OpenID
$openId = $decoded->sub;
// 邮箱地址
$email = $decoded->email;

多说两句

  1. Apple 登录授权后前端除了拿到 identityToken 还有一个 user 也是 OpenID,但是该 OpenID 不可信,可以在解码 JWT 后进行对比;
  2. JWT 的有效期是 10 分钟;

解码后的完整结果(var_dump 示例):

class stdClass#89 (10) {
  public $iss =>
  string(25) "https://appleid.apple.com"
  public $aud =>
  string(15) "Bundle ID"
  public $exp =>
  int(1695473542)
  public $iat =>
  int(1695387142)
  public $sub =>
  string(44) "此段为 OpenID"
  public $c_hash =>
  string(22) "一段 hash 值"
  public $email =>
  string(18) "Apple ID 邮箱地址"
  public $email_verified =>
  string(4) "true"
  public $auth_time =>
  int(1695387142)
  public $nonce_supported =>
  bool(true)
}