읽히는 블로그 2024. 6. 11. 16:19

▤ 목차

     

     

     

    https://hi-hahahoho.tistory.com/76

    세션과 쿠키에 대해 정리했었다. 그 연장선인 jwt를 정리해보고자 한다.

    세션과 마찬가지로 HTTP 기반이기에 (TCP/IP) 무상태인 환경에서 사용자와 데이터를 주고 받을 수 있도록 상태를 기억하는 기술이다.

     

    ✔ JWT(Json Web Token)

    로그인 기능은 보안에도 신경써야하고 사용자가 불편하지 않도록 신경써야 한다.

    회원가입시 로그인정보(ID,PW)를 바로 DB에 저장하고 로그인 시 맞춰보기만 했다면 보안상 위험하다.

    DB가 뚫리면 해당 정보는 전부 유출되기 때문이다.

    로그인 방식 또한 복잡하지만 로그인 상태를 유지하는 것도 여러 기술이 필요하다.


    jwt 또한 세션과 마찬가지로 인증을 거친 사용자에게 인가를 유지해주는 방식 중 하나이다.

    세션과 같이 로그인이 되었다는 것을 서버가 알고 있어야한다. 인가(Authorization)에 관련된 기술이다.

     

    ( 해당 방식을 잘 이해하기 위해서는  sicky Session의 방식을 한번 선행하고 오는 것을 추천한다.)

     

    ⌨ JWT란?

    JWT의 큰 흐름은 세션과같다.

    JWT는 세션 방식의 문제점을 개선하고자 나온 기술이다.

    훗날, 요청이 증가하는 경우를 상상해보자. 세션 방식으로 해당 요청자의 데이터를 서버 메모리에 담고있으면 메모리가 부족할 수 있다. 또한 서버에 문제가 생겨 서버에 재부팅이 필요 경우, 이용자들의 인가가 끊기게 된다.

    서버가 한대 이상인 경우에는 해당 클라이언트에 대한 세션 유지가 어려울 수 있다.

     

     

    json이란 xml처럼 데이터를 담아둔 파일이다. key와 value가 한 쌍으로 이루어져 있다.

    JWT에서는 해당 데이터를 name : value 라고 칭한다.

    위와 같은 json 데이터를 기반으로 웹에서 사용할 수 있는 토큰을 발급한다.

    그 토큰은 암호화시켜 각 클라이언트의 쿠키안에 담아 둔다.

    이런 방식을 사용하면 서버가 2대이상인 경우에도 클라이언트가 어떤 서버를 들어가도 인가를 유지할 수 있다. 

     

    🪙토큰

    Token은 사용자를 인증할 수 있는 정보가 숨겨진 암호화 된 Access Token을 발행한다.

    실제로, 사용자가 로그인(인증) 단계를 거치는 경우 서버는 access token과 refresh token을 발행한다.

    그중 실제 권한을 가지고 있는 토큰은 access token이다.

     

     

    🔸refresh token은 언제 사용하는가?

     

    만약 블랙 해커로 인해 토큰이 탈취된다면 서버는 토큰을 디코딩(복호화)하여 신뢰할 만한 사람이라며 인가를 허락 할 수 있다. 이런 방법을 최소화하는 방법에서 refresh token이 사용된다.

    access token에 유효 기간(시간)을 주는 것이다.

    탈취 당할 위험이 큰 access 토큰의 만료 기간을 짧게 두고 refresh token을 통해 갱신을 해주는 것이다.

    즉, 주기적으로 재발급함으로써 피해를 최소화하는 방법이다. 

     

    1. 클라이언트의 헤더에 Access Token을 넣고 API 통신을 한다.
    2. 일정 기간이 지나 Access Token의 유효기간이 만료된다. > 서버는 401 error로 응답한다..
    3. 헤더에 access Token이 아닌 Refresh Token을 넣어 API를 재요청한다.
    4. 사용자의 권한을 확인한 서버는 응답 쿼리 헤더에 새로운 Access Token을 발급한다.
    5. 만약, Refresh Token 조차 만료되었다면 클라이언트는 재로그인을 해야한다.

     

     

    클라이트가 요청에 대한 인증이 필요할 때마다 서버에 Token과 함께 요청을 보낸다. 

    서버는 서버에 저장된 데이터가 아닌 Token 해독을 통해 Token 속에서 사용자를 식별할 수 있는 정보들을 알아내고 인증/인가를 진행한다.

     

     

    https://velopert.com/2389

     

    토큰은 위와같이 .(마침표) 구분자를 사용해서 3가지 영역으로 되어있다. 

    JWT 토큰을 만들때 JWT를 담당하는 라이브러리가 자동으로 인코딩 및 해싱 작업을 해준다.

     

    • Header: Token 유형, 서명 알고리즘(HS256 or RSA 등)이 담긴다.
    • Payload: Claim이 포함되는 영역으로 전송하고자 하는 여러 데이터가 담긴다.(Claim 유형: Registered / Public / Private)
    • Signature: Base64로 인코딩 된 Header, Payload와 서버만이 가지고 있는 비밀 키를 설정한 알고리즘으로 암호화한 값이 담긴다.

    각 생김새는 아래에서 코드와 함께 정리해보겠다.

     

    ✨프로세스 진행과정

    https://velog.io/@chuu1019/%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-JWTJson-Web-Token

     

     

     

     

     

    👏 장단점

    장점  단점
    인증에 필요한 정보가 Client가 가지고 있는 Token에 포함되어 있기에 server측에 따로 인증 정보를 저장하는 저장소가 필요없다 Token은 거의 모든 Request에 대해 전송되고 검증하기 때문에 주고 받는 데이터가 커질 수 있다.
    토큰값만 있다면 어떤 서버로 요청을 해도 상관이없어서 확장성이 좋다.  claim의 데이터가 증가할 수록 Token이 길어지고 데이터가 커질 수 있다. (네트워크 부)
    헤더를 사용하기때문에 CSRF(Cross-site Request Forgery*)와 같은 쿠키와 관련된 문제가 없다. payload 영역에 중요한 정보를 넣으면 안된다.
    토큰에 정보가 들어있기때문에 서버가 요청마다 데이터베이스를 조회를 하지 않아도 된다. 한번 발급된 Token은 서버에서 조작이 불가능하다. 관리를 위해서 Refresh Token과 같은 기술이 추가로 필요하다.

     

     

     

    [  * CSRF(Cross-site Request Forgery) ]

    더보기

    사이트간의 요청 위조를 의미한다.

    웹 보안 취약점의 일종이다. 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 하는 공격이다.

    예를 들어 메일 주소를 변경하거나 암호를 변경하는 행위들을 말한다.

    공격자가 사용자 계정에 대한 완전한 제어권을 얻을 수도 있다.

     

    < 위조를 하기 위한 만족 조건 >

    1. 사용자는 보안이 취약한 서버로부터 이미 로그인되어 있는 상태여야 한다.

    2. 쿠키 기반의 서버 세션 정보를 획득할 수 있어야한다.

    3. 공격자는 서버를 공격하기 위한 요청 방법에 대해 미리 파악하고 있어야한다.

     

    < 공격 과정>

    1. 보안이 취약한 서버에 사용자가 로그인한다.
    2. 서버에 저장된 세션 정보를 사용할 수 있는 세션ID가 브라우저 쿠키에 저장된다.
    3. 공격자는 사용자가 악성 스크립트 페이지를 누르도록 유도한다.
    4. 사용자가 악성 스크립트에 접근한 경우, 웹 브라우저에 의해 쿠키에 저장된 session id와 함께 서버로 요청된다.
    5. 쿠키에 담긴 세션 ID를 통해 해당 요청이 인증된 사용자로부터 온 것으로 판단하고 처리한다.

     

    자세히 설명된 블로그

     

    https://devscb.tistory.com/123

     

    CSRF란, CSRF 동작원리, CSRF 방어방법

    CSRF란, CSRF 동작원리, CSRF 방어방법 CSRF란 CSRF란, Cross Site Request Forgery의 약자로, 한글 뜻으로는 사이트간 요청 위조를 뜻합니다. CSRF는 웹 보안 취약점의 일종이며, 사용자가 자신의 의지와는 무관

    devscb.tistory.com

     

     


    https://jwt.io/

     

    JWT.IO

    JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

    jwt.io

     

    암호화되는 과정을 볼 수있는 사이트이다.

    JWT를 만들어 볼 수 있다. JWT 공식 홈페이지다.

     

     

     

     


     

     

     

    ✔ 토큰 구성(Header, Payload, Signature)

    json으로 포맷된 부분은 Base64로 인코딩되어 표현된다.

    위에서 정리했듯 각 부분은 .(마침표)로 구분된다. 

     

     

    🗝️header (헤더)

    헤더 부분은 2가지의 정보를 가지고 있다.

     

    {
      "alg": "HS256",
      "typ": "JWT"
    }

     

    alg

    해싱 알고리즘을 지정한다. 많이 사용되는 해싱 알고리즘으 HMAC SHA256 혹은 RSA가 사용된다.

    토큰을 검증할 때 signature 부분에서 사용된다.

     

    typ

    토큰의 타입을 지정한다. JWT이다.

     

     

    JSON 형태의 객체가 base64로 인코딩 되는 과정에서는 공백, 엔터가 제거된다. { "alg": "HS256", "typ": "JWT" }의 문자열을 인코딩한다.

     

     

    🗝️PAYKOAD (정보)

    Payload 부분에는 토큰의 정보가 담겨있다. 이 부분에 담긴 한 조각을 클레임이라고 부른다.

    토큰에는 여러개의 클레임들을 넣을 수 있다. 이때 크게 3분류로 나눌 수 있다.

     

    • 등록된(registered) 클레임
    • 공개(public) 클레임
    • 비공개(private) 클레임

    페이로드 안에 있는 속성들을 클레임 셋(Claim Set)이라고 부른다.

    클레임 셋은 JWT에 대한 내용이나 클라이언트와 서버 간 주고 받기로 한 값들로 구성된다.

     

    🧩 등록된(registered) 클레임

    등록된 클레임들은 서비스에서 필요한 정보들이 아니다.

    토큰에 대한 정보들을 담기위해 사전에 정해진 클레임이다.

    등록된 클레임의 사용은 모두 선택적(optional)이다.

     

    • iss : 토큰 발급자
    • sub : 토큰 제목
    • aud : 토큰 대상자
    • exp : 토큰 만료 시간(expiration), 시간은 NumericDate 형식으로 되어있어야 하며 언제나 현재 시간보다 이후로 설정되어야한다.
    • nbf : 토큰 활성날짜, NumericDate 형식으로 날짜를 지정. 이 날짜가 지나기 전까지는 토큰이 처리되지 않았다..
    • iat : 토큰이 발급된 시간, 토큰의 age가 얼마나 되었는지 판단 할 수 있다..
    • jti : JWT의 고유 식별자, 중복적인 처리를 방지하기 위해 사용한다. (일회용 토큰에 사용하면 유용)

     

    {
      "sub": "1234567890",
      "name": "John Doe",
      "iat": 1516239022
    }

     


    🧩 공개(public) 클레임

    공개 클레임들은 충돌이 방지된( collision- resistant) 이름을 가지고 있어야한다.

    충돌 방지를 위해 클레임 이름을 URI 형식으로 짓는다.

     


    🧩 비공개(private) 클레임

    클라이언트와 서버 간 협의하에 사용되는 클레임 이름들이다.

    공개 클레임과는 달리 이름이 중복되어 충돌이 될 수 있다.

     

     

     

    🔶단순 Base64 인코딩된 파트이다. 마음만 먹으면 decoding하여 데이터 열람이 가능하다.

    비밀번호와 같은 주요 정보를 담으면 안된다. 

     

     

    🗝️SIGNATURE(서명)

    헤더의 인코딩 값과 페이로더의 인코딩 값을 합친 후 비밀키로 해쉬를 사용해 생성한다.

    아래는 서명 부분을 만드는 슈도코드(PSEUDOCODE)의 구조이다.

     

    서명은 헤더의 alg에 정의된 알고리즘과 비밀 키를 이용해 생성하고 Base64 URL-Safe로 인코딩한다.

     

     

    👏 자체 포함과 무상태

    JWT는 자체에 필요한 모든 정보를 담을 수 있다.

    헤더는 토큰에 대한 해석 방법, 페이로드는 토큰의 내용, 전달할 내용 등을 담을 수 있다.

    서명으로 헤더와 페이로드가 변조되지 않았음을 검증할 수 있다.

     

    서버 입장에서 JWT 생성 시 JWT에 검증이나 권한 인가 시 필요한 값을 넣으면 되기 때문에 JWT에 대한 상태를 따로 관리하고 있지 않아도 된다.

     

     

     


     

     

     

    ✔ JWT 실습 (생성해보기)

    *jwt 생성을 보기 위한 것이라 DB와 연동은 하지 않는다.

    *때문에 미리 값(id : haha /pw:111)을 설정해두었다.

    *html input에 value는 원래 없어야하는 게 맞지만, 실행때마다 눌러야하기에 미리 넣어놓았다.

     

    우선 로그인 html 폼을 만들어준다.

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Session login</title>
    </head>
    <body>
    <h2>* JWT 로그인 *</h2>
    <form action="jwtLogin.jsp" method="post">
    <table>
    	<tr>
    		<td>id : </td>
    		<td><input typs = "text" name="id" value="haha"> </td>
    	</tr>
    	<tr>
    		<td>password : </td>
    		<td><input typs = "text" name="password" value="111"> </td>
    	</tr>
    	<tr>
    		<td colspan="2">
    			<input type="submit" value="login">
    		</td>
    	</tr>
    </table>
    </form>
    </body>
    </html>

     

     

     

     

     

    처음 브라우저에 열었을때의 모습이다. 쿠키도 없다.

     

     

    🪪 jwtLogin.jsp

     

    • 직접 암호화 키를 만들어 봤다. (실제로는 이렇게 만들지 않는다. encoding 또는 decoding을 하여 암호화를 한다.)
    • 직접 만들때, 주의 사항이 있는데, hmac 알고리즘은 32바이트(256비트)이상의 키를 넣어줘야한다.
    • 작은 값을 넣어 줄 경우 오류(https://hi-hahahoho.tistory.com/78)
    • setHttpOnly() 메서드는 자바스크립트에서 쿠키값을 읽어가지 못하도록 하는 메서드이다.

     

    <%
    String id = request.getParameter("id");
    String pw = request.getParameter("password");
    
    String validId = "haha";
    String validPw = "111";
    
    //정상적으로 인증이 된다면 jwt 생성
    if(id != null && pw != null && id.equals(validId) && pw.equals(validPw)){
    	//직접 암호화키 만들기
    	String secretString = "haha123456789hoho123456789하하호호";
    	Key skey = Keys.hmacShaKeyFor(secretString.getBytes()); //hmac 알고리즘을 적용한 key객체 생성
    	
    	long expirationTime =60000;
    	
    	//jwt 생성
    	String jwt = Jwts.builder()
    				.setSubject(id)
    				.setIssuedAt(new Date())
    				.setExpiration(new Date(System.currentTimeMillis()+expirationTime))
    				.signWith(skey)
    				.compact();
    	
    	//쿠키에 jwt 저장
    	Cookie jwtCooKie = new Cookie("jwt",jwt);
    	jwtCooKie.setHttpOnly(true);
    	jwtCooKie.setPath("/");
    	response.addCookie(jwtCooKie);
    	
    	//인증에 성공한 경우, 페이지 이동
    	response.sendRedirect("loginSuccess.jsp");
    }else{
    	out.print("<html><body>");
    	out.print("<h2>로그인에 실패하셨습니다.</h2>");
    	out.print("<a href='jwtLogin.html'> 로그인 페이지로 바로가기</a>");
    	out.print("</body></html>");
    }
    %>

     

     

     

     

     

     

     

     

     

    🪪 인증에 성공한 경우, loginSuccess.jsp

     

    • 마찬가지로 HMAC 알고리즘은 키의 길이가 256비트 이상이어야한다.
    • JWT 유효성 검사를 한다.
    • parseClaimsJws( token ) 는 주어진 jwt토큰을 파싱하여 헤더/페이로드/서명으로 분리한다. 서명을 검증해서 데이터의 무결성을 확인한다. 해당 메서드는 파싱 결과로 Jws<Claims> 객체를 반환한다. 이 메서드는 잘못된 서명, 만료된 토큰 등 다양한 예외 상황을 처리할 수 있다.

     

    <%
    //로그인에 성공한 경우, jwt를 가져와 유효성 검사
    Cookie[] cookies = request.getCookies(); //클라이언트는 쿠키를 모두 전달한다.
    String jwt = "";
    
    //해당 쿠키 찾아서 넣어준다.
    if(cookies != null){
    	for(Cookie cook : cookies){
    		if(cook.getName().equals("jwt")){
    			jwt = cook.getValue();
    			break;
    		}
    	}
    }
    
    if(jwt != null){
    	try{
    		//고정된 비밀키 사용
    		//직접 만들어보자.
    		String secretKey = "haha123456789hoho123456789하하호호";
    		Key skey = Keys.hmacShaKeyFor(secretKey.getBytes());
    		
    		//jwt 유효성 검사
    		Jws<Claims> claims = Jwts.parserBuilder().setSigningKey(skey)
    							.build()
    							.parseClaimsJws(jwt);
    		String userid = claims.getBody().getSubject();
    	
    %>
    
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <h2>로그인에 성공하셨습니다.</h2>
    <%=userid %>님 반갑습니다.<br>
    <a href="jwtLogin.html">로그인 페이지로 이동</a><br>
    <a href="jwtLogout.jsp">로그아웃</a>
    </body>
    </html>
    <%
    
    }catch(Exception e){
    	System.out.print("success.jsp err : " + e.getMessage());
    	//jwt가 유효하지 않은 경우
    	response.sendRedirect("jwtLogin.jsp");
    }
    }else{
    	//jwt없이 들어온 경우, 로그인 페이지로 이동
    	response.sendRedirect("jwtLogin.jsp");
    }
    %>

     

     

     

     

     

     

     

    👋 jwtLogout.jsp

    <%
    Cookie jwtCookie = new Cookie("jwt","");
    jwtCookie.setMaxAge(0);
    response.addCookie(jwtCookie); //해당 name에 jwt를 없앤다.
    %>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
    <h2>정상적으로 로그아웃되셨습니다.</h2>
    <a href="jwtLogin.html">로그인 페이지로 이동</a>
    </body>
    </html>

     

     

     

     

     

    👏 해당 jwt 값을 Decoding 해보자.

     

     

    해당 값을 가지고 jwt.io에 들어가보자.

    https://jwt.io/

     

     

    아이디로 보낸 값을 알 수 있다.

     

     

     


     

     

    😊정리

    세션는 서버나 따로 저장공간(하드,DB)를 만들어 관리한다는 점에서 발생하는 문제점들이 있었다.

    토큰을 사용하면 클라이언트(로컬)영역에 정보를 암호화하여 담아두고 클라이언트가 요청과 함께 전달하기때문에 위와 같은 세션의 문제점을 해결할 수 있었다.  

    클레임이 많아지면 주고 받는 데이터가 커진다는 단점이 있으며 디코딩이 가능하기에 보안이 취약(refresh key 활용으로 강화 가)하지만 서버측에 저장소가 없어도 된다는 점과 클라이언트도 어떤 요청이든 토큰만 가지고 있으면 인증,인가가 가능하기에 확장성이 좋다는 장점을 가지고 있다.

     

    세션을 사용할지 토큰을 사용할지 정해진 답은 없다.

    이용자가 적은 경우,하나의 서버로 로직을 만든다면 세션을 사용하는 것이 좋다고 생각한다. 만약 탈취가 일어난 경우, 서버측에 저장되어 있기에 서버측에서 무효화가 가능하다는 점도 하나의 장점이라 생각한다. 

    하지만 이용자가 많아 여러 서버를 사용해 로드밸런싱을 한다면 토큰 방식을 사용하는것이 서버에 부담도 줄이고 확장성이 좋기에 토큰을 선택하는 것이 좋겠다.

     

     

     

    JWT에 대한 더 많은 내용들이 있다.

     

    https://meetup.nhncloud.com/posts/239

     

    JWT를 소개합니다. : NHN Cloud Meetup

    JWT는 일반적으로 클라이언트와 서버, 서비스와 서비스 사이 통신 시 권한 인가(Authorization)를 위해 사용하는 토큰이다.

    meetup.nhncloud.com

    https://velopert.com/2389

     

    [JWT] JSON Web Token 소개 및 구조 | VELOPERT.LOG

    지난 포스트에서는 토큰 기반 인증 시스템의 기본적인 개념에 대하여 알아보았습니다. 이 포스트를 읽기 전에, 토큰 기반 인증 시스템에 대해서 잘 모르시는 분들은 지난 포스트를 꼭 읽어주세

    velopert.com