Client_id , Team_id, Key_id 등 등록하는 방법은 생략.

 

1. JWT 설치 (bash)

composer require firebase/php-jwt

 

2.프론트 코드

<?php
$apple_apiURL = "https://appleid.apple.com/auth/authorize?client_id={발급 받은 client_id}&redirect_uri={등록한 redirect_uri}&response_type=code&scope=email name&state=state&response_mode=form_post";
?>

<a href="<?=$apple_apiURL?>">
    <img src="https://appleid.cdn-apple.com/appleid/button?locale=ko_KR&color=black&size=large&type=sign-in" alt="Sign in with Apple">
</a>

 

3. .p8 파일을 .pem 파일로 변환(bash)

※ OpenSSL 함수를 사용하기 위해 변환.

openssl pkcs8 -in AuthKey_XXXXXX.p8 -topk8 -nocrypt -out private_key.pem

 

4.서버 코드

require_once '../vendor/autoload.php'; // 필요 시 Composer로 추가 설치

function encode($data) {
    $encoded = strtr(base64_encode($data), '+/', '-_');
    return rtrim($encoded, '=');
}

function generateJWT($kid, $iss, $sub, $private_key_path) {
    $header = [
        'alg' => 'ES256',
        'kid' => $kid
    ];
    $body = [
        'iss' => $iss,
        'iat' => time(),
        'exp' => time() + 3600, // 1시간 유효
        'aud' => 'https://appleid.apple.com',
        'sub' => $sub
    ];

    $privKey = openssl_pkey_get_private(file_get_contents($private_key_path));

    if (!$privKey) {
        throw new Exception("Private key 읽기 실패");
    }

    $payload = encode(json_encode($header)) . '.' . encode(json_encode($body));

    $signature = '';
    $success = openssl_sign($payload, $signature, $privKey, OPENSSL_ALGO_SHA256);
    if (!$success) {
        throw new Exception("JWT 서명 생성 실패");
    }

    $raw_signature = fromDER($signature, 64);

    return $payload . '.' . encode($raw_signature);
}

function fromDER(string $der, int $partLength) {
    $hex = unpack('H*', $der)[1];
    if ('30' !== mb_substr($hex, 0, 2, '8bit')) {
        throw new RuntimeException("DER 포맷 오류: SEQUENCE 아님");
    }
    if ('81' === mb_substr($hex, 2, 2, '8bit')) {
        $hex = mb_substr($hex, 6, null, '8bit');
    } else {
        $hex = mb_substr($hex, 4, null, '8bit');
    }
    if ('02' !== mb_substr($hex, 0, 2, '8bit')) {
        throw new RuntimeException("DER 포맷 오류: INTEGER 아님");
    }
    $Rl = hexdec(mb_substr($hex, 2, 2, '8bit'));
    $R = retrievePositiveInteger(mb_substr($hex, 4, $Rl * 2, '8bit'));
    $R = str_pad($R, $partLength, '0', STR_PAD_LEFT);
    $hex = mb_substr($hex, 4 + $Rl * 2, null, '8bit');
    if ('02' !== mb_substr($hex, 0, 2, '8bit')) {
        throw new RuntimeException("DER 포맷 오류: INTEGER 아님");
    }
    $Sl = hexdec(mb_substr($hex, 2, 2, '8bit'));
    $S = retrievePositiveInteger(mb_substr($hex, 4, $Sl * 2, '8bit'));
    $S = str_pad($S, $partLength, '0', STR_PAD_LEFT);
    return pack('H*', $R . $S);
}

function retrievePositiveInteger(string $data) {
    while ('00' === mb_substr($data, 0, 2, '8bit') && mb_substr($data, 2, 2, '8bit') > '7f') {
        $data = mb_substr($data, 2, null, '8bit');
    }
    return $data;
}

function getAppleAccessToken($code, $client_id, $team_id, $key_id, $private_key_path, $redirect_uri) {
    $url = "https://appleid.apple.com/auth/token";

    // JWT 생성
    $jwt = generateJWT($key_id, $team_id, $client_id, $private_key_path);

    $data = [
        'grant_type' => 'authorization_code',
        'code' => $code,
        'redirect_uri' => $redirect_uri,
        'client_id' => $client_id,
        'client_secret' => $jwt
    ];

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

    $response = curl_exec($ch);
    $error = curl_error($ch);
    curl_close($ch);

    if ($error) {
        throw new Exception("cURL 오류: $error");
    }

    return json_decode($response, true);
}

// 리디렉션된 Authorization Code 처리
if (isset($_POST['code'])) {
    try {
        $code = $_POST['code'];

        $client_id = ''; // Client ID
        $team_id = ''; // Team ID
        $key_id = ''; // Key ID
        $private_key_path = '../key/private_key.pem'; // Key 파일 경로
        $redirect_uri = '';

        $tokenResponse = getAppleAccessToken($code, $client_id, $team_id, $key_id, $private_key_path, $redirect_uri);

        if (!empty($tokenResponse['id_token'])) {
            $idToken = explode('.', $tokenResponse['id_token']);
            $userInfo = json_decode(base64_decode($idToken[1]), true);

            $appleUserId = $userInfo['sub'];
            $email = $userInfo['email'] ?? 'private';

            //회원 가입 및 로그인 코드 영역

        } else {
            throw new Exception("Apple 로그인 실패! 오류 메시지: " . json_encode($tokenResponse));
        }
    } catch (Exception $e) {
        echo "오류 발생: " . $e->getMessage();
    }
}

// 사용자의 접속 경로를 결정하는 함수
function determinePath() {
    // 사용자 에이전트 문자열을 가져옵니다.
    $user_agent = $_SERVER['HTTP_USER_AGENT'];

    // 웹 브라우저를 판별합니다.
    if (strpos($user_agent, 'Android') !== false) {
        return "Android";
    } elseif (strpos($user_agent, 'iPhone') !== false || strpos($user_agent, 'iPad') !== false || strpos($user_agent, 'iPod') !== false) {
        return "iOS";
    } else {
        return "Web";
    }
}

 

※테스트를 위한 로그인 해제 URL

https://account.apple.com/account/manage

'프로그래밍 > API' 카테고리의 다른 글

[API]구글 로그인 API  (0) 2024.01.26
[API] 네이버 로그인 API 연동  (0) 2024.01.24
YouTube 간단히 연동하기  (0) 2019.06.27

+ Recent posts