API 사용 시 key 숨기기, JavaScript로 카카오 지도 API 사용하기

dejeong·2024년 8월 24일
1

API

목록 보기
5/5
post-thumbnail

카카오 지도 API 사용

키 발급 -> 코드 적용

앱키 발급 받는 방법은 참고

발급 후 카카오 지도 API

링크를 참고하여 필요한 기능 예시를 활용해 원하는 지도 기능을 넣을 수 있다.


JavaScript를 사용하여 API 사용 시 key 숨기기

인스타그램 API Java Script로 연동하기, 최근 게시물 불러오기
에서도 Key를 숨겨야 하는 이유를 작성했었는데, 더 상세하게 이유를 말하자면

  • 무단 사용 방지:
    API 키가 노출되면 악의적인 사용자가 이를 이용하여 API 사용량을 초과하거나 비정상적인 요청을 보내 서비스에 부하를 줄 수 있다. 이는 비용이 증가하거나 서비스가 중단될 수 있는 원인이 된다.

  • 서비스 악용 방지:
    공격자가 노출된 API 키를 사용하여 악성 행위를 할 수 있다. 예를 들어, 사용자 데이터에 접근하거나 비정상적인 요청을 통해 서비스에 피해를 줄 수 있다.

  • 비용 발생 방지:
    많은 API 제공업체는 사용량에 따라 요금을 부과한다. API 키가 노출되면 불법적인 요청이 들어올 수 있어 예상치 못한 비용이 발생할 수 있다.

카카오는 사용할 도메인을 따로 추가하여 사용하기에 보호(도메인 제한)가 된다는 얘기를 들었던 것 같은데,
다른 API를 사용할 때 이유를 알고 조심할 필요가 있응께..

요건 작업 내용 관련하여 클라이언트에 api 키가 노출될 것 같은데 괜찮을까요?
라는 질문에 카카오 답변이다.


클라이언트 측에서는 API 키를 완전히 숨길 수는 없지만, 키를 보호하기 위해 아래와 같은 방법을 사용할 수 있고, 서버 측에서는 키를 숨길 수 있는 더 안전한 방법이 있으니 서버측에서 작업해주는 게 맞다.

클라이언트 측에서 API 키 보호

  • 제한된 권한 설정:
    API 제공자에서 API 키의 권한을 제한할 수 있다. 예를 들어, 특정 IP 주소, 도메인, 또는 특정 API 엔드포인트에 대해서만 유효하도록 설정

  • 레이트 리미팅:
    API 제공자에서 사용량 제한을 설정하여 일정 시간 동안의 요청 수를 제한함으로써 과도한 사용을 방지할 수 있다.

  • 클라이언트 측 프록시 사용:
    클라이언트 측에서 API 키를 보호하기 어렵지만, 클라이언트 측 요청을 서버를 통해 우회하도록 설정할 수 있다. 예를 들어, 클라이언트가 직접 API를 호출하지 않고 서버를 통해 호출하도록 한다.

 // 클라이언트 측에서 직접 API 키를 사용하지 않도록 설정
    fetch('/api/proxy?endpoint=example')
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => console.error('Error:', error));
  • 도메인 제한 설정
    API 제공자에서 특정 도메인에서만 API 키가 유효하도록 설정할 수 있다. 이렇게 하면 API 키가 노출되어도 설정한 도메인 외부에서는 사용이 제한된다.

나는 간단하게 key값을 ignore 파일에 추가하는 방법을 사용해보았다.

config 생성 -> ignore 추가 -> js파일 추가

config.js 생성

const config = {
    API_KEY: "key 값",
};
export default config;

ignore 파일에 추가

gitignore 파일 추가 적용이 안될 때 참고

사용 파일(html)에 스크립트 추가

Uncaught SyntaxError: Cannot use import statement outside a module 오류 발생시 script 파일에 type="module" 추가 참고

JS 에 key 파일 불러오기

import config from "./config.js";
const { API_KEY } = config;

🤷‍♀️ Uncaught ReferenceError: kakao is not defined

Kakao 지도 API 스크립트가 완전히 로드되기 전에 kakao 객체에 접근하려고 할 때 발생함으로
API 스크립트 로드 및 Kakao 객체 사용 코드 분리해야한다.

Kakao 지도 API를 비동기적으로 로드하고, API 스크립트가 로드된 후에만 kakao 객체를 사용하는 방법을 사용. 이를 위해, API 스크립트가 로드된 후에 kakao 객체를 사용하도록 보장해야 한다.
아래는 수정한 코드이고, 해당 방법으로 변경했더니 제대로 동작하였다.

