클라이언트-서버-api 테스트 중 발생한 CSP 문제

Byeonghyeon·2024년 1월 26일
0

어디갈래?

목록 보기
2/6

API를 찾았으니 어떻게 사용하는지 테스트를 해보았다.

https://apis.data.go.kr/B551011/KorService1/searchKeyword1?serviceKey=${serviceKey}&pageNo=1&numOfRows=10&MobileApp=AppTest&MobileOS=ETC&arrange=Q&contentTypeId=&keyword=${currentEncodedData}&_type=json

해당 api url은 키워드 검색 조회시 사용하는 url이다

serviceKey 부분에 발급받은 내 서비스키를, currentEncodedData 부분에 검색할 키워드를 입력해야한다.

app.post('/', async (req:Request, res:Response) => {
  const keyword: string | undefined = req.body.keyword as string;
  const currentEncodedData=encodeURIComponent(keyword);
  await axios.get(`https://apis.data.go.kr/B551011/KorService1/searchKeyword1?serviceKey=${serviceKey}&pageNo=1&numOfRows=10&MobileApp=AppTest&MobileOS=ETC&arrange=Q&contentTypeId=&keyword=${currentEncodedData}&_type=json`)
  .then((response) => {

    const receivedData = response.data.response.body.items;
    const dataArray = receivedData.item


    res.json(dataArray);
    
  })
  .catch((err) => {
    console.error(err);
    res.status(500).json({ error: 'Internal Server Error' });
  })

  
});

서버의 코드는 이렇게 간단하게 작성해보았다.

클라이언트로부터 keyword를 입력받아 인코딩하고 api url로 요청을 보내 데이터를 받아 다시 클라이언트로 전달하는 코드이다.

const Search:React.FC = () => {
    const [travelPlace, setTravelPlace] = useState<Place[] | null>(null);
    const [keyword, setKeyword] = useState("");

    const btnClick = () => {        
        axios.post("http://localhost:8080/", {
            keyword: keyword
        })
        .then((res) => {
            console.log(res.data);
            setTravelPlace(res.data);
        })
        .catch((err) => {
            console.error(err);
        })
    }

    return(
        <div>
            <input type="text" onChange={(e) => setKeyword(e.target.value)} />
            <button onClick={btnClick}>전송</button>
        <ul>
        {travelPlace?.map((place: Place) => (
          <li key={place.contentid}>{place.title}</li>
        ))}
      </ul>
      </div>
    );
}

클라이언트는 이렇게 구성해보았다.

input 입력창에 키워드를 입력해 버튼을 누르면 서버로 키워드를 전송하고 서버로부터 데이터를 받아 li 태그로 하나씩 표시하는 코드이다.

그런데...

CSP, 즉 Content Security Policy 문제가 생기면서 작동하지 않았다.

CSP란?

CSP란 XSS와 기타 코드 인젝션 공격을 비롯한 특정 유형의 공격을 탐지하고 예방하기 위해 도입된 컴퓨터 보안 표준이다.

CSP는 HTTP 헤더를 통해 설정되며 이를 통해 웹 브라우저는 어떤 리소스를 로드하고 실행할 것인지에 대한 정책을 받게 된다. CSP 헤더는 서버 응답에 포함되어 클라이언트(브라우저)에게 전송된다.

이를 해결하는 방법은 여러가지가 있다.

HTML 파일의 meta 태그로 설정하거나 서버에서 설정하거나

먼저 HTML 파일에서 설정하는 방법은 다음과 같다

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Security-Policy" content="directive1 value1; directive2 value2">
    <title>Your Web Page</title>
</head>
<body>
</body>
</html>
<meta http-equiv="Content-Security-Policy" content="directive1 value1; directive2 value2">

directive1 value1; directive2 value2를 실제 사용할 CSP 정책에 맞게 수정한다.

예를 들어 스크립트 실행을 같은 도메인에서만 허용하고 이미지는 어디서나 로드할 수 있도록 설정하려면

<meta http-equiv="Content-Security-Policy" content="script-src 'self'; img-src *">

이와 같이 수정한다.

html의 meta 태그를 이용해 설정할 때에는 몇 가지 주의점이 있다.

  1. 서버에서도 CSP를 설정하고 있다면, 두 설정 간에 충돌이 발생하지 않도록 주의해야 한다,
  2. 가능한 한 단순하고 명확한 정책을 사용해야 한다.
  3. 개발 및 테스트 환경에서도 적절한 정책이 적용되도록 해야 한다.
  4. 설정한 CSP가 원하는 보안 목표를 달성하는지 주기적으로 확인하고 모니터링해야 한다.
  5. CSP는 모든 브라우저에서 동일한 방식으로 해석되지 않을 수 있으니 다양한 브라우저에서 테스트하여 호환성을 확인해야 한다.

일반적으로 CSP는 백엔드에서 설정하는 것이 더 안전하며 효과적일 수 있다고 한다.

예를 들어
1. 서버 측에서 CSP를 설정하면 서버와 클라이언트 간의 통일된 정책을 적용할 수 있으므로 정책의 일관성을 유지할 수 있다.
2. 클라이언트측에서 발생하는 다양한 보안 위협을 더 쉽게 방어할 수 있다. 특히 XSS와 같은 보안 문제에 대한 대응이 효과적이다.
3. 클라이언트가 정책을 변경하려는 시도를 방지할 수 있다.
4. 애플리케이션의 전반적인 관리가 용이해진다.

어디갈래? 프로젝트에서 내가 담당하는게 프론트엔드만이었다면 모르겠지만 현재 나는 백엔드까지 혼자 담당해야 하므로 백엔드 측 설정을 따르기로 결정했다.

나는 helmet이라는 모듈을 사용하였다.

참고한 글은 [NODE / 보안] 📚 helmet 모듈 사용법 - 웹 보안은 내가 👮

먼저 helmet을 설치해준다.

npm install helmet
const cspOptions = {
  directives: {
    ...helmet.contentSecurityPolicy.getDefaultDirectives(),
    "script-src": ["https://apis.data.go.kr/"],
    "img-src": ["'self'", "https://apis.data.go.kr/", "blob:"]
  }
};

app.use(helmet({
  contentSecurityPolicy: cspOptions,
}));

내 경우에는 img를 불러오는 부분에서 CSP 문제가 발생하였기 때문에 이렇게 설정해 주었다.

키워드 강원을 입력하자 검색 결과가 잘 나타난다

0개의 댓글