-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
33 changed files
with
354 additions
and
251 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 13 additions & 1 deletion
14
...main/java/sopt/makers/authentication/database/rdb/repository/UserRegisterInfoRemover.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,20 @@ | ||
package sopt.makers.authentication.database.rdb.repository; | ||
|
||
import sopt.makers.authentication.database.rdb.entity.UserRegisterInfoEntity; | ||
|
||
import org.springframework.stereotype.Component; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
|
||
@Component | ||
@Transactional | ||
public class UserRegisterInfoRemover {} | ||
@RequiredArgsConstructor | ||
public class UserRegisterInfoRemover { | ||
private final UserRegisterInfoJpaRepository jpaRepository; | ||
|
||
public void remove(final UserRegisterInfoEntity entity) { | ||
|
||
jpaRepository.delete(entity); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
148 changes: 49 additions & 99 deletions
148
src/main/java/sopt/makers/authentication/external/oauth/AppleAuthService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,128 +1,78 @@ | ||
package sopt.makers.authentication.external.oauth; | ||
|
||
import static sopt.makers.authentication.support.code.external.failure.ClientError.APPLE_RESPONSE_UNAVAILABLE; | ||
import static sopt.makers.authentication.support.code.external.failure.ClientError.FAIL_READ_APPLE_PRIVATE_KEY_FILE; | ||
import static sopt.makers.authentication.support.code.external.failure.ClientError.INVALID_APPLE_AUTH_CODE; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.ACCEPT; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.ACCEPT_VALUE; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.APPLE_ALGORITHM_HEADER; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.APPLE_ALGORITHM_VALUE; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.APPLE_KEY_ID_HEADER; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.APPLE_TOKEN_URL; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.CLIENT_ID; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.CLIENT_SECRET; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.CODE; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.CONTENT_TYPE; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.CONTENT_TYPE_VALUE; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.GRANT_TYPE; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.GRANT_TYPE_VALUE; | ||
|
||
import sopt.makers.authentication.external.oauth.dto.IdTokenResponse; | ||
import sopt.makers.authentication.support.exception.external.ClientRequestException; | ||
import sopt.makers.authentication.support.exception.external.ClientResponseException; | ||
import static sopt.makers.authentication.support.code.external.failure.ClientError.*; | ||
import static sopt.makers.authentication.support.constant.OAuthConstant.APPLE_ISSUER; | ||
|
||
import sopt.makers.authentication.external.oauth.client.AppleAuthClient; | ||
import sopt.makers.authentication.support.code.domain.failure.AuthFailure; | ||
import sopt.makers.authentication.support.code.support.failure.TokenFailure; | ||
import sopt.makers.authentication.support.exception.domain.AuthException; | ||
import sopt.makers.authentication.support.exception.support.TokenException; | ||
import sopt.makers.authentication.support.util.*; | ||
import sopt.makers.authentication.support.value.AppleOAuthProperty; | ||
|
||
import java.io.IOException; | ||
import java.security.PrivateKey; | ||
import java.text.ParseException; | ||
import java.time.Instant; | ||
import java.util.Date; | ||
|
||
import org.springframework.stereotype.Component; | ||
|
||
import com.google.gson.Gson; | ||
import com.nimbusds.jose.JOSEException; | ||
import com.nimbusds.jose.JWSVerifier; | ||
import com.nimbusds.jose.crypto.ECDSAVerifier; | ||
import com.nimbusds.jose.jwk.JWK; | ||
import com.nimbusds.jose.jwk.JWKSet; | ||
import com.nimbusds.jwt.JWTClaimsSet; | ||
import com.nimbusds.jwt.SignedJWT; | ||
|
||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.SignatureAlgorithm; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import okhttp3.FormBody; | ||
import okhttp3.OkHttpClient; | ||
import okhttp3.Request; | ||
import okhttp3.RequestBody; | ||
import okhttp3.Response; | ||
import okhttp3.ResponseBody; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
@Slf4j | ||
public class AppleAuthService implements OAuthService { | ||
private final AppleOAuthProperty appleOAuthProperty; | ||
private final Gson gson; | ||
private final OkHttpClient client; | ||
private final AppleAuthClient appleAuthClient; | ||
|
||
@Override | ||
public IdTokenResponse getIdTokenByCode(final String code) { | ||
FormBody formBody = createTokenRequestFormBody(code); | ||
Request request = createHttpRequest(formBody); | ||
Response response = executeRequest(request); | ||
|
||
return parseResponseBody(response); | ||
} | ||
|
||
private FormBody createTokenRequestFormBody(final String code) { | ||
String clientId = appleOAuthProperty.sub(); | ||
String clientSecret = createClientSecret(); | ||
return new FormBody.Builder() | ||
.add(CLIENT_ID, clientId) | ||
.add(CLIENT_SECRET, clientSecret) | ||
.add(CODE, code) | ||
.add(GRANT_TYPE, GRANT_TYPE_VALUE) | ||
.build(); | ||
} | ||
|
||
private String createClientSecret() { | ||
Date now = new Date(); | ||
PrivateKey privateKey = | ||
KeyFileUtil.getPrivateKey(appleOAuthProperty.key().path()) | ||
.orElseThrow(() -> new ClientRequestException(FAIL_READ_APPLE_PRIVATE_KEY_FILE)); | ||
|
||
return Jwts.builder() // 토큰 생성 로직은 tokenProvider? 근데 얘는 parse는 없음 | ||
.setHeaderParam(APPLE_KEY_ID_HEADER, appleOAuthProperty.key().id()) | ||
.setHeaderParam(APPLE_ALGORITHM_HEADER, APPLE_ALGORITHM_VALUE) | ||
.setIssuedAt(now) | ||
.setExpiration(new Date(now.getTime() + appleOAuthProperty.expiration().tokenExpiration())) | ||
.setIssuer(appleOAuthProperty.team().id()) | ||
.setAudience(appleOAuthProperty.aud()) | ||
.setSubject(appleOAuthProperty.sub()) | ||
.signWith(privateKey, SignatureAlgorithm.ES256) | ||
.compact(); | ||
} | ||
|
||
private static Request createHttpRequest(RequestBody requestBody) { | ||
return new Request.Builder() | ||
.url(APPLE_TOKEN_URL) | ||
.post(requestBody) | ||
.addHeader(CONTENT_TYPE, CONTENT_TYPE_VALUE) | ||
.addHeader(ACCEPT, ACCEPT_VALUE) | ||
.build(); | ||
} | ||
|
||
private Response executeRequest(Request request) { | ||
public String getIdentifierByToken(final String token) { | ||
try { | ||
Response response = client.newCall(request).execute(); | ||
|
||
validateResponse(response); | ||
return response; | ||
} catch (IOException e) { | ||
throw new ClientResponseException(APPLE_RESPONSE_UNAVAILABLE); | ||
SignedJWT signedJWT = SignedJWT.parse(token); | ||
JWK targetJwk = findMatchJWK(signedJWT); | ||
|
||
verifyAppleIdTokenJwt(signedJWT, targetJwk); | ||
String identifier = signedJWT.getJWTClaimsSet().getSubject(); | ||
return identifier; | ||
} catch (ParseException e) { | ||
throw new TokenException(TokenFailure.TOKEN_PARSE_FAILED); | ||
} | ||
} | ||
|
||
private void validateResponse(Response response) { | ||
boolean isNotSuccessResponse = !response.isSuccessful(); | ||
|
||
if (isNotSuccessResponse) { | ||
throw new ClientRequestException(INVALID_APPLE_AUTH_CODE); | ||
} | ||
private JWK findMatchJWK(final SignedJWT jwt) { | ||
JWKSet loadedJWKSet = appleAuthClient.getPublicKeySet(); | ||
String keyID = jwt.getHeader().getKeyID(); | ||
return loadedJWKSet.getKeys().stream() | ||
.filter(jwk -> jwk.getKeyID().equals(keyID)) | ||
.findFirst() | ||
.orElseThrow(() -> new AuthException(AuthFailure.NOT_FOUND_AVAILABLE_PUBLIC_KEY_SET)); | ||
} | ||
|
||
private IdTokenResponse parseResponseBody(Response response) { | ||
ResponseBody responseBody = response.body(); | ||
boolean isBodyNull = responseBody == null; | ||
|
||
if (isBodyNull) { | ||
throw new ClientResponseException(APPLE_RESPONSE_UNAVAILABLE); | ||
private void verifyAppleIdTokenJwt(final SignedJWT jwt, JWK jwk) throws ParseException { | ||
try { | ||
JWTClaimsSet jwtClaimsSet = jwt.getJWTClaimsSet(); | ||
JWSVerifier verifier = new ECDSAVerifier(jwk.toECKey()); | ||
|
||
boolean isVerifiedSignature = jwt.verify(verifier); | ||
boolean isCorrectIssuer = jwtClaimsSet.getIssuer().equals(APPLE_ISSUER); | ||
boolean isCorrectAudience = jwtClaimsSet.getAudience().contains(appleOAuthProperty.aud()); | ||
boolean isNotExpired = jwtClaimsSet.getExpirationTime().after(Date.from(Instant.now())); | ||
|
||
if (!(isVerifiedSignature && isCorrectIssuer && isCorrectAudience && isNotExpired)) { | ||
throw new AuthException(AuthFailure.INVALID_ID_TOKEN); | ||
} | ||
} catch (JOSEException e) { | ||
throw new AuthException(AuthFailure.INVALID_ID_TOKEN); | ||
} | ||
return gson.fromJson(response.body().toString(), IdTokenResponse.class); | ||
} | ||
} |
Oops, something went wrong.