출제기준 세세항목: 보안 취약성 식별 · API
서버 프로그램은 사용자의 요청을 받아 처리하고 결과를 돌려주는 프로그램입니다. 식당에 빗대면 손님의 주문을 받는 직원, 요리를 하는 주방, 재료를 보관하는 창고가 따로 있듯이, 서버 프로그램도 맡은 일에 따라 층을 나눠 만듭니다. 이렇게 하면 한 층을 고쳐도 다른 층에 미치는 영향이 줄어듭니다.
가장 흔한 구조는 세 계층입니다. 프레젠테이션 계층은 사용자의 요청을 받아 알맞은 처리로 넘기고 결과를 화면이나 응답 형태로 돌려줍니다. 비즈니스(서비스) 계층은 '재고가 있어야 주문할 수 있다'처럼 실제 업무 규칙을 처리합니다. 퍼시스턴스(데이터 접근) 계층은 데이터베이스에 데이터를 저장하고 꺼내 오는 일만 맡습니다.
요청은 위에서 아래로 내려갔다가 다시 올라옵니다. 사용자가 주문 버튼을 누르면 컨트롤러가 요청을 받고, 서비스가 재고와 결제 조건을 확인한 뒤, DAO가 주문 정보를 DB에 저장합니다. 결과는 반대 순서로 올라와 사용자에게 전달됩니다. 각 계층은 바로 아래 계층에만 기대도록 만드는 것이 원칙입니다.
MVC는 프로그램을 모델(Model), 뷰(View), 컨트롤러(Controller) 세 역할로 나누는 구조입니다. 모델은 데이터와 그 데이터를 다루는 업무 로직을, 뷰는 사용자에게 보이는 화면을, 컨트롤러는 사용자의 요청을 받아 어떤 모델을 쓸지 정하고 결과를 어떤 뷰로 보여 줄지 고르는 일을 맡습니다. 화면을 바꿔도 업무 로직은 그대로 두고, 업무 로직을 바꿔도 화면은 그대로 둘 수 있다는 것이 장점입니다.
스프링 MVC 같은 서버 프레임워크는 이 구조를 미리 갖춰 두고 있습니다. 예를 들어 스프링 MVC에서는 모든 요청을 먼저 받는 디스패처 서블릿(DispatcherServlet)이 요청 주소를 보고 알맞은 컨트롤러로 나눠 줍니다. 이렇게 모든 요청을 한 곳에서 먼저 받는 방식을 프런트 컨트롤러라고 합니다. 개발자는 컨트롤러와 서비스, DAO의 내용만 채우면 되고, 요청을 나누고 객체를 만들어 연결하는 일은 프레임워크가 맡습니다.
프레임워크는 서비스나 DAO 같은 객체를 보통 하나만 만들어 여러 요청이 함께 쓰게 합니다. 이것은 객체를 하나로 제한하는 싱글톤(Singleton) 패턴의 생각과 같습니다. 싱글톤을 직접 만들 때는 생성자를 private으로 막고, getInstance() 같은 static 메서드로 미리 만들어 둔 하나의 객체만 돌려줍니다. 여러 요청이 한 객체를 함께 쓰므로, 그 객체 안에 요청마다 달라지는 값을 필드로 저장하지 않도록 주의해야 합니다.
DAO(Data Access Object)는 데이터베이스 접근을 전담하는 객체입니다. SQL을 실행해 데이터를 넣고 꺼내는 코드를 한곳에 모아 두면, 서비스 계층은 'DB가 어떤 제품인지, SQL이 어떻게 생겼는지' 몰라도 dao.findById(15)처럼 부르기만 하면 됩니다. DBMS를 바꾸더라도 DAO만 고치면 됩니다.
DTO(Data Transfer Object)는 계층과 계층 사이, 또는 서버와 클라이언트 사이에 데이터를 묶어 실어 나르는 객체입니다. 회원 가입 화면에서 받은 이름, 이메일, 비밀번호를 하나의 DTO에 담아 컨트롤러에서 서비스로 넘기는 식입니다. 보통 필드와 그 값을 읽고 쓰는 메서드(getter·setter)만 가집니다.
VO(Value Object)는 값 그 자체를 나타내는 객체로, 한 번 만들면 내용을 바꾸지 않는 읽기 전용(불변)으로 만듭니다. '10,000원'이라는 금액이나 '서울시 ○○구'라는 주소처럼, 안에 든 값이 같으면 같은 것으로 봅니다. DTO는 데이터를 옮기는 상자, VO는 값 하나를 뜻하는 단위라고 구별하면 됩니다.
로그인 확인, 로그 기록, 날짜 형식 바꾸기, 파일 업로드처럼 여러 화면과 기능에서 반복해 쓰는 기능이 있습니다. 이것을 기능마다 따로 만들면 같은 코드가 여기저기 흩어지고, 고칠 때도 모든 곳을 찾아다녀야 합니다. 그래서 이런 기능을 따로 떼어 한곳에 만들어 두고 다 함께 불러 쓰게 하는데, 이것을 공통 모듈이라고 합니다.
공통 모듈은 재사용이 목적이므로 설계 원칙이 분명해야 합니다. 한 모듈 안의 기능들은 한 가지 목적을 위해 똘똘 뭉쳐 있어야 하고(응집도는 높게), 다른 모듈과는 꼭 필요한 만큼만 연결되어 있어야 합니다(결합도는 낮게). 그래야 한 모듈을 고쳐도 다른 곳이 덜 흔들립니다.
공통 모듈의 명세를 쓸 때는 기능이 꼭 필요한 것인지(정확성), 읽는 사람마다 다르게 해석되지 않는지(명확성), 빠진 기능은 없는지(완전성), 다른 모듈과 충돌하지 않는지(일관성), 요구사항의 출처를 따라갈 수 있는지(추적성)를 확인합니다. 이 다섯 가지를 함께 기억해 두면 좋습니다.
서버 프로그램은 누구나 보낼 수 있는 요청을 받기 때문에, 들어오는 값을 믿고 그대로 쓰면 공격의 통로가 됩니다. 보안 취약성 식별은 이런 약점을 구현 단계에서 찾아내고 안전하게 고치는 일입니다. 행정안전부의 소프트웨어 개발보안 가이드는 보안 약점을 입력데이터 검증 및 표현, 보안기능, 시간 및 상태, 에러처리, 코드오류, 캡슐화, API 오용의 일곱 가지로 묶어 둡니다.
가장 유명한 것이 SQL 삽입입니다. 사용자가 입력한 아이디를 SQL 문장에 문자열로 그대로 이어 붙이면, 공격자가 아이디 칸에 ' OR '1'='1 같은 조각을 넣어 조건을 항상 참으로 만들 수 있습니다. 이를 막으려면 SQL 문장의 틀을 먼저 고정하고 입력값은 물음표(?) 자리에 값으로만 넣는 방식, 즉 자바의 PreparedStatement 같은 매개변수 바인딩을 씁니다.
크로스사이트 스크립팅(XSS)은 게시글 같은 입력에 악성 스크립트를 숨겨 두어, 다른 사용자의 브라우저에서 실행되게 하는 공격입니다. 출력할 때 <와 > 같은 특수 문자를 화면에 글자로만 보이도록 바꾸는(이스케이프) 처리로 막습니다. 이 밖에 파일 경로에 ../ 를 넣어 허용되지 않은 폴더에 접근하는 경로 조작, 로그인한 사용자가 모르는 사이에 요청을 보내게 만드는 사이트 간 요청 위조(CSRF)도 입력 검증 단계에서 자주 다룹니다.
에러 처리도 보안과 관련이 있습니다. 오류가 났을 때 화면에 DB 테이블 이름이나 서버 경로 같은 내부 정보를 그대로 보여 주면 공격자에게 힌트를 주게 됩니다. 사용자에게는 간단한 안내만 보여 주고 자세한 내용은 서버 로그에만 남기는 것이 원칙입니다.
API(Application Programming Interface)는 한 프로그램이 다른 프로그램의 기능을 쓸 수 있도록 정해 둔 약속된 창구입니다. 식당의 메뉴판처럼 '이렇게 주문하면 이것을 준다'를 정해 두면, 주방 안을 몰라도 원하는 것을 받을 수 있습니다. 지도 서비스의 API를 불러 내 앱에 지도를 띄우는 것이 그 예입니다.
웹에서 가장 많이 쓰는 방식이 REST(Representational State Transfer)입니다. REST에서는 다룰 대상(자원)을 /members/15처럼 URI로 가리키고, 무엇을 할지는 HTTP 메서드로 나타냅니다. GET은 조회, POST는 생성, PUT은 전체 수정, PATCH는 일부 수정, DELETE는 삭제입니다. 서버는 요청마다 필요한 정보를 모두 받아 처리하고 이전 요청의 상태를 기억하지 않는(무상태) 것이 원칙입니다. 이런 원칙을 지킨 API를 RESTful API라고 부릅니다.
응답에는 결과 데이터와 함께 상태 코드가 붙습니다. 200은 성공, 201은 새로 만들어짐, 400은 잘못된 요청, 401은 인증 필요(로그인 안 됨), 403은 권한 없음, 404는 자원을 찾을 수 없음, 500은 서버 내부 오류입니다. 주고받는 데이터는 주로 JSON 형식을 쓰며, XML을 쓰기도 합니다.
API를 공개할 때는 아무나 함부로 부르지 못하게 인증 수단(API 키나 토큰)을 두고, 너무 자주 부르지 못하게 호출 횟수를 제한합니다. 또 어떤 주소에 어떤 값을 보내면 무엇이 돌아오는지를 API 명세 문서로 정리해, 쓰는 쪽이 문서만 보고도 연동할 수 있게 합니다.
보안 점검에서 로그인 기능이 아이디 입력값을 SQL 문자열에 그대로 이어 붙이고 있다는 지적을 받았습니다. 개발팀은 해당 코드를 PreparedStatement와 물음표 자리 바인딩으로 바꾸고, 같은 방식의 코드가 남아 있는지 DAO 전체를 다시 검사했습니다.
모바일 앱과 웹이 함께 쓰는 주문 서버를 만들면서, 주문 조회는 GET /orders/{번호}, 주문 생성은 POST /orders, 주문 취소는 DELETE /orders/{번호}로 정했습니다. 없는 주문 번호에는 404, 로그인하지 않은 요청에는 401을 돌려주도록 해, 앱 개발자가 상태 코드만 보고도 화면을 나눌 수 있게 했습니다.
화면마다 날짜를 '2026-10-25', '2026.10.25'처럼 제각각 표시하고 있어 고객 문의가 이어졌습니다. 날짜 형식 변환을 공통 모듈 하나로 모으고 모든 화면이 이 모듈을 쓰게 하자, 이후 형식을 바꿀 때 한 곳만 고치면 되었습니다.