CS

인증과 인가 / 쿠키와 세션 / 토큰과 JWT 이란 ?

Jerry_K 2024. 9. 6. 14:26

웹서비스를 만들기위해서는 로그인 기능이 거의 필수적이다. 

로그인 기능이 있어야 사용자에게 맞춤형으로 응답을 보낸다. 

 

그렇다면 사용자로부터 인증을 받아야하는데,

이때 쿠키와 세션이 쓰인다. 

 

이번 포스팅에서 쿠키와 세션, 그리고 토큰 

추가로 JWT에 대해 알아보고, 간단히 파이썬으로 구현해본다. 


인증 (Authetication) : 사용자의 신원을 검증

인가 (Authorization) : 인증 이후 프로세스로, 인증된 유저가 어떠한 자원에 접근할 수 있는지 확인

 

사용자가 로그인을 인증하면 → (인증) 

서버측에서 쿠키를 주던가 세션 ID를 저장한다. →  (인가)

 

 

🔖 웹 어플리케이션 인증의 특수성

비연결성 (Connectionless):

서버와 클라이언트는 연결되어 있지 않고, 리소스 절약을 위해 서버는 하나의 요청에하나의 응답을 보내고 연결을 끊는다. 

 

무상태 (Stateless):

서버는 클라이언트의 이전 상태를 저장하지 않아서, 클라이언트가 직전에 무슨 요청을 보낸지모른다.

(서버의 비용과 부담을 줄이기 위해)

 


🔖 쿠키 (Cookie)

 

서버는 사용자가 로그인을 하면  ID,PW 정보를 가진 쿠키를 사용자의 브라우저에게 보낸다. 

브라우저에서는 요청할 때마다 로그인 정보가 담긴 쿠키를 서버에 보낸다.

 

인증을 위한 추가적인 데이터 저장이 필요없지만,

사용자의 주요 정보를 매번 요청에 담아 보안상 문제가 있다. 

(누군가 쿠키 정보를 변경하고 삭제 할 수 있음

 

또한 쿠키의 사이즈가 커질수록 네트워크 부하가 심해진다. 

 

쿠키의 구성요소

Name :  쿠키를 구별하는 데 사용 (중복 불가)

Value : 쿠키의 값

Domain : 쿠키가 저장된 도메인

Path : 쿠키가 사용되는 경로

Expires : 쿠키의 만료기한

 

- 주요용도:

사용자의 기본 설정, 로그인 상태, 방문기록 등 

 


🔖세션(Session) 

 

서버에서 일정시간 동안 클라이언트 상태를 유지하고,

클라이언트별 유일한 세션 ID를 부여하여 세션 정보를 서버에 저장한다.

(여기에서 세션 ID는 사용자의 주요 정보가 아닌 사용자를 식별할 수 있는 값)

 

세션ID는 클라이언트 쿠키 값으로 저장되고, 

클라이언트에서 요청을 보낼 때 이 세션 쿠키를 함께 보낸다. 

 

그리고 서버에서는 클라언트별 세션 쿠키 값이 저장되어, 

요청으로 온 세션 쿠키 값이 어떤 클라이언트인지 식별한다.

 

고유한 세션ID를 사용하여 사용자의 정보를 주고 받지 않아 상대적으로 안전하고,

세션 ID를 통해 사용자의 상태를 확인하기 때문에 매번 DB에서 사용자 정보를 직접 조회하지 않아도 된다. 

 

서버에 저장되어 데이터 양에 제한이 적지만, 

사용자가 많아질 수록 (동접자) 서버 메모리를 많이 차지하여 서버 과부하 발생 할 수 있음

 

- 주요용도:

 로그인 후 인증상태 유지, 일시적 데이터 (장바구니)

 

쿠키클라이언트 측에 저장되고, 세션서버측에 저장된다고 생각하면 된다. 

쿠키는 브라우저가 닫혀도 계속 유지될 수 있고, 세션은 브라우저가 닫히면 세션도 종료된다.


🔖 토큰(Token) 

세션은 사용자의 아이디를 메모리에 올려서 데이터를 빠르게 확인할 수 있지만, 

메모리 공간이 부족해지면 서버에 부하가 걸리게 된다. 

 

세션 ID 대안으로 토큰은 로그인한 사용자에게 세션 아이디를 알고리즘화 하여 토큰을 발급해준다. 

(토큰은 해당 서버만이 만들 수 있고, 토큰을 발급함으로써 상태를 저장하지 않고 사용자의 로그인 상태를 파악한다.)

 

토큰을 받아간 사용자는 쿠키로 저장해두어 상태를 브라우저에 전송한다. 

(한 번 발행한 토큰은 유효기간이 끝나기 전까지 통제할 수 없다. )

 

 

토큰 방식의 단점은 토큰 자체의 데이터 길이가 길어, 

인증 요청이 많으면 네트워크 부하가 심해진다. 

 

또한 Payload 자체는 암호화되지 않아서,

중요한 정보를 담을 수 없다. 


🔖JWT (Json Web Token) 

JWT도 하나의 토큰으로 

json 객체를 이용해서 유저를인증하고, 토큰 자체의 정보를 저장하는 웹 토큰이다.

 

JWT는 모든 정보를 자체적으로 지니고 있고, 

다른 로그인 시스템에 접근 및 권한 공유가 가능하다. (소셜 계정) 

 

 

암호화된 토큰으로  복잡한 string 형태로 변환 되어있다. 

(아래에 좀 더 구체적으로 설명 되어있다.)

 

 

 

 

 

 

JWT의 구성요소로는 3가지가 있다. 

 

헤더 / 페이로드 / 서명 

 

 

 

JWT.IO

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

jwt.io

 

위의 사이트를 통해서 JWT에 대해 알 수 있다. 

 

 

Header (헤더)

토큰의 타입과 서명 알고리즘 정보를 포함한다. 

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

 

 

Payload (페이로드)

토큰에 담을 클레임 정보를 포함하는데, 여러개의 클레임을 넣을 수 있다.

이 클레임은 암호화되지 않고, Base64 URL 인코딩된 상태로 전송된다. 

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

 

여기에서 이 한쌍의 정보를 Claim이라고 한다. 

 

서명(시그니처)

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
your-256-bit-secret
)

 

 

