Bootcamp/Implementation

OAuth

개발자 오리 2026. 6. 28. 23:39

💡  오늘 학습 키워드

  • OAuth(Kakao 연동)

 

🎯  학습 내용 정리

1. OAuth(Kakao 연동)

OAuthOpen Authorization의 약자사용자의 비밀번호를 직접 전달하지 않고, 제3자 서비스의 인증을 이용하여 로그인할 수 있도록 해주는 인증 프로토콜이다.

 

카카오 로그인을 예로 들면 다음과 같은 흐름으로 동작한다.

Client
  ↓
Server
  ↓
Kakao Authorization Server
  ↓
Server
  ↓
Client

즉,

  1. 사용자가 카카오 로그인을 요청한다.
  2. 카카오 로그인 페이지에서 인증을 수행한다.
  3. 카카오가 인가 코드(Authorization Code)를 서버로 전달한다.
  4. 서버는 인가 코드로 액세스 토큰을 요청한다.
  5. 액세스 토큰을 이용해 사용자 정보를 조회한다.
  6. 회원가입 또는 로그인을 수행한다.
  7. JWT를 발급하여 클라이언트에게 전달한다.

이번 구현에서는 Server에서 수행하는 OAuth 인증 과정을 직접 구현하였다.

 

 

먼저 카카오 로그인에 성공하면 등록한 Redirect URI로 인가 코드가 전달된다.

@GetMapping("/user/kakao/callback")
public ResponseEntity<Void> kakaoLogin(@RequestParam String code) throws JsonProcessingException {

    String jwt = kakaoService.kakaoLogin(code);

    return ResponseEntity.ok()
            .header(HttpHeaders.AUTHORIZATION, "Bearer " + jwt)
            .build();
}

여기서 전달받는 code가 바로 Authorization Code(인가 코드) 이다. 이 코드는 매우 중요한 값이며 1회만 사용할 수 있다.

이후 모든 인증 과정은 Service에서 수행한다.

 

 

KakaoService에서는 크게 두 가지 작업을 수행한다.

public String kakaoLogin(String code) throws JsonProcessingException {

    // 1. 인가 코드 -> 액세스 토큰
    String accessToken = getToken(code);

    // 2. 액세스 토큰 -> 사용자 정보 조회
    KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);

    return null;
}

 

OAuth 인증 과정은 생각보다 단순하다.

Authorization Code
  ↓
Access Token
  ↓
User Info

이후 조회한 사용자 정보를 이용하여 회원가입 또는 로그인을 수행하면 된다.

 

 

OAuth에서는 외부 API를 호출해야 한다. 그렇기 때문에 RestTemplate을 Bean으로 등록하여 사용하였다.

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder
                .setConnectTimeout(Duration.ofSeconds(5))
                .setReadTimeout(Duration.ofSeconds(5))
                .build();
    }
}

 

왜 Timeout을 설정해야 할까?

외부 서버가 장애가 발생하거나 응답하지 않는 경우,

Server
  ↓
Kakao API
  ↓
응답 없음

서버는 계속 대기하게 된다.

Timeout을 지정하면 일정 시간이 지나면 예외가 발생하여 서버가 무한정 대기하는 상황을 방지할 수 있다.

 

 

OAuth의 첫 번째 과정은 인가 코드를 액세스 토큰으로 교환하는 것이다.

private String getToken(String code)

 

1. 요청 URL 생성

URI uri = UriComponentsBuilder
        .fromUriString("https://kauth.kakao.com")
        .path("/oauth/token")
        .build()
        .toUri();

Spring에서는 UriComponentsBuilder를 이용하여 URL을 안전하게 생성할 수 있다.

 

2. Header 생성

HttpHeaders headers = new HttpHeaders();
headers.add(
    "Content-type",
    "application/x-www-form-urlencoded;charset=utf-8"
);

카카오 토큰 API는 Form 데이터 방식만 지원한다.

 

3. Body 생성

MultiValueMap<String, String> body = new LinkedMultiValueMap<>();

body.add("grant_type", "authorization_code");
body.add("client_id", REST_API_KEY);
body.add("redirect_uri", REDIRECT_URI);
body.add("code", code);
body.add("client_secret", CLIENT_SECRET);

여기서 중요한 값은 다음과 같다.

  • grant_type
  • client_id
  • redirect_uri
  • code
  • client_secret

특히 redirect_uri는 카카오 개발자 사이트에 등록한 URI와 반드시 동일해야 한다.

 

