[Spring] CH22 스프링부트 인증 - Json Web Token

jaegeunsong97·2023년 4월 5일
0

[Fast Campus] Spring

목록 보기
41/44
post-thumbnail

📕 1교시


code 방식과 cc 방식이 있다. OAuth에서 제공을 해주는 것을 사용해야한다.

access token을 client가 받으면 code 방식이고 token을 사용자가 받으면 cc 방식이다.

요것만 잘 기억하자!

이게 나오면 중개인에게 권한을 위임한다는 것이 기억이 나야하고

외국인 은행 이민국 이렇게 기억을 해야한다.

우리가 원하는 방식이다.

즉 상황에 따라서 스스로 분석하는 능력을 기르자

카카오 개발자 센터 뿐만 아니라 네이버 개발자 센터도 한번 해보자

이제 JWT에 대해서 알아보자

이제 client는 매 요청마다 토큰을 들고와야한다.

원래 세션기반에서는 매 요청마다 client는 서버에게 쿠키에 JsessionId를 들고갔다.

JWT

header 어떤 알고리즘 사용
payload 바디 데이터 (id, role exp)
signature 전자서명 (header + payload -> 암호화)

내가 만들었다는 것을 증명하기 위해서

서버에서 JWT 생성과 검증 모두 한다. -> 대칭키 사용, 왜냐하면 키를 공유할 필요가 없다.

그림으로 JWT를 이해하자.

이렇게 JsessionId를 들고 있는 것을 stateful 서버라고 했따.

그리고 로그인을 하면 JsessionId에 user라는 것을 넣어둔다.

그리고 나서 clietn는 정보 (자원에 대한 요청을 한다.)

근데 서버를 이렇게 만들지 않는다. 서버는 최소 2중화가 되어있ㅇ야한다.

쿼리에대한 로그를 남긴다.(변경이 된 로그만 남긴다.)

데이터가 동일하게 유지를 한다.

만약 main이 터지더라고 서브를 이용하면 된다.

또는 이런 구성을 가질 수도 있따.

혹은 DB가 있으면 백업할 수도 있다

나중에는 서버가 1대로 부족하기 때문에 좋은 서버로 scale up 하기도 한다.

기존에 있는 서버를 scale out 할 수도 있다.

장단점이 있다.

scale up +: 간단 -: 비싸다
scale down +: 저렴(분산되어 있어서) -: 관리가 복잡하다.

scale down이 관리가 복잡한 이유가 데이터를 일관되게 유지를 해야하기 때문이다.

그리고 만약 1건을 넣을때 뻗어버리면 동기화가 되지 않은채로 0의 값이 동기화가 된다.

그럼 나중에 복구가 되는 시점에 다시 복구를 해줘야한다.

서버를 늘려보자, 그러면 해당 서버는 본인만의 세션영역을 가지고 있을 것이다.

그리고 둘다 DB는 1개를 공유해서 사용할 수 이다.

이렇게 구성이 되게 되면 앞단에 로드 밸런서가 있어야한다.

클라이언트가 로드밸런스에게 요청을 하고 로드밸런스는 부하를 분산 시킨다.

동일한 IP가 들어오면 TCP/IP 계층(IP와 서버를 보고 결정)에서 돌아가도록 할 수 있고, 주소를 파싱해서 돌아가게 할 수 있다.

주소 파싱 -> 애플리케이션 로드밸런서
TCP/IP -> 네트워크 로드밸런서

최초에는 user가 만들어 졌다.

2번째로 들어올 때는 세션이 없었다.

그러면 상태를 유지하고 있는 것은 파란색이다. 빨간색은 상태를 유지하고 있지 않는다.

서버가 계속 들어나면 서버마다 세션이 있을 것이고, 상태를 유지하고 있다.

이렇게 되면 scalue out에 약하다.

처음에 user가 들어올 것이다.

이떄 로드밸런서로 만들고 스티키 서버라고 한다. 부하가 만든 적든 항상 한 곳으로 가도록 만드는 것이다.

아니면 처음에 user를 넣자마자 동기화 되게 하는 것이다.

요즘에는 이렇게 하지 않는다.

개념은 DB에다가 세션을 저장하고 다음과 같이 된다.

