Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
import com.back.global.exception.ErrorCode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.MultipartBodyBuilder;
Expand All @@ -25,14 +25,21 @@
*/
@Slf4j
@Component
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "ai.image", name = "enabled", havingValue = "true")
public class StableDiffusionImageClient implements ImageAiClient {

private final ImageAiConfig imageAiConfig;
private final WebClient webClient;
private final ObjectMapper objectMapper = new ObjectMapper();

public StableDiffusionImageClient(
ImageAiConfig imageAiConfig,
@Qualifier("stabilityWebClient") WebClient webClient
) {
this.imageAiConfig = imageAiConfig;
this.webClient = webClient;
}

@Override
public CompletableFuture<String> generateImage(String prompt) {
return generateImage(prompt, Map.of());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.back.global.ai.config;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class ImageWebClientConfig {

@Bean("stabilityWebClient")
@ConditionalOnProperty(prefix = "ai.image", name = "enabled", havingValue = "true")
public WebClient stabilityWebClient(ImageAiConfig imageAiConfig) {
return WebClient.builder()
.baseUrl(imageAiConfig.getBaseUrl())
.defaultHeader(HttpHeaders.ACCEPT, "application/json")
.codecs(c -> c.defaultCodecs().maxInMemorySize(10 * 1024 * 1024))
.build();
}
}
19 changes: 19 additions & 0 deletions back/src/main/java/com/back/global/config/WebClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.back.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

// GitHub 전용 WebClient
@Bean
public WebClient githubWebClient() {
return WebClient.builder()
.baseUrl("https://api.github.com")
.defaultHeader(HttpHeaders.ACCEPT, "application/vnd.github+json")
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequ

private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
private final UserService userService;
private final GithubEmailFetcher githubEmailFetcher;

@Override
public OAuth2User loadUser(OAuth2UserRequest req) throws OAuth2AuthenticationException {
Expand All @@ -36,8 +37,17 @@ public OAuth2User loadUser(OAuth2UserRequest req) throws OAuth2AuthenticationExc
.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();

OAuthAttributes attrs = OAuthAttributes.of(registrationId, userNameAttr, raw.getAttributes());

String email = attrs.email();

// GitHub일 때 이메일이 비어 있으면 API로 재요청
if ("github".equalsIgnoreCase(registrationId) && (email == null || email.isBlank())) {
String accessToken = req.getAccessToken().getTokenValue();
email = githubEmailFetcher.fetchPrimaryEmail(accessToken);

log.info("GitHub 추가 조회로 얻은 이메일: {}", email);
}

// 여전히 이메일이 null이라면 예외 처리
if (email == null || email.isBlank()) {
throw new OAuth2AuthenticationException(
new OAuth2Error("email_not_found", "OAuth2 제공자로부터 이메일을 받을 수 없습니다.", null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.back.global.security.oauth2;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class GithubEmailFetcher {

private final WebClient github;

public GithubEmailFetcher(@Qualifier("githubWebClient") WebClient github) {
this.github = github;
}

public String fetchPrimaryEmail(String accessToken) {
try {
List<Map<String, Object>> emails = github.get()
.uri("/user/emails")
.header(HttpHeaders.AUTHORIZATION, "token " + accessToken)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Map<String, Object>>>() {})
.block();

if (emails == null || emails.isEmpty()) return null;

for (var e : emails)
if (Boolean.TRUE.equals(e.get("primary")) && Boolean.TRUE.equals(e.get("verified")))
return (String) e.get("email");

for (var e : emails)
if (Boolean.TRUE.equals(e.get("verified")))
return (String) e.get("email");

return (String) emails.get(0).get("email");
} catch (Exception e) {
log.warn("GitHub 이메일 추가 조회 실패: {}", e.getMessage());
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,26 @@ public class OAuth2FailureHandler extends SimpleUrlAuthenticationFailureHandler
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.error("OAuth2 Login Failure: {}", exception.getMessage());

String targetUrl = UriComponentsBuilder.fromUriString(frontUrl + "/oauth2/redirect")
.queryParam("error", exception.getMessage())
.build().toUriString();
String code = mapToErrorCode(exception);

String targetUrl = UriComponentsBuilder.fromUriString(frontUrl)
.queryParam("status", "error")
.queryParam("code", code)
.build(true)
.toUriString();

getRedirectStrategy().sendRedirect(request, response, targetUrl);
}

private String mapToErrorCode(AuthenticationException ex) {
String msg = (ex.getMessage() == null ? "" : ex.getMessage()).toLowerCase();

if (msg.contains("access_denied")) return "ACCESS_DENIED";
if (msg.contains("email")) return "EMAIL_MISSING";
if (msg.contains("invalid_state") || msg.contains("state")) return "INVALID_STATE";
if (msg.contains("temporarily_unavailable") || msg.contains("server_error")) return "PROVIDER_UNAVAILABLE";
if (msg.contains("invalid_client") || msg.contains("unauthorized_client")) return "CLIENT_CONFIG_ERROR";
return "OAUTH2_FAILURE";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo

log.info("OAuth2 로그인 완료 - 사용자: {} ({})", user.getEmail(), user.getAuthProvider());

response.sendRedirect(frontUrl + "/oauth2/redirect?success=true");
response.sendRedirect(frontUrl);
}
}
1 change: 0 additions & 1 deletion back/src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ server:
session:
cookie:
secure: true # 운영 true(HTTPS)
same-site: strict
domain: ${custom.site.baseDomain}

springdoc:
Expand Down