구글, 페이스북에 이어 애플로그인 연동한다.
구글 페이스북은 이 글 이후에 정리해서 올려야겠다.
일단 애플로그인은 구글, 페이스북보다 준비해야할 것들이 있다.
그 부분은 너무 잘 정리해주신 분이 계셔서 첨부한다.
[1] 스프링 프로젝트에 애플 로그인 API 연동을 위한 Apple Developer 설정
설정을 하면서 조금 해맸던 부분이 있다.
그분의 이미지를 토대로 본다면 세번째 부분에 Return URLs 에 원하는 주소를 입력 후 ,(쉼표)를 추가해야 넘어가진다.
Return URLs에 https://{domain}/login/oauth2/apple 을 추가하였다.
@Slf4j
@Component
public class AppleUtil {
private static ObjectMapper objectMapper = new ObjectMapper();
public String createClientSecret(String teamId, String clientId, String keyId, String keyPath, String authUrl) {
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(keyId).build();
JWTClaimsSet claimsSet = new JWTClaimsSet();
Date now = new Date();
claimsSet.setIssuer(teamId);
claimsSet.setIssueTime(now);
claimsSet.setExpirationTime(new Date(now.getTime() + 3600000));
claimsSet.setAudience(authUrl);
claimsSet.setSubject(clientId);
SignedJWT jwt = new SignedJWT(header, claimsSet);
try {
ECPrivateKey ecPrivateKey = new ECPrivateKeyImpl(readPrivateKey(keyPath));
JWSSigner jwsSigner = new ECDSASigner(ecPrivateKey.getS());
jwt.sign(jwsSigner);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (JOSEException e) {
e.printStackTrace();
}
return jwt.serialize();
}
private byte[] readPrivateKey(String keyPath) {
Resource resource = new ClassPathResource(keyPath);
byte[] content = null;
try (FileReader keyReader = new FileReader(resource.getFile());
PemReader pemReader = new PemReader(keyReader)) {
{
PemObject pemObject = pemReader.readPemObject();
content = pemObject.getContent();
}
} catch (IOException e) {
e.printStackTrace();
}
return content;
}
public String doPost(String url, Map<String, String> param) {
String result = null;
CloseableHttpClient httpclient = null;
CloseableHttpResponse response = null;
Integer statusCode = null;
String reasonPhrase = null;
try {
httpclient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
List<NameValuePair> nvps = new ArrayList<>();
Set<Map.Entry<String, String>> entrySet = param.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
String fieldName = entry.getKey();
String fieldValue = entry.getValue();
nvps.add(new BasicNameValuePair(fieldName, fieldValue));
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nvps);
httpPost.setEntity(formEntity);
response = httpclient.execute(httpPost);
statusCode = response.getStatusLine().getStatusCode();
reasonPhrase = response.getStatusLine().getReasonPhrase();
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity, "UTF-8");
if (statusCode != 200) {
log.error("[error] : " + result);
}
EntityUtils.consume(entity);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
if (httpclient != null) {
httpclient.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
public JSONObject decodeFromIdToken(String id_token) {
try {
SignedJWT signedJWT = SignedJWT.parse(id_token);
ReadOnlyJWTClaimsSet getPayload = signedJWT.getJWTClaimsSet();
ObjectMapper objectMapper = new ObjectMapper();
JSONObject payload = objectMapper.readValue(getPayload.toJSONObject().toJSONString(), JSONObject.class);
if (payload != null) {
return payload;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
apple은 로그인을 하면 code와 id_token을 주는대 이 코드?토큰으로 다시 애플에 보내 access-token을 가져오는거 같다.
그러나 나는 나만의 jwt token을 주므로 이 부분을 사용하지 않고 id_token을 decode하여 필요한 정보만 사용하는 것으로 바꾸었다.
@RequestMapping(value = "/oauth2/apple", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void oauth2AppleLoginRedirect(String user, String code, String id_token,
HttpServletResponse response) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
JSONObject data = appleUtil.decodeFromIdToken(id_token);
if (user != null) {
log.info("apple login first time : {}", user);
// 처음으로 apple로그인을 하면 user 데이터가 있으니 디비에 저장
AppleUserInfo appleUserInfo = objectMapper.readValue(user, AppleUserInfo.class);
...
} else {
log.info("already signup with web and already update oauth2 key");
// 처음이 아니므로 적절한 인증 후 넘긴다.
...
}
}
String accessToken = jwtTokenProvider.createToken(userDetails.getUserId(), userDetails.getUserEmail(), userDetails.getUsername(), userDetails.getRole());
String refreshToken = jwtTokenProvider.createRefreshToken(userDetails.getUserId(), userDetails.getUserEmail(), userDetails.getUsername(), userDetails.getRole());
// header set
ResponseCookie accessTokenCookie = ResponseCookie.from("access-token", accessToken)
.path("/")
.secure(true)
.sameSite("None")
.httpOnly(false)
.domain("{domain}")
.build();
ResponseCookie refrshTokenCookie = ResponseCookie.from("refresh-token", refreshToken)
.path("/")
.secure(true)
.sameSite("None")
.httpOnly(false)
.domain("{domain}")
.build();
try {
// redis set
redisTemplate.opsForValue().set(userDetails.getUserEmail(), refreshToken, jwtTokenProvider.refreshTokenValidTime, TimeUnit.MILLISECONDS);
} catch (Exception e) {
log.error("redis exception. please check redis server. : {},", user);
}
response.setHeader("Set-Cookie", accessTokenCookie.toString());
response.addHeader("Set-Cookie", refrshTokenCookie.toString());
response.sendRedirect(redirectUri);
}
애플은 최초 처음 로그인할때만 유저정보를 준다. 유저 정보를 가지고 Database에 저장 후 토큰을 발급해주는 식으로 만들었다.
유저 정보는 www-form-urlencoded 방식으로 user에 담아준다.
다른 블로그를 참고하여 AppleUtil에서 많은 일을 하지만.. 나에게도 맞는 부분인지는 잘 몰라 이렇게 처리를 하였다..
안녕하세요 좋은 글 잘 봤습니다! 혹시 ECPrivateKeyImpl 관련해서 오류는 없으셨나요?