헤더와 페이로드를 인코딩 한 후, 주어진 비밀키로 서명한 값 

(헤더 + 페이로드 + 서버가 갖고 있는 유일한 key) * 알고리즘 

 

문자열 헤더와 페이로드를 '.'으로 합치고 헤더의 알고리즘과 비밀키로 해시값을 구한다. 

 

JWT는 탈취 당하면 다른 유저가 조작이 가능하기 때문에,

JWT에서는 유효시간을 짧게 가지고 Refresh token을 사용한다.


그럼 이제 이것들을 간단하게 구현해보자. 

<h1>Login</h1>

<div class="form-group">
    <label for="username">Username:</label>
    <input type="text" id="username" placeholder="Enter your username">
</div>

<div class="form-group">
    <label for="password">Password:</label>
    <input type="password" id="password" placeholder="Enter your password">
</div>

<div class="form-group">
    <button onclick="login()">Login</button>
</div>

 

 HTML에서 간단하게 로그인 폼을 만들어준다. 

그리고 버튼이 눌러지면 login() 함수가 실행된다. 

 

 function login() {
        const username = document.getElementById('username').value;
        const password = document.getElementById('password').value;

        fetch('/login', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ username, password })
        })
        .then(response => response.json())
        .then(data => {
            if (data.token) {
                localStorage.setItem('token', data.token);
    }

 

해당 함수에서는 input 태그의 value 값들을 가져오고,

POST 방식으로 json 형식의 username과 password를 보내준다.

 

여기서 서버는 response은 JWT이다. 

응답 받은 JWT는 loacalStorage 넣어준다. 

 

 

app.config['SECRET_KEY'] = 'your_secret_key'
users = {
    "user1": "password1",
    "user2": "password2"
}

 

서버단에서 유일한 키를 만들어주고, 

임의 users 딕셔너리를 만들어주었다. (DB 대체)

 

@app.route('/login', methods=['POST'])
def login():
    auth = request.json
    username = auth.get('username')
    password = auth.get('password')

    if password != users[username]:
        return jsonify({'Alert': 'Invalid credentials'}), 401
    
    # 토큰 생성
    token = jwt.encode({
        'username': username,
        'email': f'{username}@example.com',  # 이메일을 예시로 추가
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    }, app.config['SECRET_KEY'], algorithm="HS256")

    print(token)
    return jsonify({'token': token})

 

front 쪽에서 POST 형식으로 fetch를 했고, 

해당 값들은 jwt 라이브러리를 활용해서  인코딩된다.

 

헤더로는 알고리즘 (alg)을 주고

페이로드는 username, email, exp

서명으로는 서버의 유일한 키를 파라미터로 주었다. 

 

 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImplcnJ5IiwiZW1haWwiOiJqZXJyeUBleGFtcGxlLmNvbSIsImV4cCI6MTcyNTU0ODU0NH0.Fu-TB0lzLZA_wYIIxg1MWT_hPWo7YDaj884uYAuCXM8

 

 

위의 String은 임의로 만든 헤더 + 페이로드 + 서명으로 부터 생성된 JWT이다. 

 

 

 

그리고 아까 위에 올린 JWT 사이트에서 해당 String을 올려 디코드하면,

 내가 입력한 파라미터와 일치하는 것을 볼 수 있다. 

 

 

 

그리고 개발자 도구의 ApplicationStorage에 들어가면,

Local storage에 잘 저장 되어있는 것을 확인 할 수 있다. 

 

jwt.zip
0.03MB

 

 

 


 

맨 처음 JWT은 정말 이해가 안됐다. 

근데 그냥 단순하게 사용자를 인증해주기 위한 하나의 토큰으로 생각하면 된다. 

 

 

🌐 JWT 토큰 인증 이란? (쿠키 vs 세션 vs 토큰)

Cookie / Session / Token 인증 방식 종류 보통 서버가 클라이언트 인증을 확인하는 방식은 대표적으로 쿠키, 세션, 토큰 3가지 방식이 있다. JWT를 배우기 앞서 우선 쿠키와 세션의 통신 방식을 복습해

inpa.tistory.com

 

Inpa님께서 글 정리를 잘해주셔서, 위의 블로그를 참고하여 정리했습니다.