서브넷팅(Subnetting)
서브넷의 등장 배경
흔히 사용되는 IPv4 주소 체계는 클래스를 나누어 IP를 할당한다. 하지만 이 방식은 비효율적일 수 있다. 예를 들어 어떤 기관에 A 클래스를 할당한다면 16,777,214개의 호스트를 할당할 수 있게 된다. 그런데 이 기관이 100개의 호스트만 할당한다면 16,777,114개의 호스트는 낭비되게 된다. 이러한 비효율성을 해결하기 위해 네트워크 장치들의 수에 따라 효율적으로 사용할 수 있는 서브넷(subnet)이 등장하게 되었다.
서브넷(subnet)과 서브넷 마스크(subnet mask)
서브넷은 IP 주소에서 네트워크 영역을 부분적으로 나눈 '부분 네트워크'를 뜻한다. 이러한 서브넷을 만들 때 사용되는 것이 바로 서브넷 마스크이다. 즉, 서브넷 마스크는 IP 주소 체계의 Network ID와 Host ID를 식별하는 데 도움을 준다.
예를 들어 C클래스는 기본적으로 앞 24비트가 Network ID, 뒤 8비트가 Host ID를 나타낸다. 이 때 서브넷 마스크를 이용하면 원본 네트워크를 여러 개의 네트워크로 분리할 수 있다. 이러한 과정을 서브넷팅(subneting)이라고 한다.
서브넷팅을 자세히 알아보기 전에 기본 서브넷 마스크에 대해 알아보자.
각 클래스마다 기본 서브넷 마스크는 위 표와 같다. (D, E클래스는 사용하지 않는다.) 이러한 기본 서브넷 마스크를 이용하면 IP 주소의 Network ID와 Host ID를 구분할 수 있다. IP 주소에 서브넷 마스크를 AND연산하면 Network ID가 된다.
예를 들어 C클래스인 192.168.32.0 이라는 IP주소가 있다고 하자. C클래스의 기본 서브넷 마스크는 255.255.255.0이므로 AND 연산을 하면 192.168.32.0이 나오는데 이것이 Network ID이다.
예시의 IP주소를 보면 192.168.32.0/24 처럼 /24가 표시되어 있다. 이것은 서브넷 마스크의 bit수(왼쪽에서부터 1의 개수)를 나타낸다. 즉 /24는 해당 IP의 서브넷 마스크 왼쪽에서부터 1이 24개라는 것을 의미한다.
그런데 애초에 IP클래스들은 Network ID를 나타내는 부분과 Host Id를 나타내는 부분이 이미 구분되어 있는데 굳이 서브넷 마스크가 필요한 이유가 무엇일까? 위에서도 설명했지만 서브넷팅을 하여 네트워크 부분의 주소 공간을 확장해 여러 개의 논리적인 네트워크로 나누어 효율적으로 사용하기 위해서이다.
서브넷팅(subnetting)
서브넷팅은 IP주소 낭비를 방지하기 위해 원본 네트워크를 여러 개의 서브넷으로 분리하는 과정을 뜻한다. 서브넷팅은 서브넷 마스크의 bit수를 증가시키는 것이라고 생각하면 이해가 편하다. 서브넷 마스크의 bit수를 1씩 증가시키면 할당할 수 있는 네트워크가 2배수로 증가하고 호스트 수는 2배수로 감소한다.
예를 들어 C클래스인 192.168.32.0/24를 서브넷 마스크의 bit수를 1 증가시켜 192.168.32.0/25로 변경한다고 하자.
192.168.32.0/24는 원래 하나의 네트워크였다. 이 때 할당 가능한 host 수는 2^8-2=254개이다. 여기서 2개를 빼는 이유는 첫 번째 주소인 192.168.32.0은 Network Address로 쓰이고 마지막 주소인 192.168.32.255는 Broadcast Address로 쓰이기 때문에 호스트에 할당할 수 없기 때문이다.
🤯 Network Address / Broadcast Address
1. 네트워크 주소(Network Address): 이 주소는 네트워크 자체를 식별하는 데 사용된다. 네트워크 주소는 해당 서브넷의 가장 낮은 IP 주소이며, 호스트 부분이 모두 0으로 설정된다.(예: 192.168.32.0) 이 주소는 네트워크를 대표하는 주소로 사용되며 개별 호스트에는 할당되지 않는다.
2. 브로드캐스트 주소(Broadcast Address): 이 주소는 네트워크 상의 모든 호스트에게 데이터를 동시에 전송하기 위한 주소이다. 브로드캐스트 주소는 해당 서브넷의 가장 높은 IP 주소이며, 호스트 부분이 모두 1로 설정된다.
(예: 192.168.32.255) 이 주소로 보낸 패킷은 네트워크 내의 모든 호스트에게 전달된다.
이 두 주소는 특별한 목적으로 예약되어 있기 때문에 네트워크 내 장치나 컴퓨터에 할당할 수 없다. 그래서 256개의 가능한 주소 중에서 이 두 개를 제외한 254개의 주소만이 실제 장비나 장치에 할당될 수 있는 것이다.
이 때 서브넷 마스크의 bit수를 1 증가시켜서(서브넷팅) 192.168.32.0/25로 변경하게 되면 Network ID부분을 나타내는 부분이 24비트에서 25비트로 증가하고 Host ID를 나타내는 부분이 8비트에서 7비트로 줄어든다. 즉 할당 가능한 네트워크 수가 2개로 증가하고 각 네트워크(서브넷) 당 할당가능한 호스트 수는 2^7-2=126개로 줄어든다. 또한 서브넷 마스크가 255.255.255.128로 변한 것을 확인할 수 있다.
위 그림을 보면 네트워크 수가 어떻게 2개로 늘어났는지 이해하기 쉽다. 정리하자면 다음과 같다.
- 192.168.32.0: 서브넷 1의 Network Address
- 192.168.32.1~192.168.32.126: 서브넷 1의 host 할당 가능 부분
- 192.168.32.127: 서브넷 1의 Broadcast Address
- 192.168.32.128: 서브넷 2의 Network Address
- 192.168.32.129~192.168.32.254: 서브넷 2의 host 할당 가능 부분
- 192.168.32.255: 서브넷 2의 Broadcast Address
C클래스를 예시로 들었지만 A,B클래스도 똑같은 방식을 적용하면 된다.
DNS(Domain Name System)
DNS란?
도메인 네임 시스템(Domain Name System, DNS)은 호스트의 도메인 네임(www.example.com)을 네트워크 주소(192.168.1.0)로 변환하거나 그 반대의 역할을 수행하는 시스템이다.
예를 들면 우리가 자주 접속하는 naver.com, google.com 모두 DNS를 가진 DN(Domain Name)이라고 할 수 있다. 이들은 사실 문자열의 탈을 쓴 IP이다.
cmd에서 naver의 DN을 적어 ping을 확인해 보면 IP주소는 223.130.192.247인 걸 확인할 수 있다.
원래는 IP 주소를 브라우저에게 제공하면 해당 서버에서 홈페이지를 제공하는 식으로 동작한다. 하지만 우리가 복잡한 IP주소를 외우기엔 너무 힘드니까, 마치 별명을 지어 전화번호부에 정리하고 접근하기 쉽게 하는 시스템인 것이다.
사용자가 웹브라우저에 도메인 'www.example.com'을 입력하면 아래와 같은 과정을 거치게 된다.
- 도메인 주소 www.example.com을 브라우저에 입력하게 되면 도메인 주소들을 가지고 있는 네임서버(DNS 서버)에 접속
- 네임서버에 접속한 도메인(www.example.com)과 연결된 IP정보(12.34.56.78)를 확인하고 IP를 사용자 PC에 전달
- 사용자 PC는 전달받은 서버의 IP주소로 접속
- 서버의 IP로 연결된 브라우저에 서버의 내용(홈페이지)을 출력
물론 실제로 이렇게 간단히 이루어지지 않고 좀 더 복잡한 과정을 통해 출력된다.
DNS 작동 원리
위에서 DNS 동작에 대해 알아봤지만 보다 자세히 어떻게 동작하는지 알아보자. DNS 동작 과정을 큰 그림으로 본다면, 클라이언트가 도메인명을 브라우저에 검색하고, 도메인 정보가 저장된 네임 서버(DNS 서버)로 가서 도메인과 일치하는 IP주소로 가라고 지시하게 되고, 다시 그 IP주소로 접속하게 되면 홈페이지가 열리는 기본적인 형태는 같다.
단지 DNS 서버에서 도메인 & IP정보를 얻는 과정이 약간 복잡하게 되어 있을 뿐이다. 전세계 도메인 수가 너무 많아 DNS 서버 종류를 계층화해서 단계적으로 처리하기 때문이다.
DNS 동작 순서
1. 웹 브라우저에 www.naver.com을 입력하면 먼저 PC에 저장된 Local DNS(기지국 DNS 서버)에게 "www.naver.com"이라는 hostname(호스트에 부여된 이름. 즉 IP주소를 갖고 있는 호스트에게 이름을 부여한 것)에 대한 IP 주소를 요청한다.
Local DNS에는 "www.naver.com의 IP주소"가 없을 수도 있다. 만일 예전에 네이버에 접속했던 전적이 있다면, Local DNS에 접속정보가 캐싱되어 있어 바로 PC에 IP주소를 주고 끝난다. (바로 1번->8번으로 넘어가 빠르게 웹페이지에 접속할 수 있다.)
💡 Local DNS(기지국 DNS 서버)란?
기본적으로 인터넷을 사용하기 위해선 IP를 할당해주는 통신사(KT, SK, LG 등)에 등록하게 된다.
컴퓨터의 LAN선을 통해 인터넷이 연결되면 가입했던 각 통신사의 기지국 DNS서버가 등록되게 된다.
만약 KT를 사용한다면 KT DNS가 되고, SK를 사용한다면 SK DNS가 자동으로 세팅된다.
2. Local DNS는 이제 "www.naver.com의 IP주소"를 찾아내기 위해 다른 DNS 서버들과 통신(DNS 쿼리)을 시작한다. 먼저 Root DNS 서버에게 "www.naver.com의 IP주소"를 요청한다.
💡 Root DNS(루트 네임서버)란?
Root DNS는 인터넷 도메인 네임 시스템의 루트 존이다. ICANN이 직접 관리하는 절대 존엄 서버로, TLD DNS서버 IP들을 저장해두고 안내하는 역할을 한다. 전세계에 961개의 루트 DNS가 운영되고 있다.
3. Root DNS 서버는 "www.naver.com의 IP주소"를 찾을 수 없어 Local DNS서버에게 "www.naver.com의 IP주소"를 찾을 수 없으니 다른 DNS 서버에게 물어보라고 응답한다.
4. 이제 Local DN 서버는 com 도메인을 관리하는 TLD DNS 서버(최상위 도메인 서버)에 다시 www.naver.com에 에 대한 IP주소를 요청한다.
💡 TLD(Top-Level Domain, 최상위 도메인) DNS Server란?
TLD는 도메인 등록 기관(Registry)이 관리하는 서버로, 도메인 네임의 가장 마지막 부분을 말한다. 예를 들어 웹사이트에서 한 번쯤은 봤던 .com이나 co.kr 같은 도메인들을 관리하고 부여하는 서버이다.
Authoriative DNS 서버 주소를 저장해두고 안내하는 역할을 한다.
5. com 도메인을 관리하는 DNS 서버에도 해당 정보가 없으면 Local DNS 서버에게 "www.naver.com의 IP주소"를 찾을 수 없으니 다른 DNS 서버에게 물어보라고 응답한다.
6. 이제 Local DNS 서버는 naver.com DNS 서버(Authoritative DNS 서버)에게 다시 "www.naver.com의 IP주소"를 요청한다.
💡 Authoritative DNS Server란?
실제 개인 도메인과 IP 주소의 관계가 기록/저장/변경되는 서버
그래서 권한의 의미인 Authoritative가 붙는다.
일반적으로 도메인/호스팅 업체의 '네임서버'를 말하지만, 개인이나 회사가 DNS 서버 구축을 한 경우에도 여기에 해당하게 된다.
7. naver.com DNS 서버엔 "www.naver.com의 IP주소"가 있다.
그래서 Local DNS 서버에게 "www.naver.com에 대한 IP주소는 222.122.195.6"라는 응답을 한다.
8. 이를 수신한 Local DNS는 www.naver.com의 IP주소를 를 캐싱하고 이후 다른 요청이 있을 시 응답할 수 있도록 IP주소를 단말(PC)에 전달해 준다.
💡 이렇게 Local DNS 서버가 여러 DNS 서버에 차례대로 (Root DNS 서버 -> TLD DNS 서버(.com) -> Authoritative DNS 서버(naver.com)) 요청하여 그 답을 찾는 과정을 재귀적 쿼리 Recursive Query라고 부른다.
정리하자면 모든 Computer들은 Root DomainDNS server의 IP주소를 알고 있다. 그리고 Root Domain을 담당하는 DNS 서버는 TLD(Top-lever domain)를 담당하는 서버 목록과 IP를, TLD를 담당하는 DNS 서버는 Second-level domain을 담당하는 서버 목록과 IP를, Second-level domain을 담당하는 DNS 서버는 Sub domain을 담당하는 서버 목록과 IP를 알고 있는 것이다.
결국, csbooks.wisedog.net의 IP주소는 Sub domain을 전담하고 있는 DNS 서버가 알고 있다.
브라우저 도메인 네임 검색 과정
우리가 웹 브라우저에 URL을 입력했을 때 어떤 일이 발생하는지 정리해보자. 웹 브라우저에 https://naver.com 과 같은 URL을 입력하면 브라우저는 인터넷에서 사이트를 호스팅하는 서버를 파악하는데, 이 때 naver.com 도메인을 검색해서 주소를 찾는다. naver.com 은 위에서 말했듯이 고유한 IP 주소를 가진다.
DNS 조회를 수행하여 도메인 이름(naver.com)을 기반으로 서버 IP주소를 찾을 수 있다.
1. 웹 브라우저에 URL을 입력한다.
2. 웹 브라우저가 도메인의 IP주소를 조회한다. (먼저 캐시를 찾고, 그 다음 DNS 서버에 검색한다.)
3. 웹 브라우저가 찾은 IP주소를 기반으로 서버와의 TCP 연결을 시작한다.
4. 웹 브라우저가 HTTP(S) 요청을 서버로 전송한다.
5. 웹 서버가 요청을 처리하고 응답을 다시 웹 브라우저로 전송한다.
6. 웹 브라우저가 전송 받은 컨텐츠를 렌더링한다.
RESTful
웹 프로젝트를 하다 보면 REST API 를 숱하게 봤을 것이다. REST API란 Representational State Transfer API로 REST를 기반으로 만들어진 API를 의미한다. REST API를 알기 위해 REST부터 살펴보겠다.
REST란?
REST는 Representational State Transfer의 약자로 인터넷에서 자원(데이터 또는 서비스)을 쉽게 접근하고 관리할 수 있는 방법을 제공하는 웹 아키텍처 스타일이다. 여기서 "자원"은 웹 페이지, 이미지, 비디오 또는 데이터베이스 데이터 등 웹에서 접근할 수 있는 모든 것을 의미한다.
즉 REST란 HTTP URL을 통해 자원(Resource)을 명시하고, HTTP Method(POST, GET, DELETE, PATCH 등)를 통해 해당 자원(URL)에 대한 CRUD Operation을 적용하는 것을 의미한다.
REST 구성 요소
1. 자원(Resource): HTTP URL
2. 자원에 대한 행위(Verb): HTTP Method
3. 자원에 대한 행위의 내용(Representations): HTTP Message Pay Load
REST의 특징
1. Server-Client(서버-클라이언트 구조)
2. Stateless(무상태)
3. Cacheable(캐시 처리 가능)
4. Layered System(계층화)
5. Uniform Interface(인터페이스 일관성)
REST API란?
REST API란 REST의 원리를 따르는 API를 의미한다. REST API를 올바르게 설계하기 위해선 지켜야 하는 몇 가지 규칙이 있으며 해당 규칙을 알아보겠다.
1. URL은 동사보다는 명사를, 대문자보다는 소문자를 사용해야 한다.
Bad Example http://example.com/Running/
Good Example http://example.com/run/
2. 마지막에 슬래시(/)를 포함하지 않는다.
Bad Example http://example.com/test/
Good Example http://example.com/test
3. 언더바 대신 하이폰을 사용한다.
Bad Example http://example.com/test_blog
Good Example http://example.com/test-blog
4. 파일확장자는 URL에 포함하지 않는다.
Bad Example http://example.com/photo.jpg
Good Example http://example.com/photo
5. 행위를 포함하지 않는다.
Bad Example http://example.com/delete-post/1
Good Example http://example.com/post/1
RESTful이란?
RESTful이란 REST의 원리를 따르는 시스템을 의미한다. 하지만 REST를 사용했다 하여 모두 RESTful한 것은 아니다. REST API의 설계 규칙을 올바르게 지킨 시스템을 RESTful하다 말할 수 있다. 예를 들어, 모든 CRUD기능을 POST로 처리하는 API 혹은 URL 규칙을 올바르게 지키지 않은 API는 REST API 설계 규칙을 올바르게 지키지 못한 시스템으로 REST API를 사용하였지만 RESTful하지 못한 시스템이라고 할 수 있다.
JWT(Json Web Token)
JWT는 정보를 비밀리에 전달하거나 인증할 때 주로 사용하는 토큰으로, Json객체를 이용한다. 일반적으로 클라이언트와 서버 사이에서 통신할 때 권한을 위해 사용하는 토큰이다. 웹 상에서 정보를 Json형태로 주고 받기 위해 표준규약에 따라 생성된 암호화된 토큰으로 복잡하고 읽을 수 없는 String 형태로 저장되어 있다.
JWT 구성요소
JWT는 헤더(header), 페이로드(payload), 서명(signature) 세 파트로 나눠져 있으며 아래와 같은 형태로 구성되어 있다.
- 헤더(Header)
어떠한 알고리즘으로 암호화할 것인지, 어떠한 토큰을 사용할 것인지에 대한 정보가 들어있다.
- 정보(Payload)
전달하려는 정보(사용자 id나 다른 데이터들, 이것들을 클레임이라고 부른다.)가 들어있다.
payload에 있는 내용은 수정이 가능하여 더 많은 정보를 추가할 수 있다. 그러나 노출과 수정이 가능한 지점이기 때문에 인증이 필요한 최소한의 정보(비밀번호 등 개인정보가 아닌 이 토큰을 가졌을 때 권한 범위나 토큰 발급일과 만료일자 등)만을 담아야 한다. 즉 민감한 개인정보가 아니라 토큰 사용 목적에 부합하는, 노출되어도 문제가 되지 않는 정보여야 한다.
- 서명(Signature)
가장 중요한 부분으로 헤더와 정보를 합친 후 발급해준 서버가 지정한 secret key로 암호화시켜 토큰을 변조하기 어렵게 만들어 준다. 한 가지 예를 들어보자면 토큰이 발급된 후 누군가가 Payload 정보를 수정하면 Payload에는 다른 누군가가 조작한 정보가 들어가 있지만 Signature에는 수정되기 전의 Payload 내용을 기반으로 이미 암호화되어 있는 결과가 저장되어 있기 때문에 조작된 Payload와는 다른 결과값이 나오게 된다.
이러한 방식으로 비교하면 서버는 토큰이 조작되었는지 아닌지를 쉽게 알 수 있고, 다른 누군가는 조작된 토큰을 악용하기가 어려워진다.
JWT 동작원리
JWT 동작원리에 대해서는 다음과 같이 설명할 수 있다.
1. 사용자가 id와 password를 입력하여 로그인 요청을 한다.
2. 서버는 회원DB에 들어가 있는 사용자인지 확인을 한다.
3. 확인이 되면 서버는 로그인 요청 확인 후, secret key를 통해 토큰을 발급한다.
4. Access Token(JWT)를 클라이언트에게 전달한다.
5. 서비스 요청과 권한을 확인하기 위해 헤더에 데이터(JWT)정보를 포함시켜 서버에 보낸다.
: Authorization 헤더 사
6. 데이터를 확인하고 JWT에서 사용자 정보를 확인한다.
7. 클라이언트 요청에 대한 응답과 요청한 데이터를 전달해준다.
이와 같이 토큰 기반 인증 방식은 사용자의 인증이 완료된 이후에 토큰을 발급한다.
클라이언트 쪽에서는 전달받은 토큰을 저장해두고 서버에 요청을 할 때마다 해당 토큰을 서버에 함께 전달한다. 그 이후 서버는 토큰을 검증하고 응답하는 방식으로 작동한다.
일반 토큰 기반 vs 클레임 토큰 기반
JWT를 사용하는 가장 큰 이유는 클레임(Claim) 토큰 기반 인증이 주는 편리함이 가장 크다고 할 수 있다. 과연 일반 토큰 기반과 클레임 토큰 기반 인증의 차이는 무엇일까?
기존에 주로 사용하던 일반 토큰 기반 인증은 사용자가 로그인을 하면 서버는 인증 토큰을 생성하여 클라이언트에게 전송한다. 이 토큰은 서버에 저장되며 클라이언트는 이후 요청에서 이 토큰을 포함시켜 서버에 전송한다. 서버는 요청을 받을 때마다 저장된 토큰을 조회하여 사용자를 인증한다.
세션 기반 인증은 사용자가 로그인을 하면 서버가 해당 사용자의 세션 정보를 서버의 저장소에 저장한다. 서버는 사용자에게 session ID를 발급하고, 사용자는 이후 요청에 이 session ID를 포함시켜 서버에 전송한다. 서버는 요청을 받을 때마다 세션 저장소에서 ID를 조회하고 사용자를 식별하여 인증한다.
두 방식은 모두 사용자 인증 정보를 관리하기 위한 추가적인 저장 공간을 필요로 하고 각 요청마다 DB 또는 다른 저장소에 접근해야 한다는 부담이 있다.
반면 JWT는 토큰 자체에 인증에 필요한 정보(Claim)를 포함하고 있어 서버 측에서 별도 저장소에 접근할 필요가 없다. 서버는 토큰을 받았을 때 토큰 유효성만 검증하면 되기 때문에 처리 과정이 훨씬 간단해지고 서버 부하를 줄일 수 있다.
JWT 사용해보기
Spring-boot에서 JWT 토큰 인증 방식을 구현하는 과정은 다음과 같은 과정으로 이루어진다.
1. JWT 라이브러리 추가
2. JWT 생성 및 검증 서비스 구현
3. 인증 필터 구현: 모든 요청에 대해 JWT 토큰을 검사하는 커스텀 필터를 구현한다.
4. Spring Security 설정: Spring Security를 설정하여 인증 필터를 등록하고, URL 별 접근 제어를 설정한다.
// JWTUtil.java - JWT 생성 및 검증 유틸리티 클래스
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtil {
// 비밀 키 - JWT를 서명할 때 사용합니다. 보안을 위해 복잡하게 설정해야 한다.
private String secret = "your_secret_key"; // 실제 환경에서는 외부에서 주입받거나 환경 변수를 사용한다.
// JWT 토큰을 생성하는 메서드
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username) // 토큰의 주제 설정, 여기서는 사용자 이름을 사용
.setIssuedAt(new Date(System.currentTimeMillis())) // 토큰 발행 시간 설정
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 토큰 만료 시간 설정 (10시간 후 만료)
.signWith(SignatureAlgorithm.HS256, secret) // HS256 알고리즘과 비밀 키를 사용하여 토큰에 서명
.compact(); // 토큰 생성
}
// JWT 토큰을 검증하고 주제(여기서는 사용자 이름)를 반환하는 메서드
public String validateTokenAndRetrieveSubject(String token){
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); // 토큰을 파싱하여 클레임 얻기
return claims.getSubject(); // 클레임에서 사용자 이름(주제)을 반환
}
}
// JwtRequestFilter.java - JWT 검증을 위한 필터
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class JwtRequestFilter extends OncePerRequestFilter {
private UserDetailsService userDetailsService;
private JwtUtil jwtUtil;
// 생성자를 통해 UserDetailsService와 JwtUtil 인스턴스를 주입받음
public JwtRequestFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) {
this.userDetailsService = userDetailsService;
this.jwtUtil = jwtUtil;
}
// 실제 필터링 로직을 구현하는 메서드
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization"); // 요청에서 Authorization 헤더를 추출
String username = null;
String jwt = null;
// Authorization 헤더가 존재하고, Bearer 토큰 형식인 경우 처리
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7); // "Bearer " 이후의 문자열을 추출하여 JWT 토큰을 얻음
username = jwtUtil.validateTokenAndRetrieveSubject(jwt); // 토큰을 검증하고 사용자 이름을 얻음
}
// 사용자 이름이 null이 아니고, SecurityContext에 인증 정보가 없는 경우
// SecurityContext에 인증 정보가 없어야 하는 이유는 현재 요청을 진행 중인 사용자가 아직 인증되지 않았음을 의미
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); // 사용자 이름으로 UserDetails 객체를 가져옴
// 토큰이 유효한 경우, SecurityContext에 인증 정보를 설정
if (Boolean.TRUE.equals(jwtUtil.validateToken(jwt, userDetails))) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); //UsernamePasswordAuthenticationToken 객체를 생성하여 사용자의 인증 정보를 설정, 이 객체는 사용자의 세부 정보와 권한을 포함
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); //를 SecurityContext에 설정함으로써 애플리케이션의 다른 부분에서 이 사용자가 인증되었다는 것을 알 수 있게 됨
}
}
chain.doFilter(request, response); // 필터 체인을 계속 진행
}
}