앱 id: 1031569
안녕하세요.
로컬에서 카카오 로그인 api를 사용할 때는 사용자 정보까지 정상적으로 받아오는 것을 확인하였습니다.
하지만 aws ec2에 배포를 한 이후에는 로그인 이후 token을 받아오는 과정에서 에러가 발생하는데 원인을 모르겠습니다.
에러와 현재 작성된 로직은 아래와 같습니다.
감사합니다.
2024-02-12T04:24:52.940Z DEBUG 1865 --- [nio-8080-exec-4] h.server.controller.LoginController : [LoginController.requestLogin]
2024-02-12T04:24:52.941Z DEBUG 1865 --- [nio-8080-exec-4] hibuy.server.service.KakaoService : [KakaoService.getAccessToken]
2024-02-12T04:24:53.497Z ERROR 1865 --- [or-http-epoll-3] hibuy.server.service.KakaoService : do on error: org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request from POST https://kauth.kakao.com/oauth/token
2024-02-12T04:24:53.498Z ERROR 1865 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request from POST https://kauth.kakao.com/oauth/token] with root cause
org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request from POST https://kauth.kakao.com/oauth/token
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:307) ~[spring-webflux-6.1.2.jar!/:6.1.2]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ 400 BAD_REQUEST from POST https://kauth.kakao.com/oauth/token [DefaultWebClient]
Original Stack Trace:
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:307) ~[spring-webflux-6.1.2.jar!/:6.1.2]
at org.springframework.web.reactive.function.client.DefaultClientResponse.lambda$createException$1(DefaultClientResponse.java:214) ~[spring-webflux-6.1.2.jar!/:6.1.2]
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.FluxOnErrorReturn$ReturnSubscriber.onNext(FluxOnErrorReturn.java:162) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:122) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2097) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:145) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:415) ~[reactor-netty-core-1.1.14.jar!/:1.1.14]
at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:446) ~[reactor-netty-core-1.1.14.jar!/:1.1.14]
at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:500) ~[reactor-netty-core-1.1.14.jar!/:1.1.14]
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:780) ~[reactor-netty-http-1.1.14.jar!/:1.1.14]
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:114) ~[reactor-netty-core-1.1.14.jar!/:1.1.14]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) ~[netty-codec-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) ~[netty-codec-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) ~[netty-codec-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1475) ~[netty-handler-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1338) ~[netty-handler-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1387) ~[netty-handler-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:529) ~[netty-codec-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:468) ~[netty-codec-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) ~[netty-codec-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:800) ~[netty-transport-classes-epoll-4.1.104.Final.jar!/:4.1.104.Final]
r.LoginController.requestKakaoLogin at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:509) ~[netty-transport-classes-epoll-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:407) ~[netty-transport-classes-epoll-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.104.Final.jar!/:4.1.104.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.104.Final.jar!/:4.1.104.Final]
at java.base/java.lang.Thread.run(Thread.java:840) ~[na:na]
Suppressed: java.lang.Exception: #block terminated with an error
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:103) ~[reactor-core-3.6.1.jar!/:3.6.1]
at reactor.core.publisher.Mono.block(Mono.java:1728) ~[reactor-core-3.6.1.jar!/:3.6.1]
at hibuy.server.service.KakaoService.getAccessToken(KakaoService.java:45) ~[!/:na]
at hibuy.server.controller.LoginController.requestKakaoLogin(LoginController.java:30) ~[!/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:262) ~[spring-web-6.1.2.jar!/:6.1.2]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:190) ~[spring-web-6.1.2.jar!/:6.1.2]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.1.2.jar!/:6.1.2]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:917) ~[spring-webmvc-6.1.2.jar!/:6.1.2]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:829) ~[spring-webmvc-6.1.2.jar!/:6.1.2]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.1.2.jar!/:6.1.2]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.1.2.jar!/:6.1.2]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.1.2.jar!/:6.1.2]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.1.2.jar!/:6.1.2]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.1.2.jar!/:6.1.2]
컨트롤러
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/login")
public class LoginController {
private final KakaoService kakaoService;
@Value("${kakao.oauth2.client_id}") private String clientId;
@Value("${kakao.oauth2.redirect_uri}") private String redirectUri;
@GetMapping("/oauth2/code/kakao")
public ResponseEntity<LoginResponse> requestKakaoLogin(@RequestParam String code) {
log.debug("[LoginController.requestLogin]");
String accessToken = kakaoService.getAccessToken(code);
LoginResponse userInfo = kakaoService.getUserInfo(accessToken);
System.out.println("userInfo.getName() = " + userInfo.getName());
System.out.println("userInfo.getEmail() = " + userInfo.getEmail());
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create("/result"));
return new ResponseEntity<>(userInfo, headers, HttpStatus.MOVED_PERMANENTLY);
}
}
서비스
package hibuy.server.service;
import hibuy.server.domain.User;
import hibuy.server.dto.oauth2.KakaoTokenResponse;
import hibuy.server.dto.oauth2.KakaoUserInfoResponse;
import hibuy.server.dto.oauth2.LoginResponse;
import hibuy.server.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.WebClient;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
@Slf4j
@Service
@RequiredArgsConstructor
public class KakaoService {
private final UserRepository userRepository;
@Value("${kakao.oauth2.client_id}") private String clientId;
@Value("${kakao.oauth2.redirect_uri}") private String redirectUri;
@Value("${kakao.oauth2.client_secret}") private String clientSecret;
public String getAccessToken(String code) {
log.debug("[KakaoService.getAccessToken]");
WebClient webClient = WebClient.builder().build();
String requestUrl = "https://kauth.kakao.com/oauth/token";
KakaoTokenResponse responseBody = webClient.post()
.uri(requestUrl)
.header(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded;charset=utf-8")
.bodyValue(buildAccessTokenRequestBody(code))
.retrieve()
.bodyToMono(KakaoTokenResponse.class)
.doOnError(error -> {
log.error("do on error: " + error);
log.error("error code: " + error.getMessage());
}).block();
return responseBody.getAccess_token();
}
public LoginResponse getUserInfo(String accessToken) {
log.debug("[KakaoService.getUserInfo]");
WebClient webClient = WebClient.builder().build();
String requestUrl = "https://kapi.kakao.com/v2/user/me";
KakaoUserInfoResponse kakaoUserInfoResponse = webClient.get()
.uri(requestUrl)
.headers(httpHeaders -> {
httpHeaders.set("Authorization", "Bearer " + accessToken);
httpHeaders.set("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
})
.retrieve()
.bodyToMono(KakaoUserInfoResponse.class)
.doOnError(error -> {
log.error("do on error: " + error);
})
.block();
Optional<User> user = userRepository.findByKakaoUserId(kakaoUserInfoResponse.getId());
if(user.isEmpty()) {
userRepository.save(new User(
kakaoUserInfoResponse.getId(),
kakaoUserInfoResponse.getKakao_account().getName(),
kakaoUserInfoResponse.getKakao_account().getEmail(),
kakaoUserInfoResponse.getKakao_account().getPhone_number()));
}
return new LoginResponse(
kakaoUserInfoResponse.getId(),
kakaoUserInfoResponse.getKakao_account().getName(),
kakaoUserInfoResponse.getKakao_account().getEmail(),
kakaoUserInfoResponse.getKakao_account().getPhone_number());
}
private MultiValueMap<String, String> buildAccessTokenRequestBody(String code) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", clientId);
params.add("client_secret", clientSecret);
params.add("redirect_uri", URLEncoder.encode(redirectUri, StandardCharsets.UTF_8));
params.add("code", code);
return params;
}
}