JWT 에 대해 배워보기전에 보안에 대해 간략히 알아보자
보안에는 크게 두가지의 고질적인 문제가 있다.
-보안의 고질적적 문제-
1. 열쇠 전달 문제 ( 암호화시켜서 열쇠를 보내면 좋은데 열쇠도 도둑맞을수 있다)
2. 누구로 부터 받았는가( 중간에 가로채거나 , 문서를 위조하거나)
-RSA 에는 개인키, 공개키 라는 개념이 있다. 공개키는 누구나 얻을수있고 개인키는 개인만 가지는 키다.
- 공개키로 잠그면 개인키로 열수있다. 개인키로 잠그면 공개키로 열수있다.
- 개인키로 잠그고 다른 사람이 공개키로 열떄 누가 보냈는지 문제 해결
- 공개키로 잠그면 개인키로만 열리기 때문에 열쇠전달문제 해결
클라이언트가 id,password로 로그인을 시도해 인증이 성공하면 서버는 권한 등 기본적인 데이터를 서버에 저장하고 세션id를 쿠키에 담아 클라이언트에게 돌려준다.
다음에 클라이언트가 리퀘스트를 쿠키의 세션아이디와 함께 보낸다.
서버에서는 해당 세션아이디가 서버에 있는지 확인후 있으면 그 데이터 안에있는 권한에 해당하는 자료를 보여준다.
서버의 트래픽이 많아지면 서버가 늘어나고 늘어남에따라 세션id가 변동되고 많은 문제점이 있다.
=> JWS 를 사용하자!
장점
기존 SpringSecurity를 통해 구현하는 방법보다 훨씬 간단하다!
서버에서는 신경을 쓸필요가 없다. 모든 정보는 클라이언트에게 주어진다.
단점
보통 동시 로그인 할떄 보통 서버에서 세션 id 하나를 만료시키면되지만 (세션id가 서버에 저장되잇으니까) jwt는 클라이언트가 관리하므로 서버에 아무것도 없다. 그래서 모든토큰을 초기화시키는거 말고는 방법이 없다. or jwt만들떄 만료기간을 작성
또는 jwt토큰이 유출 됐으면 어찌 할 방법이 없다. (가장 큰 단점) 그래서 jwt에 암호화, 핸드폰 잠금화면처럼 2차 암호화를 한다.
헤더:알고리즘 , 토큰타입
페이로드:데이타, subject, name , issuedat(언제토큰이 발행됫는지) 내용을 더추가할수도있다.// 네트워크의 데이터가 올라가기 때문에 최소한의 데이터만 적는게 좋다.
시그니처: 시크릿 키를 적으면 그 헤더값과 페이로드가 그 시크릿키로 묶인다고 보면 됀다. / =저장한 상태 이름으로 해시값을 받아온다
서버가 자기만 아는 SecretKey + Payload +head 을 조합하고 HS256으로 암호화
각각의 header / payload / signature 각각을 Base64로 인코딩함 = 디코딩하면 바로 정보 튀어나옴 = 암호화목적이 아닌 서명이 목적이다
HS256 = HMAC방식(시크릿키를 포함한 암호화 방식)으로 + SHA26(해시)으로 암호화
Secretkey가 필요없고. Header + payload 를 서버 개인키로 잠궈서 시그니처 만든다.
검증을 할떄는 공개키로 확인한다
클라가 아이디와 비밀번호 입력해 인증을 성공하면 서버에 보내면 서버는 토큰을 준다. 이 토큰값에 우리가 원하는 데이터를 넣을수 있다. 이 토큰에는 사용자의 아이디,비밀번호, 토큰종료시간 등등 데이터가 암호화 되어서 들어있다.
클라가 헤더 Authorization 객체 key-value 값에 토큰값을 넣어 보내면 서버는 key값을 넣어 value(토큰값) 을 꺼내 인증을 시도한다.(이동시)
서버에서의 인증은 토큰을 파싱한다(토큰을 우리가 읽을수 있는형태) 그뒤에 토큰안에 있는 데이터를 꺼낸다.
그 데이터로 DB에서 계정정보를 가져온뒤에 UserDetails 타입으로 만든뒤 UsernamePasswordAuthenticationToken 생성(계정,비번,권한) => Authentication가져오기 -> SecurityContextHolder에 넣기 ->세션에 저장 =>인증성공
클라이언트가 JSON 형식으로 보낸 id와 passWord를 ObjectMapper로 받은뒤 해당 userName으로 DB에서 계정을 찾는다.
계정을 찾은뒤 그 계정,비밀번호,권한을 userNamePasswordAuthenticationToken에 담아서 AuthenticationManager 에게 인증 요청을 한다.
AuthenticationManager 은 AuthenticationProvider에게 인증을 위임하고 authenticate() 메서드로 실질적 인증을 시도한다.
인증이 성공하면 그 결과를 Authentication 객체에 담아서 반환한다.
인증이 성공한 이후의 과정 메서드다
인증이 성공 했으니 서버에서는 클라이언트에게 토큰생성시간,토큰종료시간, 인증계정의 정보등 데이터를 JWT토큰으로 만든뒤 클라에게 다시 보낸다.
인증에 성공한 계정은 토큰값을 가지고 있으며 권한이 필요로하는 자원에 접근시 서버에서는 토큰값을 검사해 권한을 확인한다.
BasiceAuthenticationFilter 를 상속 해준다. 권한이나 인증이 필요하면 이 필터를 타게 되있다.
클라이언트는 헤더안의 Authorization 객체안에 토큰값을 넣어 서버로 보낸다.
그러면 서버는 그 토큰 값을 꺼내 ->시크릿키로 JWT파일을 검사한다(변경여부 확인)
서버에서는 리퀘스트로 온 헤더와 페이로드 시크릿키를 결합해 시그니처를 만들어본뒤 그값이 JWT생성시 만들었던 시그니처와 일치하면 변경여부를 통과시킨다.
검사가 통과되면 JWT에 담긴 데이터로 UserDetails타입의 user를 만든뒤 Authentication객체에 담고 이것을 SecurityContextHoler ->Session 에 저장하면 인증이 완료된다.
이코드에서는 JWT를 만들때 userName 데이터를 넣어줘서 이 데이터를 가지고 계정을 검색한뒤 userDetails 타입으로 만들어 주고 인증을 했다.
JJWT라는 라이브러리를 이용해서도 JWT토큰을 많이 사용한다.이 라이브러리에 대한 설명은 링크만 남긴다.
JJWT링크