| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- --watch
- SDK upgrade
- react
- Packet Network
- nodejs
- Camera Movement
- spread 연산자
- Digital Ocean
- Camera Zoom
- OverTheWire
- MySQL
- docker
- screencapture
- linux
- draganddrop
- unity
- express
- server
- Git
- Unity Editor
- Unity IAP
- java
- css framework
- springboot
- critical rendering path
- Google Developer API
- mongoDB
- rpg server
- Spring Boot
- Google Refund
- Today
- Total
우당탕탕 개발일지
[방치RPG 서버 제작기] 12. Apple 계정연동하기 본문
클라이언트
SDK 임포트 - unitypackage로 되어있어서 편하게 임포트 가능하다. 이 sdk는 안드로이드 상에서는 작동하지 않으므로, 관련된 코드는 모두 #if UNITY_IOS 로 감싸주는것을 잊지말자.
https://github.com/lupidan/apple-signin-unity
GitHub - lupidan/apple-signin-unity: Unity plugin to support Sign In With Apple Id
Unity plugin to support Sign In With Apple Id. Contribute to lupidan/apple-signin-unity development by creating an account on GitHub.
github.com
info.plist 에 다음 내용을 추가해야한다. 그런데 YourUnityProject/Build/iOS/Info.plist 파일은 빌드할때마다 초기회되는 문제가있다. 그러므로, 빌드할때마다 자동으로 아래 내용을 넣어주도록, 스크립트로 제작한다. Assets/Editor/IOSPostProcess.cs 를 추가한다.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>your.bundle.id.here</string>
</array>
</dict>
</array>
<key>NSUserAuthenticationUsageDescription</key>
<string>로그인을 위해 Apple ID 인증이 필요합니다.</string>
#if UNITY_IOS
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.IO;
public static class IOSPostProcess
{
[PostProcessBuild]
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
{
if (target != BuildTarget.iOS) return;
string plistPath = Path.Combine(pathToBuiltProject, "Info.plist");
PlistDocument plist = new PlistDocument();
plist.ReadFromFile(plistPath);
PlistElementDict rootDict = plist.root;
// 사용 목적 설명 (권한)
rootDict.SetString("NSUserAuthenticationUsageDescription", "로그인을 위해 Apple ID 인증이 필요합니다.");
// 저장
File.WriteAllText(plistPath, plist.WriteToString());
}
}
#endif
클라이언트 구현부 // 이런식으로 구현한다.
#if UNITY_IOS
using UnityEngine;
using AppleAuth;
using AppleAuth.Enums;
using AppleAuth.Interfaces;
using AppleAuth.Native;
using System.Text;
public class IOSLoginManager : MonoBehaviour
{
public void SignInWithApple()
{
if (AppleAuthManager.IsCurrentPlatformSupported)
{
var loginArgs = new AppleAuthLoginArgs(LoginOptions.IncludeEmail | LoginOptions.IncludeFullName);
var appleAuthManager = new AppleAuthManager(new PayloadDeserializer());
appleAuthManager.LoginWithAppleId(
loginArgs,
credential =>
{
var appleIdCredential = credential as IAppleIDCredential;
string userId = appleIdCredential.User;
string token = Encoding.UTF8.GetString(appleIdCredential.IdentityToken);
// 이 토큰을 서버로 보내서 검증하면 됨
},
error =>
{
Debug.LogError("Apple 로그인 실패: " + error);
});
}
}
}
#endif
서버
의존성 추가하기
// build.gradle
implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3'
서버쪽에서 토큰을 인증하는 작업이 필요하다. 이를 위해서, 또 com.compay..// 와 같은 client-id가 필요하다.
하드코딩 하기보다는, Application.yml파일에 다음과 같이 정의해놓고 ,AplleAuthService.java에서 사용하도록 하자.
apple:
client-id: com.my.company
사용할땐 다음처럼 가져온다.
@Value("${apple.client-id}")
private String appleClientId;
AppleAuthService.java
@Service
@RequiredArgsConstructor
public class AppleAuthService {
@Value("${apple.client-id}")
private String appleClientId;
private final UserRepository userRepository;
// 🔐 캐싱 관련 필드
private JWKSet cachedKeySet;
private Instant lastFetchTime;
private static final Duration CACHE_TTL = Duration.ofHours(24);
public AppleUserInfo verifyAppleToken(String identityToken) {
try {
System.out.println("verify Apple token called. token: "+identityToken);
SignedJWT jwt = SignedJWT.parse(identityToken);
JWKSet keySet = getKeySet();
JWK key = keySet.getKeyByKeyId(jwt.getHeader().getKeyID());
if (key == null) throw new RuntimeException("No matching Apple key found");
JWSVerifier verifier = new RSASSAVerifier(((RSAKey) key).toRSAPublicKey());
if (!jwt.verify(verifier)) throw new RuntimeException("JWT signature invalid");
JWTClaimsSet claims = jwt.getJWTClaimsSet();
if (!"https://appleid.apple.com".equals(claims.getIssuer()))
throw new RuntimeException("Invalid issuer");
if (!claims.getAudience().contains(appleClientId))
throw new RuntimeException("Invalid audience");
String sub = claims.getSubject();
String email = claims.getStringClaim("email");
return new AppleUserInfo(sub, email);
} catch (Exception e) {
System.out.println("apple token verification failed. "+e.getMessage());
throw new RuntimeException("Apple token verification failed", e);
}
}
// 🔄 키셋 가져오기 (캐싱 적용)
private JWKSet getKeySet() throws Exception {
if (cachedKeySet == null || lastFetchTime == null || Instant.now().isAfter(lastFetchTime.plus(CACHE_TTL))) {
try (InputStream is = new URI("https://appleid.apple.com/auth/keys").toURL().openStream()) {
cachedKeySet = JWKSet.load(is);
lastFetchTime = Instant.now();
System.out.println("Apple 공개키셋 새로 로딩됨");
}
}
return cachedKeySet;
}
public ApiResponse<Void> linkApple(String uid, String identityToken){
AppleUserInfo info = verifyAppleToken(identityToken);
var user = userRepository.findByUid(uid).orElseThrow(()-> new RuntimeException("user not found"));
if(!user.getPlatform().equalsIgnoreCase("GUEST")){
return ApiResponse.failure("이미 연동된 계정입니다. ("+user.getPlatform()+")");
}
if(userRepository.existsByAppleId(info.getSub())){
return ApiResponse.failure("이미 사용 중인 Apple 계정입니다.");
}
user.setPlatform("APPLE");
user.setAppleId(info.getSub());
userRepository.save(user);
return ApiResponse.success(null);
}
}
클라이언트에서 apple 로그인을 해서 토큰을 받아오면 => 서버에서 검증을 해서 subject를 가져온다. db에는 User의 appleId란에 이 subject를 저장해야한다.
'Server > 방치RPG 서버' 카테고리의 다른 글
| [방치RPG 서버 제작기] 11. 점검모드 & 화이트리스트 관리 (0) | 2025.05.24 |
|---|---|
| [방치RPG 서버 제작기] 10. csv를 이용한 확률 관리 & 뽑기 API (0) | 2025.05.21 |
| [방치RPG 서버 제작기] 9. 유니티 파일 서버로 전송하기 (0) | 2025.05.18 |
| [방치RPG 서버 제작기] Docker 내의 db에 접속하기 ( DBeaver ) (0) | 2025.05.06 |
| [방치RPG 서버 제작기] 8. 서버배포 및 SSL 인증서발급하기 (1) | 2025.04.26 |
