一、情景描述


前两天,APP因为有加了其他第三方的登录而唯独忽略了苹果登录,于是,被赤果果地拒了!因此,开发苹果登录,被提上了日程,故而,就有了这篇帖子“Generate and Validate Tokens”。 苹果开发文档地址:https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens


二、重点地方


1、通过 苹果authorization_code去请求苹果的官方接口,获取返回数据 请求参数,grant_type,如果选择authorization_code,那就只能传code;反之,就只能传refresh_token 2、对于返回数据解析,因为要用到id_token中的sub,因此,需要处理JWT反解码(这里绕了弯路,记录一下) 注意,这里不用下gitHub上的PHP的jwt的组件,直接取其中payload部分,用一个方法,就能解决!


三、代码部分

<?php

namespace App\Repositories;

/**
 * ||--------------------------------------------------------------------------------------------------------------
 * | # Apple生成和验证令牌 - 逻辑处理
 * ||——————————————————————————————————————————————————————————————————————————————————————————————————————————————
 * | Author:NangongYi
 * | Time:2020/11/17 10:52
 * | Power:用于处理苹果登录时,生成和验证令牌的请求及解码处理
 * ||--------------------------------------------------------------------------------------------------------------
 */
class AppleService
{
    /**
     * 私有属性
     */
    protected $jwt;

    /**
     * 请求地址
     */
    const URL = 'https://appleid.apple.com/auth/token';


    /**
     * 苹果 - Web服务端点 生成和验证令牌
     *
     * @param {string} $code 苹果 authorization_code
     * @return {string} sub 客户机密主体
     */
    public function appleCheck($code)
    {
        $data = [
            'client_id' => config('apple.client_id'),
            'client_secret' => config('apple.client_secret'),
            'grant_type' => config('apple.grant_type'),
            'code' => $code
        ];
        $d_string = '';
        foreach ($data as $key=>$val) {
            $d_string .= '&'.$key.'='.$val;
        }
        $d_string = substr($d_string, 1);

        $url = self::URL;
        $res = $this->curlPost($url, $d_string);

        $id_token = isset($res['id_token'])?$res['id_token']:'';
        $id_token_arr = explode('.',$id_token);
        $payload = $id_token_arr[1];
        $data = json_decode($this->base64UrlDecode($payload), true);

        return isset($data['sub']) ? $data['sub'] : '';
    }

    /**
     * 传入数组进行HTTP POST请求
     *
     * @param {string} $url 请求地址
     * @param {string} $data 请求数据
     * @param {array} $header 请求头数据
     * @return {mixed}
     */
    public function curlPost($url , $data)
    {
        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => "",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 0,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => "POST",
            CURLOPT_POSTFIELDS => $data,
            CURLOPT_HTTPHEADER => array(
                "Content-Type: application/x-www-form-urlencoded"
            ),
        ));
        $response = curl_exec($curl);
        curl_close($curl);
        return json_decode($response, true);
    }

    /**
     * base64UrlEncode  https://jwt.io/  中base64UrlEncode解码实现
     *
     * @param {string} $input 需要解码的字符串
     * @return {bool}|{string}
     */
    public function base64UrlDecode($input)
    {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $addlen = 4 - $remainder;
            $input .= str_repeat('=', $addlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
    }
}

四、最后总结


之前没有接触过JWT的相关东西,算是一个比较生疏的点。 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。 JWT的构成 第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature). header jwt的头部承载两部分信息: 声明类型,这里是jwt 声明加密的算法 通常直接使用 HMAC SHA256 完整的头部就像下面这样的JSON: { 'typ': 'JWT', 'alg': 'HS256' } 然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分. eyJ0eXAiOifgH1QiLCJhbGciasDIUzI1NiJ9 playload 载荷就是存放有效信息的地方。 标准中注册的声明 公共的声明 私有的声明 标准中注册的声明 (建议但不强制使用) : iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: jwt的过期时间,这个过期时间必须要大于签发时间 nbf: 定义在什么时间之前,该jwt都是不可用的. iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。 公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密. 私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。 定义一个payload: { "sub": "147258369", "name": "Jean", "admin": true } 然后将其进行base64加密,得到Jwt的第二部分。 eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 signature jwt的第三部分是一个签证信息,这个签证信息由三部分组成: header (base64后的) payload (base64后的) secret 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。 // javascript var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload); var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 将这三部分用.连接成一个完整的字符串,构成了最终的jwt. JWT的用途 JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。 优点 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。 它不需要在服务端保存会话信息, 所以它易于应用的扩展 安全相关 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。 保护好secret私钥,该私钥非常重要。 如果可以,请使用https协议