From a872a51b2b283590e7494abc092a4d10b361e8de Mon Sep 17 00:00:00 2001 From: Oh-Jisong Date: Mon, 6 Jul 2026 16:12:45 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Google=20OAuth=20code=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EA=B5=90=ED=99=98=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=B2=98=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- load-env.ps1 | 15 ++++ src/main/java/ceos/ipx/IpxBeApplication.java | 2 + .../auth/controller/AuthController.java | 24 ++++++ .../auth/dto/GoogleOAuthTokenRequest.java | 13 ++++ .../domain/auth/dto/GoogleTokenResponse.java | 24 ++++++ .../auth/dto/GoogleUserInfoResponse.java | 9 +++ .../ipx/domain/auth/dto/OAuthLoginStatus.java | 6 ++ .../auth/dto/OAuthSignupRequiredResponse.java | 20 +++++ .../domain/auth/dto/OAuthTokenResponse.java | 62 +++++++++++++++ .../ipx/domain/auth/service/AuthService.java | 75 ++++++++++++++++++- .../auth/service/GoogleOAuthClient.java | 72 ++++++++++++++++++ .../auth/service/OAuthSignupTokenService.java | 44 +++++++++++ .../ceos/ipx/global/config/JacksonConfig.java | 14 ++++ .../ipx/global/config/WebClientConfig.java | 14 ++++ .../ceos/ipx/global/exception/ErrorCode.java | 4 + src/main/resources/application-local.yml | 2 +- src/main/resources/application.yaml | 2 +- 18 files changed, 402 insertions(+), 4 deletions(-) create mode 100644 load-env.ps1 create mode 100644 src/main/java/ceos/ipx/domain/auth/dto/GoogleOAuthTokenRequest.java create mode 100644 src/main/java/ceos/ipx/domain/auth/dto/GoogleTokenResponse.java create mode 100644 src/main/java/ceos/ipx/domain/auth/dto/GoogleUserInfoResponse.java create mode 100644 src/main/java/ceos/ipx/domain/auth/dto/OAuthLoginStatus.java create mode 100644 src/main/java/ceos/ipx/domain/auth/dto/OAuthSignupRequiredResponse.java create mode 100644 src/main/java/ceos/ipx/domain/auth/dto/OAuthTokenResponse.java create mode 100644 src/main/java/ceos/ipx/domain/auth/service/GoogleOAuthClient.java create mode 100644 src/main/java/ceos/ipx/domain/auth/service/OAuthSignupTokenService.java create mode 100644 src/main/java/ceos/ipx/global/config/JacksonConfig.java create mode 100644 src/main/java/ceos/ipx/global/config/WebClientConfig.java diff --git a/.gitignore b/.gitignore index 2b4b4e9..2d9a482 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,8 @@ out/ ### environment files ### .env .env.prod +load-env.ps1 ### docker test ### -docker-compose.override.yml \ No newline at end of file +docker-compose.override.yml + diff --git a/load-env.ps1 b/load-env.ps1 new file mode 100644 index 0000000..ee5db35 --- /dev/null +++ b/load-env.ps1 @@ -0,0 +1,15 @@ +Get-Content ".env" | ForEach-Object { + $line = $_.Trim() + + if ($line -eq "" -or $line.StartsWith("#")) { + return + } + + $name, $value = $line.Split("=", 2) + + if ($name -and $value) { + [Environment]::SetEnvironmentVariable($name.Trim(), $value.Trim(), "Process") + } +} + +Write-Host "Loaded .env variables into current PowerShell process." \ No newline at end of file diff --git a/src/main/java/ceos/ipx/IpxBeApplication.java b/src/main/java/ceos/ipx/IpxBeApplication.java index 70641c8..35ed8f2 100644 --- a/src/main/java/ceos/ipx/IpxBeApplication.java +++ b/src/main/java/ceos/ipx/IpxBeApplication.java @@ -5,12 +5,14 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @EnableConfigurationProperties({ GoogleOAuthProperties.class, FrontendOAuthProperties.class }) @SpringBootApplication +@ConfigurationPropertiesScan public class IpxBeApplication { public static void main(String[] args) { diff --git a/src/main/java/ceos/ipx/domain/auth/controller/AuthController.java b/src/main/java/ceos/ipx/domain/auth/controller/AuthController.java index fd5b261..43ac0ac 100644 --- a/src/main/java/ceos/ipx/domain/auth/controller/AuthController.java +++ b/src/main/java/ceos/ipx/domain/auth/controller/AuthController.java @@ -26,6 +26,8 @@ import ceos.ipx.domain.auth.service.GoogleOAuthService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; +import ceos.ipx.domain.auth.dto.GoogleOAuthTokenRequest; +import ceos.ipx.domain.auth.dto.OAuthTokenResponse; import java.net.URI; @@ -165,4 +167,26 @@ public ResponseEntity handleGoogleOAuthCallback( .location(URI.create(frontendCallbackUrl)) .build(); } + + @Operation( + summary = "Google OAuth code 토큰 교환 및 로그인 처리", + description = "FE OAuth Callback 화면에서 받은 Google Authorization Code를 전달받아 Google 사용자 정보를 조회하고, 기존 Google 회원이면 로그인 성공 처리하며 신규 Google 사용자이면 추가 회원가입 필요 상태를 반환합니다." + ) + @ApiResponses({ + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Google OAuth 로그인 성공 또는 추가 회원가입 필요"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "입력값 검증 실패"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "비활성화된 계정"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "일반 로그인 계정으로 가입된 이메일"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "502", description = "Google 토큰 교환 또는 사용자 정보 조회 실패"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "500", description = "서버 내부 오류") + }) + @PostMapping("/oauth/token") + public ResponseEntity> exchangeGoogleOAuthToken( + @Valid @RequestBody GoogleOAuthTokenRequest request, + HttpServletResponse httpServletResponse + ) { + OAuthTokenResponse response = authService.exchangeGoogleOAuthToken(request, httpServletResponse); + + return ResponseEntity.ok(ApiResponse.ok(response)); + } } \ No newline at end of file diff --git a/src/main/java/ceos/ipx/domain/auth/dto/GoogleOAuthTokenRequest.java b/src/main/java/ceos/ipx/domain/auth/dto/GoogleOAuthTokenRequest.java new file mode 100644 index 0000000..7374789 --- /dev/null +++ b/src/main/java/ceos/ipx/domain/auth/dto/GoogleOAuthTokenRequest.java @@ -0,0 +1,13 @@ +package ceos.ipx.domain.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; + +@Schema(description = "Google OAuth code 토큰 교환 요청") +public record GoogleOAuthTokenRequest( + + @NotBlank(message = "Google Authorization Code는 필수입니다.") + @Schema(description = "Google Authorization Code", example = "4/0AbCdEf...") + String code +) { +} \ No newline at end of file diff --git a/src/main/java/ceos/ipx/domain/auth/dto/GoogleTokenResponse.java b/src/main/java/ceos/ipx/domain/auth/dto/GoogleTokenResponse.java new file mode 100644 index 0000000..afa7ca1 --- /dev/null +++ b/src/main/java/ceos/ipx/domain/auth/dto/GoogleTokenResponse.java @@ -0,0 +1,24 @@ +package ceos.ipx.domain.auth.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record GoogleTokenResponse( + + @JsonProperty("access_token") + String accessToken, + + @JsonProperty("expires_in") + Long expiresIn, + + @JsonProperty("refresh_token") + String refreshToken, + + String scope, + + @JsonProperty("token_type") + String tokenType, + + @JsonProperty("id_token") + String idToken +) { +} \ No newline at end of file diff --git a/src/main/java/ceos/ipx/domain/auth/dto/GoogleUserInfoResponse.java b/src/main/java/ceos/ipx/domain/auth/dto/GoogleUserInfoResponse.java new file mode 100644 index 0000000..0e731b2 --- /dev/null +++ b/src/main/java/ceos/ipx/domain/auth/dto/GoogleUserInfoResponse.java @@ -0,0 +1,9 @@ +package ceos.ipx.domain.auth.dto; + +public record GoogleUserInfoResponse( + String id, + String email, + String name, + String picture +) { +} \ No newline at end of file diff --git a/src/main/java/ceos/ipx/domain/auth/dto/OAuthLoginStatus.java b/src/main/java/ceos/ipx/domain/auth/dto/OAuthLoginStatus.java new file mode 100644 index 0000000..dbe4159 --- /dev/null +++ b/src/main/java/ceos/ipx/domain/auth/dto/OAuthLoginStatus.java @@ -0,0 +1,6 @@ +package ceos.ipx.domain.auth.dto; + +public enum OAuthLoginStatus { + LOGIN_SUCCESS, + NEED_SIGNUP +} \ No newline at end of file diff --git a/src/main/java/ceos/ipx/domain/auth/dto/OAuthSignupRequiredResponse.java b/src/main/java/ceos/ipx/domain/auth/dto/OAuthSignupRequiredResponse.java new file mode 100644 index 0000000..21781b8 --- /dev/null +++ b/src/main/java/ceos/ipx/domain/auth/dto/OAuthSignupRequiredResponse.java @@ -0,0 +1,20 @@ +package ceos.ipx.domain.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "OAuth 회원가입 필요 응답") +public record OAuthSignupRequiredResponse( + + @Schema(description = "OAuth 회원가입 토큰") + String oauthSignupToken, + + @Schema(description = "Google 이메일", example = "test@gmail.com") + String email, + + @Schema(description = "Google 이름", example = "홍길동") + String name, + + @Schema(description = "가입 제공자", example = "GOOGLE") + String provider +) { +} \ No newline at end of file diff --git a/src/main/java/ceos/ipx/domain/auth/dto/OAuthTokenResponse.java b/src/main/java/ceos/ipx/domain/auth/dto/OAuthTokenResponse.java new file mode 100644 index 0000000..f366f95 --- /dev/null +++ b/src/main/java/ceos/ipx/domain/auth/dto/OAuthTokenResponse.java @@ -0,0 +1,62 @@ +package ceos.ipx.domain.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "OAuth token 교환 결과 응답") +public record OAuthTokenResponse( + + @Schema(description = "OAuth 로그인 처리 상태", example = "LOGIN_SUCCESS") + OAuthLoginStatus status, + + @Schema(description = "JWT AccessToken") + String accessToken, + + @Schema(description = "토큰 타입", example = "Bearer") + String tokenType, + + @Schema(description = "AccessToken 만료 시간(초)", example = "3600") + Long expiresIn, + + @Schema(description = "로그인한 사용자 정보") + LoginUserResponse user, + + @Schema(description = "OAuth 회원가입 토큰") + String oauthSignupToken, + + @Schema(description = "Google 이메일", example = "test@gmail.com") + String email, + + @Schema(description = "Google 이름", example = "홍길동") + String name, + + @Schema(description = "가입 제공자", example = "GOOGLE") + String provider +) { + public static OAuthTokenResponse loginSuccess(LoginResponse loginResponse) { + return new OAuthTokenResponse( + OAuthLoginStatus.LOGIN_SUCCESS, + loginResponse.accessToken(), + loginResponse.tokenType(), + loginResponse.expiresIn(), + loginResponse.user(), + null, + null, + null, + null + ); + } + + public static OAuthTokenResponse needSignup(OAuthSignupRequiredResponse signupResponse) { + return new OAuthTokenResponse( + OAuthLoginStatus.NEED_SIGNUP, + null, + null, + null, + null, + signupResponse.oauthSignupToken(), + signupResponse.email(), + signupResponse.name(), + signupResponse.provider() + ); + } +} \ No newline at end of file diff --git a/src/main/java/ceos/ipx/domain/auth/service/AuthService.java b/src/main/java/ceos/ipx/domain/auth/service/AuthService.java index 7211588..e9ad369 100644 --- a/src/main/java/ceos/ipx/domain/auth/service/AuthService.java +++ b/src/main/java/ceos/ipx/domain/auth/service/AuthService.java @@ -20,7 +20,11 @@ import ceos.ipx.domain.auth.dto.PasswordResetRequest; import ceos.ipx.domain.user.entity.UserProvider; import java.util.regex.Pattern; - +import ceos.ipx.domain.auth.dto.GoogleOAuthTokenRequest; +import ceos.ipx.domain.auth.dto.GoogleTokenResponse; +import ceos.ipx.domain.auth.dto.GoogleUserInfoResponse; +import ceos.ipx.domain.auth.dto.OAuthSignupRequiredResponse; +import ceos.ipx.domain.auth.dto.OAuthTokenResponse; @Service @RequiredArgsConstructor @@ -39,6 +43,8 @@ public class AuthService { private final AccessTokenBlacklistService accessTokenBlacklistService; private final CookieUtils cookieUtils; private final EmailVerificationService emailVerificationService; + private final GoogleOAuthClient googleOAuthClient; + private final OAuthSignupTokenService oauthSignupTokenService; @Transactional public SignUpResponse signUp(SignUpRequest request) { @@ -111,6 +117,19 @@ public LoginResponse login(LoginRequest request, HttpServletResponse httpServlet ); } + @Transactional + public OAuthTokenResponse exchangeGoogleOAuthToken( + GoogleOAuthTokenRequest request, + HttpServletResponse httpServletResponse + ) { + GoogleTokenResponse googleTokenResponse = googleOAuthClient.exchangeCodeForToken(request.code()); + GoogleUserInfoResponse googleUserInfo = googleOAuthClient.getUserInfo(googleTokenResponse.accessToken()); + + return userRepository.findByEmail(googleUserInfo.email()) + .map(user -> handleExistingGoogleOAuthUser(user, httpServletResponse)) + .orElseGet(() -> handleNewGoogleOAuthUser(googleUserInfo)); + } + public ReissueResponse reissue(String refreshToken, HttpServletResponse httpServletResponse) { if (refreshToken == null || refreshToken.isBlank()) { throw new BusinessException(ErrorCode.REFRESH_TOKEN_NOT_FOUND); @@ -176,6 +195,47 @@ public void resetPassword(PasswordResetRequest request) { emailVerificationService.deletePasswordResetVerification(request.getVerificationToken(), email); } + private OAuthTokenResponse handleExistingGoogleOAuthUser( + User user, + HttpServletResponse httpServletResponse + ) { + if (!user.isActive()) { + throw new BusinessException(ErrorCode.INACTIVE_USER); + } + + if (user.getProvider() == UserProvider.LOCAL) { + throw new BusinessException(ErrorCode.SOCIAL_LOGIN_NOT_ALLOWED); + } + + if (user.getProvider() != UserProvider.GOOGLE) { + throw new BusinessException(ErrorCode.LOGIN_FAILED); + } + + String accessToken = jwtTokenProvider.createAccessToken(user); + String refreshToken = jwtTokenProvider.createRefreshToken(user); + + refreshTokenService.saveRefreshToken( + user.getId(), + refreshToken, + jwtTokenProvider.getRefreshTokenExpirationSeconds() + ); + + cookieUtils.addRefreshTokenCookie( + httpServletResponse, + refreshToken, + jwtTokenProvider.getRefreshTokenExpirationSeconds() + ); + + LoginResponse loginResponse = new LoginResponse( + accessToken, + jwtTokenProvider.getTokenType(), + jwtTokenProvider.getAccessTokenExpirationSeconds(), + LoginUserResponse.from(user) + ); + + return OAuthTokenResponse.loginSuccess(loginResponse); + } + private void validatePasswordResetUser(User user) { if (!user.isActive()) { throw new BusinessException(ErrorCode.INACTIVE_USER); @@ -204,6 +264,19 @@ private void validateNewPassword(String newPassword, String newPasswordConfirm, } } + private OAuthTokenResponse handleNewGoogleOAuthUser(GoogleUserInfoResponse googleUserInfo) { + String oauthSignupToken = oauthSignupTokenService.saveGoogleUserInfo(googleUserInfo); + + OAuthSignupRequiredResponse signupResponse = new OAuthSignupRequiredResponse( + oauthSignupToken, + googleUserInfo.email(), + googleUserInfo.name(), + UserProvider.GOOGLE.name() + ); + + return OAuthTokenResponse.needSignup(signupResponse); + } + @Transactional public void logout( String authorizationHeader, diff --git a/src/main/java/ceos/ipx/domain/auth/service/GoogleOAuthClient.java b/src/main/java/ceos/ipx/domain/auth/service/GoogleOAuthClient.java new file mode 100644 index 0000000..4466f8b --- /dev/null +++ b/src/main/java/ceos/ipx/domain/auth/service/GoogleOAuthClient.java @@ -0,0 +1,72 @@ +package ceos.ipx.domain.auth.service; + +import ceos.ipx.domain.auth.dto.GoogleTokenResponse; +import ceos.ipx.domain.auth.dto.GoogleUserInfoResponse; +import ceos.ipx.global.config.GoogleOAuthProperties; +import ceos.ipx.global.exception.BusinessException; +import ceos.ipx.global.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +@RequiredArgsConstructor +public class GoogleOAuthClient { + + private static final String GRANT_TYPE_AUTHORIZATION_CODE = "authorization_code"; + + private final GoogleOAuthProperties googleOAuthProperties; + + public GoogleTokenResponse exchangeCodeForToken(String code) { + try { + GoogleTokenResponse response = WebClient.create() + .post() + .uri(googleOAuthProperties.tokenUri()) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(BodyInserters.fromFormData("code", code) + .with("client_id", googleOAuthProperties.clientId()) + .with("client_secret", googleOAuthProperties.clientSecret()) + .with("redirect_uri", googleOAuthProperties.redirectUri()) + .with("grant_type", GRANT_TYPE_AUTHORIZATION_CODE)) + .retrieve() + .bodyToMono(GoogleTokenResponse.class) + .block(); + + if (response == null || response.accessToken() == null || response.accessToken().isBlank()) { + throw new BusinessException(ErrorCode.OAUTH_TOKEN_EXCHANGE_FAILED); + } + + return response; + } catch (BusinessException e) { + throw e; + } catch (RuntimeException e) { + throw new BusinessException(ErrorCode.OAUTH_TOKEN_EXCHANGE_FAILED); + } + } + + public GoogleUserInfoResponse getUserInfo(String googleAccessToken) { + try { + GoogleUserInfoResponse response = WebClient.create() + .get() + .uri(googleOAuthProperties.userInfoUri()) + .headers(headers -> headers.setBearerAuth(googleAccessToken)) + .retrieve() + .bodyToMono(GoogleUserInfoResponse.class) + .block(); + + if (response == null + || response.id() == null || response.id().isBlank() + || response.email() == null || response.email().isBlank()) { + throw new BusinessException(ErrorCode.OAUTH_USER_INFO_FAILED); + } + + return response; + } catch (BusinessException e) { + throw e; + } catch (RuntimeException e) { + throw new BusinessException(ErrorCode.OAUTH_USER_INFO_FAILED); + } + } +} \ No newline at end of file diff --git a/src/main/java/ceos/ipx/domain/auth/service/OAuthSignupTokenService.java b/src/main/java/ceos/ipx/domain/auth/service/OAuthSignupTokenService.java new file mode 100644 index 0000000..c636777 --- /dev/null +++ b/src/main/java/ceos/ipx/domain/auth/service/OAuthSignupTokenService.java @@ -0,0 +1,44 @@ +package ceos.ipx.domain.auth.service; + +import ceos.ipx.domain.auth.dto.GoogleUserInfoResponse; +import ceos.ipx.global.exception.BusinessException; +import ceos.ipx.global.exception.ErrorCode; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class OAuthSignupTokenService { + + private static final String OAUTH_SIGNUP_TOKEN_KEY_PREFIX = "oauth:google:signup:"; + private static final Duration OAUTH_SIGNUP_TOKEN_TTL = Duration.ofMinutes(10); + + private final StringRedisTemplate stringRedisTemplate; + private final ObjectMapper objectMapper; + + public String saveGoogleUserInfo(GoogleUserInfoResponse googleUserInfo) { + String token = UUID.randomUUID().toString(); + String key = createKey(token); + + try { + String value = objectMapper.writeValueAsString(googleUserInfo); + + stringRedisTemplate.opsForValue() + .set(key, value, OAUTH_SIGNUP_TOKEN_TTL); + + return token; + } catch (JsonProcessingException e) { + throw new BusinessException(ErrorCode.INTERNAL_SERVER_ERROR); + } + } + + private String createKey(String token) { + return OAUTH_SIGNUP_TOKEN_KEY_PREFIX + token; + } +} \ No newline at end of file diff --git a/src/main/java/ceos/ipx/global/config/JacksonConfig.java b/src/main/java/ceos/ipx/global/config/JacksonConfig.java new file mode 100644 index 0000000..3bf9931 --- /dev/null +++ b/src/main/java/ceos/ipx/global/config/JacksonConfig.java @@ -0,0 +1,14 @@ +package ceos.ipx.global.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JacksonConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } +} \ No newline at end of file diff --git a/src/main/java/ceos/ipx/global/config/WebClientConfig.java b/src/main/java/ceos/ipx/global/config/WebClientConfig.java new file mode 100644 index 0000000..e5cd2b7 --- /dev/null +++ b/src/main/java/ceos/ipx/global/config/WebClientConfig.java @@ -0,0 +1,14 @@ +package ceos.ipx.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +public class WebClientConfig { + + @Bean + public WebClient webClient() { + return WebClient.builder().build(); + } +} \ No newline at end of file diff --git a/src/main/java/ceos/ipx/global/exception/ErrorCode.java b/src/main/java/ceos/ipx/global/exception/ErrorCode.java index 34bf6f1..5edb080 100644 --- a/src/main/java/ceos/ipx/global/exception/ErrorCode.java +++ b/src/main/java/ceos/ipx/global/exception/ErrorCode.java @@ -30,6 +30,10 @@ public enum ErrorCode { INVALID_OAUTH_REDIRECT_URI(HttpStatus.BAD_REQUEST, "AU017", "허용되지 않은 OAuth redirectUri입니다."), OAUTH_URL_GENERATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "AU018", "OAuth URL 생성에 실패했습니다."), INVALID_OAUTH_STATE(HttpStatus.BAD_REQUEST, "AU019", "유효하지 않은 OAuth state입니다."), + OAUTH_TOKEN_EXCHANGE_FAILED(HttpStatus.BAD_GATEWAY, "AU020", "Google 토큰 교환에 실패했습니다."), + OAUTH_USER_INFO_FAILED(HttpStatus.BAD_GATEWAY, "AU021", "Google 사용자 정보 조회에 실패했습니다."), + SOCIAL_LOGIN_NOT_ALLOWED(HttpStatus.CONFLICT, "AU022", "일반 로그인으로 가입된 이메일입니다."), + SOCIAL_ACCOUNT_LOGIN_NOT_ALLOWED(HttpStatus.FORBIDDEN, "AU023", "소셜 로그인 계정은 일반 로그인을 사용할 수 없습니다."), UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "SC001", "인증이 필요합니다."), ACCESS_DENIED(HttpStatus.FORBIDDEN, "SC002", "해당 요청에 권한이 없습니다."); diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 0d32ae7..e5eb83c 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -16,7 +16,7 @@ spring: redis: host: localhost port: 6379 - password: ${REDIS_PASSWORD:} + password: ${REDIS_PASSWORD:local_dev_redis_password} mail: host: smtp.gmail.com diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4df4a30..561a741 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -31,7 +31,7 @@ google: oauth: client-id: ${GOOGLE_OAUTH_CLIENT_ID} client-secret: ${GOOGLE_OAUTH_CLIENT_SECRET} - redirect-uri: ${GOOGLE_OAUTH_REDIRECT_URI} + redirect-uri: ${GOOGLE_OAUTH_REDIRECT_URI:http://localhost:8080/api/auth/oauth/google/callback} auth-uri: https://accounts.google.com/o/oauth2/v2/auth token-uri: https://oauth2.googleapis.com/token user-info-uri: https://www.googleapis.com/oauth2/v2/userinfo