Python 클라이언트 - 서버 시스템에서 인증 구현 방법

PythonBeginner
지금 연습하기

소개

안전한 인증 구현은 강력한 Python 클라이언트 - 서버 시스템을 구축하는 데 필수적인 측면입니다. 이 튜토리얼에서는 기본적인 사용자 이름과 비밀번호 인증부터 토큰 기반 방식까지, Python 애플리케이션에서 인증을 구현하는 과정을 안내합니다. 이 Lab 을 완료하면 적절한 인증 메커니즘을 통해 Python 애플리케이션을 안전하게 보호하는 방법을 이해하게 될 것입니다.

인증 기본 사항 이해 및 환경 설정

인증은 보호된 리소스에 대한 접근 권한을 부여하기 전에 사용자 또는 시스템의 신원을 확인하는 과정입니다. 클라이언트 - 서버 시스템에서 적절한 인증은 권한이 있는 사용자만 민감한 데이터에 접근하거나 특정 작업을 수행할 수 있도록 보장합니다.

환경 설정

작업 환경을 설정하는 것으로 시작해 보겠습니다. 먼저, 프로젝트를 위한 새 디렉토리를 생성합니다.

mkdir -p ~/project/auth_demo
cd ~/project/auth_demo

다음으로, 이 Lab 전체에서 사용할 필수 Python 패키지를 설치해야 합니다.

pip install flask flask-login requests

다음과 유사한 출력을 볼 수 있습니다.

