- Basic Knowledge related to Web Communication (Session, TCP, CIA, RSA, JWT)
- Source Code to create JWT Server
- Using
- Basic Knowledge related to Web Communication
- Concept of JWT
- Http Basic과 Bearer
- Security Filter Chain보다 빠른 필터
- 프로젝트와 관련된 Security Filter
- Licence
- BackEnd - Java(JDK 1.8), MySQL(v8.0.25), Spring Boot(2.3.12.RELEASE), JPA
- Library&API - Spring Security, Lombok, java JWT
- JWT Library : JWT 토큰을 생성해주는 라이브러리
- IDE - STS (Spring Tool Suite 3.9.12.RELEASE), MySQL Workbench 8.0 CE, Postman
- Session ID 개념
- Session ID 부여 과정
- 사용자가 Web Browser에서 깃허브 서버에 https://github.com/ (GET 방식)을 요청
- 서버는 해당 주소에 맞는 Controller 메소드를 찾고 (이때 Http Header를 달아서) 메인페이지에 맞는 .html 파일을 리턴
- Header에 Cookie라는 것을 만든다.
- Cookie : 웹 브라우저의 저장 영역, 세션 ID를 Cookie에 담아서 보낸다.
- 세션 ID는 최초 요청시에 만들어 진다.
- 최초 요청이 아닐 경우 요청에 세션 ID를 헤더에 담아서 요청한다.
- 서버는 세션 ID 목록을 가지고 있다. 유효한 요청인지 확인할 수 있다.
- 로그인 요청 (인증)에 자주 사용된다.
- 로그인 요청 과정과 이후 요청 순서
- 클라이언트가 최초 서버에 접속 (Request)
- 서버쪽 세션 ID 목록에 새로운 세션 ID를 생성 (세션 ID와 관련된 추가 저장 공간도 생성)
- 해당 세션 ID를 함께 Response
- 클라이언트 Cookie에 세션 ID 저장
- 클라이언트가 서버에 로그인 요청
- 서버에서 받은 ID와 PASSWORD를 가지고 DB 조회
- 정상일 경우, 세션 목록에서 세션 ID와 관련되어 추가로 생성된 공간에 DB에서 가져온 유저 정보를 저장
- 메인 페이지 (.html) 리턴
- 클라이언트가 유저 정보를 요청하면 서버가 세션이 있는지 확인
- 세션 ID를 확인해서 유저 정보가 있으면 로그인을 한 사람이기 때문에 DB에 응답을 받아서 클라이언트로 돌려준다.
- 위 과정 반복
- 세션의 단점
- 클라이언트가 서버에 Request할 때 서버에서 Response 해주는데 동접자 수가 늘어나면 기다려야 한다.
- 동접자 수를 처리 가능 사용자 수로 나눠서 그만큼 서버를 만든다. (Load Balancing)
- 같은 서버에 계속 요청을 할 경우에는 세션 ID를 서버에서 가지고 있기 때문에 문제가 되지 않지만, Load Balancing을 통해 다른 서버로 들어갈 경우 세션이 있어도 최초 요청으로 인식하게 된다.
- 해결 방법
- 최초 요청은 무조건 Sticky Session으로 처리
- 세션 ID 목록을 각 서버에 복제 (중복)
- 세션값을 DB에 넣고 공유해서 사용 (HDD 사용 -> IO 발생, 느려짐)
- 메모리 공유 서버 (IO 발생 X, HDD가 아니라 RAM만 사용), 대표적으로 Redis, 레디스
- Session ID 부여 과정
- Session 삭제
- 서버 쪽에서 Session 삭제 (세션 ID 목록에서 해당 ID를 지운다.)
- 사용자가 브라우저를 전부 종료 (들고있는 세션 ID가 삭제) -> 서버에 요청하면 새로운 ID를 부여받음. -> 서버 세션 ID 목록에 있던 과거 ID는 특정 시간 이후에 사라진다.
- 특정 시간이 지나서 서버 세션 ID 목록에서 사라진다.
- TCP
- 웹은 TCP 통신을 한다.
- OSI 7 Layer
- 물리 계층 : 실제 케이블
- 데이터 링크 계층 : IP로 찾아간 내부망 (LAN)에서 목적지를 찾음
- 네트워크 계층 : IP 결정
- 트랜스포트 계층 : TCP / UDP (WAN)
- TCP : 신뢰성 (ACK 사용, 느림)
- UDP : 비신뢰성 (전화, 스트리밍->사람이 이해, 추측할 수 있는 통신)
- 세션 계층 : 인증 체크
- 프리젠테이션 계층 : 암호화, 압축
- 응용 계층 : 메시지
- CIA
- 보안의 3요소 : 기밀성 (Confidentiality), 무결성 (Integrity), 가용성 (Availability)
- 인증 문제 (무결성), 암호화 문제 (기밀성)가 존재한다.
- RSA 암호화
- RSA : Public Key (공개키), Private Key (개인키)
- 암호화 과정 (공개키로 암호화)
- A 공개키, 개인키 / B 공개키, 개인키 존재
- A가 B에게 데이터를 보낼 때 B 공개키로 암호화해서 보냄
- B가 데이터를 받았을 때 B 개인키로 암호화를 풀 수 있음
- B가 A에게 데이터를 보낼 때 A 공개키로 암호화해서 보냄
- A는 받은 데이터를 A 개인키로 열어볼 수 있음.
- 전자 문서 서명 (인증, 개인키로 암호화)
- A가 A 개인키로 암호화해서 B에게 보냄
- B는 A 공개키를 이용해서 암호를 풀 수 있는 것을 보고 확실히 A가 보낸 데이터라는 것을 알 수 있다.
- 정리
- A가 B에게 데이터를 보낼 때 B의 공개키로 먼저 암호화하고 A의 개인키로 암호화하여 전송
- B는 받은 메시지를 A의 공개키로 복호화 (풀리면 인증 성공)
- 인증이 성공하면 한번 더 B의 개인키로 복호화 (암호화 성공)
- JWT (JSON Web Token)
- 정보를 JSON 객체로 안전하게 전송하기 위한 방식
- 서명에 사용 (인증, 신뢰)
- 데이터 암호화도 의미가 있지만 서명된 토큰에 중점을 둔다.
- JWT 구조
- 일반적으로 xxxxx.yyyyy.zzzzz 형태 (Header.Payload.Signature)
- Header : 토큰의 타입, 암호화 방식
{ "alg": "HS256", "typ": "JWT" }
- Payload : 데이터, 예를 들어 {"username": "ssar"}
- Registered Claim (클레임은 Payload에 담는 정보의 한 조각으로 name, value 한쌍을 의미) : 서비스에 필요한 정보들이 아닌, 토큰에 대한 정보로 모두 Optional
- iss : 토큰 발급자 (issuer)
- sub : 토큰 제목 (subject)
- aud : 토큰 대상자 (audience)
- exp : 토큰의 만료시간 (expiration)
- nbf : Not Before
- iat : 토큰이 발급된 시간 (issued at)
- jti : JWT의 고유 식별자
- Public Claim : 충돌이 방지된 이름을 가지고 있고, 충돌을 방지하기 위해서 클레임 이름을 URI 형식으로 저장
- Private Calim : 클라이언트와 서버 사이에 협의 하에 사용되는 클레임 이름
- Registered Claim (클레임은 Payload에 담는 정보의 한 조각으로 name, value 한쌍을 의미) : 서비스에 필요한 정보들이 아닌, 토큰에 대한 정보로 모두 Optional
- Signature : (Header + Payload + 서버만 알고있는 Secret 각각)을 암호화
- JWT의 주요 이점
- 사용자 인증에 필요한 모든 정보는 토큰 자체에 포함하기 때문에 별도의 인증 저장소가 필요 없다.
- 서버의 확장성이나 쿠키를 사용하는 방식보다 안전하다.
- 로그인 과정
- 클라이언트가 서버에 로그인 시도
- 로그인이 올바른 시도일 경우 JWT를 만들어서 클라이언트로 보내준다.
- 웹브라우저 (클라이언트)의 로컬 스토리지에 JWT 저장
- 클라이언트가 다시 요청을 할 때 JWT을 함께 전송
- 서버에서 JWT을 받으면 Header+Payload+Secret을 암호화 해본다. 그 결과를 전송 받은 JWT와 비교해서 같으면 인증이 되었다고 판단한다
- 서버 키만 알고 있으면 Load Balancing에 의해 다른 서버에 들어가도 새로운 JWT 토큰을 생성하지않고, JWT 비교를 통해 인증이 가능해진다.
- Http Basic 방식
- 요청 Headers 안에 있는 Authorization 에 ID, PW를 담아서 전송
- 서버 확장성은 좋으나 ID, PW 가 노출될 가능성이 있다.
- Bearer 방식
- Authorization 에 Token을 담아서 전송
- Token 이 노출될 가능성은 있으나 ID, PW 가 노출되지는 않는다.
- Token 은 ID, PW 를 기반으로 만들어진다. 유효 시간이 존재한다.
- 이때 사용하는 것이 JWT 토큰
- Security Filter Chain은 우리가 추가한 필터(MyFilter1, MyFilter2)보다 먼저 동작한다.
- 우리의 필터를 먼저 동작하게 하기 위해서는 addFilterBefore 를 사용한다.
http.addFilterBefore(new MyFilter3(), SecurityContextPersistenceFilter.class);
- Security Filter Chain의 첫번째 필터 : SecurityContextPersistenceFilter
- MyFilter3는 Security Filter Chain 보다 먼저 실행된다.
- BasicAuthenticationFilter : 권한이나 인증이 필요한 특정 주소를 요청했을 때 이 필터에 걸린다. 권한이나 인증이 필요한 주소가 아니라면 이 필터를 무시한다.
public class JwtAuthorizationFilter extends BasicAuthenticationFilter{ public JwtAuthorizationFilter (AuthenticationManager authenticationManager, UserRepository userRepository){ } protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException{ } }
- UsernamePasswordAuthenticationFilter : /login 요청해서 username, password를 전송하면 (post) UsernamePasswordAuthenticationFilter가 동작한다.
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter
- attemptAuthentication 함수 : /login 요청을 하면 로그인 시도를 위해서 실행되는 함수
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException
- successfulAuthentication 함수 : attemptAuthentication 실행 후 인증이 정상적으로 되었으면 실행
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException
- attemptAuthentication 함수 : /login 요청을 하면 로그인 시도를 위해서 실행되는 함수
- Source Code based on codingspecialist'lecture