Chapter 1. 오늘날의 보안

현대에는 보안의 중요성을 인식하는 개발자들이 점차 증가하고 있다. 그러나 개발 초기부터 보안을 신중하게 고려하지는 않는 편이다. 일반적으로 개발자는 애플리케이션의 목적이 비즈니스 문제를 해결하는 것임을 배우는 것으로 시작한다. 이 목적은 일정한 요구 사항에 따라 특정한 방식으로 데이터를 처리하고 저장하며 최종적으로는 사용자에게 표시할 수 있게 하는 것 까지를 말한다. 이러한 소프트웨어 개발의 관점에서는 프로세스의 관행이 잘 드러나지 않는다.

성능, 확장성, 가용성, 보안과 같은 소프트웨어의 비기능적 특징은 시간이 지남에 따라 단기적 또는 장기적 영향을 미칠 수 있다. 초기에 이러한 특징을 고려하지 않으면 애플리케이션 사용자의 수익성에도 중대한 영향이 발생할 수 있다. 때문에 소프트웨어 시스템을 다룰 때는 여러 비기능적 측면을 고려해야 한다.

1.1 스프링 시큐리티: 개념과 장점

공식 웹사이트에서는 스프링 시큐리티를 인증과 접근 제어를 위해 세부적인 맞춤 구성이 가능한 강력한 프레임워크라고 소개하고 있다. 스프링 애플리케이션에 보안을 적용하는 과정을 크게 간소화하는 프레임워크라고도 볼 수 있다. 스프링 시큐리티는 스프링 애플리케이션에서 애플리케이션 수준의 보안을 구현할 때 가장 우선적인 선택이며 인증, 권한 부여 및 일반적인 공격에 대한 방어를 구현하는 세부적인 맞춤 구성 방법을 제공한다. 스프링 시큐리티는 아파치 2.0 라이선스에 따라 릴리즈되는 오픈 소스 소프트웨어이다.

스프링 시큐리티는 스프링 애플리케이션에서 애플리케이션 수준 보안을 구현하기 위한 사실상의 표준이다. 하지만 스프링 시큐리티를 이용해도 애플리케이션이 자동으로 보호되는 것은 아니다. 개발자는 애플링케이션의 필요에 맞게 스프링 시큐리티를 구성하고 맞춤 구성하는 방법을 알아야한다. 그 방법은 기능적 요구 사항에서 아키텍처까지 여러 요소에 따라 다르다.

스프링 시큐리티로 스프링 애플리케이션에 보안을 적용하는 과정은 기술적으로는 간단하다. 스프링 프레임워크의 철학은 스프링 컨텍스트의 관리로 시작하며, 개발자는 스프링 컨텍스트에 빈을 정의해 프레임워크가 지정된 구성을 바탕으로 이러한 빈을 관리하도록 한다. 그리고 이러한 구성은 XML이 아닌 어노테이션 만으로 작성한다.

어노테이션으로 엔드포인트 노출, 트랜잭션 내의 메서드 래핑, 애스펙트 내의 메서드 가로채기 등의 수행할 작업을 스프링에 지시하는 것처럼, 스프링 시큐리티 구성도 동일하다. 즉, 어노테이션, 빈, 그리고 일반적으로 스프링 방식의 구성 스타일을 능숙하게 이용해 애플리케이션 수준의 보안을 정의하고, 보호해야 하는 동작은 메서드로 정의된다.

애플리케이션 수준의 보안을 논할 때 집의 출입을 통제하는 방법과 비교할 수 있다. 현관 깔개 밑에 열쇠를 숨겨두는가? 현관문 열쇠가 있는가? 처럼 애플리케이션에도 같은 개념이 적용되며 이 기능을 개발하는 데 스프링 시큐리티가 도움을 준다. 스프링 시큐리티는 시스템을 묘사하는 정확한 이미지를 구축하는데 다양한 선택지를 제공하는 퍼즐이다. 보안을 구성하는 방법은 간단할수도 있고, 복잡할수도 있는데, 복잡도가 높을수록 비용은 증가한다. 애플리케이션에서 이 비용은 보안이 유지 관리와 성능에 미치는 영향을 의미한다.

