키 발급 -> 코드 적용
앱키 발급 받는 방법은 참고
발급 후 카카오 지도 API
링크를 참고하여 필요한 기능 예시를 활용해 원하는 지도 기능을 넣을 수 있다.
인스타그램 API Java Script로 연동하기, 최근 게시물 불러오기
에서도 Key를 숨겨야 하는 이유를 작성했었는데, 더 상세하게 이유를 말하자면
무단 사용 방지:
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));
나는 간단하게 key값을 ignore 파일에 추가하는 방법을 사용해보았다.
config 생성 -> ignore 추가 -> js파일 추가
const config = {
API_KEY: "key 값",
};
export default config;
gitignore 파일 추가 적용이 안될 때 참고
Uncaught SyntaxError: Cannot use import statement outside a module 오류 발생시 script 파일에 type="module"
추가 참고
import config from "./config.js";
const { API_KEY } = config;
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));
Spring Boot로 진행하는 방법
환경 변수를 설정, .env 파일을 사용하거나 시스템의 환경 변수에 직접 추가
# .env (환경 변수 파일)
API_KEY=your_api_key_here
Spring Boot에서는 application.properties 파일에 환경 변수를 설정하고, 이를 Java 코드에서 읽을 수 있다.
// application.properties 파일
api.key=${API_KEY}
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);
}
}
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는 비동기적으로 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 키의 권한을 최소화하여 필요한 기능만 사용하도록 설정한다.
🙂🙂
카카오맵은
script.src
에서 호출하는 방식이라 작성하신 서버 proxy 방식으로 요청이 안 되지 않나요?