import config from "./config.js";
const { API_KEY } = config;

// Kakao Map API 스크립트를 동적으로 로드하는 함수
function loadKakaoMapScript() {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = `https://dapi.kakao.com/v2/maps/sdk.js?appkey=${API_KEY}&autoload=false`;
        script.onload = () => resolve();
        script.onerror = () => reject(new Error('Failed to load Kakao Map API script'));
        document.head.appendChild(script);
    });
}

// Kakao API가 로드된 후에 실행될 함수
function initializeMap() {
    // Kakao API가 로드된 후에 호출됨
    kakao.maps.load(function () {
        var mapContainer = document.getElementById('map'),
            mapOption = {
                center: new kakao.maps.LatLng(33.4423379727783, 126.571449734542),
                level: 3 
            };

        var map = new kakao.maps.Map(mapContainer, mapOption);

        var markerPosition  = new kakao.maps.LatLng(33.4423379727783, 126.571449734542);

        var marker = new kakao.maps.Marker({
            position: markerPosition
        });

        marker.setMap(map);
    });
}

// 스크립트를 로드하고, 로드가 완료된 후 지도를 초기화
loadKakaoMapScript()
    .then(initializeMap)
    .catch(error => console.error(error));

서버 측에서 API 키 보호

Spring Boot로 진행하는 방법

  • 환경 변수 설정
    API 키를 환경 변수에 저장하고 이를 서버 코드에서 사용하는 방법

환경 변수 설정

환경 변수를 설정, .env 파일을 사용하거나 시스템의 환경 변수에 직접 추가

# .env (환경 변수 파일)
API_KEY=your_api_key_here

Java 코드에서 환경 변수 읽기

Spring Boot에서는 application.properties 파일에 환경 변수를 설정하고, 이를 Java 코드에서 읽을 수 있다.

// application.properties 파일

api.key=${API_KEY}

Java 코드에서 환경 변수 사용

package com.example.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class ApiController {

    @Value("${api.key}")
    private String apiKey;

    private final RestTemplate restTemplate;

    public ApiController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @GetMapping("/proxy")
    public String proxy(@RequestParam String endpoint) {
        String url = "https://api.example.com/" + endpoint + "?apikey=" + apiKey;
        return restTemplate.getForObject(url, String.class);
    }
}
  • Spring Boot에서 프록시 서버 구현
    클라이언트 요청을 서버를 통해 API에 전달하고, API 키를 서버 측에서 관리하는 방법입니다.

Spring Boot 애플리케이션 설정

pom.xml (의존성 추가)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

ApiController.java

package com.example.demo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@RestController
public class ApiController {

    @Value("${api.key}")
    private String apiKey;

    private final WebClient webClient;

    public ApiController(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("https://api.example.com").build();
    }

    @GetMapping("/proxy")
    public Mono<String> proxy(@RequestParam String endpoint) {
        return webClient.get()
                .uri(uriBuilder -> uriBuilder.path(endpoint)
                                             .queryParam("apikey", apiKey)
                                             .build())
                .retrieve()
                .bodyToMono(String.class);
    }
}

WebClient 설정

WebClient는 비동기적으로 API 요청을 처리할 수 있는 기능을 제공
application.properties 또는 application.yml에서 기본 URL을 설정할 수 있다.

application.properties

api.key=${API_KEY}

WebClientConfiguration.java

package com.example.demo;

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

@Configuration
public class WebClientConfiguration {

    @Bean
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }
}
  • 보안 고려사항 :
    HTTPS 사용: API 호출 시 HTTPS를 사용하여 데이터 전송 시 보안을 강화한다.

  • API 호출에 대한 레이트 리미팅:
    API 제공자가 제공하는 레이트 리미팅 기능을 사용하여 요청 수를 제한한다.

  • 로그 모니터링:
    서버에서 API 호출을 모니터링하고, 비정상적인 활동이 감지되면 즉시 대응할 수 있도록 한다.

  • 제한된 권한:
    API 키의 권한을 최소화하여 필요한 기능만 사용하도록 설정한다.


🙂🙂

profile
룰루

2개의 댓글

comment-user-thumbnail
2024년 12월 4일

카카오맵은 script.src에서 호출하는 방식이라 작성하신 서버 proxy 방식으로 요청이 안 되지 않나요?

1개의 답글