일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- MacOS
- IOS
- Notification
- Code
- FLUTTER
- Apple
- Session
- view
- appstore
- github
- UIButton
- Realm
- Firebase
- 웹뷰
- geofencing
- error
- Swift
- Xcode
- mac
- darkmode
- stack
- iOS16
- 한글
- 개발자
- rxswift
- Git
- JPA
- Archive
- SwiftUI
- window
- Today
- Total
EEYatHo 앱 깎는 이야기
JPA, Swift ) Apple 로그인 클라-서버 2중 인증 - EEYatHo iOS 본문
카카오, 구글 로그인 iOS 서버 2중 인증에 이어서 애플이다.
[ 카카오 로그인 iOS 서버 2중 인증 글 바로가기 ]
[ 구글 로그인 iOS 서버 2중 인증 글 바로가기 ]
[ 3. 애플 ]
카카오랑 구글을 기분좋게 구현하고 애플을 알아봤었는데
애플은 좀 많이 까다로워서 짜증났던 기억이 난다..
애플이 구현해둔 AuthenticationServices 라이브러리를 이용.
delegate로 authorization.credential 를 받아오는데,
authorization.credential.identityToken 을 이용하면 된다.
/// 애플 로그인 버튼 클릭 -> Delegate로 이어짐
@objc func tabAppleLogin() {
if #available(iOS 13.0, *) {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
} else {
ToastManager.showToast(message: "애플 로그인은 iOS 13.0 이상부터 가능합니다.", isTopWindow: false)
}
}
// 애플 로그인 Delegate
@available(iOS 13.0, *)
extension LoginViewController: ASAuthorizationControllerDelegate,
ASAuthorizationControllerPresentationContextProviding {
// Apple ID 연동 성공 시
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
switch authorization.credential {
// Apple ID
case let appleIDCredential as ASAuthorizationAppleIDCredential:
// 이름
let name = appleIDCredential.fullName?.description ?? ""
// accessToken (Data -> 아스키 인코딩 -> 스트링)
let accessToken = String(data: appleIDCredential.identityToken!, encoding: .ascii) ?? ""
// 로그인 리퀘스트 생성
let signInRequest = SignInRequest(name: name, accessToken: accessToken, socialType: .apple, uuid: "string")
// 로그인 리퀘스트 발싸
NetworkManager.singIn(request: signInRequest) { [weak self] result in
self?.signInResultProcessor(request: signInRequest, result: result)
}
default:
break
}
}
// Apple ID 연동 실패 시
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
print(error)
}
// context를 어디에 띄울지
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.view.window!
}
}
iOS 측에서 authorization.credential.identityToken 을 서버에 보내는 코드다.
( 이를 이용하여 서버는 애플의 프로필 정보를 가져올건데,
이 때 이메일만 가져올 수 있지 이름을 못가져온다....
그런데 우리 서비스 회원으로 등록할 때 이름이 필요하기 때문에
지금 이름을 보낼 수 밖에 없다...ㅜㅜ )
( uuid는 이전 글들과 동일한 이유로 필요없다. )
이를 수신한 서버는 이렇게 처리한다.
( 애플은 카카오, 구글처럼 특정 스트링을 OPEN API에 던져주는 간단한 방식이 아니다 )
( 좀 많이 더럽다 )
간단히 설명하자면,
1. 애플은 OPEN API로 3개의 공개키의 재료들을 넘겨준다.
2. iOS에서 받았던 authorization.credential.identityToken 은 JWT토큰이라는 것이며,
JWT토큰에서 말하는 헤더를 뜯어서, 3개 중 어떤 공개키를 사용해야할지 확인한다.
3. 알맞는 공개키 재료를 사용해서 공개키를 만들고,
이 공개키로 JWT토큰의 바디부분을 디코딩하면 유저 프로필을 얻을 수 있다.
else if (socialType.equals("apple")) {
// 애플 accessToken -> 구글 토큰 유효성 확인 및 프로필 정보 얻기
try {
// 애플 accessToken의 header를 Base64, UTF-8 디코딩해서, 알맞는 key 재료를 알기위한 kid, alg 알아내기
String headerOfAccessToken = accessToken.substring(0, accessToken.indexOf("."));
JsonUtil jsonUtil = new JsonUtil();
Map<String, String> header = jsonUtil.convert(new String(Base64.getDecoder().decode(headerOfAccessToken), "UTF-8"), Map.class);
// 애플한테서 key들의 재료들 얻기 + 알맞는 key의 재료 찾기
ApplePublicKeyResponse keys = ApplePublicKeyResponse.getApplePublicKeys();
MaterialsOfApplePublicKey key = keys.getMatchedKeyBy(header.get("kid"), header.get("alg"))
.orElseThrow(() -> new NullPointerException("Failed get public key from apple's id server."));
// 알맞는 key 재료로 부터, RS256 ( SHA-256 + RSA ) 암호화방식에서 사용하는 n, e 구하기
byte[] nBytes = Base64.getUrlDecoder().decode(key.getN());
byte[] eBytes = Base64.getUrlDecoder().decode(key.getE());
BigInteger n = new BigInteger(1, nBytes);
BigInteger e = new BigInteger(1, eBytes);
// n, e를 이용하여 public key 만들기
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
KeyFactory keyFactory = KeyFactory.getInstance(key.getKty());
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
// 만들어진 public key로 accessToken의 body를 디코딩해서 유효한 유저 프로필 얻기
Claims userInfo = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(accessToken).getBody();
// 유저 프로필로 SocialDTO 만들어서 리턴
// 애플은 서버인증시 이름을 알 수 없기때문에 request에 이름 추가..ㅠ
return SocialDTO.builder().name(name).email(userInfo.get("email", String.class)).build();
} catch (UnsupportedEncodingException e ) { // UTF-8 디코딩 에러
} catch (NoSuchAlgorithmException e) { // getUnstance 에러
} catch (InvalidKeySpecException e) { // generatePublic 에러
}
} else { ... }
여기서 사용하는 ApplePublicKeyResponse 클래스는 아래와 같다.
public class ApplePublicKeyResponse {
// 애플은 3개의 공개키를 줌
private List<MaterialsOfApplePublicKey> keys;
// 3개의 공개키 중 사용할 키 찾기
public Optional<MaterialsOfApplePublicKey> getMatchedKeyBy(String kid, String alg) {
return this.keys.stream()
.filter(key -> key.getKid().equals(kid) && key.getAlg().equals(alg))
.findFirst();
}
static public ApplePublicKeyResponse getApplePublicKeys() {
// URI 준비
URI url = URI.create("https://appleid.apple.com/auth/keys");
// Http Request 발싸
RestTemplate restTemplate = new RestTemplate();
ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, String.class);
// Json 파싱
JsonUtil jsonUtil = new JsonUtil();
ApplePublicKeyResponse result = jsonUtil.convert(response.getBody().toString(), ApplePublicKeyResponse.class);
return result;
}
}
이렇게 얻은 유효한 유저 정보로,
추후에 우리 서비스의 회원을 만든다.
'iOS, Swift > Feature' 카테고리의 다른 글
Swift ) xlsx 이미지까지 자유롭게 - EEYatHo iOS (0) | 2022.02.21 |
---|---|
Swift) .CSV 파일 만들기 - EEYatHo iOS (0) | 2022.02.02 |
Swift ) 디바이스 여유공간 구하기 - EEYatHo iOS (3) | 2021.07.21 |
JPA, Swift ) Google 로그인 클라-서버 2중 인증 - EEYatHo iOS (0) | 2021.02.27 |
JPA, Swift ) Kakao 로그인 클라-서버 2중 인증 - EEYatHo iOS (0) | 2021.02.27 |