보안정보
전문화된 보안 관련 자료, 보안 트렌드를 엿볼 수 있는
차세대 통합보안관리 기업 이글루코퍼레이션 보안정보입니다.
Spring Security: Part 1. 인증과 인가 아키텍처의 이해와 실전 활용
2024.07.31
18,641
01. Java 웹 애플리케이션 프레임워크 개요
1) Java 웹 애플리케이션 프레임워크의 국내 현황
웹 애플리케이션 백엔드 개발 언어로는 Java, PHP, ASP.NET이 대표적이다. ASP.NET는 국내 신규 프로젝트에서 거의 사용되지 않는 추세로 기존에 구축된 솔루션의 유지 보수용으로 사용되고 있다. 반면 “대한민국은 자바 공화국이다”라는 말이 있을 정도로 국내에서 Java 사용 비율은 월등히 높은 편이다.
Java 개발 프레임워크로는 Spring, JSF(JavaServer Faces), Hibernate, Structs 등이 있으며, 국내 웹 백엔드 개발에서는 Spring이 사실상 표준으로 사용되고 있다. 취업 포털 사이트와 개발 부트캠프에서도 다른 기술 스택에 비교 압도적으로 많은 일자리와 교육 기회를 제공하고 있는데, 이는 행정안전부 산하 기관인 한국정보화진흥원에서 2009년도에 제작 및 배포한 전자정부 프레임워크가 그 배경에 있기 때문이다.
전자정부프레임워크란 정부 및 공공기관, 공기업 등의 웹사이트에 자주 사용되는 공통 기능들을 Spring 프레임워크와 대표적인 Java 라이브러리(MyBatis, Apache Commons 등)를 이용해 미리 구현해 놓은 공통 컴포넌트로 이를 개발하기 위한 개발 환경, 실행 환경 등으로 구성된 웹 프레임워크이다. 원초 목적은 SI 업계의 표준 마련을 통한 생산성 향상이었으나, 정보 사업에 의존적인 SI업체의 풍토와 오래전 제작된 소스코드가 최신 버전의 Java에서도 동작하는 기형적인 Legacy 지원으로 인해 다른 언어 플랫폼보다 빠른 속도로 국내 SW산업 구조의 근간에 Java 생태계가 자리 잡게 되었다.
해외에서는 Node.js와 Python 기반 웹 프레임워크 비중이 높아지는 것에 비해 아직까지 국내 생태계의 변화는 미미하다는 것에 미루어 볼 때 Java생태계의 영향도는 지속될 것으로 보인다.
2) Java Spring 프레임워크 등장 배경
Java Spring 프레임워크는 오픈소스 애플리케이션 프레임워크로, 기업 대상(이하 엔터프라이즈)으로 확장 가능한 애플리케이션을 개발하기 위한 기능과 인프라를 지원하는 경량화된 솔루션이다. 즉, 대규모 데이터 처리와 트랜잭션이 동시에 여러 사용자로부터 이루어지는 대규모 환경을 개발하는 데 사용된다.
Java Spring 프레임워크가 등장하기 이전에는 서버 사이드 컴포넌트 아키텍처인 Enterprise Java Beans(이하 EJB)를 사용하여 웹 애플리케이션을 개발 및 서비스하였다. EJB는 트랜잭션 관리, 동시성 제어와 같은 복잡한 엔터프라이즈 수준의 요구사항을 처리할 수 있었으나, 많은 XML 구성 파일과 인터페이스로 인해 설정과 개발이 복잡했으며, 특히 2.x 이전 버전에서는 홈 인터페이스, 원격 인터페이스, 빈 클래스 등 여러 파일을 개발자가 직접 만들고 명시적으로 의존성을 관리해야 했다. 또한 특정 EJB 컨테이너(Weblogic, WebSphere) 실행 환경 없이는 기술 구현이 어렵고 단위 및 통합 테스트 시 필요한 환경을 설정하는 것이 매우 복잡하여 많은 시간이 소요되었다.
때문에 기존의 무거운 컨테이너와 더불어 복잡한 설정과 의존성 관리 등 EJB 사용의 단점을 해결하고 유연성을 지원하기 위한 경량 프레임워크의 필요성이 대두되었고 이러한 문제점들을 해결하고자 전 SpringSource 공동 창립자인 로드 존슨(Rod Johnson)이 2002년 “Expert One-on-One J2EE Design and Development” 책을 출간하며 Java Spring 프레임워크 초기 버전이 세상에 공개되었다.
여담으로 2000년대 초반, 오픈소스 프로젝트 인기가 급증하며 이를 활용하는 방안들이 모색되었고 많은 개발자들이 Java Spring 프레임워크 프로젝트에 기여함으로써, 빠른 성장과 개선을 가능하게 했다. 이러한 배경 속에서 등장한 Java Spring 프레임워크는 현재 Java 애플리케이션 개발에서 널리 사용되는 표준 프레임워크로 자리 잡으며 Java 커뮤니티에서 널리 사용되고 있다.
3) Java Spring 프레임워크 특징
Java Spring 프레임워크의 핵심 프로그래밍 모델은 순수 자바 객체(Plain Old Java Object, POJO), 의존성 주입(Dependency Injection, DI), 제어의 역전(Inversion of Control, IoC), 관점 지향 프로그래밍(Aspect-Oriented Programming, AOP), 일관된 서비스 추상화(Portable Service Abstraction, PSA)가 있다.
3.1) 순수 자바 객체(Plain Old Java Object)
Java Spring 프레임워크는 특정 프레임워크나 라이브러리에 종속되지 않고 자바의 기본 요소로 작성된 순수 자바 객체(Plain Old Java Object, 이하 POJO) 기반의 프로그래밍 모델로 애플리케이션을 구성한다. 즉, 특정 제약이나 요구사항에 종속되지 않으므로, Spring 컨텍스트 없이 POJO를 테스트할 수 있어 유연한 코드 작성과 재사용이 용이해진다.
[그림 1]의 왼쪽 코드는 POJO 프로그래밍 규칙에 맞춰 작성된 코드로 특정 기술에 종속되어 있지 않다. 반면에 오른쪽 코드는 SessionBean 클래스를 상속하여 작성된 코드로 특정 기술에 종속적이므로 POJO 기반의 코드가 아니다. 이렇게 특정 기술을 상속해서 코드를 작성하게 되면 코드 간 결합도가 높아지며, 추후 환경 변화 등의 문제로 상속받은 클래스를 변경해야 할 때, 명시적으로 사용했던 코드들을 일일이 제거하거나 수정해야 되는 일이 발생하게 된다. 때문에 POJO 기반의 코드 작성은 이러한 단점들을 극복할 수 있게 해준다.
3.2) 의존성 주입(Dependency Injection)
의존성이란 객체가 서로 의존하는 관계로 하나의 객체가 내부의 다른 객체를 사용하고 있음을 의미한다.
EJB의 고질적인 문제점 중 하나가 개발자가 객체 간의 의존성을 직접 관리한다는 것이다. 그리고 Java Spring 프레임워크에는 문제 해결을 위해 객체의 생성과 라이프사이클 관리를 개발자가 작성한 코드가 아닌 Spring 컨테이너에게 위임하여 Java Spring이 실행될 때 컨테이너가 의존성이 필요한 객체를 직접 생성하고 관계를 맺어준다. 이렇게 생성된 객체를 빈(Benn)이라고 하며 이러한 일련의 과정을 의존성 주입이라고 한다.
[그림 2]와 [그림 3]의 코드는 단순히 도서를 등록하는 LibraryService 서비스 클래스 예제이다. [그림 2]를 살펴보면 클래스 내부에 새로운 도서를 등록하는 register() 함수가 있으며 이 함수는 BookRepository 클래스의 insert() 함수를 통해 도서를 저장한다.
도서 정보를 저장하기 위해 반드시 BookRepository 클래스의 인스턴스가 사용되므로 LibraryService 클래스는 BookRepository 클래스에 의존한다고 볼 수 있다. 이러한 관계에서 BookRepository 클래스가 다른 클래스로 변경될 경우 해당 함수를 사용하는 모든 클래스의 소스 코드를 수정해야 한다. 즉, 한 클래스의 변화가 다른 클래스에 영향을 끼치게 되는 강한 결합도를 가지게 되는 것이다.
반면 [그림 3]은 new 키워드를 이용해 객체를 직접 생성하지 않고, 인자를 통해 의존 객체를 전달받는 의존성 주입 방식을 사용하고 있다. 따라서, Repository 클래스 정보가 변경되더라도 별도의 수정 작업이 필요 없게 된다. 이처럼 의존성 주입을 사용하면 객체 간 결합도가 낮아져 변경에 민감하지 않으며 코드의 재 사용성이 높아져 확장성이 향상된다.
3.3) 제어의 역전(Inversion of Control)
제어의 역전(Inversion of Control, 이하 IoC)은 객체의 생성과 객체 간의 의존성 관리를 개발자가 아닌 Spring 컨테이너가 담당하도록 하여, 객체 간의 결합도를 낮추고 애플리케이션의 유연성과 재 사용성을 높이는 디자인 패턴이다.
전통적인 절차적 프로그래밍에서는 [그림 4]와 같이 개발자가 필요한 객체를 직접 생성하고, 그 객체들 간의 의존성과 같은 상호작용을 명시적으로 관리(제어)하였으나, IoC를 적용하면 이러한 제어권이 Spring 컨테이너로 이동하게 되며 컨테이너가 의존성 객체를 관리하게 된다. 이 과정에서 제어권이 사용자에서 컨테이너로 역전된다고 하여 제어의 역전이라고 부른다. 더불어 앞서 살펴본 의존성 주입은 IoC를 구현하기 위한 매커니즘으로 활용된다.
3.4) 관점 지향 프로그래밍(Aspect-Oriented Programming)
관점 지향 프로그래밍(Aspect-Oriented Programming, 이하 AOP)은 애플리케이션 또는 소프트웨어 개발에서 비즈니스 로직과 별개로 여러 모듈에서 공통적으로 필요한 로깅, 보안, 트랜잭션 관리, 예외 처리 등의 부가 기능을 분리하여 모듈화하는 것이다.
■ 비즈니스 로직 : 프로그램의 핵심 로직으로 애플리케이션에서 데이터가 어떻게 생성되고 저장되고 수정되는지를 정의한 것
[그림 5]의 UserService 클래스에는 사용자 계정 생성과 삭제를 담당하는 함수가 있으며, 각 함수는 실행될 때 보안성을 위해 사용자 생성 및 삭제 로깅을 남긴다.
코드 예시에서는 단순히 두 개의 함수만 존재하기 때문에 큰 문제가 없어 보이지만, 실제 프로젝트에서는 비즈니스 요구사항에 따라 사용자 정보를 처리하는 일이 늘어나고 이에 대한 로깅을 계속 수행해야 한다면 불필요한 중복 코드가 크게 증가할 것이다. 이러한 상황에서 불필요한 중복 코드를 간소화하고 공통적으로 필요한 부가 기능과 비즈니스 로직을 분리하는 방안이 AOP이다.
앞서 설명한 바와 같이 AOP는 여러 모듈에서 공통적으로 필요한 부가 기능을 모듈화하는 방법이다. Java Spring 프레임워크에서는 이러한 코드를 모듈화 할 때 사용하는 것이 바로 Aspect이다.
[그림 6]의 LoggingAspect 클래스를 보면 @Aspect 어노테이션이 명시되어 있으며, 이는 해당 클래스가 Aspect 임을 나타내고 내부 코드가 모듈화된다는 의미이다. 또한, 이 클래스 안에는 @Before와 @After 어노테이션이 명시되어 있어 UserService 클래스의 모든 함수 실행 전과 후에 해당 함수가 호출된다. 즉, [그림 6]의 UserService 클래스에서 사용자 생성 및 삭제 함수가 호출될 때 정의된 Aspect에 의해 로깅 기록이 남게 된다.
이처럼 AOP를 사용하면 로깅, 보안, 트랜잭션 관리와 같은 공통 부가 기능들을 별도의 클래스로 분리 및 관리할 수 있어 코드의 중복이 줄어들고, 유지 보수성이 향상되면서 비즈니스 로직이 더욱 명확해진다.
3.5) 일관된 서비스 추상화(Portable Service Abstraction)
일관된 서비스 추상화(Portable Service Abstraction, 이하 PSA)는 다양한 플랫폼과 환경에서 동일한 방식으로 서비스를 사용할 수 있도록 제공하는 기능으로, 트랜잭션 관리, 메시징, 이메일 등 다양한 서비스를 하나의 추상화(서비스 추상화)로 만든다. 이를 통해 개발자는 특정 구현 기술과 환경의 변화에 관계없이 일관된 방식으로 서비스를 사용할 수 있게 된다.
[그림 7]은 데이터베이스 작업인 트랜잭션을 수행하는 예제 코드로 트랜잭션 관리를 위해 try-catch 블록을 사용한다. try 블록에서는 commit() 함수를 호출하여 새로운 직원 정보를 추가하는 SQL 쿼리의 결과를 저장하며, 작업 중 오류가 발생하면 catch 블록에서 rollback() 함수를 호출하여 데이터베이스 변경 사항을 취소한다.
이 예제 코드에서는 트랜잭션 처리를 위해 명시적으로 setAutoCommit(), commit(), rollback() 함수를 사용하는데, 단일 데이터베이스 작업 로직만 있을 경우에는 큰 문제가 없지만 여러 처리 로직이 필요한 경우 각 로직마다 try-catch 블록을 사용하여 트랜잭션을 관리해야 한다.
이는 반복적인 구문으로 인해 개발자의 코딩 피로와 실수를 유발할 수 있으며, 다른 데이터베이스 모듈로 교체해야 하는 상황이 발생할 때 관련된 모든 코드를 수정해야 하는 번거로움이 존재한다. 이때, 활용할 수 있는 것이 PSA이다.
[그림 8] 코드는 데이터베이스에서 사용자 정보를 가져오는 함수이다. [그림 7]의 코드와 비교했을 때 트랜잭션 처리를 위해 사용했던 setAutoCommit(), commit(), rollback() 함수가 사용되지 않는다. 이는 Java Spring에서 제공하는 @Transactional 어노테이션을 통해 PSA가 적용되었기 때문이다.
즉, 사용자의 코드에서는 트랜잭션 처리 로직이 명시되어 있지 않지만 실제로는 서비스 추상화에 의해 내부적으로 commit(), rollback() 등의 트랜잭션 처리가 수행된다. 더불어 기존 코드는 변경하지 않은 채로 JDBC(DatasourceTransactionManager), JPA(JPATransactionManger), Hiberate( HibernateTransactionManager)와 같은 트랜잭션 처리 구현체를 사용 기술에 따라 변경할 수 있어 애플리케이션의 요구 사항에 유연하게 대응할 수 있다.
02. Spring Security 프레임워크
1) Spring Security 개요
웹 애플리케이션 개발 과정은 방법론(Agile, Scrum 등)에 따라 차이가 있을 수 있지만, 큰 흐름은 [그림 9]와 같이 각 단계를 거쳐 최종 산출물이 도출된다. 이 중 설계 단계에서는 비즈니스 요구 사항을 기반으로 애플케이션의 구성 요소와 기능, 사용자 인터페이스, 데이터 흐름 등 아키텍쳐를 설계하게 되며, 더불어 조직의 자산 및 고객의 중요 정보를 보호하기 위한 보안 요구 사항도 함께 고려된다.
웹 애플리케이션 보안 기능을 구현하는 것은 정보가 자산인 현대 웹 개발에서 핵심적인 요소 중 하나이다. 그러나 현실적인 여러 여건으로 인해 이를 구현하는 것은 쉬운 일이 아니다. 개발 프로젝트 특성 상 짧은 기간 안에 많은 기능을 구현해야 하며, 최근 트렌드에 맞춰 정적인 구성보다는 다이내믹한 서비스를 요구하는 경우가 많아 개발 기간의 대부분이 화면 구성과 기능 구현에 집중된다.
또한, 보안 기능은 서비스 측면과 밀접한 관계에 있어 비즈니스 로직, 모듈, 네트워크 등에 미치는 영향을 고려해야 한다. 더불어 다양한 공격 기법에 대응해야 한다는 점과 복잡한 보안 요구 사항은 보안 기능 구현의 어려움을 더욱 가중시킨다. 이러한 문제점을 개선하고 보안을 강화하기 위해 Java Spring 프레임워크의 서브 프로젝트로 시작된 것이 바로 Spring Security이다.
Spring Security는 Java Spring 프레임워크 환경에 맞춤 보안 기능을 제공하는 강력하고 포괄적인 프레임워크이다. 제공되는 주요 기능에는 사용자 인증, 권한 부여, 웹 기반 보안 설정, 보안 이벤트 처리, XSS(Cross-Site Script, 크로스사이트 스크립트) 및 CSRF(Cross-Site Request Forgery, 크로스사이트 요청 위조) 방어가 있다.
2) Spring Security가 제공하는 주요 기능
Spring Security는 [그림 10]과 같이 인증부터 편의성 유틸리티, 웹 브라우저에서 악용 가능한 세션 고정, 클릭재킹, 크로스 사이트 요청 위조(CSRF) 등의 공격으로부터 보호하는 메커니즘까지 포괄적인 기능을 제공하며, 이러한 기능은 모듈을 추가하는 것만으로도 모두 사용할 수 있다. 그뿐만 아니라 Java Spring 기반의 웹 애플리케이션 개발에서는 IoC/DI 디자인 패턴을 고려한 인증과 인가 관련 프로세스를 직접 구현하는 것은 매우 복잡하고 시간 소모가 큰 작업이다.
그러나, Spring Security를 도입하면 인증과 인가 기능을 표준화하고 자동화할 수 있어 개발자가 보안 관련 코드를 직접 작성하지 않고도 높은 수준의 보안 기능을 쉽게 구현할 수 있다. 이를 통해 개발자는 핵심 비즈니스 로직에 집중할 수 있게 된다.
3) Spring Security 주요 아키텍처
Java Spring MVC 프레임워크에서 클라이언트로부터의 모든 HTTP 요청은 DispatcherServlet이 가장 먼저 수신한다. DispatcherServlet은 Java EE의 HttpServlet 클래스를 상속받아 구현된 프론트 컨트롤러로, 수신한 HTTP 요청 정보를 기반으로 적절한 컨트롤러에 요청 처리를 위임하는 역할을 한다. 또한, 운영 환경에 따라 서버로 들어오는 모든 요청에 대해 공통적으로 적용해야 할 부가 작업이 있을 때는 [그림 11]과 같이 DispatcherServlet 이전 단계에 위치한 Filter가 이를 처리하게 된다.
■ 프론트 컨트롤러(Front Controller) : 웹 애플리케이션에서 모든 클라이언트 요청을 받아 처리하는 중앙 처리 장치이다.
Filter는 javax.servlet.Filter 인터페이스를 구현한 클래스로, HTTP 요청이 DispatcherServlet에 도달하기 전에 요청과 응답을 가로채고 부가 작업을 처리하는 역할을 수행한다. [그림 11]을 보면, DispatcherServlet은 Java Spring 컨테이너의 가장 앞 단에 위치해 있는 반면, Filter는 Java Spring 컨테이너 외부의 웹 컨테이너(Servlet 컨테이너)에 위치해 있음을 알 수 있다.
그렇기 때문에 Filter 단계에서 인증, 인가, 로깅, 인코딩 등의 보안 기능을 수행하면 Java Spring 컨테이너에 도달하기 전에 잠재적인 보안 위협을 차단하여 시스템의 안정성을 높일 수 있다. 그리고 Spring Security도 Filter 단계에서 보안 기능을 수행하게 된다.
[그림 12]는 모든 HTTP 요청이 서버로 전송될 때, Spring Security가 이를 가로채어 보안 기능을 수행하기 위한 초기 과정을 설명한다. 클라이언트가 요청을 서버로 전송하면 컨테이너가 1개 이상의 Filter 인스턴스와 Servlet을 포함하는 FilterChain을 생성한다. 이때, 사용되는 Servlet은 DispatcherServlet의 인스턴스이며, Filter는 Filter 구현체의 인스턴스이다. HTTP 요청은 이렇게 생성된 FilterChain안의 여러 Filter를 순차적으로 거치며 각 Filter는 자신에게 할당된 작업을 수행한다.
다만, Filter는 [그림 11]에서 살펴본 것처럼 웹 컨테이너에서만 생성되고 실행되며, Java Spring의 IoC 컨테이너와는 별개의 컨테이너이기 때문에 Java Spring에서 정의된 Bean을 주입받아 사용할 수 없다. 즉, Spring 컨테이너에서 사용되는 기술(객체 의존성)을 필터에서 직접 활용할 수 없는 것이다. 이를 해결하기 위해 Java Spring에서는 DelegatingFilterProxy라는 Filter 구현체를 사용한다.
DelegatingFilterProxy 클래스는 Java Spring 프레임워크에서 제공하는 클래스 중 하나로, 웹 컨테이너의 Filter와 Spring IoC 컨테이너 사이의 통합(연결)을 지원한다. 웹 컨테이너는 DelegatingFilterProxy 클래스를 사용해서 Filter 정의를 Spring IoC 컨테이너에 정의된 특정 Bean(springSecurityFilterChain)에게 위임함으로써, Java Spring의 DI와 관리 기능을 사용할 수 있게 한다. 이를 통해 웹 컨테이너에서 생성된 Filter가 직접 요청을 처리하는 대신 해당 Bean이 요청을 처리하게 된다.
[그림 13] DelegatingFilterProxy에게 요청을 위임받은 Bean(springSecurityFilterChain)은 [그림 14]의 FilterChainProxy의 인스턴스로, springSecurityFilterChain을 관리하고, 각 요청에 대한 적절한 체인(SecurityFilterChain)을 선택하여 실질적인 보안 기능을 처리하는 역할을 수행한다.
FilterChainProxy에 의해 관리되는 SecurityFilterChain은 HTTP 요청에 대한 일련의 보안 필터를 구성하고 관리하는 역할을 하는 인터페이스이다. 각 SecurityFilterChain은 사용자가 지정한 URL 패턴과 매핑되며 해당 패턴에 맞는 요청에 대해 정의된 보안 필터들을 순차적으로 적용한다.
예시로 [그림 15]의 소스코드를 보면 접근 가능한 모든 리소스(URL) 요청에 CSRF 토큰 적용, 사용자 접근 권한 확인, 사용자 인증(로그인)과 관련된 보안 기능을 순차적으로 적용하고 있는 것을 볼 수 있다.
Spring Security에 의해 인증된 사용자의 정보(SecurityContext)는 SecurityContextHolder에 저장된다. 개발자는 필요시 SecurityContextHolder를 참조하여 보안 컨텍스트(SecurityContext)에 접근할 수 있으며 현재 사용자의 권한 확인이나 사용자 정보를 가져올 수 있다. 개발자는 이를 통해 세션 기반의 보안 정보를 효율적으로 사용할 수 있게 된다.
4) Spring Boot
Java Spring 프레임워크는 특정 컨테이너 종속, 복잡한 파일 관리, 의존성 문제 등의 기존 EJB 문제를 대체하기 위해 개발된 플랫폼이다. Java Spring 도입으로 개발자는 더 간결한 구성과 유연한 의존성 관리로 EJB를 사용하는 것보다 개발 시간 단축 및 생산성 향상을 도모할 수 있다.
그러나 기술발전과 시대적 요구사항에 따라 클라이언트와 서버 분리가 요구되면서 모놀리스 아키텍처에서 마이크로서비스 아키텍처로 전화되고 다양한 웹 기반의 Java기술이 등장하였다. 이러한 기술적 변화는 프로젝트 구성의 복잡성을 유발하면서 버전 관리와 설정 관리의 한계와 신규 개발자의 프레임워크 학습을 저해하는 요인으로 작용하며 개발 속도 저하를 가져왔다.
Java Spring벤더는 이러한 문제를 개선하고 현대적인 개발 환경에 맞는 서비스를 제공하기 위해 Spring Boot Starter 프로젝트를 공개했다. Spring Boot Starter는 간편한 설정, 빠른 시작 및 개발 속도 향상, 자동 설정, 내장 서버, 운영 편의성 등을 중점으로 개발되었고, 2014년 4월에 Spring Boot 1.0.0 버전이 릴리스되었다. Spring Boot는 모듈화된 형태로 제공되어 기존에 비해 의존성 관리가 훨씬 간단 해졌고, 웹 서버가 하나의 파일에 내장된 상태로 빌드 되어 배포가 매우 간편해졌다. 또한, 복잡한 Java Spring 설정을 자동화함으로써, 개발자가 쉽고 빠르게 애플리케이션을 개발할 수 있는 환경을 갖추게 되었다.
[그림 17]은 Spring Boot 기반의 Java Spring 프로젝트를 생성해 주는 온라인 도구이다. 이 도구를 통해 수동으로 라이브러리를 추가할 필요가 없으며, 개발자가 필요한 라이브러리를 선택하면 해당 의존성이 자동으로 프로젝트에 추가된다. 버전에 따라 개발 환경을 유연하게 구성할 수 있어 다양한 프로젝트 요구에 맞춰 쉽게 작업할 수 있으며, 본 문서의 실습에서 사용되는 웹 서버도 Spring Boot 기반으로 제작된 프로젝트다.
03. 실습을 통해 알아보는 Spring Security 인증(Authentication)
1) 사용자 인증 처리
인증(Authentication)은 시스템 보안의 첫 번째 단계로, 사용자가 누구인지 확인하는 과정이다. 주로 사용자가 제공한 사용자 계정 정보, 생체 정보 등의 자격 증명을 검증하여 사용자의 신원을 확인하고 시스템 접근 권한을 부여한다. 그리고 Spring Security에서는 기본적으로 아이디와 비밀번호를 이용한 로그인 자격 증명을 제공한다.
※ 실습 진행을 위해 필요한 설치 파일과 환경 설정은 “09. 실습 환경 설치“ 단락을 참고한다.
GitHub에서 다운로드한 두 개의 프로젝트 중 “demo” 프로젝트 파일을 불러와 웹 서버를 구동시킨다. 웹 브라우저를 통해 “8888” 포트로 접근했을 때 [그림 19]와 같이 “Test Page” 문자열이 출력되면 웹 서버가 정상적으로 구동된 것이다.
Spring Security에서 제공하는 로그인 자격 증명을 사용하기 위해 다음 단계를 따라 수행한다.
① “src → main → java → com.example.test.demo → SecurityConfig” 클래스 파일을 열람한다.
② “.requestMatchers(“/**”).permitAll()” 구문을 주석 처리한다.
③ 변경 사항 적용을 위해 웹 서버를 재 구동한다.
웹 페이지에 다시 접근하면 [그림 21]과 같이 Spring Security의 기본 로그인 자격 증명 페이지가 표시된다. 이 페이지에는 로그인을 시도하는 “Sign in” 버튼은 있지만 회원가입과 관련된 버튼은 없다. 이는 Spring Security가 제공하는 로그인 인증이 일반적으로 데이터베이스를 통해 사용자 계정을 생성하고 로그인하는 방식이 아닌, 인 메모리 방식으로 사용자 정보를 관리하기 때문이다.
로그인 계정 정보는 실행 도구(Alt + 4) 콘솔 창에 출력되는 로그 정보를 통해 확인할 수 있으며, 애플리케이션을 실행할 때마다 매번 다른 패스워드가 콘솔 창에 출력된다. 다만, 패스워드 정보는 확인할 수 있어도 아이디 정보는 출력되지 않는다. 이는 Spring Security에서 제공하는 기본 아이디가 “user”로 정해져 있기 때문이다.
[그림 22]에서 확인한 패스워드를 사용하여 로그인을 시도하면, 정상적으로 인증되어 접근 권한을 부여받고 메인 페이지로 이동하는 것을 볼 수 있다.
사용자가 서버 자원에 접근할 때, Spring Security는 어떤 기준으로 인증을 요구하고, 어떤 방식으로 계정 인증을 수행하는지 살펴보겠다.
build.gradle 파일은 Gradle 빌드 시스템에서 사용되는 스크립트 파일로 프로젝트의 빌드와 의존성을 관리하는 데 사용된다. [그림 24]를 보면 Spring Security에 대한 의존성이 정의되어 있는 것을 볼 수 있다. Spring Security는 별도의 설정 없이도 의존성을 추가하기만 하면 서버의 모든 자원에 접근하는 사용자를 대상으로 인증을 수행하게 된다.
[그림 25]는 사용자가 인증이 필요한 서버 자원에 접근할 때, Spring Security가 해당 사용자가 인증된 사용자인지 검증하는 일련의 프로세스를 보여준다. 앞서 “2.3) Spring Security 주요 아키텍처” 단락에서 설명했듯이, Spring Security는 사용자의 HTTP 요청 정보를 먼저 수신하고, 정의된 보안 필터(SecurityFilterChain)를 순차적으로 적용한다.
이에 따라 [그림 25]에서도 ① 사용자의 요청(GET /private)이 보안 필터를 통과하고 있으며, ② AuthorizationFilter 필터를 통해 인증 여부를 검사하게 된다. 만약 인증되지 않은 사용자의 요청이라면 AccessDeniedException 정보를 반환하고 접근이 거부되어 ③ 사용자는 인증 페이지로 이동하게 된다.
[그림 26]은 AuthorizationFilter가 인증 처리 프로세스를 수행하는 과정을 보여준다. 먼저 ① 사용자의 인증 정보를 참조하기 위해 SecurityContextHolder 객체 정보를 가져온다. 이 객체는 [그림 16]에서 설명한 대로 인증된 사용자의 정보를 저장하고 있다. ② 이후, 사용자의 인증 정보(SecurityContextHolder)를 AuthorizationManager에게 전달하고 인증 정보가 존재하는 경우 AuthorizationGrantedEvent가 발생하여 작업을 계속 수행하게 된다. 그러나, 인증 정보가 존재하지 않는 경우 AuthorizationDeniedEvent가 발생하여 AcessDeniedException(인증 거부)을 반환한다.
사용자 인증 정보를 직접 확인하기 위해 코드를 수정한다. ① “src → main → java → com.example.test.demo → Controller → MainController” 클래스 파일을 열람한다. 그리고 ② 해당 영역의 주석을 해제한다. 마지막으로 ③ [그림 20]의 “SecurityConfig” 클래스 파일에서 주석 처리했던 구문을 해제한다.
웹 페이지에 로그인되지 않은 상태로 접근하면 앞서 [그림 27]에서 작성한 코드가 실행되어 [그림 28]과 같이 사용자의 인증 정보(SecurityContextHolder)가 실행 콘솔 창에 출력된다.
여기서 주목해야 할 점은 사용자의 식별 정보를 담고 있는 “Principal”과 사용자에게 할당된 권한 정보를 나타내는 “Authorities”이다. 출력된 각 객체 값을 살펴보면 “anonymouseUser”와 “ROLE_ANONYMOUS”인 것을 알 수 있는데 이는 Spring Security에서 익명 사용자로 분류되며, 즉 인증 받지 않은 사용자를 나타낸다.
“/login” 페이지로 이동하여 로그인을 한 후 웹 페이지에 접근하면, 비인가 상태로 접근했을 때, 익명 사용자 정보가 출력되었던 [그림 28]과는 달리, 사용자 인증 정보를 담고 있는 객체(User) 정보가 출력된 것을 볼 수 있다.
“org.springframework.security.core.userdetails” 패키지는 사용자의 인증과 관련된 기능을 제공하며, 주요 인터페이스로 UserDetails와 UserDetailsService가 있다. UserDetails는 Spring Security에서 사용자의 정보를 캡슐화하는 인터페이스로, [그림 26]의 Authentication 객체를 통해 사용자 정보를 저장한다. 이를 구현한 클래스가 User이며, Spring Security는 이 클래스를 통해 사용자의 이름, 비밀번호, 권한, 계정 상태 등의 사용자 정보를 관리한다.
다만, [그림 29]에서 출력된 정보에는 사용자 식별 정보(Principal)는 확인할 수 있지만 권한 정보(Authorities)는 비어있는 것을 볼 수 있다. 이는 Spring Security에서 제공하는 인메모리 계정에 별도의 사용자 권한(ADMIN, MANAGER, USER)이 부여되지 않기 때문이다.
만약 계정에 권한 부여가 필요할 경우 UserDetailsService를 이용하여 권한을 설정할 수 있다. UserDetailsService는 앞서 설명한 userdetails 패키지의 인터페이스로 사용자의 정보를 가져와 Spring Security가 이 정보를 사용하여 인증 및 권한 부여를 처리할 수 있게 한다.
UserDetailsService 인터페이스를 구현하여 인메모리 계정에 사용자 권한을 부여해 보겠다.
① “src → main → java → com.example.test.demo → config → SecurityConfig” 클래스 파일을 열람하고 ② userDetailsService() 함수의 주석을 해제한다. ③ 설정이 완료되었다면, 서버를 재 구동 시킨다.
“/login” 페이지로 이동하여 [그림 30]에서 명시한 계정으로 로그인을 한 후 웹 페이지에 접근하면, [그림 31]와 같이 사용자 권한 정보(Authorities)에 “ADMIN” 권한이 부여된 것을 확인할 수 있다. Spring Security는 이를 통해 인증된 사용자라 하더라도 역할에 따라 접근 가능한 자원 목록을 관리할 수 있게 된다.
2) 인증 API
Spring Security는 OAuth 2.0, SAML, 폼 기반 로그인 등 다양한 사용자 인증 방식을 지원하며, 애플리케이션 간 효율적인 상호 작용을 위한 인증과 인가 API(Application Programming Interface) 도 함께 제공한다. 앞서 살펴본 UserDetails, UserDetailsService 같은 요소들 또한 이러한 인증 API의 일부이다.
[그림 32]은 secproject의 “SecurityConfig” 클래스의 소스코드이다. 여기서 filterChain() 함수는 “2.3) Spring Security 주요 아키텍처” 단락에서 설명한 SecurityFilterChain를 구현한 실제 코드이며, 내부의 HttpSecurity 객체를 통해 인증과 인가에 관련된 API와 상호작용하여, 사용자의 요청에 대한 웹 보안 기능을 수행하게 된다. [표 1]는 HttpSecurity 클래스에서 제공하는 대표적인 인증 API이다.
3) 폼(Form) 기반 로그인 인증
폼 기반 로그인은 사용자 자격 증명의 표준적인 방식 중 하나로, 사용자는 HTML 폼을 통해 이름과 비밀번호를 입력하여 인증한다. 이러한 폼 기반 로그인을 구현할 때는 로그인 성공 및 실패 후의 페이지 이동, 사용자 입력 값의 유효성 검증, 재시도 횟수 제한, 로그인 이후의 추가 작업 등 다양한 측면을 고려해야 한다. 그러나, formLogin 인증 API를 활용하면 이러한 측면들을 간편하게 처리하고 외부 위험 요소의 노출을 최소화할 수 있다.
[그림 34]에서는 formLogin 인증 API를 활용하여 구현된 로그인 기능을 볼 수 있다. 간략히 주요 요소를 살펴보면 loginPage() 함수를 사용하여 Spring Security가 사용할 로그인 페이지를 지정하고, loginProcessingUrl() 함수에 지정된 URL로 로그인 요청이 전송되면 Spring Security가 usernameParameter(), passwordParameter()에 설정된 파라미터 값을 사용하여 사용자 정보를 데이터베이스에서 조회하고 사용자 인증을 처리한다.
[그림 35]은 formLogin API의 요소 중 하나인 loginProcessingUrl("/login")에 지정된 URL로 로그인 요청이 전송될 때 발생하는 인증 처리 프로세스를 보여준다.
- ① 로그인 요청이 수신되면 UsernamePasswordAuthenticationFilter는 HttpServletRequest 인스터스에서 사용자 이름과 비밀번호를 추출하여 UsernamePasswordAuthenticationToken이라는 인증 토큰을 생성한다.
- ② 생성된 토큰은 AuthenticationManager에게 전달된다.
- ③ AuthenticationManager는 전달받은 토큰을 AuthenticationProvider에게 전달한다.
- ④ UserDetailsService를 통해 로그인 요청자의 정보와 데이터베이스에 저장된 사용자 정보를 비교한 후, 사용자 정보 있을 경우 이를 AuthenticationProvider에게 전달한다.
- ⑤ AuthenticationProvider는 전달받은 사용자 정보에서 패스워드 정보를 추출하여, 요청자가 제출한 로그인 폼 데이터의 패스워드와 비교한다. 일치할 경우 인증 토큰을 생성하여 이를 AuthenticationManager에게 반환한다.
- ⑥ AuthenticationManager는 토큰 유효성을 확인하고 사용자 인증 객체를 SecurityContextHolder에 저장한다.
- ⑦ 인증 정보가 존재하지 않으면 SecurityContextHolder에서 사용자의 정보를 제거한다.
- ⑧ 인증 정보가 존재할 경우 사용자에게 인증된 세션이 발급된다.
위 인증 필터 처리 과정에서 사용자 정보를 가져와 실질적인 인증을 수행하는 역할은 UserDetailsService가 맡고 있다. [그림 30]에서 확인할 수 있듯이, UserDetailsService는 인터페이스이며, 실제로 사용자 정보를 가져오는 역할은 이 인터페이스의 구현체(loadUserByUsername)가 수행한다. 개발자가 UserDetailsService 인터페이스를 직접 구현하면, 별도로 구성한 데이터베이스를 이용하여 인증 단계에 관여하고 추가적인 인증 기능을 수행할 수 있다.
4) 폼(Form) 기반 로그인 인증 실습
직접 실습을 통해 폼 기반 로그인 인증 처리 과정을 살펴보겠다. IntelliJ IDEA에서 “secproject” 프로젝트 파일을 로드하고 웹 애플리케이션 서버를 구동한다. 메인 페이지로 이동 후 “Login Page Without SQLi” 버튼을 클릭한다.
“회원가입” 버튼을 클릭하여 회원가입 페이지로 이동한다.
입력 폼을 작성하고 회원가입을 진행한다.
MySQL Workbench에서 [그림 39]의 순서에 따라 명령어를 실행하여, 사용자 계정이 정상적으로 생성되었는지 확인한다.
웹 서버에 적용된 보안 필터(filterChain)를 확인하면 폼 로그인 기반 인증과 관련된 설정을 확인할 수 있다. Spring Security에 설정된 로그인 페이지의 URL은 “/login”이고, 로그인 인증 처리 매핑 주소는 “/api/loginProc”이다.
[그림 41]의 로그인 페이지 HMTL 소스코드를 보면, 사용자 아이디와 비밀번호 입력 필드의 파라미터 이름이 각각 “userId”와 “userPw”로 설정되어 있고 “Sign in” 버튼을 클릭하면, [그림 42]에 나와 있는 것처럼 비동기 통신을 통해 “/api/CreateAccount” 주소로 로그인 폼이 제출된다. 이러한 파라미터와 전송 주소는 [그림 40]의 보안 필터에 설정된 formLogin API 요소인 usernameParameter() , usernameParameter(), loginProcessingUrl()와 매핑되며, 각 값이 일치할 경우 Spring Security에 의해 로그인 기능이 수행된다.
사용자 로그인 시 전송되는 실제 데이터를 확인하기 위해 Burp Suite Proxy 도구를 실행한 후, ① 상단의 “Proxy” 탭을 선택하고 ②”Intercept is off” 버튼을 클릭하여 활성화한다. 그리고 ③ “Open browser” 버튼을 클릭하여 웹 브라우저를 연다.
[그림 38]에서 생성한 계정으로 로그인을 시도하면 Burp Suite의 Intercept 기능이 작동하여 트래픽을 가로채게 된다. 그러면 [그림 44]와 같이 전송되는 HTTP 요청 패킷을 확인할 수 있게 되는데, [그림 42]에서 본 것과 동일하게 "/api/loginProc" 주소로 로그인 폼 데이터가 제출되는 것을 볼 수 있다.
[그림 35]에서 설명한 대로 loginProcessingUrl()에 지정된 URL 주소로 로그인 요청이 수신되면 UsernamePasswordAuthenticationFilter가 동작하여 사용자 정보를 가져오기 위해 UserDetailsService를 활용한다. 이때, 사용자 정보를 조회하는 기능을 수행하는 구현체가 loadUserByUsername()이다. [그림 45]은 UserDetailsService 인터페이스를 상속받아 구현한 코드로, 함수에 전달되는 "userid"는 [그림 44]에서 전송된 사용자 아이디(userId) 값이다.
① 전달받은 사용자 아이디를 이용하여 데이터베이스에서 사용자 정보를 조회한다. ② 조회된 사용자 정보가 없을 경우에는 인증이 실패하며, ③ 조회된 정보가 있을 경우에는 해당 정보를 기반으로 사용자 정보를 담는 객체를 생성하여 반환한다.
UserDetailsService를 통해 조회한 사용자 정보는 AuthenticationProvider에게 전달된다. [그림 4-47]의 FormAuthenticationProvider 클래스는 AuthenticationProvider를 구현한 코드다.
① [그림 46]의 ③에서 반환된 사용자 정보를 받는다. ② 그런 다음, 반환된 사용자 정보의 패스워드와 로그인 폼에 제출된 패스워드가 일치하는지 확인한다. ③ 패스워드가 일치하면 해당 사용자를 올바른 사용자로 판단하고 인증 토큰을 생성하여 이를 AuthenticationManager에게 전달한다. 이후 사용자에게 인증된 세션이 발급된다.
실습 코드를 통해 사용자가 제출한 로그인 요청서가 Spring Security에서 어떤 과정을 거쳐 인증되는지 살펴보았다. 여기서 주목할 점은, 지금까지 살펴본 과정에서 사용자의 입력 값을 검증하는 절차가 한 번도 이루어지지 않았다는 것이다.
Java Spring은 기본적으로 데이터베이스 관련 작업을 수행할 때 ORM(Object-Relational Mapping) 프레임워크 사용을 권장한다. ORM을 사용하면 개발자가 직접 SQL 쿼리를 작성하는 대신, 객체 지향적인 방식으로 데이터베이스와 상호작용할 수 있게 된다. 즉, [그림 48]과 같이 개발자가 객체를 조작하는 코드를 작성하면, ORM 프레임워크가 이를 기반으로 적절한 SQL 쿼리를 생성하고 실행한다.
이 방식은 쿼리를 동적으로 생성하는 대신 미리 정의된 매핑 정보를 사용하여 쿼리를 생성하기 때문에 보안 취약점이 발생할 가능성을 낮춘다. 또한, ORM은 자동으로 입력 값을 이스케이프 처리하여, 특수문자 (예: ', , ", %, _)가 쿼리문에 삽입되더라도 이를 일반 문자열로 인식한다.
이를 통해 쿼리문 조작을 방지할 수 있다. 그런 점에서 Spring Security에서는 인증을 수행할 때 데이터베이스 관련 작업 시 ORM 프레임워크 사용을 권고하고 있다. 대표적인 ORM 프레임워크에는 Hibernate, JPA, iBatis 등이 있다.
04. Spring Security를 활용한 인증 단계의 SQL Injection 방어
사용자 로그인 수행 단계에서 발생할 수 있는 대표적인 보안 위협에는 SQL Injection, 인증 정보 유출, 인증 시도 제한 기능 부재, 가짜 웹 사이트를 이용한 피싱, 취약한 비밀번호 허용 등이 있다. 이 중에서도 가장 위험도가 높은 취약점은 SQL Injection이다.
SQL Injection 취약점은 권한이 없는 사용자가 인증 단계를 우회하여 타 사용자의 계정으로 로그인하거나, 데이터베이스에 저장된 데이터를 조회, 수정, 삭제할 수 있고 더 나아가 저장 프로시저를 악용하여 원격에서 시스템 명령어를 실행하는 것도 가능하다.
이번 단락에서는 Spring Security의 formLogin API를 사용하지 않고 자체적으로 로그인 기능을 구현할 때, 잘못된 코딩으로 인해 발생할 수 있는 잠재적인 보안 위협인 SQL Injection에 대해 실습을 통해 살펴보겠다.
메인 페이지에서 “Login Page With SQLi” 버튼을 클릭하여 로그인 페이지로 이동한다. 해당 페이지에는 SQL Injection 취약점이 존재한다.
① Burp Suite 상단의 “Proxy” 탭을 선택하고 ② “Intercept is off” 버튼을 클릭하여 활성화한다.
[그림 38]에서 생성한 계정으로 로그인 한다.
Burp Suite가 패킷을 가로챌 때, 패킷 본문에서 마우스 오른쪽 버튼을 클릭한 후 “Send to Repeater” 버튼을 선택합니다.
상단의 “Repeater” 탭을 선택하면, [그림 53]에서 확인한 HTTP 요청문이 복제된 것을 볼 수 있다. Burp Suite의 Repeater는 사용자가 서버로 보내는 HTTP 요청을 수정하고 재전송할 수 있는 도구로, 다양한 취약점 시나리오에 대한 보안 테스트를 자동화하는 데 도움을 준다.
“Send” 버튼을 클릭하면 [그림 55]과 같이 사용자가 서버로 전송한 HTTP 요청에 대한 서버의 HTTP 응답을 확인할 수 있다.
SQL Injection은 서버의 반응에 따라 공격 유형과 기법이 달라진다. 대표적인 유형으로는 서버가 반환하는 오류 메시지를 활용한 Error-Based SQL Injection, 서버 응답의 차이를 통해 정보를 추론하는 Blind SQL Injection, 그리고 응답 지연을 이용하여 정보를 추론하는 Time-Based SQL Injection이 있다.
공격을 수행하기 전에 대상 웹 서버가 SQL Injection 공격에 취약한지 우선 확인해야 한다. 이를 확인하는 일반적인 방법은, 웹 서버가 동적으로 SQL 쿼리문을 생성할 때 특수 문자(‘, “, (, ), %, #, -)를 삽입하거나 변수 타입을 달리하여 잘못된 쿼리를 실행시켜 데이터베이스 쿼리 오류를 반환하는지를 확인하는 것이다.
[그림 56]에서는 "userId" 파라미터에 사용자 아이디와 함께 싱글 쿼터(')를 삽입하여 전송하면, 서버가 잘못된 쿼리로 처리할 수 없어 HTTP 500 응답을 반환한다. 이 과정에서 오류 정보에 쿼리 구조가 노출되며, 삽입한 싱글 쿼터가 쿼리문 구조에 포함된 것을 확인할 수 있다.
[그림 57]는 로그인 성공과 실패 시의 서버 응답 차이를 보여준다. 로그인에 성공하면 HTTP 302 응답이 반환되지만, 로그인에 실패하면 HTTP 401 응답이 반환된다. 이를 기반으로 Blind SQL Injection 공격을 수행하여 데이터베이스가 반환하는 값의 참/거짓 여부를 파악할 수 있어 정보를 추론하거나 추출할 수 있게 된다.
[그림 58]과 같이 쿼리문 조작을 통해 사용자 정보를 추출하거나 인증을 우회해 로그인할 수 있다.
문제의 사용자 인증 쿼리 구조를 살펴보면, 외부 입력 값을 동적으로 쿼리에 할당하여 사용자가 입력한 값을 문자열 그대로 삽입하는 방식으로 작동한다.
이는 데이터베이스 동적 쿼리를 작성할 때 개발자들이 흔히 저지르는 실수 중 하나로, 컴파일된 쿼리문이나 매개변수 바인딩을 사용하지 않고 빠른 구현을 위해 문자열 보간(String Interpolation)을 사용할 경우 별도의 입력 값 검증이 이루어지지 않으면 외부에서 쿼리를 조작할 수 있게 된다.
반면에 formLogin API를 사용한 경우, [그림 58]과 달리 추가적인 입력 값 검증 없이도 SQL Injection 공격으로부터 안전함을 확인할 수 있다.
05. Spring Security를 활용한 크로스사이트 요청 위조(Cross-Site Request Forgery) 방어
크로스사이트 요청 위조(Cross-Site Request Forgery, 이하 CSRF)는 웹 애플리케이션 환경에서 발생하는 보안 취약점 중 하나로, 공격자가 사용자로 가장하여 사용자가 의도하지 않은 요청을 서버에 보내도록 하는 공격이다. CSRF 공격은 사용자가 현재 인증된 세션을 유지하고 있을 때 발생하며, 공격자는 사용자의 권한을 도용하여 자동 게시글 등록, 정보 수정, 회원 가입 등의 악의적인 요청을 수행할 수 있다.
[그림 61]은 대표적인 CSRF 악용 피해 사례 중 하나이다. 대상 웹 서버의 패스워드 변경 프로세스가 미흡하고, 사용자 입력 값이 저장되는 게시글, 댓글, 방명록 등에 악의적인 스크립트를 삽입할 수 있는 경우, 공격자는 사용자의 패스워드를 자신이 지정한 패스워드로 변경하는 스크립트를 작성하여 저장한다. 이후 사용자가 해당 게시글에 접근하면 사용자의 패스워드가 공격자가 의도한 정보로 바뀌게 된다. 이 외에도 이메일, 악성 도메인을 이용한 방법 등이 있다.
CSRF 취약점에 대한 일반적으로 알려진 대응 방안으로는 HTTP GET 메서드 대신 POST 메서드 사용, CSRF 토큰 사용, HTTP Referer 헤더 검사, SameSite 쿠키 속성 사용, Double Submit Cookie 등이 있다. Spring Security는 이러한 대응을 위해 CSRF 토큰 생성 및 검증 기능을 제공한다.
Spring Security는 CsrfFilter를 통해 CSRF 토큰을 자동으로 생성하고 관리하는 기능을 제공한다. 먼저 서버로 HTTP 요청이 수신되면, 보안 필터 체인(SecurityFilterChain)이 동작하여 요청을 처리한다.
이 과정에서 CsrfFilter가 요청을 가로채고, GET, HEAD, TRACE, OPTIONS와 같은 안전한 HTTP 메서드 요청은 허용하고 다른 모든 요청에 대해서는 CSRF 토큰이 포함된 헤더나 요청 매개변수가 존재하는지 검사하여 유효성을 확인한다.
토큰의 생성, 저장, 검증 역할은 CsrfTokenRepository가 수행하며, 랜덤 UUID로 생성된 토큰을 사용자 HTTP 세션에 저장한다. 이후 CsrfTokenRequestHandler를 통해 HTTP 요청과 응답에 토큰을 포함시켜 사용자에게 반환하며, 이는 HTML 폼이나 AJAX(비동기) 요청에서 사용된다.
Spring Security는 별도의 환경 설정 없이도 기본적으로 CSRF 보호 기능을 활성화한다. 그러나 이번 실습에서는 CSRF 보호 기능을 비활성화하여 사용 여부에 따른 차이를 확인해 보고 CSRF 보호가 어떠한 방식으로 이루어지는지 살펴보겠다.
CSRF 취약점 테스트 진행을 위한 공격자 계정을 생성한다.
① “게시글 작성” 버튼을 클릭하여 게시글 작성 페이지로 이동한다. ② 사용자 정보를 공격자가 의도한 정보로 변경하는 악의적인 스크립트를 삽입한다. ③ “Save” 버튼을 클릭해 게시글을 등록한다.
로그아웃 후 이전에 생성한 관리자 계정으로 다시 로그인한다.
[그림 65]에서 공격자(attacker 계정)가 등록한 “CSRF 테스트 게시글”을 클릭한다.
게시글을 열람하면 [그림 65]에서 작성한 악의적인 스크립트는 확인할 수 없고 단순히 “CSRF 테스트 게시글” 문구만 보인다. 그러나 이는 스크립트 코드가 삭제된 것이 아니라 페이지에 숨겨져 있는 것이다.
[그림 68]과 같이 개발자 도구를 사용해 확인해 보면 페이지에 악의적인 스크립트가 삽입된 것을 볼 수 있다. 때문에, 사용자는 페이지에 삽입된 악의적인 스크립트가 실행되어도 이를 인식하지 못한다.
“마이페이지”에서 사용자 정보를 확인해보면 [그림 69]와 같이 공격자가 설정한 정보로 사용자 정보가 변조되었음을 확인할 수 있다.
이제 Spring Security에서 제공하는 CSRF 보호 기능을 사용하여 어떠한 방식으로 공격을 방어하는지 살펴보겠다. 먼저 [그림 70]과 같이 CSRF 보호 기능 비활성화 구문을 주석처리하고 CSRF 보호 기능을 활성화한다.
CSRF 보호 기능은 별도의 환경 설정 없이도 기본적으로 활성화된다. 하지만, CsrfFilter에 의해 생성된 토큰을 저장할 페이지 매개변수는 사용자가 직접 구현해야 한다. 따라서 [표 2]의 리소스 줄을 모두 주석 해제하여 토큰이 저장될 매개변수를 지정해준다. 모든 작업이 끝났다면 서버를 재 구동 시킨다.
CSRF 취약점 테스트를 위한 관리자 계정과 공격자 계정을 생성한다.
이전 [그림 65]과 동일하게, 사용자 정보가 공격자가 의도한 정보로 변조될 수 있도록 악의적인 스크립트를 삽입한 게시글을 등록한다.
관리자 계정(admin)으로 다시 로그인하여 공격자(attacker)가 작성한 게시글을 열람한다. CSRF 보호 기능이 활성화되지 않은 경우, 이 단계에서 [그림 68]에서 본 것처럼 악의적인 스크립트가 실행되어 사용자의 정보가 변조된다. 그러나, 현재는 CSRF 보호 기능이 활성화되어 있으므로, 악의적인 스크립트가 실행되더라도 사용자 정보는 변경되지 않는다.
이를 “마이페이지”에서 확인해보면, [그림 69]와는 달리 사용자 정보가 변경되지 않았음을 확인할 수 있다.
[그림 75]의 CSRF 보호 기능 사용 전/후 HTTP 요청문 차이를 살펴보면, CSRF 보호 기능을 활성화했을 때, CSRF 토큰(_csrf)과 관련된 매개변수가 추가된 것을 확인할 수 있다.
HTTP 요청문에 포함된 CSRF 토큰은 [표 2]에서 주석 해제한 매개변수 구문에 저장되어 있다가 요청이 전송될 때 [그림 75] 처럼 폼 데이터와 함께 전송된다.
CSRF 보호 기능이 활성화된 상태에서 CSRF 토큰 없이 HTTP 요청을 보내면, 서버는 [그림 77]과 같이 HTTP 403 오류를 반환하여 요청을 거부한다. 이는 요청이 CSRF 토큰의 유효성을 인증하는 과정에서 토큰이 없어 유효성 검사에 실패했기 때문이다. 따라서 게시글에 삽입된 악의적인 스크립트가 실행될 때 CSRF 토큰의 부재로 유효성 검사가 실패하면서 사용자가 의도하지 않은 동작을 서버에 요청하는 것을 막을 수 있게 된다.
지금까지 Spring Security의 CSRF 보호 기능이 어떠한 방식으로 CSRF 공격을 효과적으로 방어하는지 살펴보았다. 다만, [그림 76]에서 본 것처럼 CSRF 토큰은 HTML 폼 필드에 숨겨져 있기 때문에, 클라이언트 측에서 페이지 조작이 가능하다면 공격자는 이 토큰을 탈취하여 보호 기능을 무력화 시킬 수 있다.
[그림 78]은 클라이언 측에서 페이지를 조작할 수 있는 공격자가 CSRF 토큰을 탈취하는 과정을 보여준다. ① 사용자가 공격자가 등록한 게시글을 열람하면, 삽입된 스크립트가 동작하게 된다. ② 스크립트는 AJAX(비동기) 통신을 통해 "/main/user/mypage/info" 주소로 요청을 보내어 CSRF 토큰 값을 탈취하게 되고 ③ 이후 탈취한 토큰을 이용하여 사용자의 세션으로 서버에 데이터 변경 요청을 보내면, 사용자의 정보가 변경된다.
공격자의 계정(attacker)으로 로그인한 후, CSRF 토큰을 탈취하고 이를 이용하여 사용자 정보를 변조하는 악의적인 스크립트를 삽입한 게시글을 등록한다.
관리자 계정(admin)으로 다시 로그인하여 공격자(attacker)가 작성한 게시글을 열람한다.
이전의 [그림 74]와는 달리, 탈취한 CSRF 토큰을 사용하여 유효성 검사를 통과함으로써 공격자가 의도한 대로 사용자 정보가 변조된 것을 확인할 수 있다. 이처럼 CSRF 토큰이 탈취될 경우, 공격자는 CSRF 보호 기능이 적용되기 이전과 마찬가지로 사용자의 권한으로 악의적인 요청을 보낼 수 있으며, 이를 통해 보호 메커니즘을 우회할 수 있게 된다.
따라서 게시글, 방명록 등 사용자 입력 값이 저장되는 폼에는 특수문자(<, >, ', ", / 등)를 필터링하여 악의적인 스크립트 삽입을 방지해야 한다. 또한, 중요 기능이 있는 페이지에 접근할 때는 재 인증이나 CAPTCHA를 수행하여 스크립트를 통한 의도하지 않은 요청을 방어해야 한다.
여기서는 스크립트 삽입을 방지하기 위한 두 가지 방안을 소개하겠다.
Thymeleaf(이하 타임리프)는 웹 애플리케이션 환경에서 HTML을 동적으로 렌더링하는 Java 서버사이드 템플릿 엔진으로, Spring Security와의 통합을 통해 향상된 웹 보안 기능을 제공한다. 대표적인 기능으로는 사용자의 입력 값이나 데이터베이스 조회 결과가 포함된 HTTP 응답 페이지를 자동으로 이스케이프 처리하는 것이다.
이를 통해 삽입된 특수문자가 명령어가 아닌 일반 문자로 인식되어, 악의적인 스크립트 삽입을 방지한다. 실제 동작을 확인하기 위해 [그림 82]과 같이 게시글 내용이 삽입되는 폼의 속성을 변경하고 서버를 재 구동한다.
[그림 83]과 같이 사용자에게 팝업을 띄우는 스크립트를 삽입한 새로운 게시글을 등록한다.
작성한 게시글을 확인하면, [그림 84]와 같이 삽입된 스크립트가 타임리프의 보안 기능에 의해 이스케이프 처리되어 특수문자가 일반 문자열로 인식되고 있음을 볼 수 있다.
이스케이프 처리된 문자열을 HTML 소스코드로 직접 확인하면, [그림 85]과 같이 특수문자(<, >)가 HTML 인코딩되어 HTML Entity로 변환된 것을 볼 수 있다.
다음으로 소개할 대응은 클라이언트 측에서 사용할 수 있는 JavaScript 라이브러리(DOMPurify)를 이용한 방법이다. 이 방법은 클라이언트 측에서 필터링을 수행하므로 완전한 대응책은 아니지만, Dirty HTML 코드에 적용하기 쉽다는 장점이 있다. [그림 86]와 같이 주석을 해제하고 [그림 82]에서 변경했던 폼 속성을 이전 상태(th:utext)로 변경해준다.
■ DOMPurify GitHub: https://github.com/cure53/DOMPurify
[그림 87]와 같이 클릭 이벤트가 발생했을 때, 팝업 창을 띄우는 스크립트를 삽입한 게시글을 작성한다.
작성한 게시글의 HTML 소스코드를 확인하면, [그림 88]에서 볼 수 있듯이 a 태그에 삽입되었던 onclick 이벤트가 삭제된 것을 확인할 수 있으며, 더욱이 서버 측 필터링 기능과 함께 사용하면, 대부분의 스크립트 삽입을 효과적으로 방지할 수 있게 된다.
06. 실습을 통해 알아보는 Spring Security 인가(Authorization)
1) 사용자 인가 처리
인가(Authorization)는 인증된 사용자가 서버의 특정 자원이나 서비스에 접근할 수 있는 권한이 있는지를 결정하는 과정이다. 시스템은 인증된 사용자의 요청이 수신되면 사용자가 요청한 작업을 수행할 수 있는 권한(읽기, 쓰기, 실행 등)을 검사하고, 부여된 권한에 따라 요청을 처리한다.
이러한 과정을 접근 제어라고 하며, 대표적인 접근 제어 모델로는 역할 기반 접근 제어(Role-Based Access Control, RBAC), 규칙 기반 접근 제어(Rule-Based Access Control, RBAC), ACL(Access Control List) 가 있다. Spring Security는 기본적으로 역할 기반 접근 제어와 ACL 모델을 지원한다.
[그림 90]는 Spring Security에서 인증된 사용자를 대상으로 요청에 대한 인가 과정을 처리하는 일련의 프로세스를 보여준다.
먼저 ① 인증된 사용자의 요청을 Spring Security가 수신하면, AuthorizationFilter가 작동하여 사용자 인증 정보(SecurityContextHolder)를 인증 객체(Authentication)로 변환하고, 이를 요청과 함께 AuthorizationManager에게 전달한다. ② AuthorizationManager는 전달받은 요청을 authorizeHttpRequests의 패턴 일치 여부를 확인하고, 패턴이 일치할 경우 정의된 규칙을 실행한다. ③ 권한 부여가 거부되면, AuthorizationDeniedEvent에 의해 AccessDeniedException 예외가 발생한다. ④ 접근이 허용되면, AuthorizationGrantedEvent에 의해 AuthorizationFilter가 FilterChain을 계속 수행하게 된다.
2) 인가 API
Spring Security에서는 인가와 관련된 설정을 authorizeHttpRequests API를 통해 구성할 수 있다. [그림 91]의 인가 설정 정보를 살펴보면, ① 서버로 들어오는 모든 요청 중 “/admin” 이하 경로(“/admin/”)에 위치한 리소스를 요청하는 경우, 관리자 권한(ADMIN)을 가진 사용자만 접근할 수 있도록 제한되어 있으며, ② “/main” 이하 경로(“/main/”)에 위치한 리소스는 관리자 권한과 사용자 권한(USER)을 가진 사용자만 접근할 수 있도록 설정되어 있다. 그 외 ③ 로그인 기능과 회원가입 기능이 제공되는 페이지는 permitAll API를 통해 모든 사용자가 자유롭게 접근할 수 있도록 지정되어 있다.
실습 진행을 위해 관리자 권한(ADMIN)을 부여 받은 계정과 사용자 권한(USER)을 부여 받은 계정을 각각 생성한다.
관리자 계정(admin)으로 로그인 후, "/admin" 페이지에 접근하면 관리자 권한을 가지고 있어 접근이 가능한 것을 볼 수 있다.
반면, 사용자 계정(user)으로 접근하면 [그림 94]와 같이 HTTP 403 에러(접근 거부)가 발생하여 페이지에 접근이 거부된 것을 확인할 수 있다. 이는 Spring Security의 AuthorizationFilter가 요청한 사용자의 인증 정보(SecurityContextHolder)에서 Authorities 필드를 추출하고, 앞서 [그림 91]에서 설정한 authorizeHttpRequests API와 비교하여 접근 권한을 결정하기 때문이다. 따라서, ADMIN 권한을 부여받지 못한 사용자는 접근이 거부된다.
페이지에 접근하는 사용자의 인증 정보(SecurityContextHolder)를 직접 확인하기 위해, [그림 95]와 같이 “/admin” 리소스에 대해 모든 사용자가 접근할 수 있도록 설정하고, [그림 96]에서 AdminViewController에 지정된 주석을 해제한 후 서버를 재 구동한다.
콘솔 창에 출력된 사용자별 권한 정보를 보면, 관리자 계정(admin)은 "ROLE_ADMIN", 사용자 계정(user)은 "ROLE_USER"로 설정된 것을 확인할 수 있다. 이는 계정을 생성할 때 지정해 준 권한 정보로 앞의 “ROLE_” 접두사는 Spring Security에 의해 자동으로 붙여진다. 이를 통해 Spring Security는 각각의 사용자에게 부여된 역할과 권한을 구분할 수 있게 된다.
07. 실습을 통해 알아보는 부적절한 인가
앞서 authorizeHttpRequests API를 사용한 서버 리소스 권한 관리 방법을 살펴보았다. 이를 통해 사용자별 부여된 역할에 따라 접근 권한을 지정하고 중요 자원에 대한 접근을 제한함으로써, 인가되지 않은 사용자의 부적절한 접근을 방지할 수 있었다.
그러나, 이 방법은 [그림 98]과 같이 사용자 역할과 권한이 명확하게 분리된 리소스에서는 적용할 수 있지만, [그림 99]처럼 불특정 다수가 접근해야 하는 리소스에 대해서는 적용이 어려운 문제가 있다.
예를 들어, 게시글 수정 및 삭제, 댓글 수정 및 삭제, 사용자 정보 변경과 같은 기능을 포함하는 리소스들은 [그림 98]과 같이 서버가 사용자마다 맞춤형 리소스를 제공하는 것이 아닌, 공통 리소스에서 파라미터나 세션 정보를 이용하여 사용자를 식별하고 처리한다. 다만, 이러한 과정에서 서버 측의 사용자 식별이 제대로 이루어지지 않으면 중요 리소스에 권한 없는 사용자가 접근할 수 있어 사용자 데이터에 대한 무단 변경이나 정보 유출이 발생할 수 있다.
부적절한 인가 실습을 위해 관리자 권한을 가진 계정(admin)으로 임의의 게시글을 작성하고 그 게시글에 새로운 댓글을 등록한다.
다시 사용자 권한을 가진 계정(user)으로 로그인 후, 동일한 게시글에 새로운 댓글을 등록한다.
댓글 기능을 살펴보면, [그림 102]과 같이 댓글 수정과 삭제는 본인이 작성한 댓글에 대해서만 가능하다. 일반 사용자는 이를 보고 다른 사용자의 댓글은 수정 및 삭제가 불가능하다고 인식할 것이다. 그러나 이는 클라이언트 측 템플릿 엔진에서 출력만 제한한 것일 수 있기 때문에 공격자는 서버와의 데이터 통신을 조작하여 실제로 다른 사용자의 댓글을 수정 또는 삭제할 수 있는지를 확인하려고 시도한다.
게시글, 댓글, 방명록 등 클라이언트 측에서 동적으로 호출되어 사용자와 상호 작용하는 기능들은 일반적으로 JavaScript로 관리된다. 때문에, 공격자는 변조 관련 공격을 시도하기 전에 JavaScript와 같은 클라이언트 측 로직을 우선적으로 분석하여 처리 프로세스를 파악한다.
클라이언트 측 제어 로직을 분석하기 위해 게시글에서 마우스 오른쪽 버튼을 클릭하고 “페이지 소스 보기”를 선택한다. 그런 다음, 소스 코드 상단에 위치한 view.js 리소스 주소를 클릭한다.
[그림 103]는 댓글 수정 버튼을 클릭했을 때 실행되는 JavaScript 함수를 보여준다. 코드를 살펴보면, ① 댓글 수정을 위한 HTTP 요청을 생성할 때 본문(HTTP Body)에 폼 데이터가 포함되며, ② 이 폼 데이터에는 댓글 식별 번호(Id), 게시판 식별 번호(boardId), 변경 내용(commentText)이 저장된다. 그리고 해당 데이터를 “/main/board/comment/modify” 리소스로 전송함으로써, 서버 측에게 댓글 수정을 요청하게 된다.
댓글 수정 시 서버 측으로 전송되는 실제 데이터를 확인하기 위해 Burp Suite Proxy 도구에서 상단의 ① “Proxy” 탭을 선택하고 ② “Intercept is off” 버튼을 클릭하여 가로채기 기능을 활성화한다.
[그림 106]과 같이 작성된 댓글에서 “수정하기” 버튼을 클릭하여 댓글 수정을 시도한다.
[그림 107]와 같이 ① 댓글 식별 번호(Id) 파라미터 값을 1로 변조하고 ② 활성화된 “Intercept is on” 버튼을 클릭하여 가로챈 패킷을 서버로 전송한다. ③ 그런 다음, 댓글을 확인하면 [그림 106]에 작성한 내용으로 관리자의 댓글이 변조된 것을 확인할 수 있다.
이처럼 authorizeHttpRequests API를 이용한 인가 접근 제한 정책은 사용자 권한에 따라 분리된 리소스 환경에서는 구현이 용이하지만, 공용 리소스에 대해서는 세부적인 접근 제어 정책을 적용하기 어렵다. 따라서, 서버 측 로직에서 사용자 권한을 직접 비교하여 기능 사용 범위를 명확히 제한해야 한다.
부적절한 인가를 유발하는 대표적인 공격 메커니즘으로는 앞서 살펴본 [그림 107]과 같이 파라미터 조작을 통한 리소스 접근 및 URL 조작을 통한 직접 접근이 있다. 이러한 공격들은 인가 절차를 우회하거나 무력화시켜, 사용자가 허가 받지 않은 리소스에 접근할 수 있게 한다.
이를 대응하기 위한 방안으로는 보안 기능 결정에 외부 입력 값을 사용하는 것이 아닌 내부 세션 정보를 활용하는 것이다. Spring Security에서는 이러한 내부 세션 관리를 위해 @AuthenticationPrincipal 어노테이션을 제공한다.
@AuthenticationPrincipal 어노테이션은 Spring Security에서 제공하는 어노테이션으로, SecurityContextHolder에 저장된 인증된 사용자의 객체 정보를(principal) 주입받을 수 있다. 이 객체 정보는 요청을 보낸 사용자의 인증 정보를 나타내며, 이를 통해 사용자의 식별 정보와 권한 등을 가져와 인증 및 인가 처리를 간편하게 수행할 수 있다.
[그림 109]와 같이 BoardViewController 클래스 파일의 commentModify() 함수의 주석을 해제한다. commentModify() 함수는 댓글 수정 요청을 수신 받는 뷰 컨트롤러이다. 코드를 살펴보면, ① 수신 받은 폼 데이터(boardId, Id)를 이용해 댓글 정보를 가져오고, 해당 댓글을 작성한 사용자의 ID 정보를 추출한다.
그리고 @AuthenticationPrincipal 어노테이션을 통해 주입받은 사용자 객체(user) 정보와 함께 validateAllowUser() 함수의 인자로 전달한다. ② validateAllowUser() 함수에서는 댓글을 작성한 사용자의 ID와 요청을 보낸 사용자의 ID를 비교하여 일치 여부를 검증하며, 검증 실패 시 권한 관련 오류 정보를 반환한다.
서버를 재구동해서 변경 사항을 적용하고, [그림 110]와 같이 다시 작성된 사용자 댓글 수정을 시도한다.
앞서 [그림 107]와 같이 댓글 식별 번호(Id) 파라미터 값을 변조해 관리자가 작성한 댓글 수정을 시도하지만 [그림 111] 처럼 요청이 거부된 것을 확인할 수 있다.
08. 마무리
지금까지 Spring Security 아키텍처의 특징들을 살펴보았고, 이를 기반으로 Spring Security가 다양한 보안 기능을 어떻게 수행하는지에 대해 알아보았다. Spring Security는 본 문서에서 다룬 인증, 인가, CSRF 방어 외에도 XSS 방지, 세션 고정 공격 방지, 클릭재킹 방지 등 여러 보안 기능을 기본적으로 지원하며, 특히 곧 릴리즈 예정인 7.x 버전에서는 더욱 포괄적이고 강력한 보안 기능이 제공될 예정이다.
또한, 해외 웹 방화벽 솔루션 제품들 중에는 Spring Security와 상호작용하며 통합 보안 기능을 제공하는 사례가 늘고 있는 추세이다. Spring Security의 아키텍처와 메커니즘은 실제 웹 방화벽 및 보안 솔루션들에서 사용되는 메커니즘과 유사한 점이 많아, 보안 분야에 처음 입문한 이들이 웹 애플리케이션의 상호작용과 보안 원리 및 작동 과정을 배우기에 매우 유익할 것이다.
09. 실습 환경 설치
1) 프로젝트 빌드 환경
본 실습에서 사용될 프로젝트 개발 환경은 [표 3]으로 Windows OS 환경에서만 구동 테스트가 완료되었기 때문에 Linux/Unix OS 계열 환경에서는 구동되지 않을 수 있다. 실습에 필요한 자료는 [표 3] 다운로드 주소를 참고하면 된다.
2) 소스코드 다운로드
3) JDK 설치
JDK(Java Development Kit)는 Java 프로그래밍 언어를 개발하고 실행하기 위한 도구 모음이다. 본 실습 프로젝트의 개발 환경에서는 JDK 17 버전이 사용되었으며, 프로젝트 빌드 시 버전이 일치하지 않을 경우 호환성 문제가 발생할 수 있으므로, 원활한 실습을 위해 버전을 일치시켜 준다.
첨부된 다운로드 주소를 참고하여 JDK 17 버전을 설치한다. 그런 다음, 키보드에서 “Windows 단축키 + R”을 누르면 실행 창이 열리고, “SystemPropertiesAdvanced” 커맨드를 입력한다. 시스템 속성 창이 열리면 ① “환경 변수” 버튼을 클릭한다. ② 새로운 환경 변수 추가를 위해 “새로 만들기” 버튼을 클릭한다.
① “변수 이름”을 “JAVA_HOME”으로 설정하고 ② “변수 값”에는 설치된 JDK 경로를 입력한다. ③ “확인” 버튼을 클릭하여 설정을 마친다.
명령 프롬프트(CMD) 창을 열고 자바가 올바르게 설치되었는지 ‘java –version’명령어로 버전을 확인한다. 설치된 버전이 표시되면 정상적으로 설치 및 구성이 완료된 것이다.
4) Java 개발 통합 환경 도구 설치
Java 개발 통합 환경 도구로는 IntelliJ IDEA와 Eclipse가 대표적이다. 개인적인 취향에 따라 선호하는 도구가 다르겠지만, Java 개발 도구를 처음 접하는 경우 IntelliJ IDEA를 사용하는 것을 추천한다. IntelliJ IDEA는 다른 도구들과 비교 했을 때, Java 코드 제안 및 자동 완성 기능과 플러그인 기능을 무료로 제공하며, 직관적인 UI로 사용자들이 쉽게 익힐 수 있다. 본 실습에서도 IntelliJ IDEA가 사용될 예정이다.
첨부된 다운로드 주소를 참고하여 다운로드 받은 설치 파일을 실행하고 ① “다음” 버튼을 클릭한다. ② 프로그램 설치 폴더를 지정하고 ③ “다음” 버튼을 클릭한다.
① 설치 옵션에서 “데스크탑 바로가기 생성”, “PATH 변수 업데이트”, “컨텍스트 메뉴 업데이트”, “연결 생성” 체크하고 ② “다음” 버튼을 클릭한다. ③ “설치” 버튼을 클릭하여 설치를 진행한다.
5) MySQL DataBase 설치
본 실습 환경에서 사용할 데이터베이스는 MySQL로, 세계적으로 널리 사용되고 있는 오픈 소스의 관계형 데이터베이스 관리 시스템(RDBMS)이다.
첨부된 다운로드 주소를 참고하여 설치 파일 다운로드가 완료되면 파일을 실행하고 ① 설치 타입은 “Full”을 선택 후 ② “Next” 버튼을 클릭한다.
“Execute” 버튼을 클릭하여 필요한 의존성 모듈을 설치하고 “Next” 버튼을 클릭한다.
“Execute” 버튼을 클릭하여 [그림 121]와 같이 MySQL DataBase 운영을 위한 필요 프로그램을 설치하고 “Next” 버튼을 클릭한다.
“Next” 버튼을 클릭하여 설정 페이지로 이동한다.
① Config Type은 “Development Computer”로 설정한다. ② MySQL을 실행하는 기본 포트는 3306이며, 다른 포트 번호 사용을 원할 경우 원하는 포트 번호를 입력하고, Spring Boot에서 데이터베이스 연결 설정을 할 때 지정한 포트 번호를 사용하면 된다.
“Use Strong Password Encryption for Authentication”을 선택하고 “Next” 버튼을 클릭한다.
MySQL 관리자 계정(root) 패스워드를 설정한다. 해당 계정 정보는 Spring Boot에서 데이터베이스 연결 설정 시 사용되므로, 비밀번호를 반드시 기억해두어야 한다.
“Execute” 버튼을 클릭해 데이터베이스 서버 기동을 위한 설정을 진행하고 설치를 마친다.
설치가 완료되면, MySQL Workbench 도구를 실행한다. 해당 도구는 단일 개발 통합 환경을 제공하는 데이터베이스 설계 도구로, 이전에 사용되던 DBDesigner4의 후속 버전이다.
① 현재 내 PC에서 3306 포트로 실행되고 있는 MySQL 서버를 선택한다. ② [그림 127]에서 설정한 관리자 계정으로 로그인한다.
[그림 128]과 같이 데이터베이스를 생성하는 쿼리문을 작성한 후, “Ctrl + Enter” 키 조합을 눌러 쿼리문을 실행한다. 그 후 show 명령어를 사용하면 “secproject”라는 데이터베이스가 정상적으로 생성된 것을 확인할 수 있다.
6) 프로젝트 환경 설정
IntelliJ IDEA를 실행하고 다운로드 받은 프로젝트(secproject) 파일을 불러온다.
①② “src → main → resources → application.properties” 파일을 열람한다. 해당 파일은 Spring Boot 설정을 정의하는 데 사용되는 구성 파일로 애플리케이션이 시작될 때 이 파일을 자동으로 읽어 설정을 적용한다.
③ 서버의 동작 포트를 지정할 수 있으며, 원하는 포트 번호를 입력한다.
④ MySQL 설정 관리 항목으로 [그림 123], [그림 125]에서 지정한 포트 번호와 관리자 계정 정보를 입력한다.
⑤ 브라우저를 통해 파일 업로드 시 파일이 저장되는 디렉터리 경로를 지정한다.
설정이 완료되었다면, ① “src → main → java → com.spbt.secproject → SecprojectApplication” 클래스 파일을 열람한다. ② 오른쪽 상단의 작업 구성에서 “현재 파일”을 선택하고, 오른쪽 상단의 시작(▷ 모양) 버튼을 클릭하여 웹 애플리케이션 서버를 구동한다.
웹 브라우저를 통해 [그림 130]에서 설정한 포트로 접근했을 때, [그림 132]와 같이 페이지가 출력되면, 서버가 정상적으로 구동된 것이다.
7) Burp Suite 설치
Burp Suite는 PortSwigger에서 개발한 웹 애플리케이션 보안 테스트를 위한 종합적인 도구로, 본 실습에서는 Burp Suite의 Proxy 기능을 활용하여 몇 가지 취약점 테스트를 진행한다.
첨부된 다운로드 주소를 참고하여 설치를 진행한다. 설치가 완료되었다면, Burp Suite 도구를 실행 후, 상단의 “Proxy Settings” 버튼을 클릭한다.
① 사이드 바 메뉴에서 “Proxy” 메뉴를 선택하고 ②③ “Request”, “Response” 항목에서 ”Is in target scope” 필드를 체크하여 기능을 활성화한다.
“Response” 항목에서 ① “Matches” 필드를 선택 후 ② “Edit” 버튼을 클릭한다. ③ “Match condition” 입력 폼에 “| json”을 추가해준다.
① 사이드 바 메뉴에서 “Scope” 메뉴를 선택하고 ② “Add” 버튼을 클릭한다. ③ “Prefix” 입력 폼에 “localhost:7777”을 입력 후 ④ “OK” 버튼을 클릭한다.
이때, [그림 136]에서 포트 번호를 다른 번호로 변경하였다면 7777이 아닌 변경한
포트 번호를 명시해준다. 이 설정을 통해 지정된 도메인 외에는 Burp Suite가 트래픽을 잡지 않는다.
10. 참고자료
1. Java Spring
https://namu.wiki/w/%EC%A0%84%EC%9E%90%EC%A0%95%EB%B6%80%ED%91%9C%EC%A4%80%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC
https://namu.wiki/w/Spring(%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC
2. Spring Boot
https://www.elancer.co.kr/blog/view?seq=158
3. Spring Security 아키텍처
https://spring.io/projects/spring-security
https://velog.io/@qkrdbqls1001/Spring-Security-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0
4. Spring Security 인증
https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html
5. Spring Security 인가
https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html
6. Spring Security CSRF
https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html