애플 로그인.. 정말 악명이 높다. 이번 프로젝트에서 소셜 로그인 구현을 담당하게 되었는데, 그 과정에서 학습한 애플 로그인부터 탈퇴까지 그 흐름에 대해서 정리해 보았다.
Apple OAuth의 흐름
사용자가 애플 계정으로 로그인을 하면 Apple 서버로부터 다양한 정보를 얻게 된다. 이 중 사용자 인증의 핵심 열쇠인 Identity Token
과 Authorization Code
을 잘 기억해두자.
Authenticating users with Sign in with Apple | Apple Developer Documentation
위 링크는 애플 공식 문서에서 로그인에 대해 설명한 문서이다. 전체적인 흐름, 받아올 수 있는 정보 또는 받아오지 못하는 정보 등 굉장히 자세하게 적혀있으므로 꼭 위 문서를 꼼꼼히 읽어보자! 본격적으로 구현하기 전에, 저 위의 문서를 꼼꼼히 읽어 흐름을 파악했다면 구현할 때 정말 수월하다.
회원 정보 가져오기
사용자가 애플 로그인을 했을 때, 그 사용자의 정보(user information)을 가져오기 위해서는 말 그대로 신원 정보를 담고 있는 Identity Token
이 필요하다.
애플 공식 문서에 따르면 Identity Token
은 JWT형식으로 해당 토큰의 Payload에 사용자의 정보(email, serial ID)가 담겨있다. 이때, Identity Token
으로는 사용자의 이름 정보를 가져올 수는 없다.(이 내용도 위의 공식 문서에 서술되어 있다.)
때문에 사용자의 이름이 필요한 서비스라면 클라이언트에서 이름을 받아와서 서버에게 Identity Token
과 사용자의 이름 정보도 함께 보내주어야 한다.
서버에서 Identity Token
을 검증하기 위해서는 Apple 서버의 공개 키로 서명을 확인해야 한다.
Fetch Apple’s public key for verifying token signature | Apple Developer Documentation
Apple 서버에 공캐 키를 요청하면, 아래와 같이 여러 개의 키들을 보내준다.
{
"keys": [
{
"kty": "RSA",
"kid": "FftONTxoEg",
"use": "sig",
"alg": "RS256",
"n": "wio-SFzFvKKQ9vl5ctaYSi09o8k3Uh7r6Ht2eJv-hSaZ6A6xTXVIBVSm0KvPxaJlpjYPTCcl2sdEyXlD2Uh1khUKU7r9ON3rpN8pFHAere5ig_JGVEShxmt5E_jzMymYnSfkoSW44ulevQeUwP_MiC5VC1KJjTfD73ghX0tQ0-_RjTJJ2cLyFC4VFNboBMCVioUrz8IA3c0KIOl507qswQvMsh2vBTMDDSJfippAGLzUiWXxUlid-vyOC8GCtag61taSorxCw14irk-tsh7hWjDDkSTFn2gChPMfXXj10_lCv0UG29TVUVCAsay4pszzgmc4zwhgSsqQRd939BJexw",
"e": "AQAB"
},
{
"kty": "RSA",
"kid": "pyaRQpAbnY",
"use": "sig",
"alg": "RS256",
"n": "qHiwOpizi6xHG8FIOSWH4l0P1CjLIC7aBFkhbk7BrD4s9KQAs5Sj5xAtOwlZMyP2XFcqRtZBLIMM7vw_CNERtRrhc68se5hQE_vsrHy7ugcQU6ogJS6s54zqO-zTUfaa3mABM6iR-EfgSpvz33WTQZAPtwAyxaSLknHyDzWjHEZ44WqaQBdcMAvgsWMYG5dBfnV-3Or3V2r1vdbinRE5NomE2nsKDbnJ3yo3u-x9TizKazS1JV3umt71xDqbruZLybIrimrzg_i9OSIzT2o5ZWz8zdYkKHZ4cvRPh-DDt8kV7chzR2tenPF2c5WXuK-FumOrjT7WW6uwSvhnhwNZuw",
"e": "AQAB"
},
{
"kty": "RSA",
"kid": "pggnQeNCOU",
"use": "sig",
"alg": "RS256",
"n": "xyWY7ydqTVHRzft5fPZmTuD9Ahk7-_2_IekZGy07Ovhj5IhYyVU8Hq5j0_c9m9tSdJTRdKmNjMURpY4ZJ_9rd3EOQ_WnYHM2cZIQ5y3f_WxeElnv_f2fKDruA-ERaQ6duov-3NAXC3oTWdXuRGRLbbfOVCahTjvnAA8YBRUe3llW7ZvTG14g-fAEQVlMYDxxCsbjtBJiUzKxbH-8KvhIhP9AJtiLDfiK1yzVJ7Qn6HNm5AUsFQKOAgTqxDMJkhi7pyntTyxhpkLYTEndaPRXth_LM3hVmaoFb3P3TsPCbDjSEbKy1wAndfPSzUk6qjyyBYhdXH0sgVpKMBAdggylLQ",
"e": "AQAB"
},
{
"kty": "RSA",
"kid": "T8tIJ1zSrO",
"use": "sig",
"alg": "RS256",
"n": "teUbLrwScsjVrcFAvSrfben3eQaEca3ESBegGh_wdGuLKw6QgwDxY3fC1_WeSVnkJXx72ddw3j2inoADnTyzuNa_PwDSmvJhOhmzOmoltmtKHteGdaXrqMohO6A85WxVKbN7pzDqwZJNrdY12LOltlI8PHIG-elAbKM2XOHiJaZnLpAVckKy6MQYsEExpPB3plGxWZElqwNZY6SUDVeN-o9qg5FJOFg7T7iTVVEagws4DM6uZNMDQGtqg9V9VqPQkUzC-sYd5eqbB9LqH4iN5F6OB7BmD3g3jCu9zgh3O9V24N43EruBCNrmP0xLP5ZliKqozoAcd1nv71HuVm6mgQ",
"e": "AQAB"
}
]
}
이 키들 중에서 Identity Token
헤더에 포함된 kid, alg가 일치하는 키를 가져오면 된다.
회원 탈퇴
애플 심사 지침에는 앱 내에서 애플 로그인을 제공하는 경우 계정 탈퇴 시 Apple REST API를 호출하여 사용자 토큰을 만료하라는 요구가 있다.
다시 처음으로 돌아가서 흐름을 생각해보면 사용자가 애플 로그인을 할 때, 우리는 Authorization Code
을 발급 받을 수 있다. 이 코드를 사용해서 우리는 애플 서버의 token
(애플 서버에서 발급한 accesss token 또는 refresh token)을 받아올 수 있으며 이는 애플 서버에 탈퇴(revoke) 요청을 보낼 때 필수적으로 필요한 토큰 정보이다. 이 토큰 정보에 더해서 client_id
, client_secret
, token_type_hint
이 3가지 정보가 추가적으로 필요하다. 여기서 token_type_hint
는 요청 보낸 token
의 종류를 명시해주는 것이고 client_id
는 애플 개발자 계정에서 얻을 수 있다. 따라서 우리가 따로 생성하거나 얻어와야 하는 정보는 결과적으로 client_secret
과 token
이 2가지 이다.
호흡이 길다. 애플 로그인이 악명이 높은 이유가 이 회원 탈퇴 로직이 복잡하기 때문이 아닐까 싶다. 흐름을 다시 한번 찬찬히 살펴보고 구현에 들어가자.
Client Secret 생성하기
Creating a client secret | Apple Developer Documentation
먼저 client_secret
은 다음과 같은 형식을 따르고 있다.
{
"alg": "ES256",
"kid": "ABC123DEFG"
}
{
"iss": "HJGJHGGH",
"iat": 1437179036,
"exp": 1493298100,
"aud": "https://appleid.apple.com",
"sub": "com.mytest.app"
}
차례대로 각 구성요소를 살펴보면,
- alg : alg는 애플 로그인을 위한 토큰 알고리즘
- kid: 애플 개발자 계정과 연결된 Sign in with Apple private key에 대해 생성된 10자리 key 식별자
- iss: 애플 개발자 계정과 연결된 팀 ID
- iat: 클라이언트 암호를 생성한 시간
- exp: 클라이언트 암호가 만료되는 시간
- aud: 클라이언트 암호는 유효성 검사 서버로 전송되기 때문에, “https://appleid.apple.com/" 을 값으로 입력하면 됨
- sub: 애플 개발자 계정의 client_id값을 사용
즉, client_secret
을 생성하기 위해서는 우리가 애플 개발자 계정에서 애플 로그인 사용을 위해 앱을 등록하면서 발급 받은 keyId
, teamId
, clinetId
를 준비해야한다. 만료 시간의 경우, 임의로 설정해도 되지만 30일을 넘어가도록 설정할 경우 오류가 뜬다는 말이 있다. 나의 경우, 회원 탈퇴를 위해 사용하는 Authorization Code
의 만료 기한이 5분이고 우리의 서비스에서는 client_secret
을 회원 탈퇴를 위해서만 사용하기 때문에 똑같이 만료 기한을 5분으로 설정하였다.
public String generateClientSecret() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
Date expirationDate = Date.from(LocalDateTime.now().plusMinutes(5)
.atZone(ZoneId.systemDefault()).toInstant());
return Jwts.builder()
.setHeaderParam("alg", SignatureAlgorithm.ES256)
.setHeaderParam("kid", keyId)
.setIssuer(teamId)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(expirationDate)
.setAudience(AUDIENCE)
.setSubject(clientId)
.signWith(ApplePrivateKeyGenerator.getPrivateKey(), SignatureAlgorithm.ES256)
.compact();
}
위의 정보들로 만들어진 client_secret
는 반드시 private key로 서명이 되어야 한다. 이 private key는 애플 개발자 계정에서 앱을 등록하면 다운로드 받을 수 있다. 이렇게 하면 client_secret
이 완성된다.
💡 약간의 꿀팁! 내가 생성한 client_secret
이 제대로 서명됐는지 확인하고 싶다면?
- 위의 사이트에 출력된
client_secret
값을 'Encoded'에 붙여넣는다. - Public Key 파일을 만들어 준다.
- Terminal을 켠 후, private key 파일이 설치된 위치로 가서 아래 명령을 실행해준다. 아래 명령어는 개인키를 기반으로 공개키를 생성해주는 명령어이다.
openssl ec -in {private key 파일명.p8} -pubout -out {생성할 public key 파일명.p8}
- 해당 Public Key를 통째로 긁어서 Verify Signature에 복붙한다.
이렇게verified
라고 뜬다면 서명이 검증된 것으로 정상적으로 발급된 것이다.
Token 발급 받기
Generate and validate tokens | Apple Developer Documentation
토큰을 발급 받기 위해서는 client_id
, client_secret
, grant_type
, code
가 필요하다.
우리는 Authorization Code
을 이용하여 토큰을 발급 받을 것이기 때문에 grant_type
을 “authorization_code”로 설정해주면 된다.
요청이 성공하면 회원 탈퇴에 필요한 access token과 refresh token을 얻을 수 있다. 둘 중 무엇을 사용해도 상관이 없지만 만료 시간이 짧은 access token과 달리 refresh token의 경우 만료 기한이 없기 때문에 탈퇴 요청 시에 refresh token을 사용하는 것이 좀더 안정적이라 생각하여 현재 프로젝트에서는 탈퇴 요청시 refresh token을 사용하여 애플 서버에 탈퇴 요청을 하였다.
사용자 토큰 해지하기
마지막 단계이다. 지금까지 얻은 정보들을 가지고 https://appleid.apple.com/auth/revoke
로 요청을 보내면 된다.
Revoke tokens | Apple Developer Documentation
우리는 refresh token을 보내 회원 탈퇴 요청을 진행할 것이기 때문에 token_type_hint
는 “refresh_token”으로 설정해주면 된다.
public void requestRevoke(final String refreshToken, final String clientSecret) {
appleFeignClient.revoke(refreshToken, clientId, clientSecret, "refresh_token");
}
회원 탈퇴를 구현하는 2가지 방법
필자의 서비스에서는 인증만 Apple OAuth를 따르고 자체적으로 jwt 토큰을 사용해 인가를 처리하였다.
만약 로그인을 하자마자 바로 Apple 서버로부터 token 정보를 받아오도록 구현을 하였다면 후에 사용자가 탈퇴할 때 사용자 토큰을 만료하기 위해서 애플 서버에서 발급해준 refresh token을 db에 저장해 놓는 로직이 필요하다. (Authorization Code
의 만료 기한은 5분으로 매우 짧기 때문에 코드 그 자체를 db에 저장하는 것은 가입하자마자 탈퇴를 하는게 아닌 이상 효력이 없다.)
다만 탈퇴 기능만을 위해 애플 서버에서 발급 받은 Refresh Token을 DB에 저장하는 것은 비효율적이라 생각했기에 아래와 같은 로직으로 회원 탈퇴를 구현하였다.
🔑 탈퇴 로직
- 회원 탈퇴 버튼 클릭
- 애플 APP 재로그인 진행 (이때, Authorization Code를 다시 발급 받아옴)
- 재로그인이 완료되면 새로 발급된 Authorization Code를 이용하여 탈퇴 요청을 보냄
- 회원 탈퇴 완료
'대외 활동 > SOPT' 카테고리의 다른 글
중복 추가 이슈를 해결하기 위한 고민 (0) | 2025.03.19 |
---|---|
비관적 락과 낙관적 락 (0) | 2025.03.19 |
[아티클] SSH config를 통해 간편하게 SSH 연결하기 (0) | 2025.03.17 |
[아티클] JPA N+1 문제 (0) | 2025.03.17 |
우당탕탕 서브모듈 도입기 (0) | 2025.03.17 |