From 1854e11028dacba9e7debeb45b037cf00b6dde38 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Mon, 1 Jul 2024 04:59:14 +0900 Subject: [PATCH 01/23] =?UTF-8?q?Feat(SignUp):=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20idToken=EA=B8=B0=EB=B0=98=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jabiseo-api/build.gradle | 5 ++ .../com/jabiseo/JabiseoApiApplication.java | 2 + .../auth/controller/AuthController.java | 6 +- .../com/jabiseo/auth/dto/LoginRequest.java | 6 ++ .../auth/oidc/AbstractIdTokenValidator.java | 36 +++++++++ .../jabiseo/auth/oidc/IdTokenJwtHandler.java | 78 +++++++++++++++++++ .../auth/oidc/KakaoIdTokenValidator.java | 57 ++++++++++++++ .../jabiseo/auth/oidc/KakaoKauthClient.java | 15 ++++ .../jabiseo/auth/oidc/OauthMemberInfo.java | 18 +++++ .../com/jabiseo/auth/oidc/OidcPublicKey.java | 23 ++++++ .../auth/oidc/OidcPublicKeyResponse.java | 15 ++++ .../auth/oidc/TokenValidatorManager.java | 22 ++++++ .../auth/oidc/property/KakaoOidcProperty.java | 24 ++++++ .../oidc/property/OidcIdTokenProperty.java | 10 +++ .../jabiseo/auth/usecase/LoginUseCase.java | 34 +++++++- .../com/jabiseo/config/RestClientConfig.java | 26 +++++++ jabiseo-api/src/main/resources/api.yml | 23 ++++++ .../src/main/resources/application.yml | 4 +- .../auth/oidc/KakaoIdTokenValidatorTest.java | 22 ++++++ .../jabiseo/exception/CommonErrorCode.java | 4 +- .../java/com/jabiseo/exception/ErrorCode.java | 4 + .../AuthenticationBusinessException.java | 11 +++ .../exception/AuthenticationErrorCode.java | 36 +++++++++ .../jabiseo/member/domain/MemberFactory.java | 25 ++++++ .../member/domain/MemberRepository.java | 6 +- .../domain/RandomNicknameGenerator.java | 35 +++++++++ 26 files changed, 540 insertions(+), 7 deletions(-) create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/AbstractIdTokenValidator.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/IdTokenJwtHandler.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoKauthClient.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKey.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKeyResponse.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/TokenValidatorManager.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/OidcIdTokenProperty.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/config/RestClientConfig.java create mode 100644 jabiseo-api/src/main/resources/api.yml create mode 100644 jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java create mode 100644 jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationBusinessException.java create mode 100644 jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java create mode 100644 jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberFactory.java create mode 100644 jabiseo-domain/src/main/java/com/jabiseo/member/domain/RandomNicknameGenerator.java diff --git a/jabiseo-api/build.gradle b/jabiseo-api/build.gradle index 8999266..4797eea 100644 --- a/jabiseo-api/build.gradle +++ b/jabiseo-api/build.gradle @@ -17,4 +17,9 @@ dependencies { implementation project(":jabiseo-domain") implementation project(":jabiseo-infrastructure") implementation project(":jabiseo-common") + + + implementation 'io.jsonwebtoken:jjwt-api:0.11.2' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2' } diff --git a/jabiseo-api/src/main/java/com/jabiseo/JabiseoApiApplication.java b/jabiseo-api/src/main/java/com/jabiseo/JabiseoApiApplication.java index 482a2b7..6c8642b 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/JabiseoApiApplication.java +++ b/jabiseo-api/src/main/java/com/jabiseo/JabiseoApiApplication.java @@ -3,7 +3,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +@ConfigurationPropertiesScan @SpringBootApplication public class JabiseoApiApplication { diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java index 1164023..7ebfffd 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java @@ -1,5 +1,6 @@ package com.jabiseo.auth.controller; +import com.jabiseo.auth.dto.LoginRequest; import com.jabiseo.auth.dto.LoginResponse; import com.jabiseo.auth.usecase.LoginUseCase; import com.jabiseo.auth.usecase.LogoutUseCase; @@ -8,6 +9,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -25,8 +27,8 @@ public class AuthController { private final WithdrawUseCase withdrawUseCase; @PostMapping("/login") - public ResponseEntity login(String idToken) { - LoginResponse result = loginUseCase.execute(idToken); + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + LoginResponse result = loginUseCase.execute(loginRequest); return ResponseEntity.ok(result); } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java b/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java new file mode 100644 index 0000000..e180967 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java @@ -0,0 +1,6 @@ +package com.jabiseo.auth.dto; + +import jakarta.validation.constraints.NotNull; + +public record LoginRequest(@NotNull String idToken, @NotNull String oauthServer) { +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/AbstractIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/AbstractIdTokenValidator.java new file mode 100644 index 0000000..651b3e0 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/AbstractIdTokenValidator.java @@ -0,0 +1,36 @@ +package com.jabiseo.auth.oidc; + + +import com.jabiseo.auth.oidc.property.OidcIdTokenProperty; + +import java.util.Map; + +public abstract class AbstractIdTokenValidator { + + private final OidcIdTokenProperty oidcIdTokenProperty; + private final IdTokenJwtHandler idTokenJwtHandler; + + public AbstractIdTokenValidator(OidcIdTokenProperty oidcIdTokenProperty, IdTokenJwtHandler idTokenJwtHandler) { + this.oidcIdTokenProperty = oidcIdTokenProperty; + this.idTokenJwtHandler = idTokenJwtHandler; + } + + /* + * 1. idToken Header 에서 kid라는 key 를 찾고 kid에 맞는 public key를 가져온다. + * 2. 우리 앱의 issuer, client-id가 jwt token안에 있는지 확인/ signature 에 대한 검증을 진행한다. + * 3. payload에서 원하는 값을 추출해 return. + */ + + public OauthMemberInfo validate(String idToken) { + String kid = idTokenJwtHandler.findKidFromHeader(idToken); + + OidcPublicKey oidcPublicKey = getOidcPublicKey(kid); + Map payload = idTokenJwtHandler.validateAndExtractPayload(idToken, oidcPublicKey, oidcIdTokenProperty.audience(), oidcIdTokenProperty.issuer()); + + return extractMemberInfoFromPayload(payload); + } + + abstract protected OidcPublicKey getOidcPublicKey(String kid); + + abstract protected OauthMemberInfo extractMemberInfoFromPayload(Map payload); +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/IdTokenJwtHandler.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/IdTokenJwtHandler.java new file mode 100644 index 0000000..8221c14 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/IdTokenJwtHandler.java @@ -0,0 +1,78 @@ +package com.jabiseo.auth.oidc; + +import com.jabiseo.auth.exception.AuthenticationBusinessException; +import com.jabiseo.auth.exception.AuthenticationErrorCode; +import io.jsonwebtoken.*; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.RSAPublicKeySpec; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +@Component +public class IdTokenJwtHandler { + + + public String findKidFromHeader(String jwt) { + try { + Jwt headerClaimsJwt = Jwts.parserBuilder() + .build() + .parseClaimsJwt(parseToken(jwt)); + Header header = headerClaimsJwt.getHeader(); + Object kid = header.get("kid"); + if (kid == null) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_ID_TOKEN); + } + + return (String) kid; + } catch (ExpiredJwtException e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.EXPIRED_ID_TOKEN); + } catch (Exception e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_ID_TOKEN); + } + } + + public Map validateAndExtractPayload(String idToken, OidcPublicKey publicKey, String aud, String issuer) { + try { + Jws claimsJws = Jwts.parserBuilder() + .requireAudience(aud) + .requireIssuer(issuer) + .setSigningKey(getRSAPublicKey(publicKey.getN(), publicKey.getE())) + .build() + .parseClaimsJws(idToken); + + Claims body = claimsJws.getBody(); + return new HashMap<>(body); + } catch (Exception e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_ID_TOKEN); + } + } + + private String parseToken(String token) { + String[] splitToken = token.split("\\."); + if (splitToken.length != 3) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_ID_TOKEN); + } + return splitToken[0] + "." + splitToken[1] + "."; + } + + private Key getRSAPublicKey(String modulus, String exponent) + throws InvalidKeySpecException, NoSuchAlgorithmException { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + byte[] decodeN = Base64.getUrlDecoder() + .decode(modulus); + byte[] decodeE = Base64.getUrlDecoder() + .decode(exponent); + BigInteger n = new BigInteger(1, decodeN); + BigInteger e = new BigInteger(1, decodeE); + + RSAPublicKeySpec keySpec = new RSAPublicKeySpec(n, e); + return keyFactory.generatePublic(keySpec); + } +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java new file mode 100644 index 0000000..d47ea5b --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java @@ -0,0 +1,57 @@ +package com.jabiseo.auth.oidc; + +import com.jabiseo.auth.exception.AuthenticationBusinessException; +import com.jabiseo.auth.exception.AuthenticationErrorCode; +import com.jabiseo.auth.oidc.property.KakaoOidcProperty; +import com.jabiseo.exception.CommonErrorCode; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +@Component +public class KakaoIdTokenValidator extends AbstractIdTokenValidator { + + private final String KAKAO_ID_KEY = "sub"; + private final KakaoKauthClient kakaoKauthClient; + + public KakaoIdTokenValidator(KakaoOidcProperty kakaoOidcProperty, IdTokenJwtHandler idTokenJwtHandler, KakaoKauthClient kakaoKauthClient) { + super(kakaoOidcProperty.toIdTokenPropety(), idTokenJwtHandler); + this.kakaoKauthClient = kakaoKauthClient; + } + + @Override + protected OidcPublicKey getOidcPublicKey(String kid) { + + // TODO: 캐싱 적용 & 체크 필요 + + ResponseEntity publicKeys = kakaoKauthClient.getPublicKeys(); + List keys = publicKeys.getBody().getKeys(); + + return keys.stream().filter((key) -> key.getKid().equals(kid)) + .findAny() + .orElseThrow(()-> new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_ID_TOKEN)); + } + + @Override + protected OauthMemberInfo extractMemberInfoFromPayload(Map payload) { + String oauthId = (String) payload.get(KAKAO_ID_KEY); + + if (requireValueIsNull(oauthId)) { + throw new AuthenticationBusinessException(CommonErrorCode.INTERNAL_SERVER_ERROR); + } + + return OauthMemberInfo.builder() + .oauthId(oauthId) + .oauthServer("KAKAO") + .build(); + } + + /* + * 해당 예외가 발생하는건 카카오에서 프로퍼티 key 값을 바꾸지 않는 이상은 발생하지 않는다. + */ + private boolean requireValueIsNull(String oauthId) { + return oauthId == null; + } +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoKauthClient.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoKauthClient.java new file mode 100644 index 0000000..8aeafbf --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoKauthClient.java @@ -0,0 +1,15 @@ +package com.jabiseo.auth.oidc; + + +import org.springframework.http.ResponseEntity; +import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.annotation.HttpExchange; + + +@HttpExchange +public interface KakaoKauthClient { + + @GetExchange(url = "/.well-known/jwks.json") + ResponseEntity getPublicKeys(); + +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java new file mode 100644 index 0000000..74edaca --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java @@ -0,0 +1,18 @@ +package com.jabiseo.auth.oidc; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class OauthMemberInfo { + + private String oauthId; + private String oauthServer; + + + @Builder + public OauthMemberInfo(String oauthId, String oauthServer) { + this.oauthId = oauthId; + this.oauthServer = oauthServer; + } +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKey.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKey.java new file mode 100644 index 0000000..2292b99 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKey.java @@ -0,0 +1,23 @@ +package com.jabiseo.auth.oidc; + +import lombok.Getter; +import lombok.ToString; + +@ToString +@Getter +class OidcPublicKey { + + private String kid; + private String alg; + private String use; + private String n; + private String e; + + public OidcPublicKey(String kid, String alg, String use, String n, String e) { + this.kid = kid; + this.alg = alg; + this.use = use; + this.n = n; + this.e = e; + } +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKeyResponse.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKeyResponse.java new file mode 100644 index 0000000..bd75a3a --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKeyResponse.java @@ -0,0 +1,15 @@ +package com.jabiseo.auth.oidc; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +class OidcPublicKeyResponse { + + private List keys; +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/TokenValidatorManager.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/TokenValidatorManager.java new file mode 100644 index 0000000..e01bc83 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/TokenValidatorManager.java @@ -0,0 +1,22 @@ +package com.jabiseo.auth.oidc; + + +import com.jabiseo.auth.exception.AuthenticationBusinessException; +import com.jabiseo.auth.exception.AuthenticationErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class TokenValidatorManager { + + private final AbstractIdTokenValidator kakaoIdTokenValidator; + + public OauthMemberInfo validate(String idToken, String oauthServer) { + if (oauthServer.equals("KAKAO")) { + return kakaoIdTokenValidator.validate(idToken); + } else { + throw new AuthenticationBusinessException(AuthenticationErrorCode.NOT_SUPPORT_OAUTH); + } + } +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java new file mode 100644 index 0000000..1eff9e0 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java @@ -0,0 +1,24 @@ +package com.jabiseo.auth.oidc.property; + + +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@ConfigurationProperties(prefix = "oidc.kakao") +public class KakaoOidcProperty { + + private final String clientId; + private final String adminKey; + private final String issuer; + + public KakaoOidcProperty(String clientId, String adminKey, String issuer) { + this.clientId = clientId; + this.adminKey = adminKey; + this.issuer = issuer; + } + + public OidcIdTokenProperty toIdTokenPropety() { + return new OidcIdTokenProperty(issuer, clientId); + } +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/OidcIdTokenProperty.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/OidcIdTokenProperty.java new file mode 100644 index 0000000..c27c177 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/OidcIdTokenProperty.java @@ -0,0 +1,10 @@ +package com.jabiseo.auth.oidc.property; + + +/** + * @param issuer 발행 회사 url + * @param audience client-id + */ +public record OidcIdTokenProperty(String issuer, String audience) { + +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java index 457f5fb..541b543 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java @@ -1,13 +1,45 @@ package com.jabiseo.auth.usecase; +import com.jabiseo.auth.dto.LoginRequest; import com.jabiseo.auth.dto.LoginResponse; +import com.jabiseo.auth.oidc.OauthMemberInfo; +import com.jabiseo.auth.oidc.TokenValidatorManager; +import com.jabiseo.member.domain.Member; +import com.jabiseo.member.domain.MemberFactory; +import com.jabiseo.member.domain.MemberRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; + @Service +@RequiredArgsConstructor public class LoginUseCase { - public LoginResponse execute(String idToken) { + private final TokenValidatorManager tokenValidatorManager; + private final MemberFactory memberFactory; + private final MemberRepository memberRepository; + + public LoginResponse execute(LoginRequest loginRequest) { + OauthMemberInfo oauthMemberInfo = tokenValidatorManager.validate(loginRequest.idToken(), loginRequest.oauthServer()); + + String oauthId = oauthMemberInfo.getOauthId(); + String oauthServer = oauthMemberInfo.getOauthServer(); + + Member member = memberRepository.findByOauthIdAndOauthServer(oauthId, oauthServer) + .orElse(null); + + if (isRequireSignup(member)) { + Member newMember = memberFactory.createNew(oauthId, oauthServer); + member = memberRepository.save(newMember); + } + + + return new LoginResponse("access_token", "refresh_token"); } + + private static boolean isRequireSignup(Member member) { + return member == null; + } } diff --git a/jabiseo-api/src/main/java/com/jabiseo/config/RestClientConfig.java b/jabiseo-api/src/main/java/com/jabiseo/config/RestClientConfig.java new file mode 100644 index 0000000..2321928 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/config/RestClientConfig.java @@ -0,0 +1,26 @@ +package com.jabiseo.config; + +import com.jabiseo.auth.oidc.KakaoKauthClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.support.RestClientAdapter; +import org.springframework.web.service.invoker.HttpServiceProxyFactory; + +@Configuration +public class RestClientConfig { + + + @Bean + public KakaoKauthClient kakaoKauthClient() { + RestClient client = RestClient.builder() + .baseUrl("https://kauth.kakao.com") + .build(); + + return HttpServiceProxyFactory + .builder() + .exchangeAdapter(RestClientAdapter.create(client)) + .build() + .createClient(KakaoKauthClient.class); + } +} diff --git a/jabiseo-api/src/main/resources/api.yml b/jabiseo-api/src/main/resources/api.yml new file mode 100644 index 0000000..a452746 --- /dev/null +++ b/jabiseo-api/src/main/resources/api.yml @@ -0,0 +1,23 @@ +#-- 해당 부분은 override 하지 않으면 그대로 적용이 된다. + +oidc: + kakao: + issuer: ${KAKAO_ISSUER} + admin-key: ${KAKAO_ADMIN_KEY} + client-id: ${KAKAO_CLEINT_ID} + + +--- +spring: + config: + activate: + on-profile: api-local + + +--- +spring: + config: + activate: + on-profile: api-dev + + diff --git a/jabiseo-api/src/main/resources/application.yml b/jabiseo-api/src/main/resources/application.yml index 93271fe..93e2360 100644 --- a/jabiseo-api/src/main/resources/application.yml +++ b/jabiseo-api/src/main/resources/application.yml @@ -2,7 +2,7 @@ # --- -> 구분자 없는 default 영역 spring: config: - import: "classpath:/domain.yml,classpath:/infra.yml" + import: "classpath:/domain.yml,classpath:/infra.yml,classpath:/api.yml" profiles: active: local # -- 기본 값은 local로 실행한다. @@ -10,7 +10,9 @@ spring: local: - "domain-local" - "infra-local" + - "api-local" dev: - "domain-dev" - "infra-dev" + - "api-dev" diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java new file mode 100644 index 0000000..053cc23 --- /dev/null +++ b/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java @@ -0,0 +1,22 @@ +package com.jabiseo.auth.oidc; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + + +@SpringBootTest +class KakaoIdTokenValidatorTest { + + @Autowired + KakaoIdTokenValidator validator; + + @Test + void valid() { + + validator.getOidcPublicKey("kids.."); + } + +} diff --git a/jabiseo-common/src/main/java/com/jabiseo/exception/CommonErrorCode.java b/jabiseo-common/src/main/java/com/jabiseo/exception/CommonErrorCode.java index 50fb638..c4b1b9a 100644 --- a/jabiseo-common/src/main/java/com/jabiseo/exception/CommonErrorCode.java +++ b/jabiseo-common/src/main/java/com/jabiseo/exception/CommonErrorCode.java @@ -3,10 +3,10 @@ import lombok.Getter; @Getter -public enum CommonErrorCode implements ErrorCode{ +public enum CommonErrorCode implements ErrorCode { INVALID_REQUEST_BODY("Invalid request body", "COM_001", ErrorCode.BAD_REQUEST), - ; + INTERNAL_SERVER_ERROR(" 서버 에러", "COM_002", ErrorCode.INTERNAL_SERVER_ERROR); private final String message; private final String errorCode; diff --git a/jabiseo-common/src/main/java/com/jabiseo/exception/ErrorCode.java b/jabiseo-common/src/main/java/com/jabiseo/exception/ErrorCode.java index 31ca1f8..0325735 100644 --- a/jabiseo-common/src/main/java/com/jabiseo/exception/ErrorCode.java +++ b/jabiseo-common/src/main/java/com/jabiseo/exception/ErrorCode.java @@ -6,6 +6,10 @@ public interface ErrorCode { int BAD_REQUEST = 400; + int AUTHENTICATION_ERROR = 401; + + int INTERNAL_SERVER_ERROR = 500; + String getMessage(); String getErrorCode(); diff --git a/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationBusinessException.java b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationBusinessException.java new file mode 100644 index 0000000..65411ff --- /dev/null +++ b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationBusinessException.java @@ -0,0 +1,11 @@ +package com.jabiseo.auth.exception; + +import com.jabiseo.exception.BusinessException; +import com.jabiseo.exception.ErrorCode; + +public class AuthenticationBusinessException extends BusinessException { + + public AuthenticationBusinessException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java new file mode 100644 index 0000000..ef2fbfa --- /dev/null +++ b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java @@ -0,0 +1,36 @@ +package com.jabiseo.auth.exception; + +import com.jabiseo.exception.ErrorCode; + +public enum AuthenticationErrorCode implements ErrorCode { + + INVALID_ID_TOKEN("잘못된 idToken 입니다.", "AUTH_001", ErrorCode.AUTHENTICATION_ERROR), + EXPIRED_ID_TOKEN("만료된 idToken 입니다.", "AUTH_002", ErrorCode.AUTHENTICATION_ERROR), + NOT_SUPPORT_OAUTH("지원하지 않는 oauth 인증 수단입니다", "AUTH_003", ErrorCode.AUTHENTICATION_ERROR); + + private final String message; + private final String errorCode; + private final int statusCode; + + AuthenticationErrorCode(String message, String errorCode, int statusCode) { + this.message = message; + this.errorCode = errorCode; + this.statusCode = statusCode; + } + + + @Override + public String getMessage() { + return this.message; + } + + @Override + public String getErrorCode() { + return this.errorCode; + } + + @Override + public int getStatusCode() { + return this.statusCode; + } +} diff --git a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberFactory.java b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberFactory.java new file mode 100644 index 0000000..0da2983 --- /dev/null +++ b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberFactory.java @@ -0,0 +1,25 @@ +package com.jabiseo.member.domain; + + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class MemberFactory { + + private final RandomNicknameGenerator randomNicknameGenerator; + + // TODO: S3 + CDN 생성 후 변경해야 한다. + private final String DEFAULT_IMAGE_URL = "https://github.com/Jabiseo/Jabiseo-Backend/assets/28949213/fb6cb510-05fa-4791-a9c1-74a379294936"; + + public Member createNew(String oauthId, String oauthServer) { + String nickname = randomNicknameGenerator.generate(); + + // TODO: ID 생성 전략을 통해 따로 생성해야 한다. + String id = UUID.randomUUID().toString(); + return Member.of(id, "", nickname, oauthId, oauthServer, DEFAULT_IMAGE_URL); + } +} diff --git a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberRepository.java b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberRepository.java index a3d0d24..ee5bef3 100644 --- a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberRepository.java +++ b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberRepository.java @@ -2,5 +2,9 @@ import org.springframework.data.jpa.repository.JpaRepository; -public interface MemberRepository extends JpaRepository{ +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + + Optional findByOauthIdAndOauthServer(String oauthId, String oauthServer); } diff --git a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/RandomNicknameGenerator.java b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/RandomNicknameGenerator.java new file mode 100644 index 0000000..32b0165 --- /dev/null +++ b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/RandomNicknameGenerator.java @@ -0,0 +1,35 @@ +package com.jabiseo.member.domain; + +import org.springframework.stereotype.Component; + +import java.util.Random; + +@Component +public class RandomNicknameGenerator { + + private static final String[] prefixStrings = {"자격증마스터", "공부의왕", "백점만이살길", "하루공부", "재밌는자격증"}; + + public String generate() { + Random random = new Random(); + int prefixIndex = random.nextInt(prefixStrings.length); + String prefix = prefixStrings[prefixIndex]; + String suffixString = generateRandomNumber(); + + return prefix + suffixString; + } + + private static String generateRandomNumber() { + Random random = new Random(); + String randomNumber; + do { + int number = random.nextInt(9000) + 1000; // 1000부터 9999 사이의 난수 생성 + randomNumber = String.valueOf(number); + } while (!isValid(randomNumber)); + + return randomNumber; + } + + private static boolean isValid(String number) { + return number.charAt(0) != '0'; + } +} From f649189f791187bb9636cd8414faf4cf53cb6ab0 Mon Sep 17 00:00:00 2001 From: inhyeok jo Date: Mon, 1 Jul 2024 06:18:07 +0900 Subject: [PATCH 02/23] =?UTF-8?q?Feat(Login):=20token=20=EB=B0=9C=EA=B8=89?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/jabiseo/auth/jwt/JwtHandler.java | 112 ++++++++++++++++++ .../com/jabiseo/auth/jwt/JwtProperty.java | 22 ++++ .../auth/oidc/KakaoIdTokenValidator.java | 2 +- .../jabiseo/auth/oidc/OauthMemberInfo.java | 4 +- .../auth/oidc/property/KakaoOidcProperty.java | 2 +- .../jabiseo/auth/usecase/LoginUseCase.java | 8 +- .../jabiseo/auth/usecase/TokenRepository.java | 12 ++ jabiseo-api/src/main/resources/api.yml | 7 ++ .../exception/AuthenticationErrorCode.java | 4 +- 9 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtHandler.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtProperty.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/usecase/TokenRepository.java diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtHandler.java b/jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtHandler.java new file mode 100644 index 0000000..08d4d67 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtHandler.java @@ -0,0 +1,112 @@ +package com.jabiseo.auth.jwt; + +import com.jabiseo.auth.exception.AuthenticationBusinessException; +import com.jabiseo.auth.exception.AuthenticationErrorCode; +import com.jabiseo.member.domain.Member; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; + +@Component +public class JwtHandler { + + private final Key accessKey; + private final Key refreshKey; + private final Integer accessExpiredMin; + private final Integer refreshExpiredDay; + private final String APP_ISSUER = "jabiseo"; + + public JwtHandler(JwtProperty jwtProperty) { + + byte[] accessEncodeByte = Base64.getEncoder().encode((jwtProperty.getAccessKey().getBytes())); + byte[] refreshEncodeByte = Base64.getEncoder().encode(jwtProperty.getRefreshKey().getBytes()); + this.accessExpiredMin = jwtProperty.getAccessExpiredMin(); + this.refreshExpiredDay = jwtProperty.getRefreshExpiredDay(); + this.accessKey = Keys.hmacShaKeyFor(accessEncodeByte); + this.refreshKey = Keys.hmacShaKeyFor(refreshEncodeByte); + } + + + public String createAccessToken(Member member) { + Instant accessExpiredTime = Instant.now() + .plus(this.accessExpiredMin, ChronoUnit.MINUTES); + + Map payload = new HashMap<>(); + + return Jwts.builder() + .setSubject(member.getId().toString()) + .setIssuer(APP_ISSUER) + .setExpiration(Date.from(accessExpiredTime)) + .addClaims(payload) + .signWith(accessKey) + .compact(); + } + + public String createRefreshToken() { + Instant refreshExpiredTime = Instant.now() + .plus(this.refreshExpiredDay, ChronoUnit.DAYS); + return Jwts.builder() + .setExpiration(Date.from(refreshExpiredTime)) + .signWith(refreshKey) + .compact(); + } + + + public boolean validateAccessToken(String token) { + try { + Jwts.parserBuilder() + .setSigningKey(accessKey) + .build() + .parseClaimsJws(token); + return true; + } catch (ExpiredJwtException e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.EXPIRED_APP_JWT); + } catch (Exception e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_APP_JWT); + } + } + + public void validateRefreshToken(String refreshToken) { + try { + Jwts.parserBuilder() + .setSigningKey(refreshKey) + .build() + .parseClaimsJws(refreshToken); + } catch (ExpiredJwtException e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.EXPIRED_APP_JWT); + } catch (Exception e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_APP_JWT); + } + } + + + public Claims getClaimFromExpiredAccessToken(String accessToken) { + try { + return Jwts.parserBuilder() + .setSigningKey(accessKey) + .build() + .parseClaimsJws(accessToken) + .getBody(); + } catch (ExpiredJwtException e) { + return e.getClaims(); + } catch (Exception e) { + throw new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_APP_JWT); + } + } + + public Claims getClaimsFromAccessToken(String token) { + return Jwts + .parserBuilder() + .setSigningKey(accessKey) + .build() + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtProperty.java b/jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtProperty.java new file mode 100644 index 0000000..7880df8 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtProperty.java @@ -0,0 +1,22 @@ +package com.jabiseo.auth.jwt; + + +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@ConfigurationProperties(prefix = "jwt") +public class JwtProperty { + + private final String accessKey; + private final String refreshKey; + private final Integer accessExpiredMin; + private final Integer refreshExpiredDay; + + public JwtProperty(String accessKey, String refreshKey, Integer accessExpiredMin, Integer refreshExpiredDay) { + this.accessKey = accessKey; + this.refreshKey = refreshKey; + this.accessExpiredMin = accessExpiredMin; + this.refreshExpiredDay = refreshExpiredDay; + } +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java index d47ea5b..5b82733 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java @@ -17,7 +17,7 @@ public class KakaoIdTokenValidator extends AbstractIdTokenValidator { private final KakaoKauthClient kakaoKauthClient; public KakaoIdTokenValidator(KakaoOidcProperty kakaoOidcProperty, IdTokenJwtHandler idTokenJwtHandler, KakaoKauthClient kakaoKauthClient) { - super(kakaoOidcProperty.toIdTokenPropety(), idTokenJwtHandler); + super(kakaoOidcProperty.toIdTokenProperty(), idTokenJwtHandler); this.kakaoKauthClient = kakaoKauthClient; } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java index 74edaca..bfafcac 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java @@ -6,8 +6,8 @@ @Getter public class OauthMemberInfo { - private String oauthId; - private String oauthServer; + private final String oauthId; + private final String oauthServer; @Builder diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java index 1eff9e0..ffba26b 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java @@ -18,7 +18,7 @@ public KakaoOidcProperty(String clientId, String adminKey, String issuer) { this.issuer = issuer; } - public OidcIdTokenProperty toIdTokenPropety() { + public OidcIdTokenProperty toIdTokenProperty() { return new OidcIdTokenProperty(issuer, clientId); } } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java index 541b543..4e399ee 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java @@ -2,6 +2,7 @@ import com.jabiseo.auth.dto.LoginRequest; import com.jabiseo.auth.dto.LoginResponse; +import com.jabiseo.auth.jwt.JwtHandler; import com.jabiseo.auth.oidc.OauthMemberInfo; import com.jabiseo.auth.oidc.TokenValidatorManager; import com.jabiseo.member.domain.Member; @@ -17,7 +18,9 @@ public class LoginUseCase { private final TokenValidatorManager tokenValidatorManager; private final MemberFactory memberFactory; + private final JwtHandler jwtHandler; private final MemberRepository memberRepository; + private final TokenRepository tokenRepository; public LoginResponse execute(LoginRequest loginRequest) { OauthMemberInfo oauthMemberInfo = tokenValidatorManager.validate(loginRequest.idToken(), loginRequest.oauthServer()); @@ -34,8 +37,11 @@ public LoginResponse execute(LoginRequest loginRequest) { } + String accessToken = jwtHandler.createAccessToken(member); + String refreshToken = jwtHandler.createRefreshToken(); + tokenRepository.saveToken(member.getId(), refreshToken); - return new LoginResponse("access_token", "refresh_token"); + return new LoginResponse(accessToken, refreshToken); } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/TokenRepository.java b/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/TokenRepository.java new file mode 100644 index 0000000..4bbcb20 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/TokenRepository.java @@ -0,0 +1,12 @@ +package com.jabiseo.auth.usecase; + +import org.springframework.stereotype.Component; + +@Component +public class TokenRepository { + + + public void saveToken(String key, String token) { + + } +} diff --git a/jabiseo-api/src/main/resources/api.yml b/jabiseo-api/src/main/resources/api.yml index a452746..d86a011 100644 --- a/jabiseo-api/src/main/resources/api.yml +++ b/jabiseo-api/src/main/resources/api.yml @@ -6,6 +6,11 @@ oidc: admin-key: ${KAKAO_ADMIN_KEY} client-id: ${KAKAO_CLEINT_ID} +jwt: + access-expired-min: 60 + refresh-expired-day: 30 + refresh-key: ${REFRESH_KEY} + access-key: ${ACCESS_KEY} --- spring: @@ -13,6 +18,8 @@ spring: activate: on-profile: api-local +jwt: + access-expired-min: 6000 --- spring: diff --git a/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java index ef2fbfa..d14a35b 100644 --- a/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java +++ b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java @@ -6,7 +6,9 @@ public enum AuthenticationErrorCode implements ErrorCode { INVALID_ID_TOKEN("잘못된 idToken 입니다.", "AUTH_001", ErrorCode.AUTHENTICATION_ERROR), EXPIRED_ID_TOKEN("만료된 idToken 입니다.", "AUTH_002", ErrorCode.AUTHENTICATION_ERROR), - NOT_SUPPORT_OAUTH("지원하지 않는 oauth 인증 수단입니다", "AUTH_003", ErrorCode.AUTHENTICATION_ERROR); + NOT_SUPPORT_OAUTH("지원하지 않는 oauth 인증 수단입니다", "AUTH_003", ErrorCode.AUTHENTICATION_ERROR), + EXPIRED_APP_JWT("만료된 jwt 토큰 입니다", "AUTH_004", ErrorCode.AUTHENTICATION_ERROR), + INVALID_APP_JWT("잘못된 jwt 토큰입니다", "AUTH_005", ErrorCode.AUTHENTICATION_ERROR); private final String message; private final String errorCode; From ab5c77daac58795320dad9095de9fc56b3cc1493 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Thu, 11 Jul 2024 14:58:09 +0900 Subject: [PATCH 03/23] =?UTF-8?q?Refactor(*):=20=EC=95=84=ED=82=A4?= =?UTF-8?q?=ED=85=8D=EC=B2=98=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/{jwt => application}/JwtHandler.java | 3 +-- .../auth/{jwt => application}/JwtProperty.java | 2 +- .../jabiseo/auth/application}/MemberFactory.java | 4 +++- .../oidc/AbstractIdTokenValidator.java | 5 +++-- .../oidc/IdTokenJwtHandler.java | 3 ++- .../oidc/KakaoIdTokenValidator.java | 14 ++++++++------ .../{ => application}/oidc/OauthMemberInfo.java | 2 +- .../oidc/TokenValidatorManager.java | 2 +- .../oidc/property/KakaoOidcProperty.java | 2 +- .../oidc/property/OidcIdTokenProperty.java | 13 +++++++++++++ .../{ => application}/usecase/LoginUseCase.java | 11 ++++++----- .../{ => application}/usecase/LogoutUseCase.java | 2 +- .../usecase/ReissueUseCase.java | 2 +- .../usecase/WithdrawUseCase.java | 2 +- .../jabiseo/auth/controller/AuthController.java | 8 ++++---- .../auth/oidc/property/OidcIdTokenProperty.java | 10 ---------- .../auth/oidc/KakaoIdTokenValidatorTest.java | 3 +-- .../main/java/com/jabiseo/auth/domain/Token.java | 7 +++++++ .../jabiseo/auth/domain}/TokenRepository.java | 2 +- jabiseo-infrastructure/build.gradle | 1 + .../java/com/jabiseo/cache/CacheRepository.java | 16 ++++++++++++++++ .../com/jabiseo/client}/KakaoKauthClient.java | 5 +++-- .../java/com/jabiseo/client}/OidcPublicKey.java | 4 ++-- .../jabiseo/client}/OidcPublicKeyResponse.java | 4 ++-- .../com/jabiseo/client}/RestClientConfig.java | 4 ++-- .../src/main/resources/infra.yml | 6 ++++++ 26 files changed, 88 insertions(+), 49 deletions(-) rename jabiseo-api/src/main/java/com/jabiseo/auth/{jwt => application}/JwtHandler.java (99%) rename jabiseo-api/src/main/java/com/jabiseo/auth/{jwt => application}/JwtProperty.java (94%) rename {jabiseo-domain/src/main/java/com/jabiseo/member/domain => jabiseo-api/src/main/java/com/jabiseo/auth/application}/MemberFactory.java (85%) rename jabiseo-api/src/main/java/com/jabiseo/auth/{ => application}/oidc/AbstractIdTokenValidator.java (89%) rename jabiseo-api/src/main/java/com/jabiseo/auth/{ => application}/oidc/IdTokenJwtHandler.java (97%) rename jabiseo-api/src/main/java/com/jabiseo/auth/{ => application}/oidc/KakaoIdTokenValidator.java (76%) rename jabiseo-api/src/main/java/com/jabiseo/auth/{ => application}/oidc/OauthMemberInfo.java (88%) rename jabiseo-api/src/main/java/com/jabiseo/auth/{ => application}/oidc/TokenValidatorManager.java (93%) rename jabiseo-api/src/main/java/com/jabiseo/auth/{ => application}/oidc/property/KakaoOidcProperty.java (92%) create mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/property/OidcIdTokenProperty.java rename jabiseo-api/src/main/java/com/jabiseo/auth/{ => application}/usecase/LoginUseCase.java (83%) rename jabiseo-api/src/main/java/com/jabiseo/auth/{ => application}/usecase/LogoutUseCase.java (72%) rename jabiseo-api/src/main/java/com/jabiseo/auth/{ => application}/usecase/ReissueUseCase.java (85%) rename jabiseo-api/src/main/java/com/jabiseo/auth/{ => application}/usecase/WithdrawUseCase.java (73%) delete mode 100644 jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/OidcIdTokenProperty.java create mode 100644 jabiseo-domain/src/main/java/com/jabiseo/auth/domain/Token.java rename {jabiseo-api/src/main/java/com/jabiseo/auth/usecase => jabiseo-domain/src/main/java/com/jabiseo/auth/domain}/TokenRepository.java (82%) create mode 100644 jabiseo-infrastructure/src/main/java/com/jabiseo/cache/CacheRepository.java rename {jabiseo-api/src/main/java/com/jabiseo/auth/oidc => jabiseo-infrastructure/src/main/java/com/jabiseo/client}/KakaoKauthClient.java (91%) rename {jabiseo-api/src/main/java/com/jabiseo/auth/oidc => jabiseo-infrastructure/src/main/java/com/jabiseo/client}/OidcPublicKey.java (87%) rename {jabiseo-api/src/main/java/com/jabiseo/auth/oidc => jabiseo-infrastructure/src/main/java/com/jabiseo/client}/OidcPublicKeyResponse.java (73%) rename {jabiseo-api/src/main/java/com/jabiseo/config => jabiseo-infrastructure/src/main/java/com/jabiseo/client}/RestClientConfig.java (91%) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtHandler.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java similarity index 99% rename from jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtHandler.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java index 08d4d67..3c50564 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtHandler.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtHandler.java @@ -1,4 +1,4 @@ -package com.jabiseo.auth.jwt; +package com.jabiseo.auth.application; import com.jabiseo.auth.exception.AuthenticationBusinessException; import com.jabiseo.auth.exception.AuthenticationErrorCode; @@ -24,7 +24,6 @@ public class JwtHandler { private final String APP_ISSUER = "jabiseo"; public JwtHandler(JwtProperty jwtProperty) { - byte[] accessEncodeByte = Base64.getEncoder().encode((jwtProperty.getAccessKey().getBytes())); byte[] refreshEncodeByte = Base64.getEncoder().encode(jwtProperty.getRefreshKey().getBytes()); this.accessExpiredMin = jwtProperty.getAccessExpiredMin(); diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtProperty.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtProperty.java similarity index 94% rename from jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtProperty.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtProperty.java index 7880df8..bfa42b2 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/jwt/JwtProperty.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/JwtProperty.java @@ -1,4 +1,4 @@ -package com.jabiseo.auth.jwt; +package com.jabiseo.auth.application; import lombok.Getter; diff --git a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberFactory.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java similarity index 85% rename from jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberFactory.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java index 0da2983..b68e273 100644 --- a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberFactory.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java @@ -1,6 +1,8 @@ -package com.jabiseo.member.domain; +package com.jabiseo.auth.application; +import com.jabiseo.member.domain.Member; +import com.jabiseo.member.domain.RandomNicknameGenerator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/AbstractIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/AbstractIdTokenValidator.java similarity index 89% rename from jabiseo-api/src/main/java/com/jabiseo/auth/oidc/AbstractIdTokenValidator.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/AbstractIdTokenValidator.java index 651b3e0..3a4fd3d 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/AbstractIdTokenValidator.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/AbstractIdTokenValidator.java @@ -1,7 +1,8 @@ -package com.jabiseo.auth.oidc; +package com.jabiseo.auth.application.oidc; -import com.jabiseo.auth.oidc.property.OidcIdTokenProperty; +import com.jabiseo.client.OidcPublicKey; +import com.jabiseo.auth.application.oidc.property.OidcIdTokenProperty; import java.util.Map; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/IdTokenJwtHandler.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/IdTokenJwtHandler.java similarity index 97% rename from jabiseo-api/src/main/java/com/jabiseo/auth/oidc/IdTokenJwtHandler.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/IdTokenJwtHandler.java index 8221c14..b042bf1 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/IdTokenJwtHandler.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/IdTokenJwtHandler.java @@ -1,5 +1,6 @@ -package com.jabiseo.auth.oidc; +package com.jabiseo.auth.application.oidc; +import com.jabiseo.client.OidcPublicKey; import com.jabiseo.auth.exception.AuthenticationBusinessException; import com.jabiseo.auth.exception.AuthenticationErrorCode; import io.jsonwebtoken.*; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java similarity index 76% rename from jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java index 5b82733..8d59531 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoIdTokenValidator.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java @@ -1,10 +1,12 @@ -package com.jabiseo.auth.oidc; +package com.jabiseo.auth.application.oidc; +import com.jabiseo.client.KakaoKauthClient; +import com.jabiseo.client.OidcPublicKey; +import com.jabiseo.client.OidcPublicKeyResponse; import com.jabiseo.auth.exception.AuthenticationBusinessException; import com.jabiseo.auth.exception.AuthenticationErrorCode; -import com.jabiseo.auth.oidc.property.KakaoOidcProperty; +import com.jabiseo.auth.application.oidc.property.KakaoOidcProperty; import com.jabiseo.exception.CommonErrorCode; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import java.util.List; @@ -26,12 +28,12 @@ protected OidcPublicKey getOidcPublicKey(String kid) { // TODO: 캐싱 적용 & 체크 필요 - ResponseEntity publicKeys = kakaoKauthClient.getPublicKeys(); - List keys = publicKeys.getBody().getKeys(); + OidcPublicKeyResponse publicKeys = kakaoKauthClient.getPublicKeys().getBody(); + List keys = publicKeys.getKeys(); return keys.stream().filter((key) -> key.getKid().equals(kid)) .findAny() - .orElseThrow(()-> new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_ID_TOKEN)); + .orElseThrow(() -> new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_ID_TOKEN)); } @Override diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/OauthMemberInfo.java similarity index 88% rename from jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/OauthMemberInfo.java index bfafcac..ef4bd44 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OauthMemberInfo.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/OauthMemberInfo.java @@ -1,4 +1,4 @@ -package com.jabiseo.auth.oidc; +package com.jabiseo.auth.application.oidc; import lombok.Builder; import lombok.Getter; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/TokenValidatorManager.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java similarity index 93% rename from jabiseo-api/src/main/java/com/jabiseo/auth/oidc/TokenValidatorManager.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java index e01bc83..7b1cede 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/TokenValidatorManager.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java @@ -1,4 +1,4 @@ -package com.jabiseo.auth.oidc; +package com.jabiseo.auth.application.oidc; import com.jabiseo.auth.exception.AuthenticationBusinessException; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/property/KakaoOidcProperty.java similarity index 92% rename from jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/property/KakaoOidcProperty.java index ffba26b..4d72bcf 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/KakaoOidcProperty.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/property/KakaoOidcProperty.java @@ -1,4 +1,4 @@ -package com.jabiseo.auth.oidc.property; +package com.jabiseo.auth.application.oidc.property; import lombok.Getter; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/property/OidcIdTokenProperty.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/property/OidcIdTokenProperty.java new file mode 100644 index 0000000..e78c907 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/property/OidcIdTokenProperty.java @@ -0,0 +1,13 @@ +package com.jabiseo.auth.application.oidc.property; + + +/** + * @param issuer 발행 회사 url + * @param audience client-id + */ +public record OidcIdTokenProperty( + String issuer, + String audience +) { + +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java similarity index 83% rename from jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java index 4e399ee..cea3eba 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LoginUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java @@ -1,12 +1,13 @@ -package com.jabiseo.auth.usecase; +package com.jabiseo.auth.application.usecase; +import com.jabiseo.auth.application.MemberFactory; +import com.jabiseo.auth.domain.TokenRepository; import com.jabiseo.auth.dto.LoginRequest; import com.jabiseo.auth.dto.LoginResponse; -import com.jabiseo.auth.jwt.JwtHandler; -import com.jabiseo.auth.oidc.OauthMemberInfo; -import com.jabiseo.auth.oidc.TokenValidatorManager; +import com.jabiseo.auth.application.JwtHandler; +import com.jabiseo.auth.application.oidc.OauthMemberInfo; +import com.jabiseo.auth.application.oidc.TokenValidatorManager; import com.jabiseo.member.domain.Member; -import com.jabiseo.member.domain.MemberFactory; import com.jabiseo.member.domain.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LogoutUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LogoutUseCase.java similarity index 72% rename from jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LogoutUseCase.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LogoutUseCase.java index d34446e..4008f47 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/LogoutUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LogoutUseCase.java @@ -1,4 +1,4 @@ -package com.jabiseo.auth.usecase; +package com.jabiseo.auth.application.usecase; import org.springframework.stereotype.Service; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/ReissueUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java similarity index 85% rename from jabiseo-api/src/main/java/com/jabiseo/auth/usecase/ReissueUseCase.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java index 5f09fa9..06124c9 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/ReissueUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/ReissueUseCase.java @@ -1,4 +1,4 @@ -package com.jabiseo.auth.usecase; +package com.jabiseo.auth.application.usecase; import com.jabiseo.auth.dto.LoginResponse; import org.springframework.stereotype.Service; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/WithdrawUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/WithdrawUseCase.java similarity index 73% rename from jabiseo-api/src/main/java/com/jabiseo/auth/usecase/WithdrawUseCase.java rename to jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/WithdrawUseCase.java index 7a25282..42517af 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/WithdrawUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/WithdrawUseCase.java @@ -1,4 +1,4 @@ -package com.jabiseo.auth.usecase; +package com.jabiseo.auth.application.usecase; import org.springframework.stereotype.Service; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java index 7ebfffd..bf168a0 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java @@ -2,10 +2,10 @@ import com.jabiseo.auth.dto.LoginRequest; import com.jabiseo.auth.dto.LoginResponse; -import com.jabiseo.auth.usecase.LoginUseCase; -import com.jabiseo.auth.usecase.LogoutUseCase; -import com.jabiseo.auth.usecase.ReissueUseCase; -import com.jabiseo.auth.usecase.WithdrawUseCase; +import com.jabiseo.auth.application.usecase.LoginUseCase; +import com.jabiseo.auth.application.usecase.LogoutUseCase; +import com.jabiseo.auth.application.usecase.ReissueUseCase; +import com.jabiseo.auth.application.usecase.WithdrawUseCase; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/OidcIdTokenProperty.java b/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/OidcIdTokenProperty.java deleted file mode 100644 index c27c177..0000000 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/property/OidcIdTokenProperty.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.jabiseo.auth.oidc.property; - - -/** - * @param issuer 발행 회사 url - * @param audience client-id - */ -public record OidcIdTokenProperty(String issuer, String audience) { - -} diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java index 053cc23..a343a4f 100644 --- a/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java +++ b/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java @@ -1,11 +1,10 @@ package com.jabiseo.auth.oidc; +import com.jabiseo.auth.application.oidc.KakaoIdTokenValidator; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import static org.junit.jupiter.api.Assertions.*; - @SpringBootTest class KakaoIdTokenValidatorTest { diff --git a/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/Token.java b/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/Token.java new file mode 100644 index 0000000..ef4c55a --- /dev/null +++ b/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/Token.java @@ -0,0 +1,7 @@ +package com.jabiseo.auth.domain; + + +public class Token { + + private String value; +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/TokenRepository.java b/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/TokenRepository.java similarity index 82% rename from jabiseo-api/src/main/java/com/jabiseo/auth/usecase/TokenRepository.java rename to jabiseo-domain/src/main/java/com/jabiseo/auth/domain/TokenRepository.java index 4bbcb20..1a5cbb9 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/usecase/TokenRepository.java +++ b/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/TokenRepository.java @@ -1,4 +1,4 @@ -package com.jabiseo.auth.usecase; +package com.jabiseo.auth.domain; import org.springframework.stereotype.Component; diff --git a/jabiseo-infrastructure/build.gradle b/jabiseo-infrastructure/build.gradle index 89575f5..c693b6a 100644 --- a/jabiseo-infrastructure/build.gradle +++ b/jabiseo-infrastructure/build.gradle @@ -10,6 +10,7 @@ jar { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/CacheRepository.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/CacheRepository.java new file mode 100644 index 0000000..168a021 --- /dev/null +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/CacheRepository.java @@ -0,0 +1,16 @@ +package com.jabiseo.cache; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +@Component +public class CacheRepository { + + + public void save(String key, String value, int ttl){ + + } + +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoKauthClient.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/KakaoKauthClient.java similarity index 91% rename from jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoKauthClient.java rename to jabiseo-infrastructure/src/main/java/com/jabiseo/client/KakaoKauthClient.java index 8aeafbf..59078e8 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/KakaoKauthClient.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/KakaoKauthClient.java @@ -1,11 +1,12 @@ -package com.jabiseo.auth.oidc; - +package com.jabiseo.client; +; import org.springframework.http.ResponseEntity; import org.springframework.web.service.annotation.GetExchange; import org.springframework.web.service.annotation.HttpExchange; + @HttpExchange public interface KakaoKauthClient { diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKey.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKey.java similarity index 87% rename from jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKey.java rename to jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKey.java index 2292b99..6b7a6b6 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKey.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKey.java @@ -1,11 +1,11 @@ -package com.jabiseo.auth.oidc; +package com.jabiseo.client; import lombok.Getter; import lombok.ToString; @ToString @Getter -class OidcPublicKey { +public class OidcPublicKey { private String kid; private String alg; diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKeyResponse.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKeyResponse.java similarity index 73% rename from jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKeyResponse.java rename to jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKeyResponse.java index bd75a3a..dd63746 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/oidc/OidcPublicKeyResponse.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKeyResponse.java @@ -1,4 +1,4 @@ -package com.jabiseo.auth.oidc; +package com.jabiseo.client; import lombok.Getter; import lombok.NoArgsConstructor; @@ -9,7 +9,7 @@ @Getter @Setter @NoArgsConstructor -class OidcPublicKeyResponse { +public class OidcPublicKeyResponse { private List keys; } diff --git a/jabiseo-api/src/main/java/com/jabiseo/config/RestClientConfig.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/RestClientConfig.java similarity index 91% rename from jabiseo-api/src/main/java/com/jabiseo/config/RestClientConfig.java rename to jabiseo-infrastructure/src/main/java/com/jabiseo/client/RestClientConfig.java index 2321928..057e640 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/config/RestClientConfig.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/RestClientConfig.java @@ -1,6 +1,6 @@ -package com.jabiseo.config; +package com.jabiseo.client; + -import com.jabiseo.auth.oidc.KakaoKauthClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestClient; diff --git a/jabiseo-infrastructure/src/main/resources/infra.yml b/jabiseo-infrastructure/src/main/resources/infra.yml index 3e51736..97c41a4 100644 --- a/jabiseo-infrastructure/src/main/resources/infra.yml +++ b/jabiseo-infrastructure/src/main/resources/infra.yml @@ -6,6 +6,12 @@ spring: username: ${DB_USERNAME} password: ${DB_PASSWORD} + redis: + host: ${REDIS_HOST} + port: ${REDIS_PORT} + password: ${REDIS_PASSWORD} + + --- spring: config: From bde8448a5a110aa7af686ed23904dd94ec18ea80 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Thu, 11 Jul 2024 15:32:16 +0900 Subject: [PATCH 04/23] =?UTF-8?q?Feat(Oauth):=20OauthServertype=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/MemberFactory.java | 3 ++- .../oidc/AbstractIdTokenValidator.java | 3 +++ .../oidc/KakaoIdTokenValidator.java | 9 +++++++- .../application/oidc/OauthMemberInfo.java | 8 ++++--- .../oidc/TokenValidatorManager.java | 23 +++++++++++++------ .../application/usecase/LoginUseCase.java | 3 ++- .../com/jabiseo/auth/dto/LoginRequest.java | 3 ++- .../auth/oidc/KakaoIdTokenValidatorTest.java | 1 - .../com/jabiseo/member/domain/Member.java | 7 +++--- .../member/domain/MemberRepository.java | 2 +- .../jabiseo/member/domain/OauthServer.java | 5 ++++ 11 files changed, 48 insertions(+), 19 deletions(-) create mode 100644 jabiseo-domain/src/main/java/com/jabiseo/member/domain/OauthServer.java diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java index b68e273..790c6d2 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java @@ -2,6 +2,7 @@ import com.jabiseo.member.domain.Member; +import com.jabiseo.member.domain.OauthServer; import com.jabiseo.member.domain.RandomNicknameGenerator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -17,7 +18,7 @@ public class MemberFactory { // TODO: S3 + CDN 생성 후 변경해야 한다. private final String DEFAULT_IMAGE_URL = "https://github.com/Jabiseo/Jabiseo-Backend/assets/28949213/fb6cb510-05fa-4791-a9c1-74a379294936"; - public Member createNew(String oauthId, String oauthServer) { + public Member createNew(String oauthId, OauthServer oauthServer) { String nickname = randomNicknameGenerator.generate(); // TODO: ID 생성 전략을 통해 따로 생성해야 한다. diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/AbstractIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/AbstractIdTokenValidator.java index 3a4fd3d..1688da2 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/AbstractIdTokenValidator.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/AbstractIdTokenValidator.java @@ -3,6 +3,7 @@ import com.jabiseo.client.OidcPublicKey; import com.jabiseo.auth.application.oidc.property.OidcIdTokenProperty; +import com.jabiseo.member.domain.OauthServer; import java.util.Map; @@ -34,4 +35,6 @@ public OauthMemberInfo validate(String idToken) { abstract protected OidcPublicKey getOidcPublicKey(String kid); abstract protected OauthMemberInfo extractMemberInfoFromPayload(Map payload); + + abstract OauthServer getOauthServer(); } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java index 8d59531..a2ad193 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java @@ -7,6 +7,7 @@ import com.jabiseo.auth.exception.AuthenticationErrorCode; import com.jabiseo.auth.application.oidc.property.KakaoOidcProperty; import com.jabiseo.exception.CommonErrorCode; +import com.jabiseo.member.domain.OauthServer; import org.springframework.stereotype.Component; import java.util.List; @@ -46,10 +47,16 @@ protected OauthMemberInfo extractMemberInfoFromPayload(Map paylo return OauthMemberInfo.builder() .oauthId(oauthId) - .oauthServer("KAKAO") + .oauthServer(OauthServer.KAKAO) + .email("email@email.com") .build(); } + @Override + OauthServer getOauthServer() { + return OauthServer.KAKAO; + } + /* * 해당 예외가 발생하는건 카카오에서 프로퍼티 key 값을 바꾸지 않는 이상은 발생하지 않는다. */ diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/OauthMemberInfo.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/OauthMemberInfo.java index ef4bd44..764b3ab 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/OauthMemberInfo.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/OauthMemberInfo.java @@ -1,5 +1,6 @@ package com.jabiseo.auth.application.oidc; +import com.jabiseo.member.domain.OauthServer; import lombok.Builder; import lombok.Getter; @@ -7,12 +8,13 @@ public class OauthMemberInfo { private final String oauthId; - private final String oauthServer; - + private final OauthServer oauthServer; + private final String email; @Builder - public OauthMemberInfo(String oauthId, String oauthServer) { + public OauthMemberInfo(String oauthId, OauthServer oauthServer, String email) { this.oauthId = oauthId; this.oauthServer = oauthServer; + this.email = email; } } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java index 7b1cede..c7e7ed5 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java @@ -3,20 +3,29 @@ import com.jabiseo.auth.exception.AuthenticationBusinessException; import com.jabiseo.auth.exception.AuthenticationErrorCode; -import lombok.RequiredArgsConstructor; +import com.jabiseo.member.domain.OauthServer; import org.springframework.stereotype.Component; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + @Component -@RequiredArgsConstructor public class TokenValidatorManager { - private final AbstractIdTokenValidator kakaoIdTokenValidator; + private final Map validator = new HashMap<>(); + + public TokenValidatorManager(Set idTokenValidators) { + idTokenValidators.forEach((v) -> validator.put(v.getOauthServer(), v)); + } - public OauthMemberInfo validate(String idToken, String oauthServer) { - if (oauthServer.equals("KAKAO")) { - return kakaoIdTokenValidator.validate(idToken); - } else { + public OauthMemberInfo validate(String idToken, OauthServer oauthServer) { + AbstractIdTokenValidator idTokenValidator = validator.get(oauthServer); + + if (idTokenValidator == null) { throw new AuthenticationBusinessException(AuthenticationErrorCode.NOT_SUPPORT_OAUTH); } + + return idTokenValidator.validate(idToken); } } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java index cea3eba..a9e5f92 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java @@ -9,6 +9,7 @@ import com.jabiseo.auth.application.oidc.TokenValidatorManager; import com.jabiseo.member.domain.Member; import com.jabiseo.member.domain.MemberRepository; +import com.jabiseo.member.domain.OauthServer; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -27,7 +28,7 @@ public LoginResponse execute(LoginRequest loginRequest) { OauthMemberInfo oauthMemberInfo = tokenValidatorManager.validate(loginRequest.idToken(), loginRequest.oauthServer()); String oauthId = oauthMemberInfo.getOauthId(); - String oauthServer = oauthMemberInfo.getOauthServer(); + OauthServer oauthServer = oauthMemberInfo.getOauthServer(); Member member = memberRepository.findByOauthIdAndOauthServer(oauthId, oauthServer) .orElse(null); diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java b/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java index e180967..729f8f4 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java @@ -1,6 +1,7 @@ package com.jabiseo.auth.dto; +import com.jabiseo.member.domain.OauthServer; import jakarta.validation.constraints.NotNull; -public record LoginRequest(@NotNull String idToken, @NotNull String oauthServer) { +public record LoginRequest(@NotNull String idToken, @NotNull OauthServer oauthServer) { } diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java index a343a4f..2e6edf6 100644 --- a/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java +++ b/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java @@ -15,7 +15,6 @@ class KakaoIdTokenValidatorTest { @Test void valid() { - validator.getOidcPublicKey("kids.."); } } diff --git a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/Member.java b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/Member.java index f26b133..7610db5 100644 --- a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/Member.java +++ b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/Member.java @@ -36,7 +36,8 @@ public class Member { private String oauthId; - private String oauthServer; + @Enumerated(EnumType.STRING) + private OauthServer oauthServer; private boolean deleted = false; @@ -57,7 +58,7 @@ public class Member { @OneToMany(mappedBy = "member") private List bookmarks = new ArrayList<>(); - private Member(String id, String email, String nickname, String oauthId, String oauthServer, String profileImage) { + private Member(String id, String email, String nickname, String oauthId, OauthServer oauthServer, String profileImage) { this.id = id; this.email = email; this.nickname = nickname; @@ -67,7 +68,7 @@ private Member(String id, String email, String nickname, String oauthId, String } public static Member of(String id, String email, String nickname, - String oauthId, String oauthServer, String profileImage) { + String oauthId, OauthServer oauthServer, String profileImage) { return new Member(id, email, nickname, oauthId, oauthServer, profileImage); } diff --git a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberRepository.java b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberRepository.java index ee5bef3..f6f055a 100644 --- a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberRepository.java +++ b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/MemberRepository.java @@ -6,5 +6,5 @@ public interface MemberRepository extends JpaRepository { - Optional findByOauthIdAndOauthServer(String oauthId, String oauthServer); + Optional findByOauthIdAndOauthServer(String oauthId, OauthServer oauthServer); } diff --git a/jabiseo-domain/src/main/java/com/jabiseo/member/domain/OauthServer.java b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/OauthServer.java new file mode 100644 index 0000000..361aacb --- /dev/null +++ b/jabiseo-domain/src/main/java/com/jabiseo/member/domain/OauthServer.java @@ -0,0 +1,5 @@ +package com.jabiseo.member.domain; + +public enum OauthServer { + KAKAO, GOOGLE +} From 39b9c1d2af46b2e5fd3c763985ceeaf37e73fff1 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Thu, 11 Jul 2024 15:43:09 +0900 Subject: [PATCH 05/23] =?UTF-8?q?Refactor(Login):=20login=20usecase=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20getter=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20=ED=98=B8=EC=B6=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20memberfactory=EC=9D=98=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/jabiseo/auth/application/MemberFactory.java | 5 +++-- .../com/jabiseo/auth/application/usecase/LoginUseCase.java | 7 ++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java index 790c6d2..5b02855 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/MemberFactory.java @@ -1,6 +1,7 @@ package com.jabiseo.auth.application; +import com.jabiseo.auth.application.oidc.OauthMemberInfo; import com.jabiseo.member.domain.Member; import com.jabiseo.member.domain.OauthServer; import com.jabiseo.member.domain.RandomNicknameGenerator; @@ -18,11 +19,11 @@ public class MemberFactory { // TODO: S3 + CDN 생성 후 변경해야 한다. private final String DEFAULT_IMAGE_URL = "https://github.com/Jabiseo/Jabiseo-Backend/assets/28949213/fb6cb510-05fa-4791-a9c1-74a379294936"; - public Member createNew(String oauthId, OauthServer oauthServer) { + public Member createNew(OauthMemberInfo oauthMemberInfo) { String nickname = randomNicknameGenerator.generate(); // TODO: ID 생성 전략을 통해 따로 생성해야 한다. String id = UUID.randomUUID().toString(); - return Member.of(id, "", nickname, oauthId, oauthServer, DEFAULT_IMAGE_URL); + return Member.of(id, oauthMemberInfo.getEmail(), nickname, oauthMemberInfo.getOauthId(), oauthMemberInfo.getOauthServer(), DEFAULT_IMAGE_URL); } } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java index a9e5f92..0ea2743 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java @@ -27,14 +27,11 @@ public class LoginUseCase { public LoginResponse execute(LoginRequest loginRequest) { OauthMemberInfo oauthMemberInfo = tokenValidatorManager.validate(loginRequest.idToken(), loginRequest.oauthServer()); - String oauthId = oauthMemberInfo.getOauthId(); - OauthServer oauthServer = oauthMemberInfo.getOauthServer(); - - Member member = memberRepository.findByOauthIdAndOauthServer(oauthId, oauthServer) + Member member = memberRepository.findByOauthIdAndOauthServer(oauthMemberInfo.getOauthId(), oauthMemberInfo.getOauthServer()) .orElse(null); if (isRequireSignup(member)) { - Member newMember = memberFactory.createNew(oauthId, oauthServer); + Member newMember = memberFactory.createNew(oauthMemberInfo); member = memberRepository.save(newMember); } From b5309a385414f8d0559c70fc1fcaaffeb45e5421 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Thu, 11 Jul 2024 16:16:34 +0900 Subject: [PATCH 06/23] =?UTF-8?q?Feat(Login):=20Token,=20Publickey=20cache?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oidc/KakaoIdTokenValidator.java | 16 ++++-- .../application/usecase/LoginUseCase.java | 7 +-- .../java/com/jabiseo/auth/domain/Token.java | 7 --- .../jabiseo/auth/domain/TokenRepository.java | 12 ---- .../com/jabiseo/cache/CacheRepository.java | 16 ------ .../jabiseo/cache/RedisCacheRepository.java | 56 +++++++++++++++++++ 6 files changed, 70 insertions(+), 44 deletions(-) delete mode 100644 jabiseo-domain/src/main/java/com/jabiseo/auth/domain/Token.java delete mode 100644 jabiseo-domain/src/main/java/com/jabiseo/auth/domain/TokenRepository.java delete mode 100644 jabiseo-infrastructure/src/main/java/com/jabiseo/cache/CacheRepository.java create mode 100644 jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java index a2ad193..7c056b1 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java @@ -1,5 +1,6 @@ package com.jabiseo.auth.application.oidc; +import com.jabiseo.cache.RedisCacheRepository; import com.jabiseo.client.KakaoKauthClient; import com.jabiseo.client.OidcPublicKey; import com.jabiseo.client.OidcPublicKeyResponse; @@ -18,19 +19,24 @@ public class KakaoIdTokenValidator extends AbstractIdTokenValidator { private final String KAKAO_ID_KEY = "sub"; private final KakaoKauthClient kakaoKauthClient; + private final RedisCacheRepository redisCacheRepository; + private final String CACHE_KEY = "KAKAO_OIDC_PUBLIC_KEY"; - public KakaoIdTokenValidator(KakaoOidcProperty kakaoOidcProperty, IdTokenJwtHandler idTokenJwtHandler, KakaoKauthClient kakaoKauthClient) { + public KakaoIdTokenValidator(KakaoOidcProperty kakaoOidcProperty, IdTokenJwtHandler idTokenJwtHandler, KakaoKauthClient kakaoKauthClient, RedisCacheRepository redisCacheRepository) { super(kakaoOidcProperty.toIdTokenProperty(), idTokenJwtHandler); this.kakaoKauthClient = kakaoKauthClient; + this.redisCacheRepository = redisCacheRepository; } @Override protected OidcPublicKey getOidcPublicKey(String kid) { - // TODO: 캐싱 적용 & 체크 필요 - - OidcPublicKeyResponse publicKeys = kakaoKauthClient.getPublicKeys().getBody(); - List keys = publicKeys.getKeys(); + List keys = redisCacheRepository.getPublicKeys(CACHE_KEY); + if (keys == null) { + OidcPublicKeyResponse publicKeys = kakaoKauthClient.getPublicKeys().getBody(); + keys = publicKeys.getKeys(); + redisCacheRepository.savePublicKey(CACHE_KEY, keys); + } return keys.stream().filter((key) -> key.getKid().equals(kid)) .findAny() diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java index 0ea2743..95f00c7 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java @@ -1,15 +1,14 @@ package com.jabiseo.auth.application.usecase; import com.jabiseo.auth.application.MemberFactory; -import com.jabiseo.auth.domain.TokenRepository; import com.jabiseo.auth.dto.LoginRequest; import com.jabiseo.auth.dto.LoginResponse; import com.jabiseo.auth.application.JwtHandler; import com.jabiseo.auth.application.oidc.OauthMemberInfo; import com.jabiseo.auth.application.oidc.TokenValidatorManager; +import com.jabiseo.cache.RedisCacheRepository; import com.jabiseo.member.domain.Member; import com.jabiseo.member.domain.MemberRepository; -import com.jabiseo.member.domain.OauthServer; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -22,7 +21,7 @@ public class LoginUseCase { private final MemberFactory memberFactory; private final JwtHandler jwtHandler; private final MemberRepository memberRepository; - private final TokenRepository tokenRepository; + private final RedisCacheRepository cacheRepository; public LoginResponse execute(LoginRequest loginRequest) { OauthMemberInfo oauthMemberInfo = tokenValidatorManager.validate(loginRequest.idToken(), loginRequest.oauthServer()); @@ -38,7 +37,7 @@ public LoginResponse execute(LoginRequest loginRequest) { String accessToken = jwtHandler.createAccessToken(member); String refreshToken = jwtHandler.createRefreshToken(); - tokenRepository.saveToken(member.getId(), refreshToken); + cacheRepository.saveToken(member.getId(), refreshToken); return new LoginResponse(accessToken, refreshToken); } diff --git a/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/Token.java b/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/Token.java deleted file mode 100644 index ef4c55a..0000000 --- a/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/Token.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.jabiseo.auth.domain; - - -public class Token { - - private String value; -} diff --git a/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/TokenRepository.java b/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/TokenRepository.java deleted file mode 100644 index 1a5cbb9..0000000 --- a/jabiseo-domain/src/main/java/com/jabiseo/auth/domain/TokenRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.jabiseo.auth.domain; - -import org.springframework.stereotype.Component; - -@Component -public class TokenRepository { - - - public void saveToken(String key, String token) { - - } -} diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/CacheRepository.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/CacheRepository.java deleted file mode 100644 index 168a021..0000000 --- a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/CacheRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.jabiseo.cache; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.ValueOperations; -import org.springframework.stereotype.Component; - -@Component -public class CacheRepository { - - - public void save(String key, String value, int ttl){ - - } - -} diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java new file mode 100644 index 0000000..095b55f --- /dev/null +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java @@ -0,0 +1,56 @@ +package com.jabiseo.cache; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jabiseo.client.OidcPublicKey; +import com.jabiseo.exception.BusinessException; +import com.jabiseo.exception.CommonErrorCode; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Component +public class RedisCacheRepository { + + private final RedisTemplate redisStringTemplate; + private final ValueOperations operation; + private final ObjectMapper mapper = new ObjectMapper(); + + public RedisCacheRepository(RedisTemplate redisStringTemplate) { + this.redisStringTemplate = redisStringTemplate; + this.operation = redisStringTemplate.opsForValue(); + } + + + public void saveToken(String key, String value) { + operation.set(key, value); + } + + + public void savePublicKey(String key, List publicKeys) { + try { + String publicKeyString = mapper.writeValueAsString(publicKeys); + operation.set(key, publicKeyString, 1, TimeUnit.DAYS); + } catch (JsonProcessingException e) { + throw new BusinessException(CommonErrorCode.INTERNAL_SERVER_ERROR); + } + } + + public List getPublicKeys(String key) { + String values = operation.get(key); + if (values == null) { + return null; + } + try { + return Arrays.asList(mapper.readValue(values, OidcPublicKey[].class)); + } catch (JsonProcessingException e) { + throw new BusinessException(CommonErrorCode.INTERNAL_SERVER_ERROR); + } + } + + +} From 9fd0e042d95023b1709dd339fcaa55b5b577a4ca Mon Sep 17 00:00:00 2001 From: inhyeok Date: Thu, 11 Jul 2024 19:33:15 +0900 Subject: [PATCH 07/23] =?UTF-8?q?Fix(yml):=20yaml=20=EA=B0=92=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jabiseo-api/src/main/resources/api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabiseo-api/src/main/resources/api.yml b/jabiseo-api/src/main/resources/api.yml index d86a011..e66821d 100644 --- a/jabiseo-api/src/main/resources/api.yml +++ b/jabiseo-api/src/main/resources/api.yml @@ -4,7 +4,7 @@ oidc: kakao: issuer: ${KAKAO_ISSUER} admin-key: ${KAKAO_ADMIN_KEY} - client-id: ${KAKAO_CLEINT_ID} + client-id: ${KAKAO_CLIENT_ID} jwt: access-expired-min: 60 From 74ef6e0a8c20af79cc07c5a481f91f91694c4466 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Thu, 11 Jul 2024 19:42:25 +0900 Subject: [PATCH 08/23] =?UTF-8?q?Fix(test):=20oauthserver=20type=20test?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/java/com/jabiseo/fixture/MemberFixture.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jabiseo-api/src/test/java/com/jabiseo/fixture/MemberFixture.java b/jabiseo-api/src/test/java/com/jabiseo/fixture/MemberFixture.java index 720987b..3025a98 100644 --- a/jabiseo-api/src/test/java/com/jabiseo/fixture/MemberFixture.java +++ b/jabiseo-api/src/test/java/com/jabiseo/fixture/MemberFixture.java @@ -1,12 +1,13 @@ package com.jabiseo.fixture; import com.jabiseo.member.domain.Member; +import com.jabiseo.member.domain.OauthServer; public class MemberFixture { public static Member createMember(String memberId) { return Member.of(memberId, "email", "name", - "oauth2Id", "profile", "profileImage"); + "oauth2Id", OauthServer.KAKAO, "profileImage"); } } From 7e403669e134e14a7ac508e2cadb8dbb844f205d Mon Sep 17 00:00:00 2001 From: inhyeok Date: Thu, 11 Jul 2024 20:28:54 +0900 Subject: [PATCH 09/23] =?UTF-8?q?Feat(SignUp):=20kakao=EA=B0=80=EC=9E=85?= =?UTF-8?q?=EC=8B=9C=20email=EB=8F=84=20=EB=B0=9B=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/oidc/KakaoIdTokenValidator.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java index 7c056b1..facf97f 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java @@ -18,6 +18,7 @@ public class KakaoIdTokenValidator extends AbstractIdTokenValidator { private final String KAKAO_ID_KEY = "sub"; + private final String KAKAO_EMAIL_KEY = "email"; private final KakaoKauthClient kakaoKauthClient; private final RedisCacheRepository redisCacheRepository; private final String CACHE_KEY = "KAKAO_OIDC_PUBLIC_KEY"; @@ -46,15 +47,15 @@ protected OidcPublicKey getOidcPublicKey(String kid) { @Override protected OauthMemberInfo extractMemberInfoFromPayload(Map payload) { String oauthId = (String) payload.get(KAKAO_ID_KEY); - - if (requireValueIsNull(oauthId)) { + String email = (String) payload.get(KAKAO_EMAIL_KEY); + if (requireValueIsNull(oauthId, email)) { throw new AuthenticationBusinessException(CommonErrorCode.INTERNAL_SERVER_ERROR); } return OauthMemberInfo.builder() .oauthId(oauthId) .oauthServer(OauthServer.KAKAO) - .email("email@email.com") + .email(email) .build(); } @@ -66,7 +67,7 @@ OauthServer getOauthServer() { /* * 해당 예외가 발생하는건 카카오에서 프로퍼티 key 값을 바꾸지 않는 이상은 발생하지 않는다. */ - private boolean requireValueIsNull(String oauthId) { - return oauthId == null; + private boolean requireValueIsNull(String oauthId, String email) { + return oauthId == null || email == null; } } From 6a292653440e16132b4fb82bddc4a9fb105669d3 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Thu, 11 Jul 2024 21:30:02 +0900 Subject: [PATCH 10/23] =?UTF-8?q?Feat(test):=20kakaoidTokenValidator=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oidc/KakaoIdTokenValidatorTest.java | 115 ++++++++++++++++++ .../auth/oidc/KakaoIdTokenValidatorTest.java | 20 --- .../com/jabiseo/client/OidcPublicKey.java | 11 ++ .../jabiseo/client/OidcPublicKeyResponse.java | 4 + 4 files changed, 130 insertions(+), 20 deletions(-) create mode 100644 jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java delete mode 100644 jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java new file mode 100644 index 0000000..427905a --- /dev/null +++ b/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java @@ -0,0 +1,115 @@ +package com.jabiseo.auth.application.oidc; + +import com.jabiseo.auth.application.oidc.property.KakaoOidcProperty; +import com.jabiseo.auth.exception.AuthenticationBusinessException; +import com.jabiseo.auth.exception.AuthenticationErrorCode; +import com.jabiseo.cache.RedisCacheRepository; +import com.jabiseo.client.KakaoKauthClient; +import com.jabiseo.client.OidcPublicKey; +import com.jabiseo.client.OidcPublicKeyResponse; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.ResponseEntity; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + + +@ExtendWith(MockitoExtension.class) +class KakaoIdTokenValidatorTest { + + @InjectMocks + KakaoIdTokenValidator validator; + + @Mock + KakaoOidcProperty kakaoOidcProperty; + + @Mock + IdTokenJwtHandler idTokenJwtHandler; + + @Mock + KakaoKauthClient kakaoKauthClient; + + @Mock + RedisCacheRepository redisCacheRepository; + + + @BeforeEach + void setUp() { + kakaoOidcProperty = new KakaoOidcProperty("client id", "adminKey", "issuer"); + } + + @Test + @DisplayName("카카오 oidc public key 조회시 캐시에 데이터가 있다면 카카오API를 호출하지 않는다. ") + void notCallApiAlreadySavedCache() { + //given + given(redisCacheRepository.getPublicKeys(any())).willReturn(List.of(mockPublicKey("kid1"), mockPublicKey("kid2"))); + + //when + validator.getOidcPublicKey("kid1"); + + //then + verify(kakaoKauthClient, times(0)).getPublicKeys(); + } + + @Test + @DisplayName("카카오 oidc public key 조회시 캐시에 데이터가 없다면 카카오API를 호출하고 저장한다") + void callApiNotSavedCache() { + //given + List publicKeys = List.of(mockPublicKey("kid1"), mockPublicKey("kid2")); + ResponseEntity entity = ResponseEntity.of(Optional.of(new OidcPublicKeyResponse(publicKeys))); + given(redisCacheRepository.getPublicKeys(any())).willReturn(null); + given(kakaoKauthClient.getPublicKeys()).willReturn(entity); + + //when + validator.getOidcPublicKey("kid1"); + + //then + verify(redisCacheRepository, times(1)).savePublicKey(any(), any()); + } + + @Test + @DisplayName("카카오 oidc public key조회시 kid에 맞는 key가 없다면 예외를 반환한다.") + void notMatchKidThrownException() { + //given + List publicKeys = List.of(mockPublicKey("kid1"), mockPublicKey("kid2")); + given(redisCacheRepository.getPublicKeys(any())).willReturn(publicKeys); + String notMatchKid = "kidkid"; + + //when + assertThatThrownBy(() -> validator.getOidcPublicKey(notMatchKid)) + .isInstanceOf(AuthenticationBusinessException.class) + .hasMessage(AuthenticationErrorCode.INVALID_ID_TOKEN.getMessage()); + } + + @Test + @DisplayName("카카오 oidc public key조회시 kid에 맞는 key가 있다면 해당 Public Key를 리턴한다.") + void SuccessMatchKidReturnOidcPublicKey() { + //given + String matchKid = "kid0"; + OidcPublicKey matchPublicKey = mockPublicKey(matchKid); + given(redisCacheRepository.getPublicKeys(any())).willReturn(List.of(mockPublicKey("kid1"),matchPublicKey)); + + //when + OidcPublicKey oidcPublicKey = validator.getOidcPublicKey(matchKid); + + //then + Assertions.assertThat(oidcPublicKey).isEqualTo(matchPublicKey); + } + + private OidcPublicKey mockPublicKey(String kid) { + return new OidcPublicKey(kid, "a", "u", "n", "e"); + } +} diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java deleted file mode 100644 index 2e6edf6..0000000 --- a/jabiseo-api/src/test/java/com/jabiseo/auth/oidc/KakaoIdTokenValidatorTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.jabiseo.auth.oidc; - -import com.jabiseo.auth.application.oidc.KakaoIdTokenValidator; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - - -@SpringBootTest -class KakaoIdTokenValidatorTest { - - @Autowired - KakaoIdTokenValidator validator; - - @Test - void valid() { - - } - -} diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKey.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKey.java index 6b7a6b6..d35e032 100644 --- a/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKey.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKey.java @@ -3,6 +3,8 @@ import lombok.Getter; import lombok.ToString; +import java.util.Objects; + @ToString @Getter public class OidcPublicKey { @@ -20,4 +22,13 @@ public OidcPublicKey(String kid, String alg, String use, String n, String e) { this.n = n; this.e = e; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OidcPublicKey that = (OidcPublicKey) o; + return Objects.equals(kid, that.kid); + } + } diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKeyResponse.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKeyResponse.java index dd63746..c1a3579 100644 --- a/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKeyResponse.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/OidcPublicKeyResponse.java @@ -12,4 +12,8 @@ public class OidcPublicKeyResponse { private List keys; + + public OidcPublicKeyResponse(List keys) { + this.keys = keys; + } } From ab2203cd866c37c6565a803820f4f54e3dc12672 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 11:06:33 +0900 Subject: [PATCH 11/23] =?UTF-8?q?Fix(*):=20LoginUseCase=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=9C=EC=9E=AD=EC=85=94=EB=84=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/jabiseo/auth/application/usecase/LoginUseCase.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java index 95f00c7..8e5f01d 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java @@ -9,11 +9,13 @@ import com.jabiseo.cache.RedisCacheRepository; import com.jabiseo.member.domain.Member; import com.jabiseo.member.domain.MemberRepository; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @Service +@Transactional @RequiredArgsConstructor public class LoginUseCase { From 1b8578c89083b34af64ec581a808364116c59c1b Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 11:09:28 +0900 Subject: [PATCH 12/23] =?UTF-8?q?Feat(*):=20validator=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/oidc/TokenValidatorManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java index c7e7ed5..a1c33bc 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/TokenValidatorManager.java @@ -13,14 +13,14 @@ @Component public class TokenValidatorManager { - private final Map validator = new HashMap<>(); + private final Map validatorMap = new HashMap<>(); public TokenValidatorManager(Set idTokenValidators) { - idTokenValidators.forEach((v) -> validator.put(v.getOauthServer(), v)); + idTokenValidators.forEach((v) -> validatorMap.put(v.getOauthServer(), v)); } public OauthMemberInfo validate(String idToken, OauthServer oauthServer) { - AbstractIdTokenValidator idTokenValidator = validator.get(oauthServer); + AbstractIdTokenValidator idTokenValidator = validatorMap.get(oauthServer); if (idTokenValidator == null) { throw new AuthenticationBusinessException(AuthenticationErrorCode.NOT_SUPPORT_OAUTH); From c88280e82eaaa1f73d78a912d82e99deec81bb32 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 11:10:45 +0900 Subject: [PATCH 13/23] =?UTF-8?q?Refactor(*):=20kakao=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=83=81=EC=88=98=20=EA=B0=92=20static=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/application/oidc/KakaoIdTokenValidator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java index facf97f..9c54aa7 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java @@ -17,11 +17,11 @@ @Component public class KakaoIdTokenValidator extends AbstractIdTokenValidator { - private final String KAKAO_ID_KEY = "sub"; - private final String KAKAO_EMAIL_KEY = "email"; + private static final String KAKAO_ID_KEY = "sub"; + private static final String KAKAO_EMAIL_KEY = "email"; private final KakaoKauthClient kakaoKauthClient; private final RedisCacheRepository redisCacheRepository; - private final String CACHE_KEY = "KAKAO_OIDC_PUBLIC_KEY"; + private static final String CACHE_KEY = "KAKAO_OIDC_PUBLIC_KEY"; public KakaoIdTokenValidator(KakaoOidcProperty kakaoOidcProperty, IdTokenJwtHandler idTokenJwtHandler, KakaoKauthClient kakaoKauthClient, RedisCacheRepository redisCacheRepository) { super(kakaoOidcProperty.toIdTokenProperty(), idTokenJwtHandler); From 5761d6cab307ee3c80eaec8586714eaf1193a41f Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 11:18:31 +0900 Subject: [PATCH 14/23] =?UTF-8?q?Fix(*):=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=20static=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/jabiseo/auth/application/usecase/LoginUseCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java index 8e5f01d..8c9ef0e 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java @@ -45,7 +45,7 @@ public LoginResponse execute(LoginRequest loginRequest) { } - private static boolean isRequireSignup(Member member) { + private boolean isRequireSignup(Member member) { return member == null; } } From 3b93569d9ca2bb0b6b542932895385c1806c418f Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 11:25:46 +0900 Subject: [PATCH 15/23] =?UTF-8?q?Refactor(*):=20login=20usecase=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 스트림 람다 기반 로직 처리로 변경 --- .../auth/application/usecase/LoginUseCase.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java index 8c9ef0e..b42a1a3 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java @@ -29,12 +29,10 @@ public LoginResponse execute(LoginRequest loginRequest) { OauthMemberInfo oauthMemberInfo = tokenValidatorManager.validate(loginRequest.idToken(), loginRequest.oauthServer()); Member member = memberRepository.findByOauthIdAndOauthServer(oauthMemberInfo.getOauthId(), oauthMemberInfo.getOauthServer()) - .orElse(null); - - if (isRequireSignup(member)) { - Member newMember = memberFactory.createNew(oauthMemberInfo); - member = memberRepository.save(newMember); - } + .orElseGet(() -> { + Member newMember = memberFactory.createNew(oauthMemberInfo); + return memberRepository.save(newMember); + }); String accessToken = jwtHandler.createAccessToken(member); @@ -44,8 +42,4 @@ public LoginResponse execute(LoginRequest loginRequest) { return new LoginResponse(accessToken, refreshToken); } - - private boolean isRequireSignup(Member member) { - return member == null; - } } From 4f50d74504ad3b64a6c2bee50adb950437d7edfa Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 11:27:32 +0900 Subject: [PATCH 16/23] =?UTF-8?q?Refactor(*):=20error=20code=20401=20name?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/jabiseo/exception/ErrorCode.java | 2 +- .../auth/exception/AuthenticationErrorCode.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jabiseo-common/src/main/java/com/jabiseo/exception/ErrorCode.java b/jabiseo-common/src/main/java/com/jabiseo/exception/ErrorCode.java index 0325735..93744cd 100644 --- a/jabiseo-common/src/main/java/com/jabiseo/exception/ErrorCode.java +++ b/jabiseo-common/src/main/java/com/jabiseo/exception/ErrorCode.java @@ -6,7 +6,7 @@ public interface ErrorCode { int BAD_REQUEST = 400; - int AUTHENTICATION_ERROR = 401; + int UNAUTHORIZED = 401; int INTERNAL_SERVER_ERROR = 500; diff --git a/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java index d14a35b..a9b85f9 100644 --- a/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java +++ b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java @@ -4,11 +4,11 @@ public enum AuthenticationErrorCode implements ErrorCode { - INVALID_ID_TOKEN("잘못된 idToken 입니다.", "AUTH_001", ErrorCode.AUTHENTICATION_ERROR), - EXPIRED_ID_TOKEN("만료된 idToken 입니다.", "AUTH_002", ErrorCode.AUTHENTICATION_ERROR), - NOT_SUPPORT_OAUTH("지원하지 않는 oauth 인증 수단입니다", "AUTH_003", ErrorCode.AUTHENTICATION_ERROR), - EXPIRED_APP_JWT("만료된 jwt 토큰 입니다", "AUTH_004", ErrorCode.AUTHENTICATION_ERROR), - INVALID_APP_JWT("잘못된 jwt 토큰입니다", "AUTH_005", ErrorCode.AUTHENTICATION_ERROR); + INVALID_ID_TOKEN("잘못된 idToken 입니다.", "AUTH_001", ErrorCode.UNAUTHORIZED), + EXPIRED_ID_TOKEN("만료된 idToken 입니다.", "AUTH_002", ErrorCode.UNAUTHORIZED), + NOT_SUPPORT_OAUTH("지원하지 않는 oauth 인증 수단입니다", "AUTH_003", ErrorCode.UNAUTHORIZED), + EXPIRED_APP_JWT("만료된 jwt 토큰 입니다", "AUTH_004", ErrorCode.UNAUTHORIZED), + INVALID_APP_JWT("잘못된 jwt 토큰입니다", "AUTH_005", ErrorCode.UNAUTHORIZED); private final String message; private final String errorCode; From fcf3ca1af7d7fe5542ba904208e6150bdad87955 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 16:03:06 +0900 Subject: [PATCH 17/23] =?UTF-8?q?Feat(kakao):=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20jwk=20=ED=98=B8=EC=B6=9C=EC=8B=9C=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oidc/KakaoIdTokenValidator.java | 17 ++++++++-- .../oidc/KakaoIdTokenValidatorTest.java | 15 +++++++++ .../exception/AuthenticationErrorCode.java | 3 +- .../com/jabiseo/client/KakaoKauthClient.java | 3 ++ .../jabiseo/client/NetworkApiErrorCode.java | 32 +++++++++++++++++++ .../jabiseo/client/NetworkApiException.java | 11 +++++++ .../com/jabiseo/client/RestClientConfig.java | 6 ++++ 7 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 jabiseo-infrastructure/src/main/java/com/jabiseo/client/NetworkApiErrorCode.java create mode 100644 jabiseo-infrastructure/src/main/java/com/jabiseo/client/NetworkApiException.java diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java index 9c54aa7..93d3c12 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidator.java @@ -2,6 +2,7 @@ import com.jabiseo.cache.RedisCacheRepository; import com.jabiseo.client.KakaoKauthClient; +import com.jabiseo.client.NetworkApiException; import com.jabiseo.client.OidcPublicKey; import com.jabiseo.client.OidcPublicKeyResponse; import com.jabiseo.auth.exception.AuthenticationBusinessException; @@ -9,11 +10,14 @@ import com.jabiseo.auth.application.oidc.property.KakaoOidcProperty; import com.jabiseo.exception.CommonErrorCode; import com.jabiseo.member.domain.OauthServer; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import java.util.List; import java.util.Map; +@Slf4j @Component public class KakaoIdTokenValidator extends AbstractIdTokenValidator { @@ -34,8 +38,7 @@ protected OidcPublicKey getOidcPublicKey(String kid) { List keys = redisCacheRepository.getPublicKeys(CACHE_KEY); if (keys == null) { - OidcPublicKeyResponse publicKeys = kakaoKauthClient.getPublicKeys().getBody(); - keys = publicKeys.getKeys(); + keys = getOidcPublicKeyByKakaoClient(); redisCacheRepository.savePublicKey(CACHE_KEY, keys); } @@ -44,6 +47,16 @@ protected OidcPublicKey getOidcPublicKey(String kid) { .orElseThrow(() -> new AuthenticationBusinessException(AuthenticationErrorCode.INVALID_ID_TOKEN)); } + private List getOidcPublicKeyByKakaoClient() { + try { + ResponseEntity publicKeys = kakaoKauthClient.getPublicKeys(); + return publicKeys.getBody().getKeys(); + } catch (NetworkApiException e) { + log.error(e.getMessage()); + throw new AuthenticationBusinessException(AuthenticationErrorCode.GET_JWK_FAIL); + } + } + @Override protected OauthMemberInfo extractMemberInfoFromPayload(Map payload) { String oauthId = (String) payload.get(KAKAO_ID_KEY); diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java index 427905a..75a72e0 100644 --- a/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java +++ b/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java @@ -5,6 +5,7 @@ import com.jabiseo.auth.exception.AuthenticationErrorCode; import com.jabiseo.cache.RedisCacheRepository; import com.jabiseo.client.KakaoKauthClient; +import com.jabiseo.client.NetworkApiException; import com.jabiseo.client.OidcPublicKey; import com.jabiseo.client.OidcPublicKeyResponse; import org.assertj.core.api.Assertions; @@ -109,6 +110,20 @@ void SuccessMatchKidReturnOidcPublicKey() { Assertions.assertThat(oidcPublicKey).isEqualTo(matchPublicKey); } + @Test + @DisplayName("카카오 jwk 획득 api 호출 실패시 에러를 반환한다") + void getJwkKakaoApiCallingFailThrownExcetpion(){ + //given + given(redisCacheRepository.getPublicKeys(any())).willReturn(null); + given(kakaoKauthClient.getPublicKeys()).willThrow(NetworkApiException.class); + + //when then + assertThatThrownBy(() -> validator.getOidcPublicKey("key")) + .isInstanceOf(AuthenticationBusinessException.class) + .hasMessage(AuthenticationErrorCode.GET_JWK_FAIL.getMessage()); + + } + private OidcPublicKey mockPublicKey(String kid) { return new OidcPublicKey(kid, "a", "u", "n", "e"); } diff --git a/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java index a9b85f9..e95bc01 100644 --- a/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java +++ b/jabiseo-domain/src/main/java/com/jabiseo/auth/exception/AuthenticationErrorCode.java @@ -8,7 +8,8 @@ public enum AuthenticationErrorCode implements ErrorCode { EXPIRED_ID_TOKEN("만료된 idToken 입니다.", "AUTH_002", ErrorCode.UNAUTHORIZED), NOT_SUPPORT_OAUTH("지원하지 않는 oauth 인증 수단입니다", "AUTH_003", ErrorCode.UNAUTHORIZED), EXPIRED_APP_JWT("만료된 jwt 토큰 입니다", "AUTH_004", ErrorCode.UNAUTHORIZED), - INVALID_APP_JWT("잘못된 jwt 토큰입니다", "AUTH_005", ErrorCode.UNAUTHORIZED); + INVALID_APP_JWT("잘못된 jwt 토큰입니다", "AUTH_005", ErrorCode.UNAUTHORIZED), + GET_JWK_FAIL("jwk 획득 실패", "AUTH_006", ErrorCode.INTERNAL_SERVER_ERROR); private final String message; private final String errorCode; diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/client/KakaoKauthClient.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/KakaoKauthClient.java index 59078e8..5617bff 100644 --- a/jabiseo-infrastructure/src/main/java/com/jabiseo/client/KakaoKauthClient.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/KakaoKauthClient.java @@ -10,6 +10,9 @@ @HttpExchange public interface KakaoKauthClient { + /* + * ref: https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#oidc-find-public-key + * */ @GetExchange(url = "/.well-known/jwks.json") ResponseEntity getPublicKeys(); diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/client/NetworkApiErrorCode.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/NetworkApiErrorCode.java new file mode 100644 index 0000000..a6cf63a --- /dev/null +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/NetworkApiErrorCode.java @@ -0,0 +1,32 @@ +package com.jabiseo.client; + +import com.jabiseo.exception.ErrorCode; + +public enum NetworkApiErrorCode implements ErrorCode { + KAKAO_JWK_API_FAIL("카카오 kauth jwk 연결 실패", "NETWORK_001", ErrorCode.INTERNAL_SERVER_ERROR); + + private final String message; + private final String errorCode; + private final int statusCode; + + NetworkApiErrorCode(String message, String errorCode, int statusCode) { + this.message = message; + this.errorCode = errorCode; + this.statusCode = statusCode; + } + + @Override + public String getMessage() { + return ""; + } + + @Override + public String getErrorCode() { + return ""; + } + + @Override + public int getStatusCode() { + return 0; + } +} diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/client/NetworkApiException.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/NetworkApiException.java new file mode 100644 index 0000000..8e69a6a --- /dev/null +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/NetworkApiException.java @@ -0,0 +1,11 @@ +package com.jabiseo.client; + +import com.jabiseo.exception.BusinessException; +import com.jabiseo.exception.ErrorCode; + +public class NetworkApiException extends BusinessException { + + public NetworkApiException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/client/RestClientConfig.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/RestClientConfig.java index 057e640..aeb54af 100644 --- a/jabiseo-infrastructure/src/main/java/com/jabiseo/client/RestClientConfig.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/client/RestClientConfig.java @@ -1,8 +1,11 @@ package com.jabiseo.client; +import com.jabiseo.exception.CommonErrorCode; +import com.jabiseo.exception.ErrorCode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatusCode; import org.springframework.web.client.RestClient; import org.springframework.web.client.support.RestClientAdapter; import org.springframework.web.service.invoker.HttpServiceProxyFactory; @@ -15,6 +18,9 @@ public class RestClientConfig { public KakaoKauthClient kakaoKauthClient() { RestClient client = RestClient.builder() .baseUrl("https://kauth.kakao.com") + .defaultStatusHandler(HttpStatusCode::isError, ((request, response) -> { + throw new NetworkApiException(NetworkApiErrorCode.KAKAO_JWK_API_FAIL); + })) .build(); return HttpServiceProxyFactory From ea9b1dd19ffb303d967be4edacd58f54d2fe3558 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 16:05:20 +0900 Subject: [PATCH 18/23] =?UTF-8?q?Docs(*):=20=EB=AF=B8=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20Todo=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/jabiseo/cache/RedisCacheRepository.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java index 095b55f..8fc0132 100644 --- a/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java +++ b/jabiseo-infrastructure/src/main/java/com/jabiseo/cache/RedisCacheRepository.java @@ -34,6 +34,8 @@ public void saveToken(String key, String value) { public void savePublicKey(String key, List publicKeys) { try { String publicKeyString = mapper.writeValueAsString(publicKeys); + + // TODO: timeout 값 논의 필요 operation.set(key, publicKeyString, 1, TimeUnit.DAYS); } catch (JsonProcessingException e) { throw new BusinessException(CommonErrorCode.INTERNAL_SERVER_ERROR); From a52d88d6a932c84575f868882690b243ea87a038 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 18:16:13 +0900 Subject: [PATCH 19/23] =?UTF-8?q?Feat(*):=20Enum=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/usecase/LoginUseCase.java | 3 +- .../auth/controller/AuthController.java | 3 +- .../com/jabiseo/auth/dto/LoginRequest.java | 7 +- .../java/com/jabiseo/common/EnumValid.java | 29 ++++++++ .../jabiseo/common/ValueOfEnumValidator.java | 35 +++++++++ .../oidc/KakaoIdTokenValidatorTest.java | 1 + .../jabiseo/auth/dto/LoginRequestTest.java | 73 +++++++++++++++++++ 7 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 jabiseo-api/src/main/java/com/jabiseo/common/EnumValid.java create mode 100644 jabiseo-api/src/main/java/com/jabiseo/common/ValueOfEnumValidator.java create mode 100644 jabiseo-api/src/test/java/com/jabiseo/auth/dto/LoginRequestTest.java diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java index b42a1a3..3af5900 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/application/usecase/LoginUseCase.java @@ -9,6 +9,7 @@ import com.jabiseo.cache.RedisCacheRepository; import com.jabiseo.member.domain.Member; import com.jabiseo.member.domain.MemberRepository; +import com.jabiseo.member.domain.OauthServer; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -26,7 +27,7 @@ public class LoginUseCase { private final RedisCacheRepository cacheRepository; public LoginResponse execute(LoginRequest loginRequest) { - OauthMemberInfo oauthMemberInfo = tokenValidatorManager.validate(loginRequest.idToken(), loginRequest.oauthServer()); + OauthMemberInfo oauthMemberInfo = tokenValidatorManager.validate(loginRequest.idToken(), OauthServer.valueOf(loginRequest.oauthServer())); Member member = memberRepository.findByOauthIdAndOauthServer(oauthMemberInfo.getOauthId(), oauthMemberInfo.getOauthServer()) .orElseGet(() -> { diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java index bf168a0..26f15ca 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/controller/AuthController.java @@ -6,6 +6,7 @@ import com.jabiseo.auth.application.usecase.LogoutUseCase; import com.jabiseo.auth.application.usecase.ReissueUseCase; import com.jabiseo.auth.application.usecase.WithdrawUseCase; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -27,7 +28,7 @@ public class AuthController { private final WithdrawUseCase withdrawUseCase; @PostMapping("/login") - public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + public ResponseEntity login(@Valid @RequestBody LoginRequest loginRequest) { LoginResponse result = loginUseCase.execute(loginRequest); return ResponseEntity.ok(result); } diff --git a/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java b/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java index 729f8f4..86d1d46 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java +++ b/jabiseo-api/src/main/java/com/jabiseo/auth/dto/LoginRequest.java @@ -1,7 +1,12 @@ package com.jabiseo.auth.dto; +import com.jabiseo.common.EnumValid; import com.jabiseo.member.domain.OauthServer; import jakarta.validation.constraints.NotNull; -public record LoginRequest(@NotNull String idToken, @NotNull OauthServer oauthServer) { +public record LoginRequest( + @NotNull + String idToken, + @EnumValid(enumClass = OauthServer.class, message = "oauthServer Type이 잘못됐습니다.") + String oauthServer) { } diff --git a/jabiseo-api/src/main/java/com/jabiseo/common/EnumValid.java b/jabiseo-api/src/main/java/com/jabiseo/common/EnumValid.java new file mode 100644 index 0000000..f634ae2 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/common/EnumValid.java @@ -0,0 +1,29 @@ +package com.jabiseo.common; + + + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint(validatedBy = ValueOfEnumValidator.class) +public @interface EnumValid { + Class> enumClass(); + + String message() default ""; + + Class[] groups() default {}; + + Class[] payload() default {}; + + boolean ignoreCase() default false; +} diff --git a/jabiseo-api/src/main/java/com/jabiseo/common/ValueOfEnumValidator.java b/jabiseo-api/src/main/java/com/jabiseo/common/ValueOfEnumValidator.java new file mode 100644 index 0000000..2349c12 --- /dev/null +++ b/jabiseo-api/src/main/java/com/jabiseo/common/ValueOfEnumValidator.java @@ -0,0 +1,35 @@ +package com.jabiseo.common; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class ValueOfEnumValidator implements ConstraintValidator { + + private EnumValid enumValid; + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + boolean result = false; + if(value == null){ + return false; + } + + Enum[] enumValues = this.enumValid.enumClass().getEnumConstants(); + if (enumValues != null) { + for (Object enumValue : enumValues) { + if (value.equals(enumValue.toString()) + || this.enumValid.ignoreCase() && value.equalsIgnoreCase(enumValue.toString())) { + result = true; + break; + } + } + } + return result; + } + + @Override + public void initialize(EnumValid constraintAnnotation) { + ConstraintValidator.super.initialize(constraintAnnotation); + this.enumValid = constraintAnnotation; + } +} diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java index 75a72e0..af6b374 100644 --- a/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java +++ b/jabiseo-api/src/test/java/com/jabiseo/auth/application/oidc/KakaoIdTokenValidatorTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; +@DisplayName("카카오IdToken검증 테스트") @ExtendWith(MockitoExtension.class) class KakaoIdTokenValidatorTest { diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/dto/LoginRequestTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/dto/LoginRequestTest.java new file mode 100644 index 0000000..b6b960d --- /dev/null +++ b/jabiseo-api/src/test/java/com/jabiseo/auth/dto/LoginRequestTest.java @@ -0,0 +1,73 @@ +package com.jabiseo.auth.dto; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("loginRequest 입력값 검증 테스트") +class LoginRequestTest { + + private ValidatorFactory validatorFactory; + private Validator validator; + + @BeforeEach + void init() { + validatorFactory = Validation.buildDefaultValidatorFactory(); + validator = validatorFactory.getValidator(); + } + + + @Test + @DisplayName("login 요청시 null값이 오면 예외를 반환한다.") + void nullRequestThrownException() { + //given + String idToken = null; + String oauthServer = null; + LoginRequest loginRequest = new LoginRequest(idToken, oauthServer); + + //when + Set> violations = validator.validate(loginRequest); + + //then + assertThat(violations).isNotEmpty(); + } + + @DisplayName("login 요청시 idToken에 숫자가 오면 예외를 반환한다,") + @ParameterizedTest + @ValueSource(strings = {"kakao", "kakakkao", "", "value", "ggogo", "KAKAOOO"}) + void oauthServerNotAllowInputsThrowException(String oauthServer) { + String idToken = "IdTokens.."; + LoginRequest loginRequest = new LoginRequest(idToken, oauthServer); + + //when + Set> violations = validator.validate(loginRequest); + + //then + assertThat(violations).isNotEmpty(); + } + + @DisplayName("정상적인 입력 요청시 성공한다") + @ParameterizedTest + @ValueSource(strings = {"KAKAO", "GOOGLE"}) + void LoginRequestSuccess(String oauthServer) { + //given + String idToken = "IdTokens.."; + LoginRequest loginRequest = new LoginRequest(idToken, oauthServer); + //when + + Set> violations = validator.validate(loginRequest); + //then + assertThat(violations).isEmpty(); + } + +} \ No newline at end of file From f1933d8494024b0e7ec217bf5a7a9a7447283196 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 18:47:42 +0900 Subject: [PATCH 20/23] =?UTF-8?q?Test(login):=20login=20usecase=20?= =?UTF-8?q?=EC=9C=A0=EB=8B=9B=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/usecase/LoginUseCaseTest.java | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 jabiseo-api/src/test/java/com/jabiseo/auth/application/usecase/LoginUseCaseTest.java diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/application/usecase/LoginUseCaseTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/application/usecase/LoginUseCaseTest.java new file mode 100644 index 0000000..9d3562a --- /dev/null +++ b/jabiseo-api/src/test/java/com/jabiseo/auth/application/usecase/LoginUseCaseTest.java @@ -0,0 +1,95 @@ +package com.jabiseo.auth.application.usecase; + +import com.jabiseo.auth.application.JwtHandler; +import com.jabiseo.auth.application.MemberFactory; +import com.jabiseo.auth.application.oidc.OauthMemberInfo; +import com.jabiseo.auth.application.oidc.TokenValidatorManager; +import com.jabiseo.auth.dto.LoginRequest; +import com.jabiseo.auth.dto.LoginResponse; +import com.jabiseo.cache.RedisCacheRepository; +import com.jabiseo.fixture.MemberFixture; +import com.jabiseo.member.domain.Member; +import com.jabiseo.member.domain.MemberRepository; +import com.jabiseo.member.domain.OauthServer; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@DisplayName("login usecase 테스트") +@ExtendWith(MockitoExtension.class) +class LoginUseCaseTest { + + @InjectMocks + LoginUseCase loginUseCase; + + @Mock + TokenValidatorManager tokenValidatorManager; + + @Mock + MemberFactory memberFactory; + + @Mock + JwtHandler jwtHandler; + + @Mock + MemberRepository memberRepository; + + @Mock + RedisCacheRepository redisCacheRepository; + + + @Test + @DisplayName("처음 요청 오는 OAuth 회원일시 맴버 객체를 생성하고 저장한다.") + void firstOauthUserIsSignUpAndSave() { + //given + LoginRequest request = new LoginRequest("idToken", "KAKAO"); + OauthMemberInfo memberInfo = new OauthMemberInfo("id", OauthServer.KAKAO, "email@emil.com"); + Member member = MemberFixture.createMember("memberId"); + given(memberRepository.findByOauthIdAndOauthServer(memberInfo.getOauthId(), memberInfo.getOauthServer())).willReturn(Optional.empty()); + given(tokenValidatorManager.validate(request.idToken(), OauthServer.valueOf(request.oauthServer()))).willReturn(memberInfo); + given(memberFactory.createNew(memberInfo)).willReturn(member); + given(memberRepository.save(any())).willReturn(member); + + //when + loginUseCase.execute(request); + + //then + verify(memberFactory, times(1)).createNew(memberInfo); + verify(memberRepository, times(1)).save(member); + } + + @Test + @DisplayName("로그인 이후 Jwt를 발급 및 저장한다.") + void loginSuccessCreateJwtAndSave() throws Exception { + //given + LoginRequest request = new LoginRequest("idToken", "KAKAO"); + OauthMemberInfo memberInfo = new OauthMemberInfo("id", OauthServer.KAKAO, "email@emil.com"); + Member member = MemberFixture.createMember("memberId"); + String access = "access"; + String refresh = "refresh"; + given(memberRepository.findByOauthIdAndOauthServer(memberInfo.getOauthId(), memberInfo.getOauthServer())).willReturn(Optional.of(member)); + given(tokenValidatorManager.validate(request.idToken(), OauthServer.valueOf(request.oauthServer()))).willReturn(memberInfo); + given(jwtHandler.createAccessToken(member)).willReturn(access); + given(jwtHandler.createRefreshToken()).willReturn(refresh); + + //when + LoginResponse result = loginUseCase.execute(request); + + //then + assertThat(result.accessToken()).isEqualTo(access); + assertThat(result.refreshToken()).isEqualTo(refresh); + verify(redisCacheRepository, times(1)).saveToken(member.getId(), refresh); + } +} \ No newline at end of file From d9a379bb1c44201303d189432286eac476246e5e Mon Sep 17 00:00:00 2001 From: inhyeok Date: Fri, 12 Jul 2024 18:53:52 +0900 Subject: [PATCH 21/23] =?UTF-8?q?Test(*):=20Member=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20=EC=83=9D=EC=84=B1=EA=B8=B0=20test=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/RandomNicknameGeneratorTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 jabiseo-domain/src/test/java/com/jabiseo/member/domain/RandomNicknameGeneratorTest.java diff --git a/jabiseo-domain/src/test/java/com/jabiseo/member/domain/RandomNicknameGeneratorTest.java b/jabiseo-domain/src/test/java/com/jabiseo/member/domain/RandomNicknameGeneratorTest.java new file mode 100644 index 0000000..94940fb --- /dev/null +++ b/jabiseo-domain/src/test/java/com/jabiseo/member/domain/RandomNicknameGeneratorTest.java @@ -0,0 +1,30 @@ +package com.jabiseo.member.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +class RandomNicknameGeneratorTest { + + @InjectMocks + RandomNicknameGenerator generator; + + + @Test + @DisplayName("랜덤 닉네임 생성 시 문자열 뒤 숫자 4자리의 난수가 생성된다.") + void randomNicknameSuccess(){ + //given + //when + + String generate = generator.generate(); + String isSuffix = generate.substring(generate.length()-4); + //then + assertThat(isSuffix).matches("\\d{4}"); + } +} \ No newline at end of file From bbbac3cf2642515a1cc02d52d646dde621d35cb5 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Mon, 15 Jul 2024 11:28:36 +0900 Subject: [PATCH 22/23] =?UTF-8?q?Fix(*):=20display=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/java/com/jabiseo/auth/dto/LoginRequestTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabiseo-api/src/test/java/com/jabiseo/auth/dto/LoginRequestTest.java b/jabiseo-api/src/test/java/com/jabiseo/auth/dto/LoginRequestTest.java index b6b960d..9bf5c52 100644 --- a/jabiseo-api/src/test/java/com/jabiseo/auth/dto/LoginRequestTest.java +++ b/jabiseo-api/src/test/java/com/jabiseo/auth/dto/LoginRequestTest.java @@ -42,7 +42,7 @@ void nullRequestThrownException() { assertThat(violations).isNotEmpty(); } - @DisplayName("login 요청시 idToken에 숫자가 오면 예외를 반환한다,") + @DisplayName("login 요청시 oauthServer에 정확한 값이 오지 않으면 예외를 반환한다,") @ParameterizedTest @ValueSource(strings = {"kakao", "kakakkao", "", "value", "ggogo", "KAKAOOO"}) void oauthServerNotAllowInputsThrowException(String oauthServer) { From 6917cd7908992c869da73f374db2bc76104c95d6 Mon Sep 17 00:00:00 2001 From: inhyeok Date: Mon, 15 Jul 2024 11:45:02 +0900 Subject: [PATCH 23/23] =?UTF-8?q?Refactor(*):=20enum=20validtor=20?= =?UTF-8?q?=EA=B0=80=EB=8F=85=EC=84=B1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jabiseo/common/ValueOfEnumValidator.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/jabiseo-api/src/main/java/com/jabiseo/common/ValueOfEnumValidator.java b/jabiseo-api/src/main/java/com/jabiseo/common/ValueOfEnumValidator.java index 2349c12..5182c9b 100644 --- a/jabiseo-api/src/main/java/com/jabiseo/common/ValueOfEnumValidator.java +++ b/jabiseo-api/src/main/java/com/jabiseo/common/ValueOfEnumValidator.java @@ -9,22 +9,20 @@ public class ValueOfEnumValidator implements ConstraintValidator[] enumValues = this.enumValid.enumClass().getEnumConstants(); - if (enumValues != null) { - for (Object enumValue : enumValues) { - if (value.equals(enumValue.toString()) - || this.enumValid.ignoreCase() && value.equalsIgnoreCase(enumValue.toString())) { - result = true; - break; - } + if (enumValues == null) { + return false; + } + for (Object enumValue : enumValues) { + if (value.equals(enumValue.toString()) + || this.enumValid.ignoreCase() && value.equalsIgnoreCase(enumValue.toString())) { + return true; } } - return result; + return false; } @Override