• PHP 接入 Apple 登录对 identityToken 验证


    需用到 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)
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    获取 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 失败的情况
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    解码获取 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    多说两句

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    动态内存管理
    深入了解C#泛型
    二维码智慧门牌管理系统:智能化地址管理,提升社会治理效率
    MYSQL数据库恢复(误删操作)
    自定义mvc02
    牛客刷题---年会抽奖
    Docker仓库构建:官方仓库、私有仓库及企业级仓库harbor的搭建
    十种经典排序算法总结
    物联网开发自学的一些建议
    【pytorch】LeNet-5 手写数字识别MNIST
  • 原文地址:https://blog.csdn.net/maxsky/article/details/126904637