이렇게 되면 IO시간 때문에 느리다. 왜냐하면 DB는 HDD이기 때문에 오래 걸린다.

대표적인게 Redis, memcachet 같은 것이 있다.

즉, 서버가 확장하면 확장할 수록 메모리 DB(Redis)에 세션을 저장하고 접근하면 되는 것이다.

그리고 메모리 DB이니까 매우 빠르게 접근을 한다.

그래도 컴퓨터 자체가 다르니까. 결국 통신을 하는 것이다.

빠르지는 않을 것이다.

앞으로 배울 JWT는 상태가 없는 서버이다.

따라서 다음과 같이 돌아간다

서버가 있다. 그리고 세션도 있다.

세션은 그냥 메모리 영역이고, stateless 서버라는 것은 JsessionId를 쿠키로 전송을 하지 않는다는 것이다.

즉 세션은 완전 사용하지 않는 것은 아니다.

세션은 그냥 메모리 영역이라고 생각하면 된다.

client가 인증을 요청한다.

세션에 뭔가를 저장하지 않는다.

다른곳에 자원 요청을 하면 헤더에 토큰을 들고 간다.

그러면 서버는 토큰을 검증한다.

내가 만든 토큰인지 검증을 한다.

이게 stateless 서버이다. clietn의 상태를 저장하지 않는다.

JWT는 좋다. 왜냐하면 Redis에 접근할 필요가 없기 떄문이다.

접속

실행

복사

header
payload -> role, id, exp 가장 중요

signature는 전자서명인데

그림으로 이해하자.

이게 전자서명이다.

즉, header와 payload를 SHA512로 암호화한 것이다.




📕 2교시


암호화 알고리즘

  • 단방향: 인코딩(0) -> 동일성 체크
  • 양방향: 인코딩(0) 디코딩(0) -> 동일성 체크

공통: 동일성 체크
차이: 복호화

PW: 단방향
나머지: 양방향 가능

모두 DB에 들어갈떄: 암호화

양방향: AES256
단방향: SHA512

공개키의 대표 RSA

JWT: RSA or SHA512(v)

우리는 SHA512로, 왜냐하면 공유할 필요가 없으니까!

토큰을 생성하면 암호화를 했다는 것

그리고 서버가 토큰 생성, 검증 다한다. -> 대칭키 사용(키 교환 필요 없다.)

만약 공개키 기반이면

사실은 이 방법이 가장 안전하다.

왜냐하면 서버가 토큰 생성과 검증을 전부 하면, 검증 후 응답할 때 하이재킹을 해서 가로챌 수 있기 때문이다.

하지만

이 방법은 그럴 일이 없다.

이제 Base64에 대해서 알아보자

base64: 양방향(시크릿 키가 없다.)

암호화에 대한 의미가 없다. 시크릿 키가 없기 때문에 누구나 복호화가 가능하다.

보통 2진 데이터로 통신할 때가 있다.

Json으로 데이터를 보내고 싶다고 하자.

Json은 문자열이여서 pic은 들어가지 못한다.

이때 base64를 사용한다.

binary date를 문자열로 바꿔준다.

base64 표를 보자

이렇게 되면 받는쪽에서는 g를 복호화한다.

base64: 바이너리 데이터를 text기반으로 전송하기 위해서 사용한다.

따라서 base64는 보안적으로 암호화를 사용하는 것이 아니다.

JSON에 넣기전에 인코딩을 하고, 받을 떄 디코딩을 한다.

JWT는 다음과 같이 만들어진다.

3개의 JSon이 모인 것이다.

header: type, algo

payload: id, role, exp

이렇게 만드는 주체가 서버이다. 그래서 내가 만든것이 맞는지 서버가 스스로 확인을 하기 위해서 HS512로 암호화를 하는 것이다.

그래서 총 데이터가 3개가 나온다.

HS512에서는 시크릿 키가 필요하다.

즉, 누가 위조를 못하게 하는 것이다.

우리는 검증을 복호화를 하지 못하니까, 동일성 비교를 하면 되는 것이다.

이떄 각 header, payload, signature는 base64로 암호화해서 던져진다.

base64로 암호화하면 길이는 동일하게 나온다.

base64는 보안을 위한 암호화는 안니다.

