WS, WAS
- WS
- 주로 정적 컨텐츠를 클라이언트에 제공한다. 클라이언트의 요청에 따라 파일을 찾아 그대로 반환한다.
- Apache, nginx 가 있다.
- WAS
- 동적인 컨텐츠를 처리하는 데 필요한 로직을 실행하고 결과를 웹 서버에 전달하여 클라이언트에게 제공하는 역할을 한다.
- Tomcat, JBoss 가 있다.
- 웹 서버가 정적인 컨텐츠를 처리하면서 동적 요청은 WAS 로 전달하는 방식으로 작동한다.
nginx
- 수 많은 동시 연결을 효율적으로 처리할 수 있다. 제한된 수의 worker process 를 사용하여 수천 개의 동시 연결을 처리할 수 있다.
- 클라이언트 요청을 실제 서버로 전달하고, 해당 서버의 응답을 클라이언트에게 반환한다.
- 캐싱
- 백엔드 서버의 응답을 캐시에 저장하고, 후속 요청에 대해 캐시된 데이터를 반환하여 백엔드 서버의 부하를 줄일 수 있다.
- Reverse proxy
- 서버 측에서 설정되며, 클라이언트의 요청을 받아 내부 서버에 전달하고 응답을 다시 클라이언트에게 반환하는 서버이다.
- 로드밸런싱으로 여러 서버 간의 트래픽을 분산하여 부하를 줄일 수 있다.
- 자주 요청되는 컨텐츠를 캐싱하여 빠른 응답 시간을 제공한다.
- 리버스 프록시에서 SSL/TLS 연결을 종료하고(즉, 암호화를 해제하고), 내부 네트워크로는 암호화되지 않은 연결을 사용할 수 있다. 이를 통해 백엔드 서버에서의 암호화/복호화 처리 부하를 줄일 수 있다.
- 내부 서버의 실제 IP 주소와 구조를 숨길 수 있어, 외부의 악의적인 공격으로부터 보호할 수 있다.
- Front proxy
- 클라이언트 쪽에서 설정되고, 클라이언트의 요청을 받아 서버로 중계하는 서버이다.
- 특정 웹사이트나 서비스 접근을 차단하거나 허용한다. 데이터 압축 또는 캐싱을 통해 대역폭 사용을 줄인다.
Certbot
- 자동 갱신을 위해서는 우분투에 기본 설치되어 있는
Crontab
을 사용하여 Cerbot 이 만료되는 주기로 갱신하는 스크립트를 작성하여 실행시켜주면 된다.
HTTP, HTTPS
두 가지는 모두 데이터를 전송하는 데 사용되는 프로토콜이다.
HTTP
- 데이터는 암호화되지 않고 평문으로 전송된다.
- 인증서가 필요하지 않다.
- 기본 포트번호가 80이다.
HTTPS
- SSL/TLS 인증서가 필요하다.
- SSL (Secure Sockets Layer) 나 TLS (Transport Layer Security) 를 사용하여 암호화되어 전송된다. 이를 통해 데이터의 기밀성과 무결성이 보장된다.
- 기본 포트번호는 443 이다.
- 과거에는 HTTPS 가 암호화 / 복호화 과정으로 인해 HTTP 에 비해 성능 저하가 있었으나, 현재는 최적화된 알고리즘과 하드웨어 가속 기술로 차이가 거의 미미한 수준이다.
- 웹사이트의 보안성을 향상시키기 때문에, 많은 검색엔진들이 HTTPS 를 사용하는 웹사이트에 SEO 가점을 제공한다.
JPA
- JPA 와 객체지향 - 엔티티 사이의 연관관계
- OSIV 설정을 꺼 두었을 때, 컨트롤러까지 영속성 컨텍스트가 유지되지 않기 때문에 조회한 엔티티가 영속 상태를 유지하지 않고, 영속성 컨텍스트의 변경 감지도 동작하지 않는다.
- Entity 생명주기
영속성 컨텍스트
인스턴스로 존재하는 엔티티를 관리하고 영속화시키는 논리적 영역
생명 주기
- 비영속
- 엔티티가 영속성 컨텍스트와 전혀 관련이 없다.
- 영속
- 영속성 컨텍스트에서 관리되고 있는 상태이다. DB 에 저장된 상태가 아니다.
- persist 를 사용하여 비영속 엔티티를 영속상태로 만들 수 있다.
- Entity Manager 가 DB에서 조회해온 데이터도 영속 상태인 엔티티가 된다.
- 영속 상태인 같은 엔티티를 조회하면 DB 접근을 하지 않고 1차 캐시 저장소에 있는 엔티티를 반환한다.
- 준영속
- 영속성 컨텍스트에서 관리되던 엔티티가 영속성 컨텍스트에서 관리되지 않는 것이다.
- 방법
- 엔티티 매니저의
detach()
사용 - 영속성 컨텍스트 전체를 초기화 시키는
clear()
-> 쿼리문 저장소의 보관된 쿼리들도 모두 초기화된다. 모든 엔티티가 전부 준영속 상태가 된다. - 영속성 컨텍스트 닫는
close()
사용
- 엔티티 매니저의
- 삭제
- 엔티티를 영속성 컨텍스트에서 관리하지 않게 되고, 해당 엔티티를 DB 에서 삭제하는 DELETE 쿼리를 보관한다.
Dirty Checking
트랜잭션이 끝나는 시점에 최초 조회 상태에서 변화가 있는 모든 엔티티 객체를 데이터베이스에 자동으로 반영해준다.
영속성 컨텍스트가 관리하는 엔티티에만 적용된다.
OSIV
영속성 컨텍스트를 뷰까지 열어두는 기능이다. 스프링 프레임워크에서는 기본적으로 true 로 설정되어 있다.
켜져있는 경우, 트랜잭션이 끝나도 영속 상태를 유지한다. 이런 경우, 실시간 트래픽이 중요한 애플리케이션에서 커넥션이 모자라서 서비스 장애의 원인이 될 수 있다.
꺼져있는 경우, 트랜잭션이 끝나면 즉시 영속성 컨텍스트를 닫고, 데이터베이스 커넥션도 반환한다. 커넥션 리소스를 낭비하지 않아 좋지만, 지연 로딩을 모두 트랜잭션 안에서 처리해야 한다는 단점이 있다.
동일한 트랜잭션을 사용하는 경우, 항상 같은 영속성 컨텍스트를 사용하게끔 구현되어 있다. 다른 트랜잭션은 다른 영속성 컨텍스트를 사용한다.
만약 서버 사이드 렌더링을 수행하는 경우 문제가 될 수 있다.
조회한 엔티티와 연관된 엔티티를 함께 사용해야 한다. 연관 엔티티는 지연 로딩으로 설정되었다고 가정하자.
이때, 조회된 엔티티는 프록시 객체로 Presentation layer 에 반환된 상태이다.
결론적으로 실제 데이터를 불러오려고 초기화를 시도할 때 지연 로딩이 작동하지 않아 예외가 발생한다.
문제점
- Presentation Layer 가 엔티티를 변경할 수 있다. OSIV 설정이 켜진 상태에서는 변경 감지가 작동하므로, 변경된 값이 바로 DB 에 반영되어 버릴 수 있다는 것이다.
- 트랜잭션 롤백 시 주의해야 한다.
- Presentation Layer 에서 엔티티가 변경되고 난 후, 비즈니스 로직을 실행하면 실제 엔티티가 수정될 수 있다.
스프링의 OSIV
스프링 프레임워크가 제공하는 OSIV 는 비즈니스 계층에서 트랜잭션을 사용하는 OSIV 이다.
- 요청이 들어오면 서블릿 필터나 스프링 인터셉터에서 영속성 컨텍스트를 생성한다. (트랜잭션은 시작하지 않는다.)
- 서비스 계층에서 트랜잭션을 시작할 때, 1번에서 미리 생성해 둔 영속성 컨텍스트를 찾아와서 트랜잭션을 시작한다.
- 서비스 계층이 끝나면 트랜잭션을 커밋하고 영속성 컨텍스트를 플러시한다. 영속성 컨텍스트는 종료되지 않는다.
- 컨트롤러와 뷰까지 영속성 컨텍스트가 유지되므로 조회한 엔티티는 영속 상태를 '유지' 한다.
- 서블릿 필터나 스프링 인터셉터로 요청이 돌아오면 영속성 컨텍스트를 종료한다. 이때, 플러시를 호출하지 않고 바로 종료한다.
트랜잭션 없이 읽기
영속성 컨텍스트를 통한 모든 변경은 트랜잭션 안에서 이루어져야 한다.
그러나 단순 조회는 트랜잭션 없이도 가능하다.
그래서?
- admin 페이지 같이 실시간 트래픽이 중요하지 않은 경우 OSIV 를 사용해도 괜찮다.
- command 와 query 를 분리하는 것이 좋다.
- 명령 (command) 서비스, 조회 (query) 서비스를 분리한다. Query Service 에서는 DTO projection 을 사용한다.
DTO Projection
엔티티 대신에 DTO 를 편리하게 조회할 때 사용한다.
엔티티의 일부 속성만 가져오고 싶을 때 사용할 수 있다.
Join 한 모든 내용 대신, 노래와 전체 좋아요만 가져오려고 사용했다.
인터페이스 기반 Projection
구현 객체는 JPA 가 프록시로 만들어준다.
projection 된 결과 객체는 영속성이 유지되지 않는다.
클래스 기반 Projection
생성자의 파라미터 이름으로 Projection 이 동작한다. 쿼리에 패키지 이름까지 다 써줘야 한다.
Open Projection
인터페이스에 정의된 메서드에 대한 구체적인 구현을 제공할 수 있다.
개발자가 원하는 로직에 따라 값을 반환하게 된다.
Closed Projection
인터페이스에 정의된 메서드만 사용되며, 엔티티의 특정 속성에 직접 매핑된다.
주의
projection 을 사용하면 기존 설정을 무시하고 eager loading 을 하는 듯하다.
CORS
- 다른 origin 끼리 리소스를 공유하게 할 수 있다. SOP 정책으로 동일하지 않은 다른 출처의 스크립트가 실행되지 않도록 브라우저에서 사전에 방지한다.
- Preflight Request
- Credentialed Request
CORS 설정
- nginx 에서 설정
- METHOD 이름이 OPTION 인 경우 허용한다.
JWT
인증에 필요한 정보들을 JSON 으로 표현한 데이터 토큰이다.
토큰 자체가 서명되어 있고, 인증에 필요한 정보들을 지니고 있다.
구조
- Header
- 토큰의 타입, 서명에 사용된 알고리즘에 대한 정보가 담겨 있다.
- 우리 서비스에서는 HS256 을 사용했다.
- Payload
- 토큰을 통해 제공되는 데이터들이 key-value 형태로 들어가 있다.
- iat : 해당 토큰이 발급된 시간
- exp: 해당 토큰의 만료 시간
- 토큰의 발급 대상, 토큰 제목, 토큰의 활성화 날짜, 토큰 발급자, 토큰 식별자 등이 있다.
- Signature
- 서명에 대한 정보이다. 헤더와 페이로드를 인코딩한 값을 합치고 특정 알고리즘과 특정 키로 암호화되어 있는 값이다.
- 헤더와 페이로드는 Base64 로 인코딩 되어 있는 값이므로 시그니쳐를 추가하여 위변조를 판별한다.
암호화 방식
비대칭 암호 방식을 이용해서 암호화한다.
세션과 비교
세션 방식은 브라우저와 웹 서버가 연결되어 브라우저가 종료될 때까지의 시점이다.
클라이언트는 요청시 쿠키에 세션 아이디를 저장해서 보내고, 서버는 전달받은 세션 아이디로 DB 에서 세션을 조회한다.
세션 장점
- 세션 아이디가 탈취되더라도 DB 에 저장된 세션을 삭제하면 된다.
- 쿠키에 아무런 의미가 없는 세션 ID 가 저장되므로, 탈취되더라도 해석할 수 없다.
세션 단점
- 매 요청 시마다 세션 저장소를 조회해야 하는 단점이 존재한다.
- 서버에 세션 객체를 저장하므로 사용자가 다수일 경우 부하가 높아진다.
- scale out 시 모든 서버가 접근할 수 있도록 별도의 중앙 세션 관리 시스템이 필요하다.
- 서버가 추가될 경우, 각 서버마다 세션 정보가 저장되므로 확장성이 좋지 않다.
JWT 장점
- JWT 가 이미 인증된 정보이므로, 이를 저장하기 위한 저장소가 필요하지 않다.
- 서버에서 클라이언트 상태를 저장할 필요가 없다.
- 서버 측 부하를 낮출 수 있다.
JWT 단점
- 토큰을 강제로 만료시킬 방법이 없다.
- 페이로드에 민감한 정보를 담기 어렵다.
- 토큰이 쿠키나 로컬스토리지 등에 저장되므로 탈취당할 위험이 존재한다.
Refresh Token
Access Token 의 유효기간을 짧게 해서 보안을 강화하면서도, 사용자가 자주 로그인하지 않도록 하기 위해서 등장했다.
Refresh Token 이 탈취된다면, 대비하는 방법
- RTR : Refresh Token 을 한 번만 사용할 수 있도록 한다. 새로운 Access Token 을 발급받을 때마다 Refresh Token 도 새롭게 발급한다. 이미 사용된 Refresh Token 임을 검사해서 서비스 측에서 탈취를 확인할 수도 있다.
보안적 측면에서 DB 에 저장하고, RTR 과 같은 방법으로 안전하게 관리하는 것이 좋다.