프론트 + 백엔드(React + Spring)에서는세션 인증 방식으로통신이 어렵다.
。세션 기반 인증 방식은서버에 정보를요청 및 응답할 때마다JSessionId 쿠키를 포함하고, 해당쿠키내세션 ID를 저장 및HTTPS를 통해암호화되어프론트엔드에서 사용이 어려운 단점이 존재.
▶ 매번요청마다프론트엔드에서JSessionId를요청 헤더에 포함 및서버에서 해당세션 ID를 확인하여In Memory DB에서 확인해야하므로비효율적
▶ 따라서,React + Spring등의통신 방식은 주로JWT 토큰을 사용한다.
토큰 (
token)
。Client-side에서서버로 접근가능한Key같은인증정보를 보관하는 방법.
▶서버가 아닌, 인증정보를client-side에 암호화하여 저장.
。서버에서토큰의 발급만 수행하고저장 & 관리하지 않아서 부하가 적다.
▶세션방식에 반해분산환경에서 유리
。HTTP Servcer는stateless특징으로 상대를 알 수 없다.
▶사용자는 매번요청시Authorization Header에토큰을 포함하고,서버는 전달된토큰이 해당서버에서 발급했는지 판단하기 위해검증을 수행
토큰은문자열로서, 생성 시사용자에 의해임의로만료가 불가능
。오직만료기간이 경과해야만료되는 단점이 존재.
▶수명이일주일동안 지속되는Refresh Token은 해당 문제에 치명적.
。따라서,화이트리스트/블랙리스트방식을 통해 해당JWT 토큰을 등록하여서버쪽에서 해당토큰의로그인을명시적으로 차단하는로직을 작성해야함.
JSON 토큰(JWT: Json Web Token ) :
。웹에서Authentication과 정보교환 등을 수행하기위해 사용하는Token기반인증 방식
▶ 비대칭키 암호화 : RSA 방식으로암호화하여공개키와비밀키가 필요
。JWT는유효기간이만료될때까지 유지되며 빠르게 해제가 불가능.
▶Access Token / Refresh Token방식을 통해토큰수명을 짧게 관리함으로써보안을 향상
。클라이언트가 로그인 후서버가클라이언트에게 발급하여쿠키/리디렉션으로 제공
▶ 이후클라이언트에서 전송하는HTTP Request마다Authorization Header에JWT를 포함하여인증을 수행.
。클라이언트가Token을 보관하면서Stateless특징을 지니므로,Session이 필요없다.
▶ 또한Session처럼DB 조회없이Token만으로Authenticate가 가능.
JWT
서버에서JWT 토큰 발급시Cookie로,클라이언트에서서버 요청시Authentication Header에Bearer {token}형식으로 지정
JWT 토큰은Base 64로인코딩되어있어복호화가 가능
。토큰을 타인이 쉽게복호화가 가능하므로,payload에민감정보를 넣으면 안된다.
。오직JWT 토큰의Signature만Secret Key를 통해암호화가 되어있음.
JWT 토큰의서명을 생성하기 위해서Secret Key를 필수로 요구
。해당Secret Key를 기반으로RSA 알고리즘기반으로토큰의서명을 수행
。Secret Key설정 시 최소한8 Byte이상이어야한다.
▶비대칭키(RS256 , ES256) 등은 최소2048bit의 사용을 권장하므로KeyPairGenerator에서문자열 key 크기를2048 bit=8 Byte으로 생성하도록 설정.
▶레인보우 테이블에 의해 하나씩브루트포스되는것을 막고자UUID2개를 생성 및 조합 후Base64로인코딩한문자열을Key로 설정
UUID Generator, Base64 Encoder
。외부로 탈취되면 안되므로,application.yml내placeholder로 등록하는환경변수(.env)로 저장
▶어플리케이션에는@Value또는@ConfiguationProperties로 가져올 수 있음.custom: jwt: secrets: app-key: ${base64UuidKey}▶
generalorsecrets등의명칭으로 관리하는게 좋다.
JWT 토큰종류
Access Token
。짧은 유효기간:30분
。Access Token의만료시간이 짧은 경우 유출되더라도 피해를 최소화하여 보안성이 좋지만, 자주 로그인해야하는 불편함이 존재
▶Access Token의유효기간을 짧게 구축하되만료시Refresh Token를 기반으로 새로운Access Token을 발급하는 방식으로 구현
▶사용자는Access Token이 만료되더라도 다시 로그인할 필요가 없다.
。Access Token은프론트엔드 단에서Axios API의인터셉터를 통해인증 헤더의만료된 토큰을 교환하도록로직을 작성.
▶ 만약요청 전송 직전에Access Token의유효기간을 먼저 확인 후3분이내 인 경우Access Token을 재발급
Refresh Token
。Access Token보다긴 유효기간(일주일)을 가진재발급 용도 토큰
▶만료시 로그인을 다시해야함
。서버에서 사용되어Access Token이만료된 경우재발급하는 역할
▶클라이언트( =프론트엔드)에서 직접 다룰 일이 없으므로 보통HttpOnly 쿠키에 저장
。Refresh Token은갱신 용도 토큰으로서,식별용 ID를 제외한 내부Payload 정보가 필요 없음.
▶최소 정보를 포함하도록Access Token에 비해 많은 정보를 포함하지 않도록 설정.
로그아웃시Refresh Token을블랙리스트 / 화이트리스트로 등록하여 해당토큰을 통한로그인차단
。Token은 직접만료가 불가능하므로,Refresh Token이 오래 살아있으면 해당 기간동안무한 로그인이 발생
토큰의인증 관리전략
。블랙리스트 방식은규모가 큰 프로젝트/화이트리스트 방식은규모가 작은 프로젝트에서 사용 ▶블랙리스트 & 화이트리스트`를 둘다 혼합해서 사용이 가능.
블랙리스트
。블랙리스트 테이블에로그아웃 할 사용자의토큰을입력함으로써 해당토큰을 통한로그인을 차단하는 방식
。블랙리스트는 주로RDBMS의테이블상에서 관리
▶Redis등의인메모리 DB에서 관리 시휘발성이 매우 크기 때문에,로그아웃된토큰 정보를 잃은 후 해당토큰으로재로그인하는보안 위험이 존재
。블랙리스트 테이블의 경우토큰외에도로그아웃 시점의IP주소,IP주소를 통한로그아웃 장소 위치를 기록가능
화이트리스트
。화이트리스트 테이블에로그인 중 인 사용자의토큰을입력하는 방식
▶로그아웃시 해당테이블에서토큰을 삭제
。화이트리스트의 경우 주로Redis에서로그인을 관리
▶로그인시Redis에Refresh Token을 저장하므로,화이트리스트 방식으로서로그아웃시 해당Redis에서 해당Refresh Token을 삭제
▶인메모리 DB이기에,로그인 중인 사용자 토큰 정보를 잃어도, 해당사용자는 다시로그인을 수행해서 발급받으면 되므로보안위협이 약함.
Header
。서명 알고리즘에 대한 정보를 제공
。typ(Token Type)과alg(Signature Algorithm)로 구성{ "typ": "JWT", // 토큰 타입 "alg": "HS256" // 서명 알고리즘 (HMAC-SHA256) }▶
Base64로인코딩되어 포함
Payload
。JWT의Claims를 포함하는 부분
▶노출되도 상관없는값에 해당{ "iss": "example.com", "sub": "user123", "aud": "myapp", "exp": 1716239022, "iat": 1716235422, "role": "admin" // private claim }▶
Base64로인코딩되어 포함
。iss: ( issuer ) :발급자
ex )assetbox
。sub: ( subject ):주제
ex )access-token
。aud: ( audiance ) :대상자
ex )assetbox-user
。exp: ( expiration ) : 토큰만료시간
。iat( Issued at ) : 토큰발급시간
jjwt를 통한토큰생성 시
서명:Signature
。서버의 특정문자열 Key를 기반으로서명알고리즘(RSA등 )을 통해단방향 알고리즘처리한서명
。토큰을유효성 검증시 사용하는 고유한서명
▶JWT에Header.Payload를 서명 후Base64로인코딩하여 포함
。서명에 사용되는Private Key는 보안을 위해특정길이이상을 사용하도록 강제
▶비대칭키(RS256 , ES256) 등은 최소2048bit의 사용을 권장하므로KeyPairGenerator에서문자열 key 크기를2048 bit=8 Byte으로 생성하도록 설정.
。위조여부는JWT의Signature를 기반으로 해당서버에서 도출된서명인지 판단.
▶서명은복호화가 불가능하고,해싱을 통해 동일한문자열을 입력해도 항상해시값이 변화하므로 노출되도 안전.
Signature생성과정
1.Header,Payload를 각각Base64로인코딩
2.인코딩된 두문자열을.으로 결합
▶인코딩된Header.인코딩된Payload
3. 지정된알고리즘과Key로서명을 수행하여서명 Byte값을 생성
ex )RS256:SHA256으로private key를 활용하여서명
4.서명후 생성된byte값을base64로인코딩하여Signature생성
▶JWT = Header.Payload.Signature의Signature이 된다.
Signature검증과정
1.클라이언트로 부터 수신한JWT에서Header,Payload,Signature분리
。해당Header,Payload,Signature은 각각Base64Url로인코딩된 상태
2.Header를디코딩후 얻은JSON에서서명 알고리즘(alg)에서 허용된알고리즘인지검증
3.Signature을디코딩후 얻은서명 Byte값을서버의발급자의Public Key로 검증
4.검증후payload의 신뢰여부를 결정
。실패 시토큰을 거부
서버에서Access Token / Refresh Token을클라이언트에게응답하는 방법
1.Access Token은Response Body/Refresh Token은HttpOnly과Secure flagf가 적용된Cookie에 저장 후클라이언트에게 응답
쿠키 방식
2.URL의Query Parameter에AccessToken / Refresh Token을 포함 후Redirection을 통해응답
。해당 방식을 사용하는 경우클라이언트가 아닌,프론트엔드=의내장 Express 서버로 반환하면서 보호됨
。리디렉션 방식
예시 .http://localhost:3000/auth? access=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwicm9sZSI6IuqwkjIiLCJpYXQiOjE3Nzk3NTg0MjksImV4cCI6MTc3OTc2MDIyOX0.cy-HFQUXCDD-dyXpeOOPu07uTisUGaW99-oMVgC5clFPGyesVatm9-aKWkDnr729aIU1VFQUQL1j_g_1bOrPWA & refresh=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwicm9sZSI6IuqwkjIiLCJpYXQiOjE3Nzk3NTg0MjksImV4cCI6MTc4MDM2MzIyOX0.SXgYJD9AZNkime65UGFPoNHadv_wkPuY8K5P6kSAlo0gsoo7XfjY_a9ZhItQBrGR-rCui-Go1SVN7urHKIxpEA