안녕하세요. 처음 구현하는 소셜로그인이라 지식이 많이 부족한지 동일한 인가 코드를 두 번 이상 사용하는 부분을 도저히 찾을수가없습니다…ㅠㅠ 프론트와 협업을 하여 restAPI로 소셜 로그인을 진행하는것인데 프론트에서
https://kauth.kakao.com/oauth/authorize?client_id=...&redirect_uri=http://localhost:8080/oauth/callback/kakao&response_type=code
를 통해서 인가코드를 받아 백엔드에 파라미터로 code를 넘겨주는 것 아닌가요?
그러면 백엔드는 http://localhost:8080/oauth/callback/kakao로 들어오는 요청을 프론트로부터 인가코드를 넘겨받아서 jwt를 생성 후 프론트로 넘겨주는걸로 생각했는데 어느 부분에서 인가 코드가 두 번 이상 사용되었다는건지 잘 모르겠습니다…ㅠㅠ 제가 알고있는 지식이 잘못된걸까요 ? 혹시 몰라서 아래 코드를 같이 첨부하였습니다. 한 번만 봐주시면 감사하겠습니다…ㅠㅠ
@Service
@RequiredArgsConstructor
public class OauthServiceImpl implements OauthService {
private final JwtProvider jwtProvider;
private final ParentRepository parentRepository;
private final RestTemplate restTemplate = new RestTemplate(); // RestTemplate 사용
@Value("${spring.security.oauth2.client.provider.kakao.token-uri}")
private String kakaoTokenUri;
@Value("${spring.security.oauth2.client.provider.kakao.user-info-uri}")
String userInfoReqUri;
@Value("${spring.security.oauth2.client.registration.kakao.client-id}")
String kakaoClientId;
@Value("${spring.security.oauth2.client.registration.kakao.client-secret}")
private String kakaoClientSecret;
@Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}")
private String kakaoRedirectUri;
@Override
public String getKakaoAccessToken(String code) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "authorization_code");
body.add("client_id", kakaoClientId);
body.add("client_secret", kakaoClientSecret);
body.add("redirect_uri", kakaoRedirectUri);
body.add("code", code);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
ResponseEntity<String> response = restTemplate.postForEntity(kakaoTokenUri, request, String.class);
if (response.getStatusCode() == HttpStatus.OK) {
JsonParser parser = new JsonParser();
JsonElement element = parser.parse(response.getBody());
return element.getAsJsonObject().get("access_token").getAsString();
} else {
throw new RuntimeException("카카오 액세스 토큰 요청 실패: " + response.getStatusCode());
}
}
@Override
public HashMap<String, Object> getUserKakaoInfo(String accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + accessToken);
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(userInfoReqUri, HttpMethod.POST, entity, String.class);
if (response.getStatusCode() == HttpStatus.OK) {
JsonParser parser = new JsonParser();
JsonElement element = parser.parse(response.getBody());
HashMap<String, Object> userInfo = new HashMap<>();
userInfo.put("id", element.getAsJsonObject().get("id").getAsString());
JsonObject properties = element.getAsJsonObject().get("properties").getAsJsonObject();
userInfo.put("nickname", properties.get("nickname").getAsString());
if (element.getAsJsonObject().get("kakao_account").getAsJsonObject().has("email")) {
userInfo.put("email", element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("email").getAsString());
}
return userInfo;
} else {
throw new RuntimeException("카카오 사용자 정보 요청 실패: " + response.getStatusCode());
}
}
@Override
public ParentLoginResponseDto kakaoLogin(String accessToken, HttpServletResponse response) {
ParentSignUpRequest requestDto = getUserKakaoSignupRequestDto(getUserKakaoInfo(accessToken));
ParentResponse parentResponse = findByUserKakaoIdentifier(requestDto.id());
if (parentResponse == null) {
signUp(requestDto);
parentResponse = findByUserKakaoIdentifier(requestDto.id());
}
String token = jwtProvider.createJwt(parentResponse.email(), parentResponse.roles(), "SOCIAL_KAKAO", null);
response.addHeader("Authorization", "Bearer " + token);
Cookie cookie = new Cookie("Authorization", token);
cookie.setPath("/");
response.addCookie(cookie);
return new ParentLoginResponseDto(token, parentResponse.email());
}
@Override
public ParentResponse findByUserKakaoIdentifier(String kakaoIdentifier) {
List<Parent> parents = parentRepository.findParentByProviderId(kakaoIdentifier).orElse(List.of());
if (parents.isEmpty()) {
return null;
}
return new ParentResponse(parents.get(0));
}
@Override
@Transactional
public Long signUp(ParentSignUpRequest requestDto) {
try {
System.out.println("회원가입 요청 데이터: " + requestDto);
return parentRepository.save(requestDto.toEntity(requestDto.email(), requestDto.nickname(), requestDto.id())).getId();
} catch (Exception e) {
e.printStackTrace();
throw new BusinessException(ErrorCode.FAILED_TO_SAVE_UESR);
}
}
private ParentSignUpRequest getUserKakaoSignupRequestDto(HashMap<String, Object> userInfo) {
return new ParentSignUpRequest(
(String) userInfo.get("email"),
(String) userInfo.get("nickname"),
(String) userInfo.get("id")
);
}
}
@RestController
@RequestMapping("/oauth")
@RequiredArgsConstructor
public class OAuthController {
private final OauthService oauthService;
@Operation(summary = "카카오 소셜 로그인 콜백 컨트롤러")
@GetMapping("/callback/kakao")
public ResponseEntity<?> getKaKaoAuthorizeCode(
@RequestParam(value = "code") String code,
HttpServletResponse response) {
System.out.println("Received Authorization Code: " + code); // ✅ code를 콘솔에 출력
if (code == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("code 파라미터가 없습니다.");
}
try {
String accessToken = oauthService.getKakaoAccessToken(code);
System.out.println("✅ Access Token: " + accessToken); // ✅ 확인용 로그 추가
ParentLoginResponseDto loginResponse = oauthService.kakaoLogin(accessToken, response);
return ResponseEntity.ok(loginResponse);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("카카오 로그인 처리 중 오류 발생");
}
}
}