코드를 보자

검증하기(내가 적은게 맞는지?)

문서 자체를 복호화하지 못하기 떄문에 동일성만 체크

mvnrepository 검색

이거를 검증해보자

주석처리

만약 시크릿 키 틀리면

검증도 시크릿 키를 알고 있는 사람만 할 수 있다.

생성과 검증이 한 곳에서 이루어진다.

이 과정은 토큰을 생성해서 base64를 한 것이다.

그리고 인코딩을 했으니 디코딩은 해당 코드에서 이루어진다.

즉, 시크릿키가 동일해야지 검증이 된다.

검증이 된다는 것은 내가 만든 토큰이 맞다라는 것을 알 수 있다.

그리고 시간 만료를 줄여버리면

검증 만료가 뜬다

왜 그러면 test에서 JWT를 할까? 이유는 예외 처리 떄문이다.

이것을 catch로 잡아야 한다.

그리고 이번에는 위조된 jwt를 넣어준다.

이렇게 catch로 잡아줘야한다.

보면 검증 메소드에서 throw로 날리는 것을 할 수 있기 때문이다.

토큰 생성은 로그인 완료시에 해야되고 검증은 인증이 필요한 리소스 접근할 때 해야한다.

envtest

이거는 어떻게 사용하라는 건가?

시크릿 키에 넣을 때 변수로 넣고, 변수값을 환경변수에 저장을 하는 방법이다.

이렇게 민감한 것들은 환경변수로 접근해서 OS에 저장하는 것이 좋다.




📕 3교시


코드 시작

401: 인증되지 않음

서버 실행시에 실행됨, 초기화라고 생각

user

prepersist: insert 되기전에 발동
preupdate: update 직전에 발동

usercontroller

RequestBodt: Json 데이터 받을 것이다.

UserRequest -> 내부 클래스

관리가 쉽다.

optional로 받아서

create

값을 변경했기 때문에 transactional을 붙이자

이제 로그인 하는 것을 한번 보자

admin 추가

응답의 헤더에 토큰이 있다. JWT는 bearer 인증방식 프로토콜을 사용한다.

위의 코드와 위의 과정이 같은 것들이다.

따라서 만약 로그인은 1을 하고 2로 간다면

로그인을 해야한다.

그리고 매 요청을 할 떄마다 넣어주면 된다

정확하게는 여기에 넣어줘야한다.

관리자로 로그인을 했고, Authorization 키도 있어서 1번 2번을 모두 볼 수 있다.

보면 2개다 인증이 필요한데, 이것들은 filter 또는 intercceptor에서 토큰 검증이 일어나고 있다. 우리는 가장 앞단에서 막을 것이다.

뿐만아니라 ex도 나눌 것이다.

400 번대는 client의 잘못이고, 500은 서버의 잘못이다.

1번으로 jwtPROVIDER를 만들어야한다.

header, token_prefix 프로토콜이다.

secret는 환경변수로 바꾸자!

이렇게 위에다가 적어놓은 이유는 재사용 할 것이고, 실수를 줄이기 위해서 아니면 오타가능성이 있어서다.

2번쨰로 만들어야ㅏ는 것은 filter를 만들자

헤더값이 있는지 검증을 하자

error 메소드를 가면

Filter 에러는 DS 앞단에 있기 때문에 직접 처리를 해줘야한다.

그러면 순수한 jwt만 남는다.

여기서 JWT를 사용하면 서버가 상태관리를 안한다는 것이다. JSessionId를 사용하지 않는 다는 것이지, session은 사용한다

CORS 설정 -> 이거 안하면 프로트랑 일 못함

JS의 모든 요청이 들어오면 전부 밑의 설정을 따른다는 것이다.

가장 중요한 것은 프론트 엔드 호스트 주소 허용해주기

이 loginUser는 session에 들어가는 것이다.

그리고 보면 DTO를 맞춰줘야한다.

그래야지 프론트랑 협업을 할 때 편하다.

따라서 공통 Dto를 만들자

가장 중요한 것은 설명을 듣고 스스로 PDF파일 보면서 해봐라 가장 좋은 방법이다.

수업 끝

profile
블로그 이전 : https://medium.com/@jaegeunsong97

0개의 댓글