그러면 스프링 애플리케이션에 스프링 시큐리티를 어떻게 이용하면 좋을까? 일반적으로 애플리케이션 수준에서 가장 흔한 보안의 활용 사례는 누가 작업을 수행할 수 있는지 (권한그룹), 특정 데이터를 이용할 수 있는지를 결정하는 것이다. 구성을 기반으로 요청을 가로채고 권한을 가진 사용자만 보호된 리소스에 접근할 수 있도록 스프링 시큐리티 구성 요소를 작성한다. 개발자는 원하는 것을 정확하게 수행하도록 구성요소를 구성한다.

스프링 시큐리티 구성 요소의 다른 책임은 시스템의 다른 부분 간의 데이터 전송 및 저장과 관련이 있다. 구성 요소는 이 다른 부분에 대한 호출을 가로채서 데이터에 작업을 수행한다. 예를 들어, 데이터가 저장될 때 암호화나 해싱 알고리즘을 적용할 수 있으며 데이터 인코딩으로 이용 권리가 있는 주체만 데이터에 접근하게 할 수 있다. 개발자는 스프링 애플리케이션에서 필요할 때마다 작업의 이 부분을 수행하도록 구성 요소를 추가하고 구성해야 하낟. 스프링 시큐리티는 프레임워크를 위해 구현해야 하는 것을 알려주는 계약을 제공하고, 개발자는 애플리케이션 설계에 따라 구현을 작성한다. 데이터 전송에도 같은 개념이 적용된다.

실제 구현에서는 통신하는 두 구성 요소가 서로를 신뢰하지 않는 겨우가 있다. 어떤 구성 요소가 특정 메시지를 보냈는지 어떻게 확신할 수 있을가? 전화 통화를 하며 상대방에게 자신의 개인 정보를 알려줘야 하는 경우를 생각해보자. 상대방이 이러한 정보를 받아도 되는 사람인지 어떻게 알 수 있을까? 이 상황은 애플리케이션 보안에도 적용된다. 스프링 시큐리티는 이러한 무넺를 여러 방법으로 해결할 수 있는 구성 요소를 제공하지만 어 떤 부분을 구성해야 하는지 알고 시스템에서 이를 설정하는 것은 개발자의 몫이다. 스프링 시큐리티는 메시지를 가로채서 애플리케이션이 주고받은 모든 종류의 데이터를 이용하기 전에 통신을 검증한다.

다른 프레인 워크와 마찬가지로 스프링의 주요 목적 중 하나는 원하는 기능을 더 적은 코드로 구현할 수 있게 하는 것이며 스프링 시큐리티도 예외는 아니다. 스프링 시큐리티는 애플리케이션 보안의 가장 중요한 측면을 더 적은 코드로 수행할 수 있게 하여 스프링 프레임워크를 완성한다. 스프링 시큐리티는 상용구 코드를 작성하거나 앱 간에 같은 로직을 반복해서 작성할 필요가 없도록 사전 정의된 기능을 제공한다. 또한 구성 요소를 구성할 수 있으므로 유연성이 매우 높다.

1.2 소프트웨어 보안이란?

현대의 소프트웨어 시스템은 특히 GDPR(General Data Protection Regulation) 요구 사항을 고려할 때 상당 부분이 민감한 정보일 수 있는 대량의 데이터를 관리한다. 사용자가 개인적이라고 생각하는 모든 정보는 소프트웨어 애플리케이션에서 민감한 정보가 된다. 민감한 정보에는 전화번호, 이메일 주소 또는 식별 번호와 같은 무해한 정보도 있지만, 유출됐을 때 위험성이 높은 신용 카드 정보 등의 정보는 더 중요하게 고려해야 한다. 애플리케이션은 이러한 정보에 접근, 변경 또는 가로챌 기회가 없게 해야하며 의도된 사용자 이외의 대상은 어떤 식으로든 데이터와 상호 작용할 수 없게 해야한다.

보안은 계층별로 적용해야 하며 각 계층에는 다른 접근 방식이 필요하다. 이러한 각 계층은 성을 보호하는 일과 비교할 수 있다. 해커는 앱이 보호하는 리소스를 획득하기 위해 여러 장애물을 통과해야 한다. 각 계층을 더 잘 보호할수록 악의적인 대상이 데이터에 접근하거나 무단 작업을 수행할 가능성이 낮아진다.

소프트웨어 시스템에서 보안은 애플리케이션 수준에만 적용되는 것이 아니다. 예를 들어, 네트워킹의 경우 여러 문제를 고려하고 특정한 관행을 적용해야 하며 스토리지에도 완전히 다른 사항이 적용된다. 또한 배포 등에 관한 다른 철학도 있다. 스프링 시큐리티는 애플리케이션 수준의 보안에 속하는 프레임워크다.

