Skip to content

Commit 9ca4e18

Browse files
authored
Merge pull request #142 from Pinback-Team/feat/#141
feat: 구글 로그인 다중 uri 가능하도록 구현
2 parents 93aae6d + 094ebae commit 9ca4e18

10 files changed

Lines changed: 116 additions & 13 deletions

File tree

api/src/main/java/com/pinback/api/google/controller/GoogleLoginControllerV3.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import org.springframework.web.bind.annotation.RestController;
88

99
import com.pinback.api.auth.dto.request.SignUpRequestV3;
10-
import com.pinback.api.google.dto.request.GoogleLoginRequest;
10+
import com.pinback.api.google.dto.request.GoogleLoginRequestV3;
1111
import com.pinback.application.auth.dto.SignUpResponse;
1212
import com.pinback.application.auth.usecase.AuthUsecase;
1313
import com.pinback.application.google.dto.response.GoogleLoginResponseV3;
@@ -31,9 +31,9 @@ public class GoogleLoginControllerV3 {
3131
@Operation(summary = "구글 소셜 로그인 V3", description = "구글 소셜 로그인을 진행하며, 응답에 직무 선택 여부를 포함합니다.")
3232
@PostMapping("/google")
3333
public Mono<ResponseDto<GoogleLoginResponseV3>> googleLogin(
34-
@Valid @RequestBody GoogleLoginRequest request
34+
@Valid @RequestBody GoogleLoginRequestV3 request
3535
) {
36-
return googleUsecase.getUserInfo(request.toCommand())
36+
return googleUsecase.getUserInfoV3(request.toCommand())
3737
.flatMap(googleResponse -> {
3838
return authUsecase.getInfoAndTokenV3(googleResponse.email(), googleResponse.pictureUrl(),
3939
googleResponse.name())
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.pinback.api.google.dto.request;
2+
3+
import com.pinback.application.google.dto.GoogleLoginCommandV3;
4+
5+
import jakarta.validation.constraints.NotNull;
6+
7+
public record GoogleLoginRequestV3(
8+
@NotNull(message = "인가 코드(code)는 비어있을 수 없습니다.")
9+
String code,
10+
@NotNull(message = "구글 로그인 리다이렉션 uri는 비어있을 수 없습니다.")
11+
String uri
12+
) {
13+
public GoogleLoginCommandV3 toCommand() {
14+
return new GoogleLoginCommandV3(code, uri);
15+
}
16+
}

api/src/main/resources/application.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fcm: ${FCM_JSON}
3131
google:
3232
client-id: ${CLIENT_ID}
3333
client-secret: ${CLIENT_SECRET}
34-
redirect-uri: ${REDIRECT_URI}
34+
redirect-uris: ${REDIRECT_URI},${REDIRECT_URI_LOCAL1},${REDIRECT_URI_LOCAL2}
3535
token-uri: ${TOKEN_URI}
3636
user-info-uri: ${USER_INFO_URI}
3737

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.pinback.application.common.exception;
2+
3+
import com.pinback.shared.constant.ExceptionCode;
4+
import com.pinback.shared.exception.ApplicationException;
5+
6+
public class InvalidGoogleUriException extends ApplicationException {
7+
public InvalidGoogleUriException() {
8+
super(ExceptionCode.INVALID_REDIRECT_URI);
9+
}
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.pinback.application.google.dto;
2+
3+
public record GoogleLoginCommandV3(
4+
String code,
5+
String uri
6+
) {
7+
}

application/src/main/java/com/pinback/application/google/port/out/GoogleOAuthPort.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66

77
public interface GoogleOAuthPort {
88
Mono<GoogleUserInfoResponse> fetchUserInfo(String code);
9+
10+
Mono<GoogleUserInfoResponse> fetchUserInfoV3(String code, String uri);
911
}

application/src/main/java/com/pinback/application/google/service/GoogleOAuthClient.java

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.pinback.application.google.service;
22

3+
import java.util.List;
4+
35
import org.springframework.beans.factory.annotation.Qualifier;
46
import org.springframework.http.MediaType;
57
import org.springframework.stereotype.Service;
@@ -10,6 +12,7 @@
1012
import com.pinback.application.common.exception.GoogleNameMissingException;
1113
import com.pinback.application.common.exception.GoogleProfileImageMissingException;
1214
import com.pinback.application.common.exception.GoogleTokenMissingException;
15+
import com.pinback.application.common.exception.InvalidGoogleUriException;
1316
import com.pinback.application.google.dto.response.GoogleApiResponse;
1417
import com.pinback.application.google.dto.response.GoogleTokenResponse;
1518
import com.pinback.application.google.dto.response.GoogleUserInfoResponse;
@@ -24,21 +27,21 @@ public class GoogleOAuthClient implements GoogleOAuthPort {
2427
private final WebClient googleWebClient;
2528
private final String googleClientId;
2629
private final String googleClientSecret;
27-
private final String googleRedirectUri;
30+
private final List<String> googleRedirectUris;
2831
private final String googleTokenUri;
2932
private final String googleUserInfoUri;
3033

3134
public GoogleOAuthClient(
3235
WebClient googleWebClient,
3336
@Qualifier("googleClientId") String googleClientId,
3437
@Qualifier("googleClientSecret") String googleClientSecret,
35-
@Qualifier("googleRedirectUri") String googleRedirectUri,
38+
@Qualifier("googleRedirectUris") List<String> googleRedirectUris,
3639
@Qualifier("googleTokenUri") String googleTokenUri,
3740
@Qualifier("googleUserInfoUri") String googleUserInfoUri) {
3841
this.googleWebClient = googleWebClient;
3942
this.googleClientId = googleClientId;
4043
this.googleClientSecret = googleClientSecret;
41-
this.googleRedirectUri = googleRedirectUri;
44+
this.googleRedirectUris = googleRedirectUris;
4245
this.googleTokenUri = googleTokenUri;
4346
this.googleUserInfoUri = googleUserInfoUri;
4447
}
@@ -61,12 +64,65 @@ public Mono<GoogleUserInfoResponse> fetchUserInfo(String code) {
6164
});
6265
}
6366

67+
@Override
68+
public Mono<GoogleUserInfoResponse> fetchUserInfoV3(String code, String uri) {
69+
70+
return requestAccessTokenV3(code, uri)
71+
// 토큰 응답을 UserInfo 요청으로 변환하여 연결
72+
.flatMap(tokenResponse -> {
73+
74+
// Access Token 유효성 검증
75+
if (tokenResponse == null || tokenResponse.accessToken() == null) {
76+
log.info("tokenResponse: {}", tokenResponse);
77+
log.error("Google Access Token 획득 실패: 응답 본문에 토큰이 없습니다. Code: {}", code);
78+
return Mono.error(new GoogleTokenMissingException());
79+
}
80+
// Access Token으로 사용자 정보 요청
81+
return getUserInfo(tokenResponse.accessToken());
82+
});
83+
}
84+
6485
private Mono<GoogleTokenResponse> requestAccessToken(String code) {
65-
log.info("redirect: {}", googleRedirectUri);
86+
String firstRedirectUri = googleRedirectUris.getFirst();
87+
log.info("redirect: {}", firstRedirectUri);
88+
String requestBody = "code=" + code +
89+
"&client_id=" + googleClientId +
90+
"&client_secret=" + googleClientSecret +
91+
"&redirect_uri=" + firstRedirectUri +
92+
"&grant_type=authorization_code";
93+
94+
return googleWebClient.post()
95+
.uri(googleTokenUri)
96+
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
97+
.bodyValue(requestBody)
98+
.retrieve()
99+
// HTTP 오류 발생 시
100+
.onStatus(status -> status.isError(), clientResponse ->
101+
clientResponse.bodyToMono(String.class)
102+
.flatMap(body -> {
103+
String errorLog = String.format(
104+
"[GoogleOAuth API 에러] HTTP Status: %s, Detail: %s",
105+
clientResponse.statusCode(), body
106+
);
107+
log.error(errorLog);
108+
return Mono.error(new GoogleApiException());
109+
})
110+
)
111+
.bodyToMono(GoogleTokenResponse.class);
112+
}
113+
114+
private Mono<GoogleTokenResponse> requestAccessTokenV3(String code, String uri) {
115+
if (!googleRedirectUris.contains(uri)) {
116+
log.error("허용되지 않은 Redirect URI 요청: {}", uri);
117+
118+
return Mono.error(new InvalidGoogleUriException());
119+
}
120+
121+
log.info("redirect: {}", uri);
66122
String requestBody = "code=" + code +
67123
"&client_id=" + googleClientId +
68124
"&client_secret=" + googleClientSecret +
69-
"&redirect_uri=" + googleRedirectUri +
125+
"&redirect_uri=" + uri +
70126
"&grant_type=authorization_code";
71127

72128
return googleWebClient.post()

application/src/main/java/com/pinback/application/google/usecase/GoogleUsecase.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.springframework.stereotype.Service;
44

55
import com.pinback.application.google.dto.GoogleLoginCommand;
6+
import com.pinback.application.google.dto.GoogleLoginCommandV3;
67
import com.pinback.application.google.dto.response.GoogleUserInfoResponse;
78
import com.pinback.application.google.port.out.GoogleOAuthPort;
89

@@ -20,4 +21,12 @@ public Mono<GoogleUserInfoResponse> getUserInfo(GoogleLoginCommand command) {
2021

2122
return googleOAuthPort.fetchUserInfo(code);
2223
}
24+
25+
public Mono<GoogleUserInfoResponse> getUserInfoV3(GoogleLoginCommandV3 command) {
26+
27+
String code = command.code();
28+
String uri = command.uri();
29+
30+
return googleOAuthPort.fetchUserInfoV3(code, uri);
31+
}
2332
}

infrastructure/src/main/java/com/pinback/infrastructure/config/google/GoogleConfig.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.pinback.infrastructure.config.google;
22

3+
import java.util.List;
4+
35
import org.springframework.beans.factory.annotation.Value;
46
import org.springframework.context.annotation.Bean;
57
import org.springframework.context.annotation.Configuration;
@@ -14,8 +16,8 @@ public class GoogleConfig {
1416
@Value("${google.client-secret}")
1517
private String clientSecret;
1618

17-
@Value("${google.redirect-uri}")
18-
private String redirectUri;
19+
@Value("${google.redirect-uris}")
20+
private List<String> redirectUris;
1921

2022
@Value("${google.token-uri}")
2123
private String tokenUri;
@@ -39,8 +41,8 @@ public String googleClientSecret() {
3941
}
4042

4143
@Bean
42-
public String googleRedirectUri() {
43-
return redirectUri;
44+
public List<String> googleRedirectUris() {
45+
return redirectUris;
4446
}
4547

4648
@Bean

shared/src/main/java/com/pinback/shared/constant/ExceptionCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public enum ExceptionCode {
1515
INVALID_FCM_TOKEN(HttpStatus.BAD_REQUEST, "c40004", "유효하지 않은 FCM 토큰입니다."),
1616
INVALID_URL(HttpStatus.BAD_REQUEST, "c40005", "유효하지 않은 URL이거나 접속할 수 없는 사이트입니다."),
1717
INVALID_READSTATUS(HttpStatus.BAD_REQUEST, "c40006", "잘못된 read-status 상태 값입니다.(전체보기: 생략/안읽음: false)"),
18+
INVALID_REDIRECT_URI(HttpStatus.BAD_REQUEST, "c40007", "등록되지 않은 리다이렉트 uri 입니다"),
1819

1920
//401
2021
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "c40101", "유효하지 않은 토큰입니다."),

0 commit comments

Comments
 (0)