4. HTTP 요청

RequestEntity<MultiValueMap<String, String>> requestEntity =
        RequestEntity.post(uri)
                .headers(headers)
                .body(body);

ResponseEntity<String> response =
        restTemplate.exchange(
                requestEntity,
                String.class
        );

RestTemplate.exchange()를 이용하면 Header와 Body를 함께 전송할 수 있다.

 

5. JSON 파싱

응답은 JSON으로 내려온다.

{
  "access_token":"xxxxxxxx",
  "refresh_token":"xxxxxxxx"
}

이를 Jackson을 이용하여 파싱한다.

JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());

return jsonNode.get("access_token").asText();

 

 

액세스 토큰을 얻었다면 사용자 정보를 요청할 수 있다.

private KakaoUserInfoDto getKakaoUserInfo(String accessToken)

 

이번에는 Authorization Header가 필요하다.

headers.add(
    "Authorization",
    "Bearer " + accessToken
);

카카오는 Bearer Token 방식으로 인증한다.

 

 

요청 후 응답은 다음과 같다.

{
  "id":1632335751,
  "properties":{
      "nickname":"르탄이"
  },
  "kakao_account":{
      "email":"letan@sparta.com"
  }
}

 

필요한 값만 추출한다.

Long id = jsonNode.get("id").asLong();

String nickname = jsonNode.get("properties")
                		.get("nickname")
                		.asText();

String email = jsonNode.get("kakao_account")
                	.get("email")
                	.asText();

 

이후 DTO에 저장하여 반환한다.

return new KakaoUserInfoDto(
        id,
        nickname,
        email
);

 

 

현재 코드에서는 사용자 정보 조회까지만 구현되어 있다.

실제 서비스에서는 다음 과정이 추가되어야 한다.

사용자 정보 조회
  ↓
기존 회원 조회
  ↓
없으면 회원가입
  ↓
JWT 생성
  ↓
Client 전달

 

즉,

User user = userRepository.findByEmail(...);

if (user == null) {
    회원가입
}

JWT 생성

Response 반환

까지 구현되어야 OAuth 로그인이 완성된다.

 

 

그렇다면 구현하면서 주의해야 할 점에는 어떠한 것들이 있을까?

 

1. Secret Key를 코드에 작성하지 않기

예제에서는 이해를 위해

body.add("client_secret", "...");

body.add("client_id", "...");

를 직접 작성했지만 실제 프로젝트에서는 절대 이렇게 작성하지 않는다.

 

application.yml 또는 환경 변수로 관리하는 것이 안전하다.

kakao:
  client-id:
  client-secret:

 

2. Redirect URI는 반드시 동일해야 한다

카카오 개발자 사이트
  ↓
http://localhost:8080/api/user/kakao/callback
  ↓
Spring 코드
  ↓
http://localhost:8080/api/user/kakao/callback

한 글자라도 다르면 인증이 실패한다.

 

3. 이메일 제공 여부 확인

모든 사용자가 이메일 제공에 동의하는 것은 아니다.

현재 코드는

jsonNode.get("kakao_account")
        .get("email")
        .asText();

로 바로 접근한다.

하지만 이메일 제공에 동의하지 않은 경우 NullPointerException이 발생할 수 있으므로

JsonNode account = jsonNode.path("kakao_account");

String email = account.path("email").asText(null);

처럼 안전하게 처리하는 것이 좋다.

 

4. RestTemplate 대신 WebClient

Spring Boot 3.x 이후에는

RestTemplate

보다

WebClient

사용이 권장된다.

 

 

📚  한줄 정리

이번에는 OAuth 2.0을 이용하여 카카오 로그인 과정을 직접 구현해보았다. 단순히 로그인 버튼을 연동하는 것이 아니라, 인가 코드로 액세스 토큰을 발급받고, 액세스 토큰을 이용해 사용자 정보를 조회하는 전체 인증 흐름을 이해할 수 있었다.

또한 외부 API를 호출하는 과정에서 RestTemplate의 사용법과 HTTP Header, Body 구성 방식, JSON 응답 파싱 방법까지 함께 학습할 수 있었다. 앞으로는 여기서 한 단계 더 나아가 사용자 정보를 기반으로 회원가입과 로그인을 처리하고, JWT를 발급하여 Spring Security와 연계한 완전한 소셜 로그인 기능까지 구현해보며 인증 시스템에 대한 이해를 더욱 깊게 다져나갈 예정이다.