From 5d4303650487f674ecb9e090acaf7063ef3d0cad Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Sat, 26 Aug 2023 15:14:11 +0900 Subject: [PATCH 01/14] =?UTF-8?q?chore:=20submodule=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitmodules | 3 +++ src/main/resources/HACKER-CONFIG | 1 + src/main/resources/applicaiton.yml | 4 ++++ 3 files changed, 8 insertions(+) create mode 100644 .gitmodules create mode 160000 src/main/resources/HACKER-CONFIG diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f52b052 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/main/resources/HACKER-CONFIG"] + path = src/main/resources/HACKER-CONFIG + url = https://github.com/zaranaramorimori/HACKER-CONFIG.git diff --git a/src/main/resources/HACKER-CONFIG b/src/main/resources/HACKER-CONFIG new file mode 160000 index 0000000..3617407 --- /dev/null +++ b/src/main/resources/HACKER-CONFIG @@ -0,0 +1 @@ +Subproject commit 36174074c54150cb74f60b879135f78f55988570 diff --git a/src/main/resources/applicaiton.yml b/src/main/resources/applicaiton.yml index e69de29..0bec47c 100644 --- a/src/main/resources/applicaiton.yml +++ b/src/main/resources/applicaiton.yml @@ -0,0 +1,4 @@ +spring: + config: + import: + - classpath:/HACKER-CONFIG/application.yml From 47098e03827282dbfc1a4215b88fc7c78e27e013 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Sat, 26 Aug 2023 22:02:47 +0900 Subject: [PATCH 02/14] =?UTF-8?q?chore:=20submodule=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/HACKER-CONFIG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/HACKER-CONFIG b/src/main/resources/HACKER-CONFIG index 3617407..0389d3b 160000 --- a/src/main/resources/HACKER-CONFIG +++ b/src/main/resources/HACKER-CONFIG @@ -1 +1 @@ -Subproject commit 36174074c54150cb74f60b879135f78f55988570 +Subproject commit 0389d3b86eca6627d182ce38039a0230c6af1c6d From 7953527768708699c6da8292b18b3ee94081f954 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Sat, 26 Aug 2023 22:03:12 +0900 Subject: [PATCH 03/14] =?UTF-8?q?chore:=20spring=20security=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index 6a12f40..dfe5c4e 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,6 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' @@ -31,7 +30,6 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.security:spring-security-test' } tasks.named('test') { From 342f7612e5939bc1fae23d3fc333758207619a30 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Sat, 26 Aug 2023 22:05:40 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat:=20OauthClient=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hacker/application/oauth/OauthClient.java | 13 +++++++++ .../application/oauth/OauthClients.java | 28 +++++++++++++++++++ .../teamzzong/hacker/config/OauthConfig.java | 21 ++++++++++++++ .../teamzzong/hacker/domain/SocialType.java | 4 +++ .../com/teamzzong/hacker/dto/UserInfo.java | 12 ++++++++ 5 files changed, 78 insertions(+) create mode 100644 src/main/java/com/teamzzong/hacker/application/oauth/OauthClient.java create mode 100644 src/main/java/com/teamzzong/hacker/application/oauth/OauthClients.java create mode 100644 src/main/java/com/teamzzong/hacker/config/OauthConfig.java create mode 100644 src/main/java/com/teamzzong/hacker/domain/SocialType.java create mode 100644 src/main/java/com/teamzzong/hacker/dto/UserInfo.java diff --git a/src/main/java/com/teamzzong/hacker/application/oauth/OauthClient.java b/src/main/java/com/teamzzong/hacker/application/oauth/OauthClient.java new file mode 100644 index 0000000..8e6d442 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/application/oauth/OauthClient.java @@ -0,0 +1,13 @@ +package com.teamzzong.hacker.application.oauth; + +import com.teamzzong.hacker.domain.SocialType; +import com.teamzzong.hacker.dto.UserInfo; + +public interface OauthClient { + + SocialType socialType(); + + String requestAccessToken(String code); + + UserInfo requestUserInfo(String accessToken); +} diff --git a/src/main/java/com/teamzzong/hacker/application/oauth/OauthClients.java b/src/main/java/com/teamzzong/hacker/application/oauth/OauthClients.java new file mode 100644 index 0000000..ee02503 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/application/oauth/OauthClients.java @@ -0,0 +1,28 @@ +package com.teamzzong.hacker.application.oauth; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.teamzzong.hacker.domain.SocialType; +import com.teamzzong.hacker.dto.UserInfo; + +public class OauthClients { + + private final Map clients; + + public OauthClients(Set clients) { + this.clients = clients.stream() + .collect(Collectors.toMap(OauthClient::socialType, oauthClient -> oauthClient)); + } + + public UserInfo requestUserInfo(SocialType socialType, String code) { + OauthClient oauthClient = clients.get(socialType); + if (oauthClient == null) { + throw new IllegalArgumentException("올바르지 않은 SocialType 입니다."); + } + String accessToken = oauthClient.requestAccessToken(code); + return oauthClient.requestUserInfo(accessToken); + } + +} diff --git a/src/main/java/com/teamzzong/hacker/config/OauthConfig.java b/src/main/java/com/teamzzong/hacker/config/OauthConfig.java new file mode 100644 index 0000000..85afa5a --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/config/OauthConfig.java @@ -0,0 +1,21 @@ +package com.teamzzong.hacker.config; + +import java.util.Set; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.teamzzong.hacker.application.oauth.OauthClient; +import com.teamzzong.hacker.application.oauth.OauthClients; +import com.teamzzong.hacker.infrastructure.oauth.GithubOauthProperty; + +@Configuration +@EnableConfigurationProperties({GithubOauthProperty.class}) +public class OauthConfig { + + @Bean + public OauthClients oAuth2Clients(Set clients) { + return new OauthClients(clients); + } +} diff --git a/src/main/java/com/teamzzong/hacker/domain/SocialType.java b/src/main/java/com/teamzzong/hacker/domain/SocialType.java new file mode 100644 index 0000000..b3e34c5 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/domain/SocialType.java @@ -0,0 +1,4 @@ +package com.teamzzong.hacker.domain; + +public enum SocialType { +} diff --git a/src/main/java/com/teamzzong/hacker/dto/UserInfo.java b/src/main/java/com/teamzzong/hacker/dto/UserInfo.java new file mode 100644 index 0000000..87b0808 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/dto/UserInfo.java @@ -0,0 +1,12 @@ +package com.teamzzong.hacker.dto; + +import com.teamzzong.hacker.domain.SocialType; + +public record UserInfo( + SocialType socialType, + String socialId, + String nickname, + String profileImage +) { + +} From 86c58a7fdb8d9a4e287fbbf57391da23deb7d4cd Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Sat, 26 Aug 2023 22:06:03 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20Github=20OauthClient=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 --- .../teamzzong/hacker/domain/SocialType.java | 2 + .../hacker/dto/oauth/GithubTokenResponse.java | 10 ++++ .../dto/oauth/GithubUserInfoResponse.java | 21 ++++++++ .../oauth/GithubOauthClient.java | 37 +++++++++++++ .../oauth/GithubOauthProperty.java | 14 +++++ .../oauth/GithubTokenClient.java | 53 +++++++++++++++++++ .../oauth/GithubTokenExceptionHandler.java | 21 ++++++++ .../oauth/GithubUserInfoClient.java | 49 +++++++++++++++++ .../oauth/GithubUserInfoExceptionHandler.java | 21 ++++++++ src/main/resources/application.yml | 4 ++ 10 files changed, 232 insertions(+) create mode 100644 src/main/java/com/teamzzong/hacker/dto/oauth/GithubTokenResponse.java create mode 100644 src/main/java/com/teamzzong/hacker/dto/oauth/GithubUserInfoResponse.java create mode 100644 src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthClient.java create mode 100644 src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthProperty.java create mode 100644 src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java create mode 100644 src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenExceptionHandler.java create mode 100644 src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java create mode 100644 src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoExceptionHandler.java diff --git a/src/main/java/com/teamzzong/hacker/domain/SocialType.java b/src/main/java/com/teamzzong/hacker/domain/SocialType.java index b3e34c5..c0b69fc 100644 --- a/src/main/java/com/teamzzong/hacker/domain/SocialType.java +++ b/src/main/java/com/teamzzong/hacker/domain/SocialType.java @@ -1,4 +1,6 @@ package com.teamzzong.hacker.domain; public enum SocialType { + GITHUB, + ; } diff --git a/src/main/java/com/teamzzong/hacker/dto/oauth/GithubTokenResponse.java b/src/main/java/com/teamzzong/hacker/dto/oauth/GithubTokenResponse.java new file mode 100644 index 0000000..8c03468 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/dto/oauth/GithubTokenResponse.java @@ -0,0 +1,10 @@ +package com.teamzzong.hacker.dto.oauth; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record GithubTokenResponse( + @JsonProperty("access_token") String accessToken, + String scope, + @JsonProperty("token_type") String tokenType +) { +} diff --git a/src/main/java/com/teamzzong/hacker/dto/oauth/GithubUserInfoResponse.java b/src/main/java/com/teamzzong/hacker/dto/oauth/GithubUserInfoResponse.java new file mode 100644 index 0000000..fc3b830 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/dto/oauth/GithubUserInfoResponse.java @@ -0,0 +1,21 @@ +package com.teamzzong.hacker.dto.oauth; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.teamzzong.hacker.domain.SocialType; +import com.teamzzong.hacker.dto.UserInfo; + +public record GithubUserInfoResponse( + String id, + String login, + @JsonProperty("avatar_url") String avatarUrl +) { + + public UserInfo toUserInfo() { + return new UserInfo( + SocialType.GITHUB, + id, + login, + avatarUrl + ); + } +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthClient.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthClient.java new file mode 100644 index 0000000..7908eac --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthClient.java @@ -0,0 +1,37 @@ +package com.teamzzong.hacker.infrastructure.oauth; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.stereotype.Component; + +import com.teamzzong.hacker.application.oauth.OauthClient; +import com.teamzzong.hacker.domain.SocialType; +import com.teamzzong.hacker.dto.UserInfo; + +@Component +public class GithubOauthClient implements OauthClient { + + private final GithubOauthProperty property; + private final GithubTokenClient tokenClient; + private final GithubUserInfoClient userInfoClient; + + public GithubOauthClient(GithubOauthProperty property, RestTemplateBuilder restTemplateBuilder) { + this.property = property; + this.tokenClient = new GithubTokenClient(property, restTemplateBuilder); + this.userInfoClient = new GithubUserInfoClient(property, restTemplateBuilder); + } + + @Override + public SocialType socialType() { + return SocialType.GITHUB; + } + + @Override + public String requestAccessToken(String code) { + return tokenClient.request(code); + } + + @Override + public UserInfo requestUserInfo(String accessToken) { + return userInfoClient.request(accessToken); + } +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthProperty.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthProperty.java new file mode 100644 index 0000000..7f4088b --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthProperty.java @@ -0,0 +1,14 @@ +package com.teamzzong.hacker.infrastructure.oauth; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "oauth2.github") +public record GithubOauthProperty( + String clientId, + String clientSecret, + String redirectUri, + String tokenUri, + String userInfoUri +) { + +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java new file mode 100644 index 0000000..ffcb92c --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java @@ -0,0 +1,53 @@ +package com.teamzzong.hacker.infrastructure.oauth; + +import java.util.List; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import com.teamzzong.hacker.dto.oauth.GithubTokenResponse; + +public class GithubTokenClient { + + private final GithubOauthProperty property; + private final RestTemplate restTemplate; + + public GithubTokenClient(GithubOauthProperty property, RestTemplateBuilder restTemplateBuilder) { + this.property = property; + this.restTemplate = restTemplateBuilder + .errorHandler(new GithubTokenExceptionHandler()) + .build(); + } + + public String request(String code) { + String uri = getRequestUri(code); + HttpHeaders header = getRequestHeader(); + GithubTokenResponse response = restTemplate + .postForEntity( + uri, + new HttpEntity<>(header), + GithubTokenResponse.class + ) + .getBody(); + + return response.accessToken(); + } + + private String getRequestUri(String code) { + return UriComponentsBuilder.fromUriString(property.tokenUri()) + .queryParam("client_id", property.clientId()) + .queryParam("client_secret", property.clientSecret()) + .queryParam("code", code) + .toUriString(); + } + + private HttpHeaders getRequestHeader() { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + return headers; + } +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenExceptionHandler.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenExceptionHandler.java new file mode 100644 index 0000000..0b5bf77 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenExceptionHandler.java @@ -0,0 +1,21 @@ +package com.teamzzong.hacker.infrastructure.oauth; + +import java.io.IOException; + +import org.springframework.http.HttpStatusCode; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.DefaultResponseErrorHandler; + +public class GithubTokenExceptionHandler extends DefaultResponseErrorHandler { + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + HttpStatusCode statusCode = response.getStatusCode(); + if (statusCode.is4xxClientError()) { + throw new IllegalArgumentException(); // TODO + } + if (statusCode.is5xxServerError()) { + throw new IllegalArgumentException("[Github] Internal Server Error"); // TODO + } + } +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java new file mode 100644 index 0000000..b84f28b --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java @@ -0,0 +1,49 @@ +package com.teamzzong.hacker.infrastructure.oauth; + +import java.util.List; + +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; + +import com.teamzzong.hacker.dto.UserInfo; +import com.teamzzong.hacker.dto.oauth.GithubUserInfoResponse; + +public class GithubUserInfoClient { + + private final GithubOauthProperty property; + private final RestTemplate restTemplate; + + public GithubUserInfoClient(GithubOauthProperty property, RestTemplateBuilder restTemplateBuilder) { + this.property = property; + this.restTemplate = restTemplateBuilder + .errorHandler(new GithubUserInfoExceptionHandler()) + .build(); + } + + public UserInfo request(String accessToken) { + HttpHeaders headers = getHttpHeaders(accessToken); + + System.out.println("property = " + property); + GithubUserInfoResponse response = restTemplate + .exchange( + property.userInfoUri(), + HttpMethod.GET, + new HttpEntity<>(headers), + GithubUserInfoResponse.class + ) + .getBody(); + + return response.toUserInfo(); + } + + private HttpHeaders getHttpHeaders(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + return headers; + } +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoExceptionHandler.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoExceptionHandler.java new file mode 100644 index 0000000..21a2e78 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoExceptionHandler.java @@ -0,0 +1,21 @@ +package com.teamzzong.hacker.infrastructure.oauth; + +import java.io.IOException; + +import org.springframework.http.HttpStatusCode; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.web.client.DefaultResponseErrorHandler; + +public class GithubUserInfoExceptionHandler extends DefaultResponseErrorHandler { + + @Override + public void handleError(ClientHttpResponse response) throws IOException { + HttpStatusCode statusCode = response.getStatusCode(); + if (statusCode.is4xxClientError()) { + throw new IllegalArgumentException(); // TODO + } + if (statusCode.is5xxServerError()) { + throw new IllegalArgumentException("[Github] Internal Server Error"); // TODO + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e69de29..0bec47c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -0,0 +1,4 @@ +spring: + config: + import: + - classpath:/HACKER-CONFIG/application.yml From 222c88514ae1eb084f3c21c8e7c06e17b3e6ecac Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Sat, 26 Aug 2023 22:12:02 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20GithubExceptionHandler=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=EB=B0=8F=204xxError=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ndler.java => GithubExceptionHandler.java} | 13 +++++++++++- .../oauth/GithubTokenClient.java | 2 +- .../oauth/GithubUserInfoClient.java | 2 +- .../oauth/GithubUserInfoExceptionHandler.java | 21 ------------------- 4 files changed, 14 insertions(+), 24 deletions(-) rename src/main/java/com/teamzzong/hacker/infrastructure/oauth/{GithubTokenExceptionHandler.java => GithubExceptionHandler.java} (61%) delete mode 100644 src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoExceptionHandler.java diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenExceptionHandler.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubExceptionHandler.java similarity index 61% rename from src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenExceptionHandler.java rename to src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubExceptionHandler.java index 0b5bf77..889037b 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenExceptionHandler.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubExceptionHandler.java @@ -1,21 +1,32 @@ package com.teamzzong.hacker.infrastructure.oauth; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.springframework.http.HttpStatusCode; import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.client.DefaultResponseErrorHandler; -public class GithubTokenExceptionHandler extends DefaultResponseErrorHandler { +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class GithubExceptionHandler extends DefaultResponseErrorHandler { @Override public void handleError(ClientHttpResponse response) throws IOException { HttpStatusCode statusCode = response.getStatusCode(); if (statusCode.is4xxClientError()) { + log4xxError(response); throw new IllegalArgumentException(); // TODO } if (statusCode.is5xxServerError()) { throw new IllegalArgumentException("[Github] Internal Server Error"); // TODO } } + + private void log4xxError(ClientHttpResponse response) throws IOException { + log.warn("[{}] {}", + response.getStatusText(), + new String(getResponseBody(response), StandardCharsets.UTF_8)); + } } diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java index ffcb92c..99fd16f 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java @@ -19,7 +19,7 @@ public class GithubTokenClient { public GithubTokenClient(GithubOauthProperty property, RestTemplateBuilder restTemplateBuilder) { this.property = property; this.restTemplate = restTemplateBuilder - .errorHandler(new GithubTokenExceptionHandler()) + .errorHandler(new GithubExceptionHandler()) .build(); } diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java index b84f28b..451fa56 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java @@ -20,7 +20,7 @@ public class GithubUserInfoClient { public GithubUserInfoClient(GithubOauthProperty property, RestTemplateBuilder restTemplateBuilder) { this.property = property; this.restTemplate = restTemplateBuilder - .errorHandler(new GithubUserInfoExceptionHandler()) + .errorHandler(new GithubExceptionHandler()) .build(); } diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoExceptionHandler.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoExceptionHandler.java deleted file mode 100644 index 21a2e78..0000000 --- a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoExceptionHandler.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.teamzzong.hacker.infrastructure.oauth; - -import java.io.IOException; - -import org.springframework.http.HttpStatusCode; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.web.client.DefaultResponseErrorHandler; - -public class GithubUserInfoExceptionHandler extends DefaultResponseErrorHandler { - - @Override - public void handleError(ClientHttpResponse response) throws IOException { - HttpStatusCode statusCode = response.getStatusCode(); - if (statusCode.is4xxClientError()) { - throw new IllegalArgumentException(); // TODO - } - if (statusCode.is5xxServerError()) { - throw new IllegalArgumentException("[Github] Internal Server Error"); // TODO - } - } -} From 8b162935a09da272d76d418db2cf1b5dc11829cb Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 31 Aug 2023 22:45:40 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20AuthTokenProvider=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 --- build.gradle | 5 ++ .../hacker/application/AuthTokenProvider.java | 11 +++ .../hacker/config/JwtAuthConfig.java | 24 +++++ .../hacker/domain/AuthTokenPayload.java | 6 ++ .../hacker/domain/AuthTokenType.java | 7 ++ .../infrastructure/jwt/JwtAuthProperty.java | 19 ++++ .../jwt/JwtAuthTokenProvider.java | 88 +++++++++++++++++++ 7 files changed, 160 insertions(+) create mode 100644 src/main/java/com/teamzzong/hacker/application/AuthTokenProvider.java create mode 100644 src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java create mode 100644 src/main/java/com/teamzzong/hacker/domain/AuthTokenPayload.java create mode 100644 src/main/java/com/teamzzong/hacker/domain/AuthTokenType.java create mode 100644 src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthProperty.java create mode 100644 src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthTokenProvider.java diff --git a/build.gradle b/build.gradle index dfe5c4e..dafcb64 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,11 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' } tasks.named('test') { diff --git a/src/main/java/com/teamzzong/hacker/application/AuthTokenProvider.java b/src/main/java/com/teamzzong/hacker/application/AuthTokenProvider.java new file mode 100644 index 0000000..8204923 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/application/AuthTokenProvider.java @@ -0,0 +1,11 @@ +package com.teamzzong.hacker.application; + +import com.teamzzong.hacker.domain.AuthTokenPayload; +import com.teamzzong.hacker.domain.AuthTokenType; + +public interface AuthTokenProvider { + + String generate(AuthTokenType type, AuthTokenPayload payload); + + AuthTokenPayload extract(AuthTokenType type, String token); +} diff --git a/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java b/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java new file mode 100644 index 0000000..89040c0 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java @@ -0,0 +1,24 @@ +package com.teamzzong.hacker.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.teamzzong.hacker.application.AuthTokenProvider; +import com.teamzzong.hacker.infrastructure.jwt.JwtAuthProperty; +import com.teamzzong.hacker.infrastructure.jwt.JwtAuthTokenProvider; + +import lombok.AllArgsConstructor; + +@Configuration +@EnableConfigurationProperties(JwtAuthProperty.class) +@AllArgsConstructor +public class JwtAuthConfig { + + private final JwtAuthProperty property; + + @Bean + public AuthTokenProvider authTokenProvider(JwtAuthProperty property) { + return JwtAuthTokenProvider.from(property); + } +} diff --git a/src/main/java/com/teamzzong/hacker/domain/AuthTokenPayload.java b/src/main/java/com/teamzzong/hacker/domain/AuthTokenPayload.java new file mode 100644 index 0000000..c64a878 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/domain/AuthTokenPayload.java @@ -0,0 +1,6 @@ +package com.teamzzong.hacker.domain; + +public record AuthTokenPayload( + Long memberId +) { +} diff --git a/src/main/java/com/teamzzong/hacker/domain/AuthTokenType.java b/src/main/java/com/teamzzong/hacker/domain/AuthTokenType.java new file mode 100644 index 0000000..5be1323 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/domain/AuthTokenType.java @@ -0,0 +1,7 @@ +package com.teamzzong.hacker.domain; + +public enum AuthTokenType { + ACCESS, + REFRESH, + ; +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthProperty.java b/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthProperty.java new file mode 100644 index 0000000..98f11a8 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthProperty.java @@ -0,0 +1,19 @@ +package com.teamzzong.hacker.infrastructure.jwt; + +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import com.teamzzong.hacker.domain.AuthTokenType; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@ConfigurationProperties(prefix = "auth") +@AllArgsConstructor +@Getter +public class JwtAuthProperty { + + private final Map expirationMinute; + private final Map secretKey; +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthTokenProvider.java b/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthTokenProvider.java new file mode 100644 index 0000000..59e2040 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthTokenProvider.java @@ -0,0 +1,88 @@ +package com.teamzzong.hacker.infrastructure.jwt; + +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.crypto.SecretKey; + +import com.teamzzong.hacker.application.AuthTokenProvider; +import com.teamzzong.hacker.domain.AuthTokenPayload; +import com.teamzzong.hacker.domain.AuthTokenType; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; + +public class JwtAuthTokenProvider implements AuthTokenProvider { + + private static final int SECOND_FACTOR = 60; + private static final int MILLISECOND_FACTOR = 1000; + private static final String MEMBER_ID_KEY = "id"; + + private final Map secretKeys; + private final Map expirationMinutes; + + private JwtAuthTokenProvider(Map secretKeys, + Map expirationMinutes) { + this.secretKeys = secretKeys; + this.expirationMinutes = expirationMinutes; + } + + public static JwtAuthTokenProvider from(JwtAuthProperty property) { + Map secretKeys = property.getSecretKey() + .entrySet() + .stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> Keys.hmacShaKeyFor(entry.getValue().getBytes(StandardCharsets.UTF_8)) + )); + return new JwtAuthTokenProvider(secretKeys, property.getExpirationMinute()); + } + + @Override + public String generate(AuthTokenType type, AuthTokenPayload payload) { + Integer expMinutes = expirationMinutes.get(type); + SecretKey secretKey = secretKeys.get(type); + if (expMinutes == null || secretKey == null) { + throw new IllegalArgumentException(); // TODO + } + Date now = new Date(); + Date expiration = new Date(now.getTime() + expMinutes * SECOND_FACTOR * MILLISECOND_FACTOR); + return Jwts.builder() + .claim(MEMBER_ID_KEY, payload.memberId()) + .setIssuedAt(now) + .setExpiration(expiration) + .signWith(secretKey, SignatureAlgorithm.HS256) + .compact(); + } + + @Override + public AuthTokenPayload extract(AuthTokenType type, String token) { + SecretKey secretKey = secretKeys.get(type); + if (secretKey == null) { + throw new IllegalArgumentException(); // TODO + } + Claims claims = getClaims(secretKey, token); + Long memberId = claims.get(MEMBER_ID_KEY, Long.class); + return new AuthTokenPayload(memberId); + } + + private Claims getClaims(SecretKey secretKey, String code) { + try { + return Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(code) + .getBody(); + } catch (ExpiredJwtException e) { + throw new IllegalArgumentException("토큰 만료"); // TODO + } catch (JwtException | IllegalArgumentException e) { + throw new IllegalArgumentException("토큰 유효 X"); // TODO + } + } +} From 42dfd1d491c103104a2690073968fbf7172cebfb Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 31 Aug 2023 22:46:02 +0900 Subject: [PATCH 08/14] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20API?= =?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 --- .../hacker/application/AuthService.java | 40 +++++++++++++++++++ .../com/teamzzong/hacker/domain/Member.java | 9 +++++ .../teamzzong/hacker/dto/LoginResponse.java | 24 +++++++++++ .../hacker/dto/LoginTokenResponse.java | 7 ++++ .../hacker/dto/LoginUserInfoResponse.java | 20 ++++++++++ .../infrastructure/MemberRepository.java | 5 +++ 6 files changed, 105 insertions(+) create mode 100644 src/main/java/com/teamzzong/hacker/application/AuthService.java create mode 100644 src/main/java/com/teamzzong/hacker/dto/LoginResponse.java create mode 100644 src/main/java/com/teamzzong/hacker/dto/LoginTokenResponse.java create mode 100644 src/main/java/com/teamzzong/hacker/dto/LoginUserInfoResponse.java diff --git a/src/main/java/com/teamzzong/hacker/application/AuthService.java b/src/main/java/com/teamzzong/hacker/application/AuthService.java new file mode 100644 index 0000000..f992a6e --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/application/AuthService.java @@ -0,0 +1,40 @@ +package com.teamzzong.hacker.application; + +import java.util.Optional; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.teamzzong.hacker.application.oauth.OauthClients; +import com.teamzzong.hacker.domain.AuthTokenPayload; +import com.teamzzong.hacker.domain.AuthTokenType; +import com.teamzzong.hacker.domain.Member; +import com.teamzzong.hacker.domain.SocialType; +import com.teamzzong.hacker.dto.LoginResponse; +import com.teamzzong.hacker.dto.UserInfo; +import com.teamzzong.hacker.infrastructure.MemberRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional +@RequiredArgsConstructor +public class AuthService { + + private final OauthClients oauthClients; + private final MemberRepository memberRepository; + private final AuthTokenProvider tokenProvider; + + public LoginResponse login(SocialType socialType, String code) { + UserInfo userInfo = oauthClients.requestUserInfo(socialType, code); + Optional optionalMember = memberRepository.findBySocialTypeAndSocialId(socialType, userInfo.socialId()); + if (optionalMember.isEmpty()) { + return LoginResponse.signUp(userInfo); + } + Member member = optionalMember.get(); + AuthTokenPayload payload = new AuthTokenPayload(member.getId()); + String accessToken = tokenProvider.generate(AuthTokenType.ACCESS, payload); + String refreshToken = tokenProvider.generate(AuthTokenType.REFRESH, payload); + return LoginResponse.login(accessToken, refreshToken); + } +} diff --git a/src/main/java/com/teamzzong/hacker/domain/Member.java b/src/main/java/com/teamzzong/hacker/domain/Member.java index a04ca5f..3d4a3c9 100644 --- a/src/main/java/com/teamzzong/hacker/domain/Member.java +++ b/src/main/java/com/teamzzong/hacker/domain/Member.java @@ -1,14 +1,23 @@ package com.teamzzong.hacker.domain; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.Getter; @Entity +@Getter public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + + @Enumerated(value = EnumType.STRING) + private SocialType socialType; + + private String socialId; } diff --git a/src/main/java/com/teamzzong/hacker/dto/LoginResponse.java b/src/main/java/com/teamzzong/hacker/dto/LoginResponse.java new file mode 100644 index 0000000..66ab862 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/dto/LoginResponse.java @@ -0,0 +1,24 @@ +package com.teamzzong.hacker.dto; + +public record LoginResponse( + Boolean isNew, + LoginTokenResponse tokens, + LoginUserInfoResponse userInfo +) { + + public static LoginResponse login(String accessToken, String refreshToken) { + return new LoginResponse( + false, + new LoginTokenResponse(accessToken, refreshToken), + null + ); + } + + public static LoginResponse signUp(UserInfo userInfo) { + return new LoginResponse( + true, + null, + LoginUserInfoResponse.from(userInfo) + ); + } +} diff --git a/src/main/java/com/teamzzong/hacker/dto/LoginTokenResponse.java b/src/main/java/com/teamzzong/hacker/dto/LoginTokenResponse.java new file mode 100644 index 0000000..c07a0f5 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/dto/LoginTokenResponse.java @@ -0,0 +1,7 @@ +package com.teamzzong.hacker.dto; + +public record LoginTokenResponse( + String accessToken, + String refreshToken +) { +} diff --git a/src/main/java/com/teamzzong/hacker/dto/LoginUserInfoResponse.java b/src/main/java/com/teamzzong/hacker/dto/LoginUserInfoResponse.java new file mode 100644 index 0000000..fda845a --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/dto/LoginUserInfoResponse.java @@ -0,0 +1,20 @@ +package com.teamzzong.hacker.dto; + +import com.teamzzong.hacker.domain.SocialType; + +public record LoginUserInfoResponse( + SocialType socialType, + String socialId, + String username, + String profileImage +) { + + public static LoginUserInfoResponse from(UserInfo userInfo) { + return new LoginUserInfoResponse( + userInfo.socialType(), + userInfo.socialId(), + userInfo.nickname(), + userInfo.profileImage() + ); + } +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/MemberRepository.java b/src/main/java/com/teamzzong/hacker/infrastructure/MemberRepository.java index d12e6e1..6f5990f 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/MemberRepository.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/MemberRepository.java @@ -1,8 +1,13 @@ package com.teamzzong.hacker.infrastructure; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import com.teamzzong.hacker.domain.Member; +import com.teamzzong.hacker.domain.SocialType; public interface MemberRepository extends JpaRepository { + + Optional findBySocialTypeAndSocialId(SocialType socialType, String socialId); } From cecd7601f8e3e36d1ce275b11f74f6db08879342 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 31 Aug 2023 22:47:18 +0900 Subject: [PATCH 09/14] =?UTF-8?q?refactor:=20Config=20Property=20config=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{infrastructure/oauth => config}/GithubOauthProperty.java | 2 +- src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java | 1 - .../hacker/{infrastructure/jwt => config}/JwtAuthProperty.java | 2 +- src/main/java/com/teamzzong/hacker/config/OauthConfig.java | 1 - .../hacker/infrastructure/jwt/JwtAuthTokenProvider.java | 1 + .../hacker/infrastructure/oauth/GithubOauthClient.java | 1 + .../hacker/infrastructure/oauth/GithubTokenClient.java | 1 + .../hacker/infrastructure/oauth/GithubUserInfoClient.java | 1 + 8 files changed, 6 insertions(+), 4 deletions(-) rename src/main/java/com/teamzzong/hacker/{infrastructure/oauth => config}/GithubOauthProperty.java (84%) rename src/main/java/com/teamzzong/hacker/{infrastructure/jwt => config}/JwtAuthProperty.java (89%) diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthProperty.java b/src/main/java/com/teamzzong/hacker/config/GithubOauthProperty.java similarity index 84% rename from src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthProperty.java rename to src/main/java/com/teamzzong/hacker/config/GithubOauthProperty.java index 7f4088b..9460682 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthProperty.java +++ b/src/main/java/com/teamzzong/hacker/config/GithubOauthProperty.java @@ -1,4 +1,4 @@ -package com.teamzzong.hacker.infrastructure.oauth; +package com.teamzzong.hacker.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java b/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java index 89040c0..a79136e 100644 --- a/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java +++ b/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java @@ -5,7 +5,6 @@ import org.springframework.context.annotation.Configuration; import com.teamzzong.hacker.application.AuthTokenProvider; -import com.teamzzong.hacker.infrastructure.jwt.JwtAuthProperty; import com.teamzzong.hacker.infrastructure.jwt.JwtAuthTokenProvider; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthProperty.java b/src/main/java/com/teamzzong/hacker/config/JwtAuthProperty.java similarity index 89% rename from src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthProperty.java rename to src/main/java/com/teamzzong/hacker/config/JwtAuthProperty.java index 98f11a8..9dbd301 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthProperty.java +++ b/src/main/java/com/teamzzong/hacker/config/JwtAuthProperty.java @@ -1,4 +1,4 @@ -package com.teamzzong.hacker.infrastructure.jwt; +package com.teamzzong.hacker.config; import java.util.Map; diff --git a/src/main/java/com/teamzzong/hacker/config/OauthConfig.java b/src/main/java/com/teamzzong/hacker/config/OauthConfig.java index 85afa5a..d7cedbd 100644 --- a/src/main/java/com/teamzzong/hacker/config/OauthConfig.java +++ b/src/main/java/com/teamzzong/hacker/config/OauthConfig.java @@ -8,7 +8,6 @@ import com.teamzzong.hacker.application.oauth.OauthClient; import com.teamzzong.hacker.application.oauth.OauthClients; -import com.teamzzong.hacker.infrastructure.oauth.GithubOauthProperty; @Configuration @EnableConfigurationProperties({GithubOauthProperty.class}) diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthTokenProvider.java b/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthTokenProvider.java index 59e2040..4918629 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthTokenProvider.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/jwt/JwtAuthTokenProvider.java @@ -8,6 +8,7 @@ import javax.crypto.SecretKey; import com.teamzzong.hacker.application.AuthTokenProvider; +import com.teamzzong.hacker.config.JwtAuthProperty; import com.teamzzong.hacker.domain.AuthTokenPayload; import com.teamzzong.hacker.domain.AuthTokenType; diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthClient.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthClient.java index 7908eac..1c68723 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthClient.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubOauthClient.java @@ -4,6 +4,7 @@ import org.springframework.stereotype.Component; import com.teamzzong.hacker.application.oauth.OauthClient; +import com.teamzzong.hacker.config.GithubOauthProperty; import com.teamzzong.hacker.domain.SocialType; import com.teamzzong.hacker.dto.UserInfo; diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java index 99fd16f..0d26a5d 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubTokenClient.java @@ -9,6 +9,7 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import com.teamzzong.hacker.config.GithubOauthProperty; import com.teamzzong.hacker.dto.oauth.GithubTokenResponse; public class GithubTokenClient { diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java index 451fa56..4eef1f8 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java @@ -9,6 +9,7 @@ import org.springframework.http.MediaType; import org.springframework.web.client.RestTemplate; +import com.teamzzong.hacker.config.GithubOauthProperty; import com.teamzzong.hacker.dto.UserInfo; import com.teamzzong.hacker.dto.oauth.GithubUserInfoResponse; From 627fd55c5a47af22b051040cc0a73c81c6e79970 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Thu, 31 Aug 2023 22:48:04 +0900 Subject: [PATCH 10/14] =?UTF-8?q?refactor:=20@JsonProperty=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/teamzzong/hacker/dto/oauth/GithubTokenResponse.java | 6 ++---- .../teamzzong/hacker/dto/oauth/GithubUserInfoResponse.java | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/teamzzong/hacker/dto/oauth/GithubTokenResponse.java b/src/main/java/com/teamzzong/hacker/dto/oauth/GithubTokenResponse.java index 8c03468..693b647 100644 --- a/src/main/java/com/teamzzong/hacker/dto/oauth/GithubTokenResponse.java +++ b/src/main/java/com/teamzzong/hacker/dto/oauth/GithubTokenResponse.java @@ -1,10 +1,8 @@ package com.teamzzong.hacker.dto.oauth; -import com.fasterxml.jackson.annotation.JsonProperty; - public record GithubTokenResponse( - @JsonProperty("access_token") String accessToken, + String accessToken, String scope, - @JsonProperty("token_type") String tokenType + String tokenType ) { } diff --git a/src/main/java/com/teamzzong/hacker/dto/oauth/GithubUserInfoResponse.java b/src/main/java/com/teamzzong/hacker/dto/oauth/GithubUserInfoResponse.java index fc3b830..4b359ce 100644 --- a/src/main/java/com/teamzzong/hacker/dto/oauth/GithubUserInfoResponse.java +++ b/src/main/java/com/teamzzong/hacker/dto/oauth/GithubUserInfoResponse.java @@ -1,13 +1,12 @@ package com.teamzzong.hacker.dto.oauth; -import com.fasterxml.jackson.annotation.JsonProperty; import com.teamzzong.hacker.domain.SocialType; import com.teamzzong.hacker.dto.UserInfo; public record GithubUserInfoResponse( String id, String login, - @JsonProperty("avatar_url") String avatarUrl + String avatarUrl ) { public UserInfo toUserInfo() { From 52fe63454ee55d8cb798c4097cb873d8fe2e7116 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Fri, 1 Sep 2023 12:16:39 +0900 Subject: [PATCH 11/14] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=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 --- .../hacker/application/AuthService.java | 26 +++++++++++++++++++ .../com/teamzzong/hacker/domain/Member.java | 11 ++++++++ .../teamzzong/hacker/dto/SignUpRequest.java | 11 ++++++++ .../teamzzong/hacker/dto/SignUpResponse.java | 7 +++++ .../infrastructure/MemberRepository.java | 2 ++ 5 files changed, 57 insertions(+) create mode 100644 src/main/java/com/teamzzong/hacker/dto/SignUpRequest.java create mode 100644 src/main/java/com/teamzzong/hacker/dto/SignUpResponse.java diff --git a/src/main/java/com/teamzzong/hacker/application/AuthService.java b/src/main/java/com/teamzzong/hacker/application/AuthService.java index f992a6e..d834c00 100644 --- a/src/main/java/com/teamzzong/hacker/application/AuthService.java +++ b/src/main/java/com/teamzzong/hacker/application/AuthService.java @@ -11,6 +11,8 @@ import com.teamzzong.hacker.domain.Member; import com.teamzzong.hacker.domain.SocialType; import com.teamzzong.hacker.dto.LoginResponse; +import com.teamzzong.hacker.dto.SignUpRequest; +import com.teamzzong.hacker.dto.SignUpResponse; import com.teamzzong.hacker.dto.UserInfo; import com.teamzzong.hacker.infrastructure.MemberRepository; @@ -37,4 +39,28 @@ public LoginResponse login(SocialType socialType, String code) { String refreshToken = tokenProvider.generate(AuthTokenType.REFRESH, payload); return LoginResponse.login(accessToken, refreshToken); } + + public SignUpResponse signUp(SignUpRequest request) { + validateExistMember(request.socialType(), request.socialId()); + validateNickname(request.nickname()); + Member member = memberRepository.save(new Member(request.socialType(), request.socialId(), request.nickname())); + AuthTokenPayload payload = new AuthTokenPayload(member.getId()); + String accessToken = tokenProvider.generate(AuthTokenType.ACCESS, payload); + String refreshToken = tokenProvider.generate(AuthTokenType.REFRESH, payload); + return new SignUpResponse(accessToken, refreshToken); + } + + private void validateExistMember(SocialType socialType, String socialId) { + memberRepository.findBySocialTypeAndSocialId(socialType, socialId) + .ifPresent(member -> { + throw new IllegalArgumentException("이미 존재하는 회원입니다."); + }); + } + + private void validateNickname(String nickname) { + memberRepository.findByNickname(nickname) + .ifPresent(member -> { + throw new IllegalArgumentException("이미 존재하는 닉네임입니다."); + }); + } } diff --git a/src/main/java/com/teamzzong/hacker/domain/Member.java b/src/main/java/com/teamzzong/hacker/domain/Member.java index 3d4a3c9..6ac9702 100644 --- a/src/main/java/com/teamzzong/hacker/domain/Member.java +++ b/src/main/java/com/teamzzong/hacker/domain/Member.java @@ -6,10 +6,15 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; @Entity @Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor public class Member { @Id @@ -20,4 +25,10 @@ public class Member { private SocialType socialType; private String socialId; + + private String nickname; + + public Member(SocialType socialType, String socialId, String nickname) { + this(null, socialType, socialId, nickname); + } } diff --git a/src/main/java/com/teamzzong/hacker/dto/SignUpRequest.java b/src/main/java/com/teamzzong/hacker/dto/SignUpRequest.java new file mode 100644 index 0000000..b1de6ce --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/dto/SignUpRequest.java @@ -0,0 +1,11 @@ +package com.teamzzong.hacker.dto; + +import com.teamzzong.hacker.domain.SocialType; + +public record SignUpRequest( + SocialType socialType, + String socialId, + String username, + String nickname +) { +} diff --git a/src/main/java/com/teamzzong/hacker/dto/SignUpResponse.java b/src/main/java/com/teamzzong/hacker/dto/SignUpResponse.java new file mode 100644 index 0000000..8d86d6d --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/dto/SignUpResponse.java @@ -0,0 +1,7 @@ +package com.teamzzong.hacker.dto; + +public record SignUpResponse( + String accessToken, + String refreshToken +) { +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/MemberRepository.java b/src/main/java/com/teamzzong/hacker/infrastructure/MemberRepository.java index 6f5990f..de0819d 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/MemberRepository.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/MemberRepository.java @@ -10,4 +10,6 @@ public interface MemberRepository extends JpaRepository { Optional findBySocialTypeAndSocialId(SocialType socialType, String socialId); + + Optional findByNickname(String nickname); } From 5361b21a75ac796c747dddb13c8228950d562637 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Fri, 1 Sep 2023 12:34:19 +0900 Subject: [PATCH 12/14] =?UTF-8?q?refactor:=20AuthTokens=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=A0=95=EC=9D=98=EB=A1=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../teamzzong/hacker/application/AuthService.java | 12 +++++++----- .../java/com/teamzzong/hacker/domain/AuthTokens.java | 12 ++++++++++++ .../java/com/teamzzong/hacker/dto/LoginResponse.java | 6 ++++-- .../com/teamzzong/hacker/dto/SignUpResponse.java | 9 +++++++++ 4 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/teamzzong/hacker/domain/AuthTokens.java diff --git a/src/main/java/com/teamzzong/hacker/application/AuthService.java b/src/main/java/com/teamzzong/hacker/application/AuthService.java index d834c00..a327790 100644 --- a/src/main/java/com/teamzzong/hacker/application/AuthService.java +++ b/src/main/java/com/teamzzong/hacker/application/AuthService.java @@ -8,6 +8,7 @@ import com.teamzzong.hacker.application.oauth.OauthClients; import com.teamzzong.hacker.domain.AuthTokenPayload; import com.teamzzong.hacker.domain.AuthTokenType; +import com.teamzzong.hacker.domain.AuthTokens; import com.teamzzong.hacker.domain.Member; import com.teamzzong.hacker.domain.SocialType; import com.teamzzong.hacker.dto.LoginResponse; @@ -34,20 +35,21 @@ public LoginResponse login(SocialType socialType, String code) { return LoginResponse.signUp(userInfo); } Member member = optionalMember.get(); + return LoginResponse.login(createAuthToken(member)); + } + + private AuthTokens createAuthToken(Member member) { AuthTokenPayload payload = new AuthTokenPayload(member.getId()); String accessToken = tokenProvider.generate(AuthTokenType.ACCESS, payload); String refreshToken = tokenProvider.generate(AuthTokenType.REFRESH, payload); - return LoginResponse.login(accessToken, refreshToken); + return new AuthTokens(accessToken, refreshToken); } public SignUpResponse signUp(SignUpRequest request) { validateExistMember(request.socialType(), request.socialId()); validateNickname(request.nickname()); Member member = memberRepository.save(new Member(request.socialType(), request.socialId(), request.nickname())); - AuthTokenPayload payload = new AuthTokenPayload(member.getId()); - String accessToken = tokenProvider.generate(AuthTokenType.ACCESS, payload); - String refreshToken = tokenProvider.generate(AuthTokenType.REFRESH, payload); - return new SignUpResponse(accessToken, refreshToken); + return SignUpResponse.from(createAuthToken(member)); } private void validateExistMember(SocialType socialType, String socialId) { diff --git a/src/main/java/com/teamzzong/hacker/domain/AuthTokens.java b/src/main/java/com/teamzzong/hacker/domain/AuthTokens.java new file mode 100644 index 0000000..99baa4b --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/domain/AuthTokens.java @@ -0,0 +1,12 @@ +package com.teamzzong.hacker.domain; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class AuthTokens { + + private final String accessToken; + private final String refreshToken; +} diff --git a/src/main/java/com/teamzzong/hacker/dto/LoginResponse.java b/src/main/java/com/teamzzong/hacker/dto/LoginResponse.java index 66ab862..9d66cc1 100644 --- a/src/main/java/com/teamzzong/hacker/dto/LoginResponse.java +++ b/src/main/java/com/teamzzong/hacker/dto/LoginResponse.java @@ -1,15 +1,17 @@ package com.teamzzong.hacker.dto; +import com.teamzzong.hacker.domain.AuthTokens; + public record LoginResponse( Boolean isNew, LoginTokenResponse tokens, LoginUserInfoResponse userInfo ) { - public static LoginResponse login(String accessToken, String refreshToken) { + public static LoginResponse login(AuthTokens authTokens) { return new LoginResponse( false, - new LoginTokenResponse(accessToken, refreshToken), + new LoginTokenResponse(authTokens.getAccessToken(), authTokens.getRefreshToken()), null ); } diff --git a/src/main/java/com/teamzzong/hacker/dto/SignUpResponse.java b/src/main/java/com/teamzzong/hacker/dto/SignUpResponse.java index 8d86d6d..a53dfd3 100644 --- a/src/main/java/com/teamzzong/hacker/dto/SignUpResponse.java +++ b/src/main/java/com/teamzzong/hacker/dto/SignUpResponse.java @@ -1,7 +1,16 @@ package com.teamzzong.hacker.dto; +import com.teamzzong.hacker.domain.AuthTokens; + public record SignUpResponse( String accessToken, String refreshToken ) { + + public static SignUpResponse from(AuthTokens authTokens) { + return new SignUpResponse( + authTokens.getAccessToken(), + authTokens.getRefreshToken() + ); + } } From c3b15dc1786fe826246f43e900446effc8142c35 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Fri, 1 Sep 2023 12:36:20 +0900 Subject: [PATCH 13/14] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java b/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java index a79136e..145525d 100644 --- a/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java +++ b/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java @@ -17,7 +17,7 @@ public class JwtAuthConfig { private final JwtAuthProperty property; @Bean - public AuthTokenProvider authTokenProvider(JwtAuthProperty property) { + public AuthTokenProvider authTokenProvider() { return JwtAuthTokenProvider.from(property); } } From 3f69b61f01f9c87c7e05422f3e1988393211c884 Mon Sep 17 00:00:00 2001 From: xxeol2 Date: Fri, 1 Sep 2023 22:53:06 +0900 Subject: [PATCH 14/14] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EC=9D=B8=EC=A6=9D/=EC=9D=B8=EA=B0=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 --- .../{JwtAuthConfig.java => AuthConfig.java} | 2 +- .../teamzzong/hacker/config/LoginConfig.java | 24 +++++++++ .../com/teamzzong/hacker/dto/LoginMember.java | 6 +++ .../oauth/GithubExceptionHandler.java | 2 +- .../oauth/GithubUserInfoClient.java | 1 - .../hacker/presentation/AuthController.java | 27 ++++++++++ .../teamzzong/hacker/presentation/Login.java | 11 ++++ .../presentation/LoginMemberResolver.java | 53 +++++++++++++++++++ 8 files changed, 123 insertions(+), 3 deletions(-) rename src/main/java/com/teamzzong/hacker/config/{JwtAuthConfig.java => AuthConfig.java} (95%) create mode 100644 src/main/java/com/teamzzong/hacker/config/LoginConfig.java create mode 100644 src/main/java/com/teamzzong/hacker/dto/LoginMember.java create mode 100644 src/main/java/com/teamzzong/hacker/presentation/AuthController.java create mode 100644 src/main/java/com/teamzzong/hacker/presentation/Login.java create mode 100644 src/main/java/com/teamzzong/hacker/presentation/LoginMemberResolver.java diff --git a/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java b/src/main/java/com/teamzzong/hacker/config/AuthConfig.java similarity index 95% rename from src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java rename to src/main/java/com/teamzzong/hacker/config/AuthConfig.java index 145525d..8c2c4f2 100644 --- a/src/main/java/com/teamzzong/hacker/config/JwtAuthConfig.java +++ b/src/main/java/com/teamzzong/hacker/config/AuthConfig.java @@ -12,7 +12,7 @@ @Configuration @EnableConfigurationProperties(JwtAuthProperty.class) @AllArgsConstructor -public class JwtAuthConfig { +public class AuthConfig { private final JwtAuthProperty property; diff --git a/src/main/java/com/teamzzong/hacker/config/LoginConfig.java b/src/main/java/com/teamzzong/hacker/config/LoginConfig.java new file mode 100644 index 0000000..e42be30 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/config/LoginConfig.java @@ -0,0 +1,24 @@ +package com.teamzzong.hacker.config; + +import java.util.List; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.teamzzong.hacker.presentation.LoginMemberResolver; + +@Configuration +public class LoginConfig implements WebMvcConfigurer { + + private final LoginMemberResolver loginMemberResolver; + + public LoginConfig(LoginMemberResolver loginMemberResolver) { + this.loginMemberResolver = loginMemberResolver; + } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(loginMemberResolver); + } +} diff --git a/src/main/java/com/teamzzong/hacker/dto/LoginMember.java b/src/main/java/com/teamzzong/hacker/dto/LoginMember.java new file mode 100644 index 0000000..432112b --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/dto/LoginMember.java @@ -0,0 +1,6 @@ +package com.teamzzong.hacker.dto; + +public record LoginMember( + Long memberId +) { +} diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubExceptionHandler.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubExceptionHandler.java index 889037b..4bf466e 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubExceptionHandler.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubExceptionHandler.java @@ -17,7 +17,7 @@ public void handleError(ClientHttpResponse response) throws IOException { HttpStatusCode statusCode = response.getStatusCode(); if (statusCode.is4xxClientError()) { log4xxError(response); - throw new IllegalArgumentException(); // TODO + // throw new IllegalArgumentException(); // TODO } if (statusCode.is5xxServerError()) { throw new IllegalArgumentException("[Github] Internal Server Error"); // TODO diff --git a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java index 4eef1f8..46cfadc 100644 --- a/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java +++ b/src/main/java/com/teamzzong/hacker/infrastructure/oauth/GithubUserInfoClient.java @@ -28,7 +28,6 @@ public GithubUserInfoClient(GithubOauthProperty property, RestTemplateBuilder re public UserInfo request(String accessToken) { HttpHeaders headers = getHttpHeaders(accessToken); - System.out.println("property = " + property); GithubUserInfoResponse response = restTemplate .exchange( property.userInfoUri(), diff --git a/src/main/java/com/teamzzong/hacker/presentation/AuthController.java b/src/main/java/com/teamzzong/hacker/presentation/AuthController.java new file mode 100644 index 0000000..0b51288 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/presentation/AuthController.java @@ -0,0 +1,27 @@ +package com.teamzzong.hacker.presentation; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.teamzzong.hacker.application.AuthService; +import com.teamzzong.hacker.domain.SocialType; +import com.teamzzong.hacker.dto.LoginResponse; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +public class AuthController { + + private final AuthService authService; + + @PostMapping("/login/{socialType}/{code}") + public ResponseEntity login(@PathVariable SocialType socialType, @PathVariable String code) { + LoginResponse login = authService.login(socialType, code); + return ResponseEntity.ok(login); + } +} diff --git a/src/main/java/com/teamzzong/hacker/presentation/Login.java b/src/main/java/com/teamzzong/hacker/presentation/Login.java new file mode 100644 index 0000000..96ee334 --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/presentation/Login.java @@ -0,0 +1,11 @@ +package com.teamzzong.hacker.presentation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface Login { +} diff --git a/src/main/java/com/teamzzong/hacker/presentation/LoginMemberResolver.java b/src/main/java/com/teamzzong/hacker/presentation/LoginMemberResolver.java new file mode 100644 index 0000000..426064c --- /dev/null +++ b/src/main/java/com/teamzzong/hacker/presentation/LoginMemberResolver.java @@ -0,0 +1,53 @@ +package com.teamzzong.hacker.presentation; + +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import com.teamzzong.hacker.application.AuthTokenProvider; +import com.teamzzong.hacker.domain.AuthTokenPayload; +import com.teamzzong.hacker.domain.AuthTokenType; +import com.teamzzong.hacker.dto.LoginMember; + +import lombok.AllArgsConstructor; + +@Component +@AllArgsConstructor +public class LoginMemberResolver implements HandlerMethodArgumentResolver { + + private static final String BEARER_TOKEN_PREFIX = "Bearer "; + + private final AuthTokenProvider authTokenProvider; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().equals(LoginMember.class) && parameter.hasParameterAnnotation(Login.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { + String header = webRequest.getHeader(HttpHeaders.AUTHORIZATION); + String token = extractToken(header); + AuthTokenPayload payload = authTokenProvider.extract(AuthTokenType.ACCESS, token); + return new LoginMember(payload.memberId()); + } + + private String extractToken(String header) { + validateHeader(header); + return header.substring(BEARER_TOKEN_PREFIX.length()).trim(); + } + + private void validateHeader(String header) { + if (header == null) { + throw new IllegalArgumentException("토큰이 없습니다."); // TODO + } + if (!header.toLowerCase().startsWith(BEARER_TOKEN_PREFIX.toLowerCase())) { + throw new IllegalArgumentException("Bearer Type의 토큰이 아닙니다."); // TODO + } + } +}