Sign In With Apple 从登陆到服务器验证
-
服务端向苹果请求验证
手机端需要提交 user 、authorizationCode 、 identityToken 字段信息(code和token字段苹果返回的是 base64 Data 形式,手机端可以先转换 base64 字符串之后在给服务器)到服务器。然后服务器通过 https://appleid.apple.com/auth/token 该接口,并拼接对应参数去验证,接口相关信息苹果有提供 Generate and validate tokens 。参考链接:https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens
下面着重介绍几个参数及其获取方法。首先先看下该接口需要的参数,如下:client_id: string (Required) (Authorization and Validation) The application identifier for your appclient_secret: string (Required) (Authorization and Validation) A secret generated as a JSON Web Token that uses the secret key generated by the WWDR portal.code: string (Authorization) The authorization code received from your application’s user agent. The code is single use only and valid for five minutes.grant_type: string (Required) (Authorization and Validation) The grant type that determines how the client interacts with the server. For authorization code validation, use authorization_code. For refresh token validation requests, use refresh_token.refresh_token: string (Validation) The refresh token received during the authorization request.redirect_uri: string (Authorization) The destination URI the code was originally sent to.
其中 client_id 为app的 bundle identifier , code 即为手机端获取到的 authorizationCode 信息, grant_type 传入固定字符串 authorization_code 即可。还剩下一个必要参数 client_secret 那么这个参数相对麻烦点,需要我们自己生成。client_secret参数是一个JWT,singature部分使用非对称加密 RSASSA【RSA签名算法】 和 ECDSA【椭圆曲线数据签名算法】。
生成 client_secret 之前,我们需要做如下工作- 获取 APP 的 bundleID
- 获取开发者账号的TeamID
- 创建 privateKey,获取到 Key ID 和 私钥
创建完之后把私钥下载下来,并保存好,注意,私钥只能下载一次。
-
生成client_secret
{ //首先composer编译加载"lcobucci/jwt": "^3.4" "require": {"facebook/graph-sdk": "^5.7", //facebook"lcobucci/jwt": "^3.4"//apple } //然后拼凑参数生成jwt格式密钥 $payload = array("iss" => 'XXXX',//开发者账号的TeamID"aud" => "https://appleid.apple.com","iat" => time(),"sub" => 'XXXX',//bundle id"exp" => time()+ 86400*180);$jwt_header = array('typ' => 'JWT','alg' => 'ES256','kid' => 'XXXX',//上面截图的keyId );$key_path = 'XXXX';//下载的私钥文件,此文件为.p8结尾的文件 $signer = new Lcobucci\JWT\Signer\Ecdsa\Sha256(); $key = new Lcobucci\JWT\Signer\Key($key_path); $builder = new Lcobucci\JWT\Builder(); $builder->sign($signer, $key); foreach($jwt_header as $key => $value)$builder->withHeader($key, $value); foreach($payload as $key => $value)$builder->withClaim($key, $value); $jwt_token = $builder->getToken(); $jwt = (string)$jwt_token;//此jwt就为生成的client_secret//接下来就是拼凑请求参数 $appleConfig = array(); $appleConfig['client_id'] = 'XXXX;//bundle id $appleConfig['client_secret'] = $jwt; $appleConfig['code'] = $authorizationCode;//客户端获取的code $appleConfig['grant_type'] = 'authorization_code';//authorization_code,refresh_token $szUrl = 'https://appleid.apple.com/auth/token'; $headers[] = "Content-Type:application/x-www-form-urlencoded";//设置请求头 $curl = curl_init(); //设置抓取的url curl_setopt($curl, CURLOPT_URL, $szUrl); //设置头文件的信息作为数据流输出 curl_setopt($curl, CURLOPT_HEADER,0); //设置请求头 curl_setopt($curl, CURLOPT_HTTPHEADER,$headers); //设置获取的信息以文件流的形式返回,而不是直接输出。 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); //设置post方式提交 curl_setopt($curl, CURLOPT_POST, 1); //设置post数据 $post_data = urldecode(http_build_query($appleConfig)); curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data); //执行命令 $response = curl_exec($curl); //关闭URL请求 curl_close($curl); //返回结果 $response = json_decode($response,true);
-
返回结果如下
{"access_token": "一个token,此处省略","token_type": "Bearer","expires_in": 3600,"refresh_token": "一个token,此处省略","id_token": "结果是JWT,字符串形式,此处省略"}
参数解释看这个文档:https://developer.apple.com/documentation/sign_in_with_apple/tokenresponse
-
服务器拿到相应结果,其中 id_token 也是 JWT 数据,decode 出 payload 部分如下
其中 aud 部分与你的app的bundleID一致, sub 部分即与手机端获得的 user 一致,服务器端通过对比 sub 字段信息是否与手机端上传的 user 信息一致来确定是否成功登录。