애플리에키션 수준 보안은 애플리케이션이 실행되는 환경과 애플리케이션이 처리하고 저장하는 데이터를 보호하기 위해 해야하는 모든 것을 나타낸다. 이것은 애플리에키션이 사용되고 영향을 받는 데이터에만 국한되는 문제가 아니다. 애플리케이션에 악의적인 개인이 전체 시스템에 영향을 줄 수 있는 취약성이 있을 수 있다.

마이크로서비스 아키텍처에서는 다양한 취약성이 생길 수 있으므로 주의해야 한다. 보안은 우리가 여러 계층에서 설계해야 하는 공통 관심사다. 한 계층의 보안 문제를 해결할 때는 되도록 위 계층이 존재하지 않는다고 가정하는 것이 바람직하다.

인증과 권한 부여는 사실상 거의 모든 애플리케이션에 사용된다. 인증은 애플리케이션이 사용자를 식별하는 방법이다. 사용자를 식별하는 목적은 나중에 그들이 무엇을 하도록 허용해야 하는지 결정하기 위한것이다. 이것이 바로 권한부여다.

애플리케이션에서 다양한 시나리오를 위해 권한부여를 구현해야 하는 경우가 많다. 대부분 애플리케이션에는 사용자가 특정 기능에 대한 접근 권한을 얻는데 제한이 있으며 접근 권한을 얻으려면 먼저 누가 접근을 요청하는지 알아야 한다. 즉, 인증을 해야한다. 또한 사용자가 시스템의 해당 부분을 이용하도록 허용하려면 사용자에게 어떤 이용 권리가 있는지 알아야 한다. 시스템이 복잡해지면 특정한 인증과 권한 부여의 구현이 필요한 다양한 상황이 발생한다.

예를 들어, 사용자를 대신해 시스템의 특정 구성 요소에 데이터의 하위 집합이나 작업에 대한 권한을 부여하려면 어떻게 해야할까? 예를 들어, 프린터에 사용자의 문서를 읽을 수 있는 접근 권한이 필요할 수 있다. 간단하게 사용자의 자격 증명을 프린터에 제공해야 할까? 하지만 이렇게 하면 프린터에 필요 이상의 권한이 부여되고 사용자의 자격 증명이 노출되는 문제가 있다. 사용자를 가장하지 않고 해결하는 올바른 방법이 있을가? 이는 핵심적인 질문이고 애플리케이션을 개발한면서 자주 접할 질문이다.

인증과 권한 부여는 시스템을 위해 선택한 아키텍처에 따라 구성 요소만이 아닌 전체 시스템 수준에 이용될 수 있다. 또한 스프링 시큐리티를 이용할 때는 같은 구성 요소의 다른 계층에도 권한 부여를 적용하는 것이 좋을 수 있다. 사전 정의된 역할과 권한의 집합이 있으면 이 설계는 더욱 복잡해진다.

데이터 저장소에 관해서도 주의할 필요가 있다. 저장 데이터(Data at Rest)는 애플리케이션의 책임을 가중한다. 모든 데이터를 읽을 수 잇는 형식으로 저장하지 말고 개인 키로 암호화한 데이터나 해시된 데이터로 저장해야 한다. 자격 증명 및 개인 키와 같은 비밀도 저장 데이터로 간주할 수 있는데, 일반적으로 이러한 데이터는 비밀 볼트에 조심스럽게 저장해야 한다.

데이터는 저장 데이터와 전송 중 데이터로 구분한다. 이 맥락에서 저장 데이터는 컴퓨터 스토리지에 있는 데이터, 즉 지속된 데이터를 말한다. 전송 중 데이터는 한 위치에서 다른 위치로 교환 중인 모든 데이터를 말한다. 데이터의 유형에 따라 다른 보안 조치를 적용해야 한다.

실행 중인 애플리케이션은 내부 메모리도 관리해야 한다. 애플리케이션의 힙에 저장된 데이터도 취약성의 원인이 될 수 있다. 클래스 디자인에 따라 앱이 자격 증명이나 개인키 등의 민감한 데이터를 장시간 보관할 때가 있는데 힙 덤프 이용 권리가 있는 누군가가 이 사실을 알고 악용할 수 있다.