본문 바로가기

Spring

[Spring] WebSocket

WebSocket

WebSocket(RFC 6455)은 TCP 연결을 통해 클라이언트와 서버간의 양방향통신을 사용할 수 있게 해줍니다. 

일반적인 Http 프로토콜과는 다른 프로토콜이지만 80, 443 을 사용하고, Http와 호환되게 설계하여 기존 방화벽 규칙을 재사용할 수도 있습니다.

 

HTTP 와의 차이점

Http 통신은 애플리케이션이 다수의 URL로 모델링됩니다. 또한 클라이언트는 Req-Res 스타일로 해당 URL에 액세스합니다. 서버는 HTTP URL, 메서드, 헤더를 기반으로 라우팅합니다.

 

하지만 WebSocket 은 초기연결에 대한 URL이 1개만 존재합니다. 이후 모든 애플리케이션 메시지가 동일한 TCP 연결을 통해 전달됩니다. 이는 비동기식 이벤트 중심 메시지 아키텍쳐를 의미합니다.

 

** WebSocket 서버가 WS(ex : nginx, apache) 뒤에서 실행 중인 경우 upgrade 요청을 WebSocket 서버로 전달하도록 구성해야합니다. (마찬가지로 애플리케이션이 클라우드 환경에서 실행중인 경우 클라우드 제공업체의 WebSocket 제공지침을 확인해야합니다.)

 

WebSocket 에서 upgrade 요청이란 현재 사용중인 프로토콜을 다른 프로토콜로 바꿔달라는 의미입니다. (ex : HTTP 1.1 -> HTTP 2.0)

 

TLS HandShake

TLS 는 암호화 인증 프로토콜입니다. 기존 암호화 인증 프로토콜을 SSL 이라고 알고있을 수 있습니다. SSL/TLS 모두 암호화 인증 프로토콜이 맞고, 1999년에 SSL의 후속으로 TLS 1.0이 나오면서 이제는 SSL은 더 이상 사용되지 않습니다.

 

TLS의 원리는 웹에서 전송되는 데이터를 암호화하여 데이터 스누핑/스니핑을 당하더라도 복호화가 불가능하기 때문에 안전하다라는 개념입니다. 따라서 SSL/TLS는 클라이언트와 서버간의 HandShake 과정을 거치게하여 인증을 하게합니다. 또한 디지털 서명을 하여 데이터의 무결성도 챙길 수 있습니다. 

 

**

  1. 스푸핑 (Spoofing)  : 사용자의 시스템 권한을 획득한 뒤, 정보를 빼감
  2. 스누핑 (Snooping) : 네트워크 상에 떠도는 중요 정보를 몰래 획득
  3. 스니핑 (Sniffing) : 타인의 패킷교환을 훔쳐보는 행위

SSL/TLS 핸드 셰이크는 일반적으로 HTTPS 웹에 처음 커넥션할 때 적용됩니다.

핸드셰이크 과정은 클라이언트와 서버에서 지원하는 암호화 알고리즘이나 키 교환 알고리즘에 따라 달라지는데, 일반적으로는 RSA 키 교환 알고리즘이 사용됩니다.

 

  1. 클라이언트가 서버에게 메시지를 전송합니다. (메시지 내용 : TLS 버전, 암호화 알고리즘, 무작위 바이트 문자열)
  2. 서버가 클라이언트에게 메시지를 전송합니다. (메시지 내용 : TLS 인증서, 선택한 암호화 알고리즘, 서버에서 생성한 무작위 바이트 문자열)
  3. 클라이언트가 서버의 TLS 인증서를 인증 발행기관에 검증합니다.
  4. 클라이언트가 무작위 바이트 문자열을 서버의 공개 키로 암호화 해 premaster secret key를 만들어 서버에게 보냅니다.
  5. 서버가 premaster secret key를 개인 키를 통해 복호화합니다.
  6. 클라이언트가 생성한 무작위 키 + 서버가 생성한 무작위 키 + premaster secret key 를 통해 세션 키를 생성합니다.
  7. 클라이언트와 서버 모두 세션 키를 사용하여 암호화해 Cyper Message 를 전송합니다

 

공통의 SSL/TLS 핸드셰이크 과정은 다음과 같습니다.

 

  1. 클라이언트가 SSL/TLS 보안 웹 사이트를 열고 서버에 연결합니다.
  2. 클라이언트는 식별 가능한 정보를 요청하여 서버의 진위여부를 파악하려고 시도합니다.
  3. 서버는 공개 키가 포함된 SSL/TLS 인증서를 클라이언트에게 보냅니다.
  4. 클라이언트는 인증서가 유효하고 접속중인 웹 사이트 도메인과 일치하는지 확인합니다.
  5. 만족한다면 인증서에 적혀있는 공개키를 사용해 비밀 세션 키가 포함된 메시지를 암호화하고 전송합니다.
  6. 서버는 개인 키를 가지고 메시지를 해독하고 세션 키를 찾습니다. 
  7. 클라이언트와 서버 모두 세션 키를 사용하여 암호화해 Cyper Message 를 전송합니다

 

