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
'프로그래밍 > API' 카테고리의 다른 글
[API]구글 로그인 API (0) | 2024.01.26 |
---|---|
[API] 네이버 로그인 API 연동 (0) | 2024.01.24 |
YouTube 간단히 연동하기 (0) | 2019.06.27 |