애플 로그인은 다음과 같은 방식으로 처리 됩니다.
① 프론트엔드(SwiftUI) 에서 애플 로그인을 하면
② 인증토큰을 백엔드 서버로 보내고
③ 백엔드 서버에서는 인증토큰이 유효한지 검증합니다.
검증방법은 애플 서버에서 공개키들(복수)을 가져와서 인증토큰의 서명이 맞는지 검증합니다.
④ 인증토큰이 유효하면 인증토큰에서 아이디, 이메일을 추출해서 사용할 수 있습니다.
⑤ 이후 회원가입 처리
※ 프론트엔드(SwiftUI) 에서 인증토큰에서 바로 아이디, 이메일을 추출해서 사용할 수도 있겠지만,
이렇게 하면 위변조가 가능하기 때문에 백엔드 서버에서 검증을 해야 합니다.
※ 공개키, 비공개키, 서명에 대한 지식이 있으면 이해하기 좋습니다.
※ 애플 로그인이 구글 로그인보다 복잡한 것 같고, 잘 이해가 안되면 구글 로그인과 비교해가며 보는 것을 추천합니다.
구글 로그인도 포스팅 해놨습니다.
백엔드 스프링 프레임워크에서 애플 인증토큰 검증하는 예제 입니다.
maven
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
Controller
// 애플 로그인
@RequestMapping(value = "/loginAppleOAuth.ajax", produces = "application/json; charset=UTF8", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> logiAppleOAuth(@RequestParam Map<String, Object> param, HttpServletRequest request,
HttpServletResponse response, Model model, HttpSession session) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
try {
// 인증토큰 파라미터
String authToken = param.get("authToken");
// 애플 인증토큰 검증
// 애플 서버에서 PublicKeys 가져오기
URL url = new URL("https://appleid.apple.com/auth/keys");
ObjectMapper mapper = new ObjectMapper();
JsonNode keys = mapper.readTree(url).get("keys");
RSAKeyProvider keyProvider = new RSAKeyProvider() {
@Override
public RSAPublicKey getPublicKeyById(String keyId) {
for (JsonNode key : keys) {
if (keyId.equals(key.get("kid").asText())) {
try {
String n = key.get("n").asText();
String e = key.get("e").asText();
byte[] nBytes = Base64.getUrlDecoder().decode(n);
byte[] eBytes = Base64.getUrlDecoder().decode(e);
BigInteger modulus = new BigInteger(1, nBytes);
BigInteger publicExponent = new BigInteger(1, eBytes);
RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
KeyFactory factory = KeyFactory.getInstance("RSA");
RSAPublicKey publicKey = (RSAPublicKey) factory.generatePublic(spec);
return publicKey;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
}
return null;
}
@Override
public RSAPrivateKey getPrivateKey() {
return null;
}
@Override
public String getPrivateKeyId() {
return null;
}
};
// JWT 검증 생성
Algorithm algorithm = Algorithm.RSA256(keyProvider);
JWTVerifier verifier = JWT.require(algorithm).withIssuer("https://appleid.apple.com").build();
// 인증토큰 검증
DecodedJWT jwt;
try {
jwt = verifier.verify(authToken);
} catch (JWTVerificationException ex) {
map.put("code", "false");
map.put("msg", "Apple authentication token verification failed.\nError : " + ex.toString());
return map;
}
// 인증토큰으로부터 userId, email 추출
String userId = jwt.getSubject();
String userEmail = jwt.getClaim("email").asString();
// TODO : 회원가입 처리
map.put("code", "true");
map.put("msg", "Request was successful.");
return map;
} catch (Exception e) {
e.printStackTrace();
map.put("code", "false");
map.put("msg", "Unknown error has occurred on the server.\nError : " + e.toString());
return map;
}
}
'개발 - iOS SwiftUI' 카테고리의 다른 글
iOS SwiftUI Button 탭(클릭) 영역 넓히기 (0) | 2023.11.08 |
---|---|
iOS SwiftUI List Scroll 맨 아래로 이동 방법 (0) | 2023.11.06 |
iOS SwiftUI current View 상태 관리 예제 (0) | 2023.11.05 |
iOS SwiftUI HTTP Multipart POST 예제 (0) | 2023.10.27 |
iOS SwiftUI EnvironmentObject NavigationStack 예제 (0) | 2023.10.25 |