SSL/TLS 인증서는 사람의 신분증에 비유하셔도 좋습니다. 인증서가 있어야 SSL/TLS가 실행 될 수 있습니다. 해당 인증서에는 공개키가 포함되는데, 클라이언트는 포함된 공개키로 서버에 암호화하여 전달하게됩니다. 그리고 이런 인증서를 발급해주는 기관을 CA라고합니다.

 

SSL/TLS 인증서의 구성요소는 도메인 이름, 인증 기관, 인증 기관의 디지털 서명, 발급 날짜, 만료 날짜, 퍼블릭 키, SSL/TLS 버전이 있습니다. 또한 인증서의 유효기간은 최대 13개월로 점점 짧아지고 있습니다. 

 

HTTP HandShake

3-Way HandShake와 같은 용어입니다. 다음은 3-Way HandShake 과정입니다.

 

A클라이언트는 B서버에 접속을 요청하는 SYN 패킷을 보냅니다. 이때 A클라이언트는 SYN 을 보내고 SYN/ACK 응답을 기다리는SYN_SENT 상태가 됩니다.

 

B서버는 SYN요청을 받고 A클라이언트에게 요청을 수락한다는 ACK 와 SYN flag 가 설정된 패킷을 발송하고 A가 다시 ACK으로 응답하기를 기다립니다. 이때 B서버는 SYN_RECEIVED 상태가 됩니다.

 

A클라이언트는 B서버에게 ACK을 보내고 이후로부터는 연결이 이루어지고 데이터가 오가게 되는것입니다. 이때의 B서버 상태가 ESTABLISHED 입니다.

 

 

다음은 4-Way HandShake 과정입니다.

 

A클라이언트가 연결을 종료하겠다는 FIN플래그를 전송합니다.

 

B서버는 일단 확인메시지를 보내고 자신의 통신이 끝날때까지 기다리는데 이 상태가 TIME_WAIT상태가 됩니다.

 

B서버가 통신이 끝났으면 연결이 종료되었다고 클라이언트에게 FIN플래그를 전송합니다.

 

A클라이언트는 확인했다는 메시지를 보냅니다.

 

 

왜 TIME_WAIT 상태를 가질까?

 

TIME_WAIT는 먼저 연결을 끊는 쪽에서 생성되는 소켓으로, 혹시 모를 전송 실패에 대비하기 위해 존재하는 소켓이며,

TIME_WAIT이 없다면, 패킷의 손실이 발생하거나 통신자 간 연결 해제가 제대로 되지 않을 수 있습니다.

 

추가사항

개발하다보면 "WebSocket 을 사용해야겠다" 라는 근거가 필요할 때가 있습니다.

 

해당 공식문서에서는 뉴스, 메일, 소셜 피드 등 몇분마다 업데이트 되도 괜찮은 애플리케이션은 기존 HTTP 통신으로 솔루션을 가져갈 만합니다. 하지만 협업, 게임, 금융 앱과 같은 경우에는 실시간으로 업데이트가 되야하므로 WebSocket을 사용할 명분이 존재합니다.

 

또한, 업데이트 되는 시간뿐아니라 WebSocket을 사용할 만한 명분은 트래픽의 양으로 판단할 수 있습니다. 트래픽이 적은 경우에는 폴링이나 HTTP 스트리밍과같은 솔루션을 가져갈 수 있습니다.

 

 

SSL/TLS 인증서 유형

(1) 편에 인증서는 적용되는 도메인의 개수 및 유효성 검사수준에 따라 유형이 나뉜다고 했습니다.

검증 수준에 따른 분류 (유효성 검사)

인증서를 신청할 때 해당 도메인의 소유권을 증명하는 심사가 필요합니다. 해당 심사는 인증서의 종류에 따라 심사의 수준이 정해집니다.

 

 

확장 검증 인증서

 

  • 최고 수준의 암호화, 검증 및 신뢰도를 제공하는 디지털 인증서입니다. 심사에는 실제 사업장의 주소, 독점적 도메인 사용권 증명, 인증서 신청의 적절성등을 확인합니다.
  • 해당 인증서는 의료기록이나 보안과 같은 민감한 정보를 처리할 때 적합합니다.

조직 검증 인증서

 

  • 두번째로 높은 수준의 검증 및 신뢰도를 제공합니다. 심사에는 인증기관에 도메인 소유권을 증명해야합니다.
  • 해당 인증서는 소매 및 상업기업과 같은곳에서 고객의 정보를 보호하는 때에 적합합니다.