Collecting flask
  Downloading Flask-2.2.3-py3-none-any.whl (101 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.8/101.8 KB 6.2 MB/s eta 0:00:00
Collecting flask-login
  Downloading Flask_Login-0.6.2-py3-none-any.whl (17 kB)
Collecting requests
  Downloading requests-2.29.0-py3-none-any.whl (62 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.5/62.5 KB 5.5 MB/s eta 0:00:00
...
Successfully installed flask-2.2.3 flask-login-0.6.2 requests-2.29.0 ...

인증 개념

구현에 들어가기 전에 기본적인 인증 개념을 이해해 보겠습니다.

  1. 사용자 이름 및 비밀번호 인증: 사용자가 신원을 확인하기 위해 자격 증명을 제공하는 가장 일반적인 형태입니다.

  2. 토큰 기반 인증: 성공적인 로그인 후, 서버는 클라이언트가 후속 요청에 사용할 토큰을 생성하여 매번 자격 증명을 보낼 필요가 없도록 합니다.

  3. 세션 기반 인증: 서버는 성공적인 로그인 후 세션 정보를 저장하고 클라이언트에게 세션 ID 를 제공합니다.

기본적인 인증 흐름을 시각화해 보겠습니다.

Client                                Server
  |                                     |
  |--- Login Request (credentials) ---->|
  |                                     |--- Verify Credentials
  |                                     |
  |<---- Authentication Response -------|
  |                                     |
  |--- Subsequent Requests (with auth) >|
  |                                     |--- Verify Auth
  |                                     |
  |<---- Protected Resource Response ---|

이제 기본 사항을 이해하고 환경을 설정했으므로, 프로젝트를 위한 간단한 파일 구조를 생성해 보겠습니다.

touch ~/project/auth_demo/server.py
touch ~/project/auth_demo/client.py

다음 단계에서는 서버 측에서 Flask 를 사용하여 기본적인 사용자 이름 및 비밀번호 인증 시스템을 구현하고, Python 클라이언트를 사용하여 인증합니다.

기본적인 사용자 이름 및 비밀번호 인증 구현

이 단계에서는 사용자 이름과 비밀번호를 사용하는 기본적인 인증 시스템을 구현합니다. 다음을 생성합니다.

  1. 인증을 처리하는 Flask 서버
  2. 서버로 인증하는 Python 클라이언트

서버 생성

기본적인 인증 기능을 갖춘 Flask 서버를 구현해 보겠습니다. VSCode 편집기에서 server.py 파일을 엽니다.

code ~/project/auth_demo/server.py

이제 다음 코드를 추가하여 간단한 인증 서버를 구현합니다.

from flask import Flask, request, jsonify, session
import os

app = Flask(__name__)
## Generate a random secret key for session management
app.secret_key = os.urandom(24)

## Simple in-memory user database
users = {
    "alice": "password123",
    "bob": "qwerty456"
}

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()

    ## Extract username and password from the request
    username = data.get('username')
    password = data.get('password')

    ## Check if the user exists and the password is correct
    if username in users and users[username] == password:
        session['logged_in'] = True
        session['username'] = username
        return jsonify({"status": "success", "message": f"Welcome {username}!"})
    else:
        return jsonify({"status": "error", "message": "Invalid credentials"}), 401

@app.route('/protected', methods=['GET'])
def protected():
    ## Check if the user is logged in
    if session.get('logged_in'):
        return jsonify({
            "status": "success",
            "message": f"You are viewing protected content, {session.get('username')}!"
        })
    else:
        return jsonify({"status": "error", "message": "Authentication required"}), 401

@app.route('/logout', methods=['POST'])
def logout():
    ## Remove session data
    session.pop('logged_in', None)
    session.pop('username', None)
    return jsonify({"status": "success", "message": "Logged out successfully"})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

이 서버는 다음과 같습니다.

  • 사용자 이름과 비밀번호를 저장하기 위해 간단한 인 메모리 딕셔너리를 사용합니다.
  • 로그인, 보호된 콘텐츠 접근 및 로그아웃을 위한 엔드포인트를 제공합니다.
  • 인증 상태를 유지하기 위해 Flask 의 세션 관리를 사용합니다.

클라이언트 생성

이제 서버로 인증할 수 있는 클라이언트를 만들어 보겠습니다. client.py 파일을 엽니다.

code ~/project/auth_demo/client.py

다음 코드를 추가합니다.

import requests
import json

## Server URL
BASE_URL = "http://localhost:5000"

def login(username, password):
    """Authenticate with the server using username and password"""
    response = requests.post(
        f"{BASE_URL}/login",
        json={"username": username, "password": password}
    )

    print(f"Login response: {response.status_code}")
    print(response.json())

    ## Return the session cookies if login was successful
    return response.cookies if response.status_code == 200 else None

def access_protected(cookies=None):
    """Access a protected resource"""
    response = requests.get(f"{BASE_URL}/protected", cookies=cookies)

    print(f"Protected resource response: {response.status_code}")
    print(response.json())

    return response

def logout(cookies=None):
    """Logout from the server"""
    response = requests.post(f"{BASE_URL}/logout", cookies=cookies)

    print(f"Logout response: {response.status_code}")
    print(response.json())

    return response

if __name__ == "__main__":
    ## Test with valid credentials
    print("=== Authenticating with valid credentials ===")
    cookies = login("alice", "password123")

    if cookies:
        print("\n=== Accessing protected resource ===")
        access_protected(cookies)

        print("\n=== Logging out ===")
        logout(cookies)

    ## Test with invalid credentials
    print("\n=== Authenticating with invalid credentials ===")
    login("alice", "wrongpassword")

    ## Try to access protected resource without authentication
    print("\n=== Accessing protected resource without authentication ===")
    access_protected()

이 클라이언트는 다음과 같습니다.

  • 로그인, 보호된 리소스 접근 및 로그아웃 기능을 제공합니다.
  • 요청 간 세션을 유지하기 위해 쿠키를 처리합니다.
  • 유효한 자격 증명과 유효하지 않은 자격 증명을 모두 테스트합니다.

인증 시스템 실행

인증 시스템이 작동하는 것을 확인하려면 서버와 클라이언트를 모두 실행해야 합니다. 먼저, 서버를 시작해 보겠습니다.

python ~/project/auth_demo/server.py

다음과 유사한 출력을 볼 수 있습니다.

 * Serving Flask app 'server'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000

이제 터미널 옆에 있는 "+" 버튼을 클릭하여 새 터미널 탭을 열고 클라이언트를 실행합니다.

cd ~/project/auth_demo
python client.py

다음과 같은 출력을 볼 수 있습니다.

  1. 유효한 자격 증명으로 성공적인 로그인
  2. 유효한 인증으로 보호된 리소스에 대한 접근
  3. 성공적인 로그아웃
  4. 잘못된 자격 증명으로 로그인 시도 실패
  5. 인증 없이 보호된 리소스에 접근하려는 시도 실패

이 출력은 기본적인 사용자 이름 및 비밀번호 인증 시스템이 올바르게 작동하고 있음을 확인합니다.

다음 단계에서는 보안을 개선하기 위해 토큰 기반 인증을 구현하여 이 시스템을 개선할 것입니다.

토큰 기반 인증 구현

이전 단계에서는 세션을 사용하여 기본적인 인증 시스템을 만들었습니다. 이는 간단한 애플리케이션에 적합하지만, 토큰 기반 인증은 특히 API 및 분산 시스템에 여러 가지 이점을 제공합니다.

토큰 기반 인증에서:

  • 서버는 성공적인 인증 후 토큰을 생성합니다.
  • 클라이언트는 이 토큰을 저장하고 후속 요청과 함께 보냅니다.
  • 서버는 매번 자격 증명을 확인하는 대신 토큰의 유효성을 검사합니다.

토큰 기반 인증을 위한 널리 사용되는 표준인 JSON Web Token (JWT) 을 사용하도록 시스템을 업그레이드해 보겠습니다.

필수 패키지 설치

먼저, PyJWT 패키지를 설치해야 합니다.

pip install pyjwt

설치를 확인하는 출력을 볼 수 있습니다.

Collecting pyjwt
  Downloading PyJWT-2.6.0-py3-none-any.whl (20 kB)
Installing collected packages: pyjwt
Successfully installed pyjwt-2.6.0

토큰 기반 인증을 위한 서버 업데이트

세션 대신 JWT 토큰을 사용하도록 서버를 수정해 보겠습니다. server.py 파일을 업데이트합니다.

code ~/project/auth_demo/server.py

기존 코드를 다음으로 바꿉니다.

from flask import Flask, request, jsonify
import jwt
import datetime
import os

app = Flask(__name__)
## Secret key for signing JWT tokens
SECRET_KEY = os.urandom(24)

## Simple in-memory user database
users = {
    "alice": "password123",
    "bob": "qwerty456"
}

def generate_token(username):
    """Generate a JWT token for the authenticated user"""
    ## Token expires after 30 minutes
    expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=30)

    payload = {
        'username': username,
        'exp': expiration
    }

    ## Create the JWT token
    token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
    return token

def verify_token(token):
    """Verify the JWT token"""
    try:
        ## Decode and verify the token
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload
    except jwt.ExpiredSignatureError:
        ## Token has expired
        return None
    except jwt.InvalidTokenError:
        ## Invalid token
        return None

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()

    ## Extract username and password from the request
    username = data.get('username')
    password = data.get('password')

    ## Check if the user exists and the password is correct
    if username in users and users[username] == password:
        ## Generate a JWT token
        token = generate_token(username)

        return jsonify({
            "status": "success",
            "message": f"Welcome {username}!",
            "token": token
        })
    else:
        return jsonify({"status": "error", "message": "Invalid credentials"}), 401

@app.route('/protected', methods=['GET'])
def protected():
    ## Get the token from the Authorization header
    auth_header = request.headers.get('Authorization')

    if not auth_header or not auth_header.startswith('Bearer '):
        return jsonify({"status": "error", "message": "Missing or invalid token"}), 401

    ## Extract the token
    token = auth_header.split(' ')[1]

    ## Verify the token
    payload = verify_token(token)

    if payload:
        return jsonify({
            "status": "success",
            "message": f"You are viewing protected content, {payload['username']}!"
        })
    else:
        return jsonify({"status": "error", "message": "Invalid or expired token"}), 401

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

이 업데이트된 서버는 다음과 같습니다.

  • 성공적인 인증 시 JWT 토큰을 생성합니다.
  • 보호된 리소스에 대한 토큰의 유효성을 검사합니다.
  • 로그인 및 보호된 콘텐츠 접근을 위한 엔드포인트를 갖습니다.

토큰 기반 인증을 위한 클라이언트 업데이트

이제 JWT 토큰을 사용하도록 클라이언트를 업데이트해 보겠습니다. client.py 파일을 업데이트합니다.

code ~/project/auth_demo/client.py

기존 코드를 다음으로 바꿉니다.

import requests
import json

## Server URL
BASE_URL = "http://localhost:5000"

def login(username, password):
    """Authenticate with the server using username and password"""
    response = requests.post(
        f"{BASE_URL}/login",
        json={"username": username, "password": password}
    )

    print(f"Login response: {response.status_code}")
    data = response.json()
    print(data)

    ## Return the token if login was successful
    return data.get('token') if response.status_code == 200 else None

def access_protected(token=None):
    """Access a protected resource using JWT token"""
    headers = {}
    if token:
        headers['Authorization'] = f'Bearer {token}'

    response = requests.get(f"{BASE_URL}/protected", headers=headers)

    print(f"Protected resource response: {response.status_code}")
    print(response.json())

    return response

if __name__ == "__main__":
    ## Test with valid credentials
    print("=== Authenticating with valid credentials ===")
    token = login("alice", "password123")

    if token:
        print("\n=== Accessing protected resource with valid token ===")
        access_protected(token)

    ## Test with invalid credentials
    print("\n=== Authenticating with invalid credentials ===")
    login("alice", "wrongpassword")

    ## Try to access protected resource without token
    print("\n=== Accessing protected resource without token ===")
    access_protected()

    ## Try to access protected resource with invalid token
    print("\n=== Accessing protected resource with invalid token ===")
    access_protected("invalid.token.value")

이 업데이트된 클라이언트는 다음과 같습니다.

  • 성공적인 인증 후 JWT 토큰을 검색하고 저장합니다.
  • 보호된 리소스에 접근하기 위해 Authorization 헤더에 토큰을 보냅니다.
  • 유효 및 무효 인증 시도를 포함한 다양한 시나리오를 테스트합니다.

토큰 기반 인증 시스템 실행

이제 업데이트된 토큰 기반 인증 시스템을 실행해 보겠습니다. 먼저, Ctrl+C를 사용하여 실행 중인 모든 서버 프로세스를 중지한 다음 업데이트된 서버를 시작합니다.

python ~/project/auth_demo/server.py

새 터미널 탭을 열거나 기존 두 번째 탭을 사용하여 클라이언트를 실행합니다.

cd ~/project/auth_demo
python client.py

다음과 같은 출력을 볼 수 있습니다.

  1. 유효한 자격 증명으로 성공적인 로그인, JWT 토큰 수신
  2. 유효한 토큰으로 보호된 리소스에 대한 접근
  3. 잘못된 자격 증명으로 로그인 시도 실패
  4. 토큰 없이 보호된 리소스에 접근하려는 시도 실패
  5. 잘못된 토큰으로 보호된 리소스에 접근하려는 시도 실패

이 출력은 토큰 기반 인증 시스템이 올바르게 작동하고 있음을 확인합니다.

토큰 기반 인증의 장점

토큰 기반 인증은 세션 기반 인증보다 몇 가지 장점을 제공합니다.

  1. Stateless (상태 비저장): 서버는 세션 정보를 저장할 필요가 없습니다.
  2. Scalability (확장성): 여러 서버가 있는 분산 환경에서 잘 작동합니다.
  3. Mobile-friendly (모바일 친화적): 쿠키가 제대로 작동하지 않을 수 있는 모바일 애플리케이션에 적합합니다.
  4. Cross-domain (크로스 도메인): 서로 다른 도메인에서 쉽게 사용할 수 있습니다.
  5. Security (보안): 토큰은 만료되도록 구성할 수 있어 세션 하이재킹 위험을 줄입니다.

실제 애플리케이션에서는 토큰 갱신, 역할 기반 접근 제어 및 안전한 토큰 저장과 같은 기능으로 인증 시스템을 더욱 향상시킬 수 있습니다.

비밀번호 해싱으로 보안 강화

이전 구현에서는 비밀번호를 일반 텍스트로 저장했는데, 이는 심각한 보안 위험입니다. 이 단계에서는 bcrypt 알고리즘을 사용하여 비밀번호 해싱을 구현하여 인증 시스템을 강화합니다.

왜 비밀번호를 해싱해야 할까요?

비밀번호를 일반 텍스트로 저장하면 다음과 같은 여러 가지 보안 위험이 발생합니다.

  • 데이터베이스가 손상되면 공격자는 즉시 비밀번호를 사용할 수 있습니다.
  • 사용자는 여러 사이트에서 비밀번호를 재사용하는 경우가 많으므로 침해는 다른 서비스에 영향을 미칠 수 있습니다.
  • 보안 모범 사례를 위반하며 데이터 보호 규정을 위반할 수 있습니다.

비밀번호 해싱은 다음을 통해 이러한 문제를 해결합니다.

  • 비밀번호를 되돌릴 수 없는 고정 길이 문자열로 변환합니다.
  • 공격자가 미리 계산된 조회 테이블 (레인보우 테이블) 을 사용하는 것을 방지하기 위해 "솔트"를 추가합니다.
  • 두 사용자가 동일한 비밀번호를 가지고 있더라도 해시가 다르게 되도록 합니다.

필수 패키지 설치

먼저, bcrypt 패키지를 설치해야 합니다.

pip install bcrypt

설치를 확인하는 출력을 볼 수 있습니다.

Collecting bcrypt
  Downloading bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl (593 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 593.8/593.8 KB 10.5 MB/s eta 0:00:00
Installing collected packages: bcrypt
Successfully installed bcrypt-4.0.1

사용자 등록 시스템 생성

비밀번호 해싱을 포함하는 사용자 등록을 포함하도록 서버를 강화해 보겠습니다. server.py 파일을 업데이트합니다.

code ~/project/auth_demo/server.py

기존 코드를 다음으로 바꿉니다.

from flask import Flask, request, jsonify
import jwt
import datetime
import os
import bcrypt

app = Flask(__name__)
## Secret key for signing JWT tokens
SECRET_KEY = os.urandom(24)

## Store users as a dictionary with username as key and a dict of hashed password and roles as value
users = {}

def hash_password(password):
    """Hash a password using bcrypt"""
    ## Convert password to bytes if it's a string
    if isinstance(password, str):
        password = password.encode('utf-8')

    ## Generate a salt and hash the password
    salt = bcrypt.gensalt()
    hashed = bcrypt.hashpw(password, salt)

    return hashed

def check_password(password, hashed):
    """Verify a password against its hash"""
    ## Convert password to bytes if it's a string
    if isinstance(password, str):
        password = password.encode('utf-8')

    ## Check if the password matches the hash
    return bcrypt.checkpw(password, hashed)

def generate_token(username, role):
    """Generate a JWT token for the authenticated user"""
    ## Token expires after 30 minutes
    expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=30)

    payload = {
        'username': username,
        'role': role,
        'exp': expiration
    }

    ## Create the JWT token
    token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
    return token

def verify_token(token):
    """Verify the JWT token"""
    try:
        ## Decode and verify the token
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload
    except jwt.ExpiredSignatureError:
        ## Token has expired
        return None
    except jwt.InvalidTokenError:
        ## Invalid token
        return None

@app.route('/register', methods=['POST'])
def register():
    data = request.get_json()

    ## Extract registration data
    username = data.get('username')
    password = data.get('password')

    ## Basic validation
    if not username or not password:
        return jsonify({"status": "error", "message": "Username and password are required"}), 400

    ## Check if the username already exists
    if username in users:
        return jsonify({"status": "error", "message": "Username already exists"}), 409

    ## Hash the password and store the user
    hashed_password = hash_password(password)
    users[username] = {
        'password': hashed_password,
        'role': 'user'  ## Default role
    }

    return jsonify({
        "status": "success",
        "message": f"User {username} registered successfully"
    })

@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()

    ## Extract username and password from the request
    username = data.get('username')
    password = data.get('password')

    ## Check if the user exists
    if username not in users:
        return jsonify({"status": "error", "message": "Invalid credentials"}), 401

    ## Check if the password is correct
    if check_password(password, users[username]['password']):
        ## Generate a JWT token
        token = generate_token(username, users[username]['role'])

        return jsonify({
            "status": "success",
            "message": f"Welcome {username}!",
            "token": token
        })
    else:
        return jsonify({"status": "error", "message": "Invalid credentials"}), 401

@app.route('/protected', methods=['GET'])
def protected():
    ## Get the token from the Authorization header
    auth_header = request.headers.get('Authorization')

    if not auth_header or not auth_header.startswith('Bearer '):
        return jsonify({"status": "error", "message": "Missing or invalid token"}), 401

    ## Extract the token
    token = auth_header.split(' ')[1]

    ## Verify the token
    payload = verify_token(token)

    if payload:
        return jsonify({
            "status": "success",
            "message": f"You are viewing protected content, {payload['username']}!",
            "role": payload['role']
        })
    else:
        return jsonify({"status": "error", "message": "Invalid or expired token"}), 401

@app.route('/admin', methods=['GET'])
def admin():
    ## Get the token from the Authorization header
    auth_header = request.headers.get('Authorization')

    if not auth_header or not auth_header.startswith('Bearer '):
        return jsonify({"status": "error", "message": "Missing or invalid token"}), 401

    ## Extract the token
    token = auth_header.split(' ')[1]

    ## Verify the token
    payload = verify_token(token)

    if not payload:
        return jsonify({"status": "error", "message": "Invalid or expired token"}), 401

    ## Check if user has admin role
    if payload['role'] != 'admin':
        return jsonify({"status": "error", "message": "Admin access required"}), 403

    return jsonify({
        "status": "success",
        "message": f"Welcome to the admin panel, {payload['username']}!"
    })

## Add an admin user for testing
admin_password = hash_password("admin123")
users["admin"] = {
    'password': admin_password,
    'role': 'admin'
}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

이 향상된 서버는 다음과 같습니다.

  • 보안 비밀번호 해싱을 위해 bcrypt 를 사용합니다.
  • 사용자 등록 엔드포인트를 포함합니다.
  • 역할 기반 접근 제어 (관리자 대 일반 사용자) 를 추가합니다.
  • 테스트를 위해 관리자 사용자를 미리 생성합니다.

클라이언트 업데이트

이제 이러한 새로운 기능을 테스트하기 위해 클라이언트를 업데이트해 보겠습니다. client.py 파일을 업데이트합니다.

code ~/project/auth_demo/client.py

기존 코드를 다음으로 바꿉니다.

import requests
import json

## Server URL
BASE_URL = "http://localhost:5000"

def register(username, password):
    """Register a new user"""
    response = requests.post(
        f"{BASE_URL}/register",
        json={"username": username, "password": password}
    )

    print(f"Registration response: {response.status_code}")
    print(response.json())

    return response.status_code == 200

def login(username, password):
    """Authenticate with the server using username and password"""
    response = requests.post(
        f"{BASE_URL}/login",
        json={"username": username, "password": password}
    )

    print(f"Login response: {response.status_code}")
    data = response.json()
    print(data)

    ## Return the token if login was successful
    return data.get('token') if response.status_code == 200 else None

def access_protected(token=None):
    """Access a protected resource using JWT token"""
    headers = {}
    if token:
        headers['Authorization'] = f'Bearer {token}'

    response = requests.get(f"{BASE_URL}/protected", headers=headers)

    print(f"Protected resource response: {response.status_code}")
    print(response.json())

    return response

def access_admin(token=None):
    """Access the admin panel using JWT token"""
    headers = {}
    if token:
        headers['Authorization'] = f'Bearer {token}'

    response = requests.get(f"{BASE_URL}/admin", headers=headers)

    print(f"Admin panel response: {response.status_code}")
    print(response.json())

    return response

if __name__ == "__main__":
    ## Test user registration
    print("=== Registering a new user ===")
    register("testuser", "testpass123")

    ## Try to register with the same username (should fail)
    print("\n=== Registering with existing username ===")
    register("testuser", "differentpass")

    ## Test regular user login and access
    print("\n=== Regular user login ===")
    user_token = login("testuser", "testpass123")

    if user_token:
        print("\n=== Regular user accessing protected resource ===")
        access_protected(user_token)

        print("\n=== Regular user trying to access admin panel ===")
        access_admin(user_token)

    ## Test admin login and access
    print("\n=== Admin login ===")
    admin_token = login("admin", "admin123")

    if admin_token:
        print("\n=== Admin accessing protected resource ===")
        access_protected(admin_token)

        print("\n=== Admin accessing admin panel ===")
        access_admin(admin_token)

이 업데이트된 클라이언트는 다음과 같습니다.

  • 사용자 등록 기능을 테스트합니다.
  • 역할 기반 접근 제어를 보여줍니다.
  • 일반 사용자 및 관리자 인증을 모두 테스트합니다.

향상된 인증 시스템 실행

이제 향상된 인증 시스템을 실행해 보겠습니다. 먼저, Ctrl+C를 사용하여 실행 중인 모든 서버 프로세스를 중지한 다음 업데이트된 서버를 시작합니다.

python ~/project/auth_demo/server.py

새 터미널 탭을 열거나 기존 두 번째 탭을 사용하여 클라이언트를 실행합니다.

cd ~/project/auth_demo
python client.py

다음과 같은 출력을 볼 수 있습니다.

  1. 성공적인 사용자 등록
  2. 기존 사용자 이름으로 등록 실패
  3. 일반 사용자 로그인 및 보호된 리소스에 대한 접근
  4. 일반 사용자가 관리자 패널에 접근하려는 시도 실패
  5. 관리자 로그인 및 보호된 리소스와 관리자 패널 모두에 대한 접근

이 출력은 비밀번호 해싱 및 역할 기반 접근 제어가 있는 향상된 인증 시스템이 올바르게 작동하고 있음을 확인합니다.

보안상의 이점

이제 향상된 시스템은 다음을 제공합니다.

  1. Secure Password Storage (안전한 비밀번호 저장): 비밀번호는 무차별 대입 공격을 방지하도록 설계된 느린 해싱 함수인 bcrypt 를 사용하여 해시됩니다.
  2. Role-Based Access Control (역할 기반 접근 제어): 서로 다른 사용자는 역할에 따라 서로 다른 접근 수준을 가질 수 있습니다.
  3. Token-Based Authentication (토큰 기반 인증): 이전에 구현한 JWT 토큰의 이점을 계속 제공합니다.
  4. User Registration (사용자 등록): 안전한 비밀번호 저장으로 동적 사용자 생성을 허용합니다.

이러한 향상으로 인증 시스템이 훨씬 더 강력해지고 실제 애플리케이션에 적합해집니다.

요약

이 랩에서는 기본적인 개념부터 고급 기술까지, Python 클라이언트 - 서버 시스템에서 인증을 구현하는 방법을 배웠습니다. 다음을 성공적으로 수행했습니다.

  1. 클라이언트 - 서버 시스템에서 인증의 기본 개념을 이해했습니다.
  2. Flask 세션을 사용하여 기본적인 사용자 이름 및 비밀번호 인증을 구현했습니다.
  3. JWT 를 사용하여 토큰 기반 인증으로 보안을 강화했습니다.
  4. bcrypt 를 사용하여 안전한 비밀번호 해싱을 추가했습니다.
  5. 서로 다른 사용자 유형에 대한 역할 기반 접근 제어를 구현했습니다.

이러한 기술은 안전한 Python 애플리케이션을 구축하기 위한 견고한 기반을 제공합니다. 인증은 애플리케이션 보안의 한 측면일 뿐이며, 프로덕션 시스템에서는 다음 사항도 고려해야 합니다.

  • 모든 통신에 HTTPS 를 사용합니다.
  • 무차별 대입 공격을 방지하기 위해 속도 제한을 구현합니다.
  • 의심스러운 활동에 대한 로깅 및 모니터링을 추가합니다.
  • 보안 취약점을 해결하기 위해 모든 종속성을 최신 상태로 유지합니다.

이러한 원칙을 적용하여 사용자 데이터를 효과적으로 보호하고 안전한 운영을 유지하는 Python 애플리케이션을 구축할 수 있습니다.