From 81c0a333f0bca807407a08819f0b074278085be0 Mon Sep 17 00:00:00 2001 From: KWY Date: Wed, 29 May 2024 20:42:10 +0900 Subject: [PATCH 01/42] =?UTF-8?q?#T-10855=20[feat]=20oauth=20config=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 애플 로그인, 구글 로그인 시 필요한 oauth 관련 상수들을 정의했습니다 --- .../sopt/makers/operation/config/ValueConfig.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java b/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java index cdeaa8a7..21e5274e 100644 --- a/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java +++ b/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java @@ -36,6 +36,20 @@ public class ValueConfig { private String playGroundURI; @Value("${sopt.makers.playground.token}") private String playGroundToken; + @Value("${oauth.apple.key.id}") + private String appleKeyId; + @Value("${oauth.apple.team.id}") + private String appleTeamId; + @Value("${oauth.apple.aud}") + private String appleAud; + @Value("${oauth.apple.sub}") + private String appleSub; + @Value("${oauth.google.client.id}") + private String googleClientId; + @Value("${oauth.google.client.secret}") + private String googleClientSecret; + @Value("${oauth.google.redirect.url}") + private String googleRedirectUrl; private final int SUB_LECTURE_MAX_ROUND = 2; private final int MAX_LECTURE_COUNT = 2; From db6fa351ebf22b2e0998920813c701e515820202 Mon Sep 17 00:00:00 2001 From: KWY Date: Wed, 29 May 2024 20:45:46 +0900 Subject: [PATCH 02/42] =?UTF-8?q?#T-10855=20[feat]=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jsonwebtoken 라이브러리는 애플 로그인시 필요한 client secret key를 생성할 때 사용합니다 nimbusds 라이브러리는 SocialLoginManager 내의 getUserInfo 시 id token 을 검증할 때 사용합니다 bouncycastle 라이브러리는 애플 로그인 시 private key를 생성할 때 사용합니다 --- operation-external/build.gradle | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/operation-external/build.gradle b/operation-external/build.gradle index 4c29cb51..2f9c2dc4 100644 --- a/operation-external/build.gradle +++ b/operation-external/build.gradle @@ -11,6 +11,16 @@ dependencies { implementation project(path: ':operation-domain') implementation 'org.springframework.boot:spring-boot-starter-web' + + implementation 'org.bouncycastle:bcprov-jdk18on:1.75' + implementation 'org.bouncycastle:bcpkix-jdk18on:1.75' + + // jwt builder library + implementation 'io.jsonwebtoken:jjwt-api:0.11.2' + runtimeOnly "io.jsonwebtoken:jjwt-impl:0.11.2" + runtimeOnly "io.jsonwebtoken:jjwt-jackson:0.11.2" + // jwt payload read library + implementation "com.nimbusds:nimbus-jose-jwt:7.8.1" } test { From 69006813e8b8ba932b81fc1657af3a42fb4b339b Mon Sep 17 00:00:00 2001 From: KWY Date: Wed, 29 May 2024 20:46:51 +0900 Subject: [PATCH 03/42] =?UTF-8?q?#T-10855=20[feat]=20=EA=B5=AC=EA=B8=80=20?= =?UTF-8?q?=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 클라이언트로 받은 google code를 통해 google open id token을 반환 받습니다 --- .../client/social/GoogleSocialLogin.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 operation-external/src/main/java/org/sopt/makers/operation/client/social/GoogleSocialLogin.java diff --git a/operation-external/src/main/java/org/sopt/makers/operation/client/social/GoogleSocialLogin.java b/operation-external/src/main/java/org/sopt/makers/operation/client/social/GoogleSocialLogin.java new file mode 100644 index 00000000..068afbfd --- /dev/null +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/social/GoogleSocialLogin.java @@ -0,0 +1,43 @@ +package org.sopt.makers.operation.client.social; + +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.sopt.makers.operation.client.social.dto.IdTokenResponse; +import org.sopt.makers.operation.config.ValueConfig; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.util.Collections; + +@Component +@RequiredArgsConstructor +public class GoogleSocialLogin { + private final RestTemplate restTemplate; + private final ValueConfig valueConfig; + + public IdTokenResponse getIdTokenByCode(String code) { + val params = new LinkedMultiValueMap<>(); + val grantType = "authorization_code"; + val clientId = valueConfig.getGoogleClientId(); + val clientSecret = valueConfig.getGoogleClientSecret(); + val redirectUri = valueConfig.getGoogleRedirectUrl(); + + params.add("client_id", clientId); + params.add("client_secret", clientSecret); + params.add("code", code); + params.add("grant_type", grantType); + params.add("redirect_uri", redirectUri); + + val headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + val entity = new HttpEntity<>(params, headers); + + val host = "https://oauth2.googleapis.com/token"; + return restTemplate.postForObject(host, entity, IdTokenResponse.class); + } +} From 37019ad714178cb0333120f88b7a3dfe875fab7d Mon Sep 17 00:00:00 2001 From: KWY Date: Wed, 29 May 2024 20:48:20 +0900 Subject: [PATCH 04/42] =?UTF-8?q?#T-10855=20[feat]=20=EC=95=A0=ED=94=8C=20?= =?UTF-8?q?=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 클라이언트로 받은 apple code를 통해 client secret key를 만든 이후 해당 값을 사용하여 apple open id token을 반환 받습니다 --- .../client/social/AppleSocialLogin.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 operation-external/src/main/java/org/sopt/makers/operation/client/social/AppleSocialLogin.java diff --git a/operation-external/src/main/java/org/sopt/makers/operation/client/social/AppleSocialLogin.java b/operation-external/src/main/java/org/sopt/makers/operation/client/social/AppleSocialLogin.java new file mode 100644 index 00000000..bf425bbc --- /dev/null +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/social/AppleSocialLogin.java @@ -0,0 +1,93 @@ +package org.sopt.makers.operation.client.social; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.sopt.makers.operation.client.social.dto.IdTokenResponse; +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.exception.AuthException; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.util.Collections; +import java.util.Date; +import java.util.Optional; + +import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.FAILURE_READ_PRIVATE_KEY; + +@Component +@RequiredArgsConstructor +public class AppleSocialLogin { + private final RestTemplate restTemplate; + private final ValueConfig valueConfig; + + public IdTokenResponse getIdTokenByCode(String code) { + val tokenRequest = new LinkedMultiValueMap<>(); + val grantType = "authorization_code"; + val clientId = valueConfig.getAppleSub(); + val clientSecret = createClientSecret(); + + tokenRequest.add("client_id", clientId); + tokenRequest.add("client_secret", clientSecret); + tokenRequest.add("code", code); + tokenRequest.add("grant_type", grantType); + + val headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + val entity = new HttpEntity<>(tokenRequest, headers); + + val host = "https://appleid.apple.com/auth/token"; + return restTemplate.postForObject(host, entity, IdTokenResponse.class); + } + + private String createClientSecret() { + val now = new Date(); + val privateKey = getPrivateKey() + .orElseThrow(() -> new AuthException(FAILURE_READ_PRIVATE_KEY)); + val kid = valueConfig.getAppleKeyId(); + val issuer = valueConfig.getAppleTeamId(); + val aud = valueConfig.getAppleAud(); + val sub = valueConfig.getAppleSub(); + + return Jwts.builder() + .setHeaderParam("kid", kid) + .setHeaderParam("alg", "ES256") + .setIssuedAt(now) + .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) + .setIssuer(issuer) + .setAudience(aud) + .setSubject(sub) + .signWith(privateKey, SignatureAlgorithm.ES256) + .compact(); + } + + private Optional getPrivateKey() { + val appleKeyPath = "authConfig.getAppleKeyPath()"; + try { + val resource = new ClassPathResource(appleKeyPath); + val privateKey = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + val pemReader = new StringReader(privateKey); + val pemParser = new PEMParser(pemReader); + val converter = new JcaPEMKeyConverter(); + val object = (PrivateKeyInfo) pemParser.readObject(); + return Optional.of(converter.getPrivateKey(object)); + } catch (IOException e) { + return Optional.empty(); + } + } + +} From fed5aba2d7ab746b01587db7db1c1353bfd869d4 Mon Sep 17 00:00:00 2001 From: KWY Date: Wed, 29 May 2024 20:51:38 +0900 Subject: [PATCH 05/42] =?UTF-8?q?T-10855=20[feat]=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A7=A4=EB=8B=88=EC=A0=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 애플 로그인과 구글 로그인을 도와주는 역할을 합니다 getIdTokenByCode 함수는 social type을 매개변수로 받아서 해당 type의 소셜 로그인을 실행한 뒤 id token을 반환합니다 getUserInfo 함수는 id token을 매개변수로 받아서 payload를 확인하여 sub에 담긴 값을 반환합니다 --- .../client/social/SocialLoginManager.java | 39 +++++++++++++++++++ .../client/social/dto/IdTokenResponse.java | 9 +++++ 2 files changed, 48 insertions(+) create mode 100644 operation-external/src/main/java/org/sopt/makers/operation/client/social/SocialLoginManager.java create mode 100644 operation-external/src/main/java/org/sopt/makers/operation/client/social/dto/IdTokenResponse.java diff --git a/operation-external/src/main/java/org/sopt/makers/operation/client/social/SocialLoginManager.java b/operation-external/src/main/java/org/sopt/makers/operation/client/social/SocialLoginManager.java new file mode 100644 index 00000000..cdd296fd --- /dev/null +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/social/SocialLoginManager.java @@ -0,0 +1,39 @@ +package org.sopt.makers.operation.client.social; + +import com.nimbusds.jwt.SignedJWT; +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.sopt.makers.operation.client.social.dto.IdTokenResponse; +import org.sopt.makers.operation.exception.AuthException; +import org.sopt.makers.operation.user.domain.SocialType; +import org.springframework.stereotype.Component; + +import java.text.ParseException; + +import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.INVALID_ID_TOKEN; + +@Component +@RequiredArgsConstructor +public class SocialLoginManager { + private final AppleSocialLogin appleSocialLogin; + private final GoogleSocialLogin googleSocialLogin; + + public IdTokenResponse getIdTokenByCode(SocialType type, String code) { + return switch (type) { + case APPLE -> appleSocialLogin.getIdTokenByCode(code); + case GOOGLE -> googleSocialLogin.getIdTokenByCode(code); + }; + } + + public String getUserInfo(IdTokenResponse tokenResponse) { + val idToken = tokenResponse.idToken(); + try { + val signedJWT = SignedJWT.parse(idToken); + val payload = signedJWT.getJWTClaimsSet(); + val userId = payload.getSubject(); + return userId; + } catch (ParseException e) { + throw new AuthException(INVALID_ID_TOKEN); + } + } +} diff --git a/operation-external/src/main/java/org/sopt/makers/operation/client/social/dto/IdTokenResponse.java b/operation-external/src/main/java/org/sopt/makers/operation/client/social/dto/IdTokenResponse.java new file mode 100644 index 00000000..2d92aa1c --- /dev/null +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/social/dto/IdTokenResponse.java @@ -0,0 +1,9 @@ +package org.sopt.makers.operation.client.social.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record IdTokenResponse( + @JsonProperty("id_token") + String idToken +) { +} From 2a17ed4074b594823c85125648991ac83cc51495 Mon Sep 17 00:00:00 2001 From: KWY Date: Wed, 29 May 2024 22:53:39 +0900 Subject: [PATCH 06/42] =?UTF-8?q?#T-10855=20[feat]=20=ED=8C=80=20=EB=B3=84?= =?UTF-8?q?=20oauth=20=EC=A0=95=EB=B3=B4=EA=B0=80=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=EB=90=98=EB=8A=94=20entity=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 해당 값이 변경되었을 때 서버를 재배포 하는 일이 발생하지 않도록 각 팀 별로 client id와 redirect uri를 config가 아닌 테이블로 저장한다 --- .../makers/operation/auth/domain/Team.java | 5 ++++ .../operation/auth/domain/TeamOAuthInfo.java | 24 +++++++++++++++++++ .../repository/TeamOAuthInfoRepository.java | 8 +++++++ 3 files changed, 37 insertions(+) create mode 100644 operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/Team.java create mode 100644 operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/TeamOAuthInfo.java create mode 100644 operation-domain/src/main/java/org/sopt/makers/operation/auth/repository/TeamOAuthInfoRepository.java diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/Team.java b/operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/Team.java new file mode 100644 index 00000000..bd95112c --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/Team.java @@ -0,0 +1,5 @@ +package org.sopt.makers.operation.auth.domain; + +public enum Team { + PLAYGROUND, CREW, APP +} diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/TeamOAuthInfo.java b/operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/TeamOAuthInfo.java new file mode 100644 index 00000000..003c5a3b --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/TeamOAuthInfo.java @@ -0,0 +1,24 @@ +package org.sopt.makers.operation.auth.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import static jakarta.persistence.GenerationType.IDENTITY; + +@Entity +public class TeamOAuthInfo { + @Id + @GeneratedValue(strategy = IDENTITY) + private Long id; + @Column(name = "client_id", nullable = false) + private String clientId; + @Column(name = "redirect_uri", nullable = false) + private String redirectUri; + @Column(name = "team", nullable = false) + @Enumerated(EnumType.STRING) + private Team team; +} diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/auth/repository/TeamOAuthInfoRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/auth/repository/TeamOAuthInfoRepository.java new file mode 100644 index 00000000..586a652a --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/auth/repository/TeamOAuthInfoRepository.java @@ -0,0 +1,8 @@ +package org.sopt.makers.operation.auth.repository; + +import org.sopt.makers.operation.auth.domain.TeamOAuthInfo; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TeamOAuthInfoRepository extends JpaRepository { + boolean existsByClientIdAndRedirectUri(String clientId, String redirectUri); +} From 8b183a28c839f24c6d67c33392a2deb5eeaa6262 Mon Sep 17 00:00:00 2001 From: KWY Date: Wed, 29 May 2024 23:51:26 +0900 Subject: [PATCH 07/42] =?UTF-8?q?#T-10855=20[feat]=20=ED=94=8C=EB=9E=AB?= =?UTF-8?q?=ED=8F=BC=20=EC=9D=B8=EA=B0=80=EC=BD=94=EB=93=9C=20=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=EC=9D=84=20=EC=9C=84=ED=95=9C=20secert=20key=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/sopt/makers/operation/config/ValueConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java b/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java index 21e5274e..cf0fdd91 100644 --- a/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java +++ b/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java @@ -50,6 +50,8 @@ public class ValueConfig { private String googleClientSecret; @Value("${oauth.google.redirect.url}") private String googleRedirectUrl; + @Value("${spring.jwt.secretKey.platform_code}") + private String platformCodeSecretKey; private final int SUB_LECTURE_MAX_ROUND = 2; private final int MAX_LECTURE_COUNT = 2; From 69d84e8774c65457e98da2b6d122202f8aa4ebaa Mon Sep 17 00:00:00 2001 From: KWY Date: Wed, 29 May 2024 23:53:02 +0900 Subject: [PATCH 08/42] =?UTF-8?q?#T-10855=20[feat]=20=EC=9D=B8=EA=B0=80=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B0=9C=EA=B8=89=20=EC=8B=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=90=98=EB=8A=94=20=EC=83=81=EC=88=98=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 인가 코드 발급시 사용되는 성공 메시지, 실패 메시지, 에러 model 구현 및 정의 --- .../code/failure/auth/AuthFailureCode.java | 29 +++++++++++++++++++ .../code/success/auth/AuthSuccessCode.java | 16 ++++++++++ .../operation/exception/AuthException.java | 14 +++++++++ 3 files changed, 59 insertions(+) create mode 100644 operation-common/src/main/java/org/sopt/makers/operation/code/failure/auth/AuthFailureCode.java create mode 100644 operation-common/src/main/java/org/sopt/makers/operation/code/success/auth/AuthSuccessCode.java create mode 100644 operation-common/src/main/java/org/sopt/makers/operation/exception/AuthException.java diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/auth/AuthFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/auth/AuthFailureCode.java new file mode 100644 index 00000000..30864858 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/auth/AuthFailureCode.java @@ -0,0 +1,29 @@ +package org.sopt.makers.operation.code.failure.auth; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.failure.FailureCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; + +@Getter +@RequiredArgsConstructor +public enum AuthFailureCode implements FailureCode { + // 400 + NOT_NULL_PARAMS(BAD_REQUEST, "존재하지 않는 출석 세션입니다."), + INVALID_SOCIAL_TYPE(BAD_REQUEST, "유효하지 않는 type 입니다."), + INVALID_ID_TOKEN(BAD_REQUEST, "잘못된 토큰이 전달되었습니다."), + INVALID_SOCIAL_CODE(BAD_REQUEST, "코드가 유효하지 않습니다."), + FAILURE_READ_PRIVATE_KEY(BAD_REQUEST, "Private key 읽기 실패"), + // 401 + UNREGISTERED_TEAM(UNAUTHORIZED, "등록되지 않은 팀입니다."), + // 404 + NOT_FOUND_USER_SOCIAL_IDENTITY_INFO(NOT_FOUND, "등록된 소셜 정보가 없습니다."), + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/auth/AuthSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/auth/AuthSuccessCode.java new file mode 100644 index 00000000..d86b1081 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/auth/AuthSuccessCode.java @@ -0,0 +1,16 @@ +package org.sopt.makers.operation.code.success.auth; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.success.SuccessCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.OK; + +@Getter +@RequiredArgsConstructor +public enum AuthSuccessCode implements SuccessCode { + SUCCESS_GET_AUTHORIZATION_CODE(OK, "플랫폼 인가코드 발급 성공"); + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/exception/AuthException.java b/operation-common/src/main/java/org/sopt/makers/operation/exception/AuthException.java new file mode 100644 index 00000000..a391e5ac --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/exception/AuthException.java @@ -0,0 +1,14 @@ +package org.sopt.makers.operation.exception; + +import lombok.Getter; +import org.sopt.makers.operation.code.failure.FailureCode; + +@Getter +public class AuthException extends RuntimeException{ + private final FailureCode failureCode; + + public AuthException(FailureCode failureCode) { + super("[AuthException] : " + failureCode.getMessage()); + this.failureCode = failureCode; + } +} From f6f722cb4170aa5b53ac0e426cf20982adf097a5 Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 00:01:32 +0900 Subject: [PATCH 09/42] =?UTF-8?q?#T-10855=20[feat]=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../identityinfo/UserIdentityInfoRepository.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 operation-domain/src/main/java/org/sopt/makers/operation/user/repository/identityinfo/UserIdentityInfoRepository.java diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/user/repository/identityinfo/UserIdentityInfoRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/user/repository/identityinfo/UserIdentityInfoRepository.java new file mode 100644 index 00000000..1f513fe7 --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/user/repository/identityinfo/UserIdentityInfoRepository.java @@ -0,0 +1,11 @@ +package org.sopt.makers.operation.user.repository.identityinfo; + +import org.sopt.makers.operation.user.domain.SocialType; +import org.sopt.makers.operation.user.domain.UserIdentityInfo; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserIdentityInfoRepository extends JpaRepository { + Optional findBySocialTypeAndSocialId(SocialType socialType, String socialId); +} From a12ef89eafc7a08bf837b8570a2696673b695cf1 Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 00:03:25 +0900 Subject: [PATCH 10/42] =?UTF-8?q?#T-10855=20[feat]=20social=20type?= =?UTF-8?q?=EC=97=90=20=EC=A1=B4=EC=9E=AC=20=EC=97=AC=EB=B6=80=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit APPLE 또는 GOOGLE 외의 값이 들어올 때 false를 반환한다 --- .../sopt/makers/operation/user/domain/SocialType.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/user/domain/SocialType.java b/operation-domain/src/main/java/org/sopt/makers/operation/user/domain/SocialType.java index b3274ddf..765d7fc3 100644 --- a/operation-domain/src/main/java/org/sopt/makers/operation/user/domain/SocialType.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/user/domain/SocialType.java @@ -1,6 +1,15 @@ package org.sopt.makers.operation.user.domain; +import java.util.Arrays; + public enum SocialType { GOOGLE, - APPLE, + APPLE; + + + public static boolean isContains(String type) { + SocialType[] socialTypes = SocialType.values(); + return Arrays.stream(socialTypes) + .anyMatch(socialType -> socialType.name().equals(type)); + } } From 87f5f626371e7ed158ff315a9e55b08648d52872 Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 00:06:54 +0900 Subject: [PATCH 11/42] =?UTF-8?q?#T-10855=20[feat]=20api=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EB=82=B4=20jwt=20builder=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 플랫폼 인가 코드 발급 시 사용하기 위해 의존성 추가 --- operation-api/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/operation-api/build.gradle b/operation-api/build.gradle index 2d20d26b..f01b8b8c 100644 --- a/operation-api/build.gradle +++ b/operation-api/build.gradle @@ -21,4 +21,8 @@ dependencies { // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + // jwt builder library + implementation 'io.jsonwebtoken:jjwt-api:0.11.2' + runtimeOnly "io.jsonwebtoken:jjwt-impl:0.11.2" + runtimeOnly "io.jsonwebtoken:jjwt-jackson:0.11.2" } \ No newline at end of file From bb61d077d4cd14492e18f6914b5076d66ebcbfed Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 00:07:41 +0900 Subject: [PATCH 12/42] =?UTF-8?q?#T-10855=20[feat]=20/authorize=20response?= =?UTF-8?q?=20body=20dto=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/dto/response/AuthorizationCodeResponse.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 operation-api/src/main/java/org/sopt/makers/operation/auth/dto/response/AuthorizationCodeResponse.java diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/dto/response/AuthorizationCodeResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/dto/response/AuthorizationCodeResponse.java new file mode 100644 index 00000000..595c45fb --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/dto/response/AuthorizationCodeResponse.java @@ -0,0 +1,4 @@ +package org.sopt.makers.operation.auth.dto.response; + +public record AuthorizationCodeResponse(String platformCode) { +} From 4fc2a2b74843ee83ef39ef1077a299b78268ecab Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 00:20:36 +0900 Subject: [PATCH 13/42] =?UTF-8?q?#T-10855=20[feat]=20/authorize=20swagger?= =?UTF-8?q?=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 해당 api 실패시 전달되는 메시지 내용 수정 --- .../makers/operation/auth/api/AuthApi.java | 47 +++++++++++++++++++ .../code/failure/auth/AuthFailureCode.java | 12 ++--- 2 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java new file mode 100644 index 00000000..313aec69 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java @@ -0,0 +1,47 @@ +package org.sopt.makers.operation.auth.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import org.sopt.makers.operation.dto.BaseResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestParam; + +public interface AuthApi { + @Operation( + security = @SecurityRequirement(name = "Authorization"), + summary = "플랫폼 인가코드 반환 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "플랫폼 인가코드 반환 성공" + ), + @ApiResponse( + responseCode = "400", + description = """ + 쿼리 파라미터 중 데이터가 들어오지 않았습니다. + 유효하지 않은 social type 입니다. + 유효하지 않은 id token 입니다. + 유효하지 않은 social code 입니다. + """ + ), + @ApiResponse( + responseCode = "404", + description = """ + 등록되지 않은 팀입니다. + 등록된 소셜 정보가 없습니다. + """ + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> authorize( + @RequestParam String type, + @RequestParam String code, + @RequestParam String clientId, + @RequestParam String redirectUri + ); +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/auth/AuthFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/auth/AuthFailureCode.java index 30864858..b519a879 100644 --- a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/auth/AuthFailureCode.java +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/auth/AuthFailureCode.java @@ -7,20 +7,18 @@ import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.NOT_FOUND; -import static org.springframework.http.HttpStatus.UNAUTHORIZED; @Getter @RequiredArgsConstructor public enum AuthFailureCode implements FailureCode { // 400 - NOT_NULL_PARAMS(BAD_REQUEST, "존재하지 않는 출석 세션입니다."), - INVALID_SOCIAL_TYPE(BAD_REQUEST, "유효하지 않는 type 입니다."), - INVALID_ID_TOKEN(BAD_REQUEST, "잘못된 토큰이 전달되었습니다."), - INVALID_SOCIAL_CODE(BAD_REQUEST, "코드가 유효하지 않습니다."), + NOT_NULL_PARAMS(BAD_REQUEST, "쿼리 파라미터 중 데이터가 들어오지 않았습니다."), + INVALID_SOCIAL_TYPE(BAD_REQUEST, "유효하지 않은 social type 입니다."), + INVALID_ID_TOKEN(BAD_REQUEST, "유효하지 않은 id token 입니다."), + INVALID_SOCIAL_CODE(BAD_REQUEST, "유효하지 않은 social code 입니다."), FAILURE_READ_PRIVATE_KEY(BAD_REQUEST, "Private key 읽기 실패"), - // 401 - UNREGISTERED_TEAM(UNAUTHORIZED, "등록되지 않은 팀입니다."), // 404 + NOT_FOUNT_REGISTERED_TEAM(NOT_FOUND, "등록되지 않은 팀입니다."), NOT_FOUND_USER_SOCIAL_IDENTITY_INFO(NOT_FOUND, "등록된 소셜 정보가 없습니다."), ; From c8264627d1f381d459d7413d114e64e024e1c0f0 Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 00:23:09 +0900 Subject: [PATCH 14/42] =?UTF-8?q?#T-10855=20[feat]=20/authorize=20controll?= =?UTF-8?q?er=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. query parameter로 들어오는 값이 null 인지 확인한다. 2. 입력한 client id와 redirect uri가 존재하는지 확인한다. 3. 입력한 type 값이 유효한지 확인한다. 4. social type 과 code를 이용하여 social login 이후 user id를 불러온다. 5. user id 기반으로 platform 인가코드를 생성하고 반환한다. --- .../operation/auth/api/AuthApiController.java | 66 +++++++++++++++++++ .../operation/auth/service/AuthService.java | 13 ++++ 2 files changed, 79 insertions(+) create mode 100644 operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java create mode 100644 operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthService.java diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java new file mode 100644 index 00000000..4af29c9f --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java @@ -0,0 +1,66 @@ +package org.sopt.makers.operation.auth.api; + +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.sopt.makers.operation.auth.dto.response.AuthorizationCodeResponse; +import org.sopt.makers.operation.auth.service.AuthService; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.exception.AuthException; +import org.sopt.makers.operation.user.domain.SocialType; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.ConcurrentHashMap; + +import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.INVALID_SOCIAL_TYPE; +import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.NOT_NULL_PARAMS; +import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.NOT_FOUNT_REGISTERED_TEAM; +import static org.sopt.makers.operation.code.success.auth.AuthSuccessCode.SUCCESS_GET_AUTHORIZATION_CODE; + +@RestController +@RequestMapping("/api/v1") +@RequiredArgsConstructor +public class AuthApiController implements AuthApi { + private final ConcurrentHashMap tempPlatformCode = new ConcurrentHashMap<>(); + private final AuthService authService; + + @Override + @GetMapping("/authorize") + public ResponseEntity> authorize( + @RequestParam String type, + @RequestParam String code, + @RequestParam String clientId, + @RequestParam String redirectUri + ) { + if (checkParamsIsNull(type, code, clientId, redirectUri)) throw new AuthException(NOT_NULL_PARAMS); + if (authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)) throw new AuthException(NOT_FOUNT_REGISTERED_TEAM); + if (!SocialType.isContains(type)) throw new AuthException(INVALID_SOCIAL_TYPE); + + val userId = findUserIdBySocialTypeAndCode(type, code); + val platformCode = generatePlatformCode(userId); + return ApiResponseUtil.success(SUCCESS_GET_AUTHORIZATION_CODE, new AuthorizationCodeResponse(platformCode)); + } + + private boolean checkParamsIsNull(String... params) { + for (String param : params) { + if (param == null) return true; + } + return false; + } + + private Long findUserIdBySocialTypeAndCode(String type, String code) { + val socialType = SocialType.valueOf(type); + val userSocialId = authService.getSocialUserInfo(socialType, code); + return authService.getUserId(socialType, userSocialId); + } + + private String generatePlatformCode(Long userId) { + val platformCode = authService.generatePlatformCode(userId); + tempPlatformCode.putIfAbsent(platformCode, platformCode); + return platformCode; + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthService.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthService.java new file mode 100644 index 00000000..3464ef0e --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthService.java @@ -0,0 +1,13 @@ +package org.sopt.makers.operation.auth.service; + +import org.sopt.makers.operation.user.domain.SocialType; + +public interface AuthService { + boolean checkRegisteredTeamOAuthInfo(String clientId, String redirectUri); + + String getSocialUserInfo(SocialType type, String code); + + Long getUserId(SocialType socialType, String userSocialId); + + String generatePlatformCode(Long userId); +} From 1fae6f0528a30e21e30eb787d126e97a8692e278 Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 00:26:35 +0900 Subject: [PATCH 15/42] =?UTF-8?q?#T-10855=20[feat]=20auth=20service=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=B2=B4=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit checkRegisteredTeamOAuthInfo() -> 입력한 client id와 redirect uri가 동시에 존재하는지 여부를 반환한다. getSocialUserInfo() -> social manager를 통해 social login 진행 후, user social id를 반환한다. getUserId() -> social id 및 social type에 해당하는 user id를 조회 후, 반환한다. String generatePlatformCode(Long userId) -> 만료 시간이 5분인 platorm code를 반환한다. --- .../auth/service/AuthServiceImpl.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java new file mode 100644 index 00000000..8d9f1495 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java @@ -0,0 +1,67 @@ +package org.sopt.makers.operation.auth.service; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.xml.bind.DatatypeConverter; +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.sopt.makers.operation.auth.repository.TeamOAuthInfoRepository; +import org.sopt.makers.operation.client.social.SocialLoginManager; +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.exception.AuthException; +import org.sopt.makers.operation.user.domain.SocialType; +import org.sopt.makers.operation.user.repository.identityinfo.UserIdentityInfoRepository; +import org.springframework.stereotype.Service; + +import javax.crypto.spec.SecretKeySpec; +import java.time.ZoneId; +import java.util.Date; + +import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.INVALID_SOCIAL_CODE; +import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.NOT_FOUND_USER_SOCIAL_IDENTITY_INFO; + +@Service +@RequiredArgsConstructor +public class AuthServiceImpl implements AuthService { + private static final ZoneId KST = ZoneId.of("Asia/Seoul"); + private final SocialLoginManager socialLoginManager; + private final TeamOAuthInfoRepository teamOAuthInfoRepository; + private final UserIdentityInfoRepository userIdentityInfoRepository; + + private final ValueConfig valueConfig; + + @Override + public boolean checkRegisteredTeamOAuthInfo(String clientId, String redirectUri) { + return teamOAuthInfoRepository.existsByClientIdAndRedirectUri(clientId, redirectUri); + } + + @Override + public String getSocialUserInfo(SocialType type, String code) { + val idToken = socialLoginManager.getIdTokenByCode(type, code); + if (idToken == null) throw new AuthException(INVALID_SOCIAL_CODE); + return socialLoginManager.getUserInfo(idToken); + } + + @Override + public Long getUserId(SocialType socialType, String userSocialId) { + val userIdentityInfo = userIdentityInfoRepository.findBySocialTypeAndSocialId(socialType, userSocialId) + .orElseThrow(() -> new AuthException(NOT_FOUND_USER_SOCIAL_IDENTITY_INFO)); + return userIdentityInfo.getUserId(); + } + + @Override + public String generatePlatformCode(Long userId) { + val platformCodeSecretKey = valueConfig.getPlatformCodeSecretKey(); + + val signatureAlgorithm = SignatureAlgorithm.HS256; + val secretKeyBytes = DatatypeConverter.parseBase64Binary(platformCodeSecretKey); + val signingKey = new SecretKeySpec(secretKeyBytes, signatureAlgorithm.getJcaName()); + val exp = new Date().toInstant().atZone(KST) + .toLocalDateTime().plusMinutes(5).atZone(KST).toInstant(); + return Jwts.builder() + .setSubject(Long.toString(userId)) + .setExpiration(Date.from(exp)) + .signWith(signingKey, signatureAlgorithm) + .compact(); + } +} From ebf4996984d9432d2138ceb43385e610017e734d Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 00:41:57 +0900 Subject: [PATCH 16/42] =?UTF-8?q?#T-10855=20[chore]=20swagger=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=20=EB=82=B4=EC=9A=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 400 시 에러 내용 줄바꿈 적용 --- .../java/org/sopt/makers/operation/auth/api/AuthApi.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java index 313aec69..bfd7e797 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java @@ -19,10 +19,10 @@ public interface AuthApi { @ApiResponse( responseCode = "400", description = """ - 쿼리 파라미터 중 데이터가 들어오지 않았습니다. - 유효하지 않은 social type 입니다. - 유효하지 않은 id token 입니다. - 유효하지 않은 social code 입니다. + 1. 쿼리 파라미터 중 데이터가 들어오지 않았습니다.\n + 2. 유효하지 않은 social type 입니다.\n + 3. 유효하지 않은 id token 입니다.\n + 4. 유효하지 않은 social code 입니다. """ ), @ApiResponse( From 98b4cd6d7c1ed19fb402336fd5935872683af637 Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 21:35:17 +0900 Subject: [PATCH 17/42] =?UTF-8?q?#T-10855=20[feat]=20/authorize=20swagger?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 404 에러 문구 넘버링 & 개행 처리 적용 --- .../main/java/org/sopt/makers/operation/auth/api/AuthApi.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java index bfd7e797..4a40708f 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java @@ -28,8 +28,8 @@ public interface AuthApi { @ApiResponse( responseCode = "404", description = """ - 등록되지 않은 팀입니다. - 등록된 소셜 정보가 없습니다. + 1. 등록되지 않은 팀입니다.\n + 2. 등록된 소셜 정보가 없습니다. """ ), @ApiResponse( From 249110906507ceb6b84591388f107701e5d480cb Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 21:37:13 +0900 Subject: [PATCH 18/42] =?UTF-8?q?#T-10855=20[fix]=20end=20point=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 request mapping을 제거하고 get mapping으로 버저닝 적용 --- .../makers/operation/auth/api/AuthApiController.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java index 4af29c9f..05e05be2 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java @@ -10,26 +10,24 @@ import org.sopt.makers.operation.util.ApiResponseUtil; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.ConcurrentHashMap; import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.INVALID_SOCIAL_TYPE; -import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.NOT_NULL_PARAMS; import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.NOT_FOUNT_REGISTERED_TEAM; +import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.NOT_NULL_PARAMS; import static org.sopt.makers.operation.code.success.auth.AuthSuccessCode.SUCCESS_GET_AUTHORIZATION_CODE; @RestController -@RequestMapping("/api/v1") @RequiredArgsConstructor public class AuthApiController implements AuthApi { private final ConcurrentHashMap tempPlatformCode = new ConcurrentHashMap<>(); private final AuthService authService; @Override - @GetMapping("/authorize") + @GetMapping("/api/v1/authorize") public ResponseEntity> authorize( @RequestParam String type, @RequestParam String code, @@ -37,7 +35,8 @@ public ResponseEntity> authorize( @RequestParam String redirectUri ) { if (checkParamsIsNull(type, code, clientId, redirectUri)) throw new AuthException(NOT_NULL_PARAMS); - if (authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)) throw new AuthException(NOT_FOUNT_REGISTERED_TEAM); + if (authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)) + throw new AuthException(NOT_FOUNT_REGISTERED_TEAM); if (!SocialType.isContains(type)) throw new AuthException(INVALID_SOCIAL_TYPE); val userId = findUserIdBySocialTypeAndCode(type, code); From 7b2f524fd491a956aa6397bcb75f803782c4540a Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 21:39:54 +0900 Subject: [PATCH 19/42] =?UTF-8?q?#T-10855=20[feat]=20=EC=83=81=EC=88=98?= =?UTF-8?q?=EC=99=80=20final=20=EC=82=AC=EC=9D=B4=20=EA=B0=9C=ED=96=89?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/sopt/makers/operation/auth/service/AuthServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java index 8d9f1495..f62ce1d3 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java @@ -24,10 +24,10 @@ @RequiredArgsConstructor public class AuthServiceImpl implements AuthService { private static final ZoneId KST = ZoneId.of("Asia/Seoul"); + private final SocialLoginManager socialLoginManager; private final TeamOAuthInfoRepository teamOAuthInfoRepository; private final UserIdentityInfoRepository userIdentityInfoRepository; - private final ValueConfig valueConfig; @Override From 9f5a36e25d2c69cddf1d5182958fb7b272a4c443 Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 21:52:23 +0900 Subject: [PATCH 20/42] =?UTF-8?q?#T-10855=20[chore]=20=EC=83=81=EC=88=98?= =?UTF-8?q?=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/makers/operation/config/ValueConfig.java | 2 ++ .../operation/client/social/AppleSocialLogin.java | 15 +++++++++------ .../client/social/GoogleSocialLogin.java | 9 +++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java b/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java index cf0fdd91..ab98dcb7 100644 --- a/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java +++ b/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java @@ -38,6 +38,8 @@ public class ValueConfig { private String playGroundToken; @Value("${oauth.apple.key.id}") private String appleKeyId; + @Value("${oauth.apple.key.path}") + private String appleKeyPath; @Value("${oauth.apple.team.id}") private String appleTeamId; @Value("${oauth.apple.aud}") diff --git a/operation-external/src/main/java/org/sopt/makers/operation/client/social/AppleSocialLogin.java b/operation-external/src/main/java/org/sopt/makers/operation/client/social/AppleSocialLogin.java index bf425bbc..1d2b5a5a 100644 --- a/operation-external/src/main/java/org/sopt/makers/operation/client/social/AppleSocialLogin.java +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/social/AppleSocialLogin.java @@ -31,27 +31,30 @@ @Component @RequiredArgsConstructor public class AppleSocialLogin { + // 1시간 + private static final int EXPIRATION_TIME_IN_MILLISECONDS = 3600 * 1000; + private static final String GRANT_TYPE = "authorization_code"; + private static final String HOST = "https://appleid.apple.com/auth/token"; + private final RestTemplate restTemplate; private final ValueConfig valueConfig; public IdTokenResponse getIdTokenByCode(String code) { val tokenRequest = new LinkedMultiValueMap<>(); - val grantType = "authorization_code"; val clientId = valueConfig.getAppleSub(); val clientSecret = createClientSecret(); tokenRequest.add("client_id", clientId); tokenRequest.add("client_secret", clientSecret); tokenRequest.add("code", code); - tokenRequest.add("grant_type", grantType); + tokenRequest.add("grant_type", GRANT_TYPE); val headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); val entity = new HttpEntity<>(tokenRequest, headers); - val host = "https://appleid.apple.com/auth/token"; - return restTemplate.postForObject(host, entity, IdTokenResponse.class); + return restTemplate.postForObject(HOST, entity, IdTokenResponse.class); } private String createClientSecret() { @@ -67,7 +70,7 @@ private String createClientSecret() { .setHeaderParam("kid", kid) .setHeaderParam("alg", "ES256") .setIssuedAt(now) - .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) + .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME_IN_MILLISECONDS)) .setIssuer(issuer) .setAudience(aud) .setSubject(sub) @@ -76,7 +79,7 @@ private String createClientSecret() { } private Optional getPrivateKey() { - val appleKeyPath = "authConfig.getAppleKeyPath()"; + val appleKeyPath = valueConfig.getAppleKeyPath(); try { val resource = new ClassPathResource(appleKeyPath); val privateKey = new String(resource.getInputStream().readAllBytes(), StandardCharsets.UTF_8); diff --git a/operation-external/src/main/java/org/sopt/makers/operation/client/social/GoogleSocialLogin.java b/operation-external/src/main/java/org/sopt/makers/operation/client/social/GoogleSocialLogin.java index 068afbfd..c6860255 100644 --- a/operation-external/src/main/java/org/sopt/makers/operation/client/social/GoogleSocialLogin.java +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/social/GoogleSocialLogin.java @@ -16,12 +16,14 @@ @Component @RequiredArgsConstructor public class GoogleSocialLogin { + private static final String GRANT_TYPE = "authorization_code"; + private static final String HOST = "https://oauth2.googleapis.com/token"; + private final RestTemplate restTemplate; private final ValueConfig valueConfig; public IdTokenResponse getIdTokenByCode(String code) { val params = new LinkedMultiValueMap<>(); - val grantType = "authorization_code"; val clientId = valueConfig.getGoogleClientId(); val clientSecret = valueConfig.getGoogleClientSecret(); val redirectUri = valueConfig.getGoogleRedirectUrl(); @@ -29,7 +31,7 @@ public IdTokenResponse getIdTokenByCode(String code) { params.add("client_id", clientId); params.add("client_secret", clientSecret); params.add("code", code); - params.add("grant_type", grantType); + params.add("grant_type", GRANT_TYPE); params.add("redirect_uri", redirectUri); val headers = new HttpHeaders(); @@ -37,7 +39,6 @@ public IdTokenResponse getIdTokenByCode(String code) { headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); val entity = new HttpEntity<>(params, headers); - val host = "https://oauth2.googleapis.com/token"; - return restTemplate.postForObject(host, entity, IdTokenResponse.class); + return restTemplate.postForObject(HOST, entity, IdTokenResponse.class); } } From 8405fc1c4b19c0c887d52826ba4326086c13f616 Mon Sep 17 00:00:00 2001 From: KWY Date: Thu, 30 May 2024 22:35:35 +0900 Subject: [PATCH 21/42] =?UTF-8?q?#T-10855=20[feat]=20payload=20=EB=82=B4?= =?UTF-8?q?=20issuer=20aud=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 플랫폼 인가 코드를 검증할 때, client id와 redirect uri가 일치하는지 검증하기 위해 issuer 내에는 client id를 aud 내에는 redirect uri를 기입했다 --- .../sopt/makers/operation/auth/api/AuthApiController.java | 6 +++--- .../org/sopt/makers/operation/auth/service/AuthService.java | 2 +- .../sopt/makers/operation/auth/service/AuthServiceImpl.java | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java index 05e05be2..e7f78b5f 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java @@ -40,7 +40,7 @@ public ResponseEntity> authorize( if (!SocialType.isContains(type)) throw new AuthException(INVALID_SOCIAL_TYPE); val userId = findUserIdBySocialTypeAndCode(type, code); - val platformCode = generatePlatformCode(userId); + val platformCode = generatePlatformCode(clientId, redirectUri, userId); return ApiResponseUtil.success(SUCCESS_GET_AUTHORIZATION_CODE, new AuthorizationCodeResponse(platformCode)); } @@ -57,8 +57,8 @@ private Long findUserIdBySocialTypeAndCode(String type, String code) { return authService.getUserId(socialType, userSocialId); } - private String generatePlatformCode(Long userId) { - val platformCode = authService.generatePlatformCode(userId); + private String generatePlatformCode(String clientId, String redirectUri, Long userId) { + val platformCode = authService.generatePlatformCode(clientId, redirectUri, userId); tempPlatformCode.putIfAbsent(platformCode, platformCode); return platformCode; } diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthService.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthService.java index 3464ef0e..aca12053 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthService.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthService.java @@ -9,5 +9,5 @@ public interface AuthService { Long getUserId(SocialType socialType, String userSocialId); - String generatePlatformCode(Long userId); + String generatePlatformCode(String clientId, String redirectUri, Long userId); } diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java index f62ce1d3..f7dff82c 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java @@ -50,7 +50,7 @@ public Long getUserId(SocialType socialType, String userSocialId) { } @Override - public String generatePlatformCode(Long userId) { + public String generatePlatformCode(String clientId, String redirectUri, Long userId) { val platformCodeSecretKey = valueConfig.getPlatformCodeSecretKey(); val signatureAlgorithm = SignatureAlgorithm.HS256; @@ -59,6 +59,8 @@ public String generatePlatformCode(Long userId) { val exp = new Date().toInstant().atZone(KST) .toLocalDateTime().plusMinutes(5).atZone(KST).toInstant(); return Jwts.builder() + .setIssuer(clientId) + .setAudience(redirectUri) .setSubject(Long.toString(userId)) .setExpiration(Date.from(exp)) .signWith(signingKey, signatureAlgorithm) From 6bf98a08cec53c0c98e025d4ba3e64f5db5d2c60 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 09:33:19 +0900 Subject: [PATCH 22/42] =?UTF-8?q?#T-10855=20[chore]=20=EC=A7=80=EC=97=AD?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/sopt/makers/operation/user/domain/SocialType.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/user/domain/SocialType.java b/operation-domain/src/main/java/org/sopt/makers/operation/user/domain/SocialType.java index 765d7fc3..bfca14f9 100644 --- a/operation-domain/src/main/java/org/sopt/makers/operation/user/domain/SocialType.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/user/domain/SocialType.java @@ -8,8 +8,7 @@ public enum SocialType { public static boolean isContains(String type) { - SocialType[] socialTypes = SocialType.values(); - return Arrays.stream(socialTypes) + return Arrays.stream(SocialType.values()) .anyMatch(socialType -> socialType.name().equals(type)); } } From fa4f8897c1d2a290fd1d74b0e0634f41e2d13d76 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 09:40:19 +0900 Subject: [PATCH 23/42] =?UTF-8?q?#T-10855=20[chore]=20=EA=B0=9C=ED=96=89?= =?UTF-8?q?=20=EB=AC=B8=EC=9E=90=20=EB=B0=8F=20if=20=EB=AC=B8=20=EC=BD=94?= =?UTF-8?q?=EB=94=A9=20=EC=BB=A8=EB=B0=B4=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/makers/operation/auth/api/AuthApi.java | 1 + .../operation/auth/api/AuthApiController.java | 16 ++++++++++++---- .../operation/auth/service/AuthServiceImpl.java | 5 ++++- .../operation/auth/domain/TeamOAuthInfo.java | 4 ++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java index 4a40708f..ad898260 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApi.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestParam; public interface AuthApi { + @Operation( security = @SecurityRequirement(name = "Authorization"), summary = "플랫폼 인가코드 반환 API", diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java index e7f78b5f..ba7a7f87 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java @@ -23,6 +23,7 @@ @RestController @RequiredArgsConstructor public class AuthApiController implements AuthApi { + private final ConcurrentHashMap tempPlatformCode = new ConcurrentHashMap<>(); private final AuthService authService; @@ -34,10 +35,15 @@ public ResponseEntity> authorize( @RequestParam String clientId, @RequestParam String redirectUri ) { - if (checkParamsIsNull(type, code, clientId, redirectUri)) throw new AuthException(NOT_NULL_PARAMS); - if (authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)) + if (checkParamsIsNull(type, code, clientId, redirectUri)) { + throw new AuthException(NOT_NULL_PARAMS); + } + if (authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)) { throw new AuthException(NOT_FOUNT_REGISTERED_TEAM); - if (!SocialType.isContains(type)) throw new AuthException(INVALID_SOCIAL_TYPE); + } + if (!SocialType.isContains(type)) { + throw new AuthException(INVALID_SOCIAL_TYPE); + } val userId = findUserIdBySocialTypeAndCode(type, code); val platformCode = generatePlatformCode(clientId, redirectUri, userId); @@ -46,7 +52,9 @@ public ResponseEntity> authorize( private boolean checkParamsIsNull(String... params) { for (String param : params) { - if (param == null) return true; + if (param == null) { + return true; + } } return false; } diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java index f7dff82c..486e38aa 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/service/AuthServiceImpl.java @@ -23,6 +23,7 @@ @Service @RequiredArgsConstructor public class AuthServiceImpl implements AuthService { + private static final ZoneId KST = ZoneId.of("Asia/Seoul"); private final SocialLoginManager socialLoginManager; @@ -38,7 +39,9 @@ public boolean checkRegisteredTeamOAuthInfo(String clientId, String redirectUri) @Override public String getSocialUserInfo(SocialType type, String code) { val idToken = socialLoginManager.getIdTokenByCode(type, code); - if (idToken == null) throw new AuthException(INVALID_SOCIAL_CODE); + if (idToken == null) { + throw new AuthException(INVALID_SOCIAL_CODE); + } return socialLoginManager.getUserInfo(idToken); } diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/TeamOAuthInfo.java b/operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/TeamOAuthInfo.java index 003c5a3b..fd8de518 100644 --- a/operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/TeamOAuthInfo.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/auth/domain/TeamOAuthInfo.java @@ -11,13 +11,17 @@ @Entity public class TeamOAuthInfo { + @Id @GeneratedValue(strategy = IDENTITY) private Long id; + @Column(name = "client_id", nullable = false) private String clientId; + @Column(name = "redirect_uri", nullable = false) private String redirectUri; + @Column(name = "team", nullable = false) @Enumerated(EnumType.STRING) private Team team; From 7a4239188c387ffdeb6fccf694f9eca35e669620 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 11:32:44 +0900 Subject: [PATCH 24/42] =?UTF-8?q?#T-10855=20[fix]=20=ED=8C=80=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EA=B0=80=20=EC=97=86=EC=9D=84=20=EC=8B=9C=20throw=20?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit checkRegisteredTeamOAuthInfo가 false 일 때 throw 하도록 로직 수정 --- .../org/sopt/makers/operation/auth/api/AuthApiController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java index ba7a7f87..9186ac6e 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java @@ -38,7 +38,7 @@ public ResponseEntity> authorize( if (checkParamsIsNull(type, code, clientId, redirectUri)) { throw new AuthException(NOT_NULL_PARAMS); } - if (authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)) { + if (!authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)) { throw new AuthException(NOT_FOUNT_REGISTERED_TEAM); } if (!SocialType.isContains(type)) { From 2dcc51146baa0bca3f06461e5c7dad0f16f71d7c Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 11:33:11 +0900 Subject: [PATCH 25/42] =?UTF-8?q?#T-10855=20[feat]=20auth=20exception=20er?= =?UTF-8?q?ror=20handler=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/makers/operation/common/handler/ErrorHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/common/handler/ErrorHandler.java b/operation-api/src/main/java/org/sopt/makers/operation/common/handler/ErrorHandler.java index 5a4093f2..630b59ec 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/common/handler/ErrorHandler.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/common/handler/ErrorHandler.java @@ -1,6 +1,7 @@ package org.sopt.makers.operation.common.handler; import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.exception.AuthException; import org.sopt.makers.operation.util.ApiResponseUtil; import org.sopt.makers.operation.exception.AdminFailureException; import org.sopt.makers.operation.exception.AlarmException; @@ -74,4 +75,10 @@ public ResponseEntity> attendanceException(AttendanceException e return ApiResponseUtil.failure(ex.getFailureCode()); } + @ExceptionHandler(AuthException.class) + public ResponseEntity> authException(AuthException ex) { + log.error(ex.getMessage()); + return ApiResponseUtil.failure(ex.getFailureCode()); + } + } From e28e609d73263c344c479e3f36e1da6094aa1913 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 12:04:36 +0900 Subject: [PATCH 26/42] =?UTF-8?q?#T-10855=20[test]=20auth=20service=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/AuthServiceTest.java | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 operation-api/src/test/java/org/sopt/makers/operation/auth/service/AuthServiceTest.java diff --git a/operation-api/src/test/java/org/sopt/makers/operation/auth/service/AuthServiceTest.java b/operation-api/src/test/java/org/sopt/makers/operation/auth/service/AuthServiceTest.java new file mode 100644 index 00000000..66e7292f --- /dev/null +++ b/operation-api/src/test/java/org/sopt/makers/operation/auth/service/AuthServiceTest.java @@ -0,0 +1,114 @@ +package org.sopt.makers.operation.auth.service; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.SignatureException; +import jakarta.xml.bind.DatatypeConverter; +import lombok.val; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.sopt.makers.operation.auth.repository.TeamOAuthInfoRepository; +import org.sopt.makers.operation.client.social.SocialLoginManager; +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.exception.AuthException; +import org.sopt.makers.operation.user.domain.SocialType; +import org.sopt.makers.operation.user.repository.identityinfo.UserIdentityInfoRepository; + +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.BDDMockito.given; + +@ExtendWith(MockitoExtension.class) +class AuthServiceTest { + @Mock + SocialLoginManager socialLoginManager; + @Mock + TeamOAuthInfoRepository teamOAuthInfoRepository; + @Mock + UserIdentityInfoRepository userIdentityInfoRepository; + @Mock + ValueConfig valueConfig; + + AuthService authService; + + @BeforeEach + void setUp() { + authService = new AuthServiceImpl(socialLoginManager, teamOAuthInfoRepository, userIdentityInfoRepository, valueConfig); + } + + @Nested + @DisplayName("getSocialUserInfo 메서드 테스트") + class GetSocialUserInfoMethodTest { + @DisplayName("socialLoginManager 으로부터 id token 값을 null 값을 받았다면 AuthException 을 반환한다.") + @Test + void test() { + // given + val socialType = SocialType.APPLE; + val code = "social code"; + given(socialLoginManager.getIdTokenByCode(socialType, code)).willReturn(null); + + // when, then + assertThatThrownBy(() -> authService.getSocialUserInfo(socialType, code)) + .isInstanceOf(AuthException.class) + .hasMessage("[AuthException] : 유효하지 않은 social code 입니다."); + } + } + + @Nested + @DisplayName("getUserId 메서드 테스트") + class GetUserIdMethodTest { + @DisplayName("사전에 등록되지 않은 social type, social id 가 들어왔을 때, AuthException 을 반환한다.") + @Test + void test() { + // given + val socialType = SocialType.APPLE; + val userSocialId = "user social id"; + given(userIdentityInfoRepository.findBySocialTypeAndSocialId(socialType, userSocialId)).willReturn(Optional.empty()); + + // when, then + assertThatThrownBy(() -> authService.getUserId(socialType, userSocialId)) + .isInstanceOf(AuthException.class) + .hasMessage("[AuthException] : 등록된 소셜 정보가 없습니다."); + } + } + + @Nested + @DisplayName("generatePlatformCode 메서드 테스트") + class GeneratePlatformCodeMethodTest { + final String platformSecretKey = "123456789123456789123456789123456789123456789123456789"; + + @DisplayName("iss:clientId, aud:redirectUri , sub:userId 인 jwt 토큰을 발급한다.") + @Test + void test() { + // given + val clientId = "clientId"; + val redirectUri = "redirectUri"; + val userId = 1L; + given(valueConfig.getPlatformCodeSecretKey()).willReturn(platformSecretKey); + + // when + String platformCode = authService.generatePlatformCode(clientId, redirectUri, userId); + Claims claims = getClaimsFromToken(platformCode); + + // then + assertThat(claims.getIssuer()).isEqualTo("clientId"); + assertThat(claims.getAudience()).isEqualTo("redirectUri"); + assertThat(claims.getSubject()).isEqualTo("1"); + } + + private Claims getClaimsFromToken(String token) throws SignatureException { + return Jwts.parserBuilder() + .setSigningKey(DatatypeConverter.parseBase64Binary(platformSecretKey)) + .build() + .parseClaimsJws(token) + .getBody(); + } + } +} \ No newline at end of file From 5b69e2d6eaeeb09858d9817a031d8fd92895f0f7 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 12:32:58 +0900 Subject: [PATCH 27/42] =?UTF-8?q?#T-10855=20[test]=20auth=20api=20controll?= =?UTF-8?q?er=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/api/AuthApiControllerTest.java | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 operation-api/src/test/java/org/sopt/makers/operation/auth/api/AuthApiControllerTest.java diff --git a/operation-api/src/test/java/org/sopt/makers/operation/auth/api/AuthApiControllerTest.java b/operation-api/src/test/java/org/sopt/makers/operation/auth/api/AuthApiControllerTest.java new file mode 100644 index 00000000..1706a681 --- /dev/null +++ b/operation-api/src/test/java/org/sopt/makers/operation/auth/api/AuthApiControllerTest.java @@ -0,0 +1,130 @@ +package org.sopt.makers.operation.auth.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.val; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.sopt.makers.operation.auth.service.AuthService; +import org.sopt.makers.operation.common.handler.ErrorHandler; +import org.sopt.makers.operation.user.domain.SocialType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ContextConfiguration(classes = AuthApiController.class) +@WebMvcTest(controllers = {AuthApiController.class}, excludeAutoConfiguration = {SecurityAutoConfiguration.class}) +@Import({AuthApiController.class, ErrorHandler.class}) +class AuthApiControllerTest { + @MockBean + AuthService authService; + @Autowired + MockMvc mockMvc; + @Autowired + ObjectMapper objectMapper; + final String uri = "/api/v1/authorize"; + + @Nested + @DisplayName("API 통신 성공 테스트") + class SuccessTest { + @DisplayName("유효한 type, code, clientId, redirectUri 값이 들어왔을 때, 플랫폼 인가코드를 반환한다.") + @ParameterizedTest + @CsvSource({ + "APPLE,code,clientId,redirectUri" + }) + void successTest(String type, String code, String clientId, String redirectUri) throws Exception { + // given + given(authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)).willReturn(true); + val socialType = SocialType.valueOf(type); + given(authService.getSocialUserInfo(socialType, code)).willReturn("123"); + given(authService.getUserId(socialType, "123")).willReturn(1L); + given(authService.generatePlatformCode(clientId, redirectUri, 1L)).willReturn("Platform Code"); + + // when, then + mockMvc.perform(get(uri) + .contentType(MediaType.APPLICATION_JSON) + .param("type", type) + .param("code", code) + .param("clientId", clientId) + .param("redirectUri", redirectUri)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.message").value("플랫폼 인가코드 발급 성공")); + } + } + + @Nested + @DisplayName("쿼리 파라미터 유효성 검사 테스트") + class QueryParameterValidateTest { + + @DisplayName("type, code, clientId, redirectUri 중 하나라도 null 이 들어오면 400을 반환한다.") + @ParameterizedTest + @CsvSource({ + ",code,clientId,redirectUri", + "type,,clientId,redirectUri", + "type,code,,redirectUri", + "type,code,clientId," + }) + void validateTest(String type, String code, String clientId, String redirectUri) throws Exception { + // when, then + mockMvc.perform(get(uri) + .contentType(MediaType.APPLICATION_JSON) + .param("type", type) + .param("code", code) + .param("clientId", clientId) + .param("redirectUri", redirectUri)) + .andExpect(status().isBadRequest()); + } + + @DisplayName("등록되지 않은 clientId, redirectUri 라면 404를 반환한다.") + @ParameterizedTest + @CsvSource({ + "type,code,clientId,redirectUri" + }) + void validateTest2(String type, String code, String clientId, String redirectUri) throws Exception { + // given + given(authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)).willReturn(false); + + // when, then + mockMvc.perform(get(uri) + .contentType(MediaType.APPLICATION_JSON) + .param("type", type) + .param("code", code) + .param("clientId", clientId) + .param("redirectUri", redirectUri)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.message").value("등록되지 않은 팀입니다.")); + ; + } + + @DisplayName("등록되지 않은 social type 이라면 400을 반환한다.") + @ParameterizedTest + @CsvSource({ + "KAKAO,code,clientId,redirectUri" + }) + void validateTest3(String type, String code, String clientId, String redirectUri) throws Exception { + // given + given(authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)).willReturn(true); + + // when, then + mockMvc.perform(get(uri) + .contentType(MediaType.APPLICATION_JSON) + .param("type", type) + .param("code", code) + .param("clientId", clientId) + .param("redirectUri", redirectUri)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("유효하지 않은 social type 입니다.")); + } + } +} \ No newline at end of file From a06028bf74999547ac4dfaa76032fa1e518e5855 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 12:33:44 +0900 Subject: [PATCH 28/42] =?UTF-8?q?#T-10855=20[fix]=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20null=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit spring에서 쿼리 파라미터가 null일 때 400 에러를 자동으로 반환함 --- .../operation/auth/api/AuthApiController.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java index 9186ac6e..740b130b 100644 --- a/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/auth/api/AuthApiController.java @@ -17,7 +17,6 @@ import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.INVALID_SOCIAL_TYPE; import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.NOT_FOUNT_REGISTERED_TEAM; -import static org.sopt.makers.operation.code.failure.auth.AuthFailureCode.NOT_NULL_PARAMS; import static org.sopt.makers.operation.code.success.auth.AuthSuccessCode.SUCCESS_GET_AUTHORIZATION_CODE; @RestController @@ -35,9 +34,6 @@ public ResponseEntity> authorize( @RequestParam String clientId, @RequestParam String redirectUri ) { - if (checkParamsIsNull(type, code, clientId, redirectUri)) { - throw new AuthException(NOT_NULL_PARAMS); - } if (!authService.checkRegisteredTeamOAuthInfo(clientId, redirectUri)) { throw new AuthException(NOT_FOUNT_REGISTERED_TEAM); } @@ -50,15 +46,6 @@ public ResponseEntity> authorize( return ApiResponseUtil.success(SUCCESS_GET_AUTHORIZATION_CODE, new AuthorizationCodeResponse(platformCode)); } - private boolean checkParamsIsNull(String... params) { - for (String param : params) { - if (param == null) { - return true; - } - } - return false; - } - private Long findUserIdBySocialTypeAndCode(String type, String code) { val socialType = SocialType.valueOf(type); val userSocialId = authService.getSocialUserInfo(socialType, code); From 6830ab09ddf5d8c11197f9f219f1e74b1eae970f Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 17:05:24 +0900 Subject: [PATCH 29/42] =?UTF-8?q?#T-10855=20[feat]=20ci=20=EB=82=B4=20appl?= =?UTF-8?q?e=20key=20=ED=8C=8C=EC=9D=BC=20=EC=A0=80=EC=9E=A5=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32b14339..86dd612c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,11 @@ jobs: echo "${{ secrets.APPLICATION_DEV }}" >> ./application-dev.yml cat ./application-dev.yml + - name: 'Get key from Github Secrets' + run: | + mkdir -p src/main/resources/static + echo ${{ secrets.APPLE_KEY }} | base64 --decode > src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + - name: Build with Gradle run: ./gradlew build shell: bash \ No newline at end of file From 4838ec193ac91aa17802588781a16f5569077774 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 17:09:54 +0900 Subject: [PATCH 30/42] =?UTF-8?q?#T-10855=20[ci]=20apple=20key=20=EB=94=94?= =?UTF-8?q?=EC=BD=94=EB=94=A9=EC=9D=84=20=EC=9C=84=ED=95=9C=20workflow=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 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86dd612c..22b96ee1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: 'Get key from Github Secrets' run: | mkdir -p src/main/resources/static - echo ${{ secrets.APPLE_KEY }} | base64 --decode > src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + echo "${{ secrets.APPLE_KEY }}" | base64 --decode > ./src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From f9e3268eb365e4b22111b9ac6f821534f3996704 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 17:13:27 +0900 Subject: [PATCH 31/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22b96ee1..8743ebd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: 'Get key from Github Secrets' run: | mkdir -p src/main/resources/static - echo "${{ secrets.APPLE_KEY }}" | base64 --decode > ./src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + echo ${{ secrets.APPLE_KEY }} | base64 -d > ./src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From 39f5a2bae8a3f659b1a81acc38a81c2cfddde904 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 17:44:20 +0900 Subject: [PATCH 32/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8743ebd3..62fd24e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: 'Get key from Github Secrets' run: | mkdir -p src/main/resources/static - echo ${{ secrets.APPLE_KEY }} | base64 -d > ./src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + echo "${{ secrets.APPLE_KEY }}" | base64 -d > ./src/main/resources/static/"${{ secrets.APPLE_KEY_NAME }}" - name: Build with Gradle run: ./gradlew build From f04ec56d4703aba08773c33bd9f06c10d6917bc8 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 17:47:46 +0900 Subject: [PATCH 33/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 62fd24e9..8743ebd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: 'Get key from Github Secrets' run: | mkdir -p src/main/resources/static - echo "${{ secrets.APPLE_KEY }}" | base64 -d > ./src/main/resources/static/"${{ secrets.APPLE_KEY_NAME }}" + echo ${{ secrets.APPLE_KEY }} | base64 -d > ./src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From 3ccaaad1fe1333d8929f3228dec1ade6d297c0e4 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 17:56:50 +0900 Subject: [PATCH 34/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8743ebd3..1812ca5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: 'Get key from Github Secrets' run: | mkdir -p src/main/resources/static - echo ${{ secrets.APPLE_KEY }} | base64 -d > ./src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + echo "${{ secrets.APPLE_KEY }}" | base64 -d > src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From e5a3acb5f12d279c62e8132dbfa8bc1c8437b8ef Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 17:58:36 +0900 Subject: [PATCH 35/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1812ca5b..3552f6fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: 'Get key from Github Secrets' run: | mkdir -p src/main/resources/static - echo "${{ secrets.APPLE_KEY }}" | base64 -d > src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + echo "${{ secrets.APPLE_KEY }}" | tr -d '\n' | base64 --decode > src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From deea533dc51230d1df349d091face2af43a3b042 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 18:00:15 +0900 Subject: [PATCH 36/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3552f6fe..b2343c95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: 'Get key from Github Secrets' run: | mkdir -p src/main/resources/static - echo "${{ secrets.APPLE_KEY }}" | tr -d '\n' | base64 --decode > src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + printf "%s" "${{ secrets.APPLE_KEY }}" | base64 --decode > src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From a3c1e2f40d1d679339f990072b1acb4c0b9b89e2 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 18:02:05 +0900 Subject: [PATCH 37/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2343c95..86dd612c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: - name: 'Get key from Github Secrets' run: | mkdir -p src/main/resources/static - printf "%s" "${{ secrets.APPLE_KEY }}" | base64 --decode > src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + echo ${{ secrets.APPLE_KEY }} | base64 --decode > src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From 6402942393c7c68395cd212ae2fe9b0a8400569b Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 18:06:25 +0900 Subject: [PATCH 38/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86dd612c..e7bb94e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,8 +33,9 @@ jobs: - name: 'Get key from Github Secrets' run: | - mkdir -p src/main/resources/static - echo ${{ secrets.APPLE_KEY }} | base64 --decode > src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + pwd + mkdir -p ./operation-api/src/main/resources/static + echo ${{ secrets.APPLE_KEY }} | base64 --decode > ./operation-api/src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From 801b7cece31aeab903edd642bacce395f6497f74 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 18:12:45 +0900 Subject: [PATCH 39/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7bb94e9..279426f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: | pwd mkdir -p ./operation-api/src/main/resources/static - echo ${{ secrets.APPLE_KEY }} | base64 --decode > ./operation-api/src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + echo "${{ secrets.APPLE_KEY }}" | base64 -d > ./operation-api/src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From 2a066de4cc03c6a74453301778235d1cf8432371 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 18:18:33 +0900 Subject: [PATCH 40/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 279426f2..bd87cbf1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: | pwd mkdir -p ./operation-api/src/main/resources/static - echo "${{ secrets.APPLE_KEY }}" | base64 -d > ./operation-api/src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + echo "${{ secrets.APPLE_KEY }}" | base64 -D > ./operation-api/src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From 37a04084832df3fde204c9e26589bad69f0d46ad Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 18:20:40 +0900 Subject: [PATCH 41/42] =?UTF-8?q?#T-10855=20[ci]=20workflow=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd87cbf1..2a6f1089 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: | pwd mkdir -p ./operation-api/src/main/resources/static - echo "${{ secrets.APPLE_KEY }}" | base64 -D > ./operation-api/src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + echo "${{ secrets.APPLE_KEY }}" | base64 --decode > ./operation-api/src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} - name: Build with Gradle run: ./gradlew build From a7c381b5cd239f61798daabf12b1560be3d77647 Mon Sep 17 00:00:00 2001 From: KWY Date: Fri, 31 May 2024 18:30:13 +0900 Subject: [PATCH 42/42] =?UTF-8?q?#T-10855=20[feat]=20cd=20workflow=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 apple secret key 생성 로직 구현 --- .github/workflows/cd-dev.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/cd-dev.yml b/.github/workflows/cd-dev.yml index 44d549ad..cb374a49 100644 --- a/.github/workflows/cd-dev.yml +++ b/.github/workflows/cd-dev.yml @@ -31,6 +31,12 @@ jobs: echo "${{ secrets.APPLICATION_DEV }}" >> ./application-dev.yml cat ./application-dev.yml + - name: 'Get key from Github Secrets' + run: | + pwd + mkdir -p ./operation-api/src/main/resources/static + echo "${{ secrets.APPLE_KEY }}" | base64 --decode > ./operation-api/src/main/resources/static/${{ secrets.APPLE_KEY_NAME }} + - name: Build with Gradle run: ./gradlew build shell: bash