도메인 검증 인증서

 

  • 가장 낮은 검증수준의 디지털 인증서입니다. 심사에는 확인 이메일이나 전화에 응답하여 도메인 소유권을 증명합니다.
  • 해당 인증서는 블로그와 같은 정보 웹 사이트에 적합합니다.

도메인 개수에 따른 분류 

 

단일 도메인 인증서

 

  • 하나의 도메인 또는 하위 도메인만 보호하는 인증서입니다.
  • 여기서 하위 도메인이란 기본 도메인 앞에 텍스트 확장명이 있는 도메인입니다. 예를들어 example.com의 하위도메인은 pnc.example.com 이 있을 수 있습니다.
  • 또한 단일 도메인 인증서는 http://example.com 에 인증서를 사용할 수 있어도, http://example.com  sub.example.com 을  같은 인증서에 적용시킬 수 없습니다.

 

와일드카드 인증서

 

  • 와일드카드라하면 보통 * 표시로 사용하여 모든 것을 포함한다는 의미로 많이 사용됩니다.
  • 해당 인증서는 하위 도메인을 모두 보호할 수 있는 인증서입니다.
  • 따라서 하나의 와일드카드 인증서만으로 http://example.com, pnc.example.com, csms.example.com 과 같은 도메인들을 모두 보호할 수 있습니다.

 

다중 도메인 인증서

 

  • 통합 통신 인증서라고도 불리는 이 인증서는 소유자가 동일한 같은 서버 또는 다른 서버에서 호스팅되는 여러 도메인 이름에대해 모두 보호해줍니다.
  • 따라서 http://example.com 뿐만 아니라 secure.first.co.uk then.ride.auto 같은 도메인도 모두 보호 할 수 있습니다.

 

Spring WebSocket

실제 Spring 에 적용해 보겠습니다.

실제 실험을 하고 싶으시다면 Chrome 의 확장 프로그램인 Simple Web Socket Client 어플이나 Postman을 이용하여 실험하실 수 있습니다.

 

우선 TextWebSocketHandler 를 상속받아 구현할 CustomHandler를 만들어줍니다.

 

CustomHandler.java

 

그 후 WebSocketConfigurer를 구현하는 Config파일을 만들어 상속받아야하는 메서드를 정의해줍니다.

또한 전에 생성한 Handler를 Bean으로 등록합니다.

 

 

WebSocketConfig.java

 

Message 객체는 WebSocket으로 통신할 때의 형식을 정의해줍니다.

 

Message.java

 

그 후 차례대로 웹소켓 연결 메서드, 양 방향 데이터 통신 메서드, 소켓 연결 종료 메서드, 소켓 통신 에러 메서드의 기능을 구현해줍니다.

 

 

서버쪽 세션을 열고, 세션들을 관리할 Map 을 하나 정의해줍니다.

또한 json 과 Object 를 변환시킬 ObjectMapper도 하나 정의해줍니다. 

 

 

웹 소켓 연결 메서드

 

해당 메서드에서는 클라이언트들이 연결 되었을 때의 작업을 관리하는 메서드 입니다.

세션들을 관리하는 Map 에 새로 들어온 세션을 (세션 ID, 세션 객체) 로 저장합니다.

또한 각 클라이언트 별 세션 ID는 WebSocketSession 인터페이스를 구현하는 구현체를 찾아가 확인해 본 결과 아래와 같이 idGenerator 를 통해 생성되었고 idGenerator는 랜덤문자열을 생성하는 로직에 의해 생성되는것으로 알 수 있었습니다.

 

 

 

 

양방향 데이터 통신 메서드

 

해당 메서드는 클라이언트가 보낸 Json 타입의 데이터를 받아 작업을 처리하는 메서드입니다.

위의 메서드에서는 형식에 맞춰 잘 왔다는 가정하에 작성하였고, 보내고 싶은 Receiver에게 메시지가 가도록 작성하였습니다.

 

 

소켓 연결 종료 메서드

 

해당 메서드는 각 클라이언트들이 연결을 종료할 때 동작하는 메서드입니다.

Map 에 있는 세션을 삭제해주고 나머지 클라이언트들에게 종료 메시지를 보내게 작성하였습니다.

 

 

 


● 참고자료 :

'Spring' 카테고리의 다른 글

[Spring] UnknownNamedQueryException  (0) 2024.04.11
[Spring] Annotation Processor  (0) 2024.04.11
[Spring] AOP  (0) 2023.06.08
[Spring] JPA? Hibernate? Persistence?  (0) 2023.04.30
[Spring] Session (feat. 프로젝트 경험)  (0) 2023.04.28