개발 - iOS SwiftUI

애플 로그인 - 백엔드 스프링 프레임워크

개미v 2024. 6. 20. 22:37

애플 로그인은 다음과 같은 방식으로 처리 됩니다.

 

① 프론트엔드(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;
	}
}