소개
안전한 인증 구현은 강력한 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 ...
인증 개념
구현에 들어가기 전에 기본적인 인증 개념을 이해해 보겠습니다.
사용자 이름 및 비밀번호 인증: 사용자가 신원을 확인하기 위해 자격 증명을 제공하는 가장 일반적인 형태입니다.
토큰 기반 인증: 성공적인 로그인 후, 서버는 클라이언트가 후속 요청에 사용할 토큰을 생성하여 매번 자격 증명을 보낼 필요가 없도록 합니다.
세션 기반 인증: 서버는 성공적인 로그인 후 세션 정보를 저장하고 클라이언트에게 세션 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 클라이언트를 사용하여 인증합니다.
기본적인 사용자 이름 및 비밀번호 인증 구현
이 단계에서는 사용자 이름과 비밀번호를 사용하는 기본적인 인증 시스템을 구현합니다. 다음을 생성합니다.
- 인증을 처리하는 Flask 서버
- 서버로 인증하는 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
다음과 같은 출력을 볼 수 있습니다.
- 유효한 자격 증명으로 성공적인 로그인
- 유효한 인증으로 보호된 리소스에 대한 접근
- 성공적인 로그아웃
- 잘못된 자격 증명으로 로그인 시도 실패
- 인증 없이 보호된 리소스에 접근하려는 시도 실패
이 출력은 기본적인 사용자 이름 및 비밀번호 인증 시스템이 올바르게 작동하고 있음을 확인합니다.
다음 단계에서는 보안을 개선하기 위해 토큰 기반 인증을 구현하여 이 시스템을 개선할 것입니다.
토큰 기반 인증 구현
이전 단계에서는 세션을 사용하여 기본적인 인증 시스템을 만들었습니다. 이는 간단한 애플리케이션에 적합하지만, 토큰 기반 인증은 특히 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
다음과 같은 출력을 볼 수 있습니다.
- 유효한 자격 증명으로 성공적인 로그인, JWT 토큰 수신
- 유효한 토큰으로 보호된 리소스에 대한 접근
- 잘못된 자격 증명으로 로그인 시도 실패
- 토큰 없이 보호된 리소스에 접근하려는 시도 실패
- 잘못된 토큰으로 보호된 리소스에 접근하려는 시도 실패
이 출력은 토큰 기반 인증 시스템이 올바르게 작동하고 있음을 확인합니다.
토큰 기반 인증의 장점
토큰 기반 인증은 세션 기반 인증보다 몇 가지 장점을 제공합니다.
- Stateless (상태 비저장): 서버는 세션 정보를 저장할 필요가 없습니다.
- Scalability (확장성): 여러 서버가 있는 분산 환경에서 잘 작동합니다.
- Mobile-friendly (모바일 친화적): 쿠키가 제대로 작동하지 않을 수 있는 모바일 애플리케이션에 적합합니다.
- Cross-domain (크로스 도메인): 서로 다른 도메인에서 쉽게 사용할 수 있습니다.
- 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
다음과 같은 출력을 볼 수 있습니다.
- 성공적인 사용자 등록
- 기존 사용자 이름으로 등록 실패
- 일반 사용자 로그인 및 보호된 리소스에 대한 접근
- 일반 사용자가 관리자 패널에 접근하려는 시도 실패
- 관리자 로그인 및 보호된 리소스와 관리자 패널 모두에 대한 접근
이 출력은 비밀번호 해싱 및 역할 기반 접근 제어가 있는 향상된 인증 시스템이 올바르게 작동하고 있음을 확인합니다.
보안상의 이점
이제 향상된 시스템은 다음을 제공합니다.
- Secure Password Storage (안전한 비밀번호 저장): 비밀번호는 무차별 대입 공격을 방지하도록 설계된 느린 해싱 함수인 bcrypt 를 사용하여 해시됩니다.
- Role-Based Access Control (역할 기반 접근 제어): 서로 다른 사용자는 역할에 따라 서로 다른 접근 수준을 가질 수 있습니다.
- Token-Based Authentication (토큰 기반 인증): 이전에 구현한 JWT 토큰의 이점을 계속 제공합니다.
- User Registration (사용자 등록): 안전한 비밀번호 저장으로 동적 사용자 생성을 허용합니다.
이러한 향상으로 인증 시스템이 훨씬 더 강력해지고 실제 애플리케이션에 적합해집니다.
요약
이 랩에서는 기본적인 개념부터 고급 기술까지, Python 클라이언트 - 서버 시스템에서 인증을 구현하는 방법을 배웠습니다. 다음을 성공적으로 수행했습니다.
- 클라이언트 - 서버 시스템에서 인증의 기본 개념을 이해했습니다.
- Flask 세션을 사용하여 기본적인 사용자 이름 및 비밀번호 인증을 구현했습니다.
- JWT 를 사용하여 토큰 기반 인증으로 보안을 강화했습니다.
- bcrypt 를 사용하여 안전한 비밀번호 해싱을 추가했습니다.
- 서로 다른 사용자 유형에 대한 역할 기반 접근 제어를 구현했습니다.
이러한 기술은 안전한 Python 애플리케이션을 구축하기 위한 견고한 기반을 제공합니다. 인증은 애플리케이션 보안의 한 측면일 뿐이며, 프로덕션 시스템에서는 다음 사항도 고려해야 합니다.
- 모든 통신에 HTTPS 를 사용합니다.
- 무차별 대입 공격을 방지하기 위해 속도 제한을 구현합니다.
- 의심스러운 활동에 대한 로깅 및 모니터링을 추가합니다.
- 보안 취약점을 해결하기 위해 모든 종속성을 최신 상태로 유지합니다.
이러한 원칙을 적용하여 사용자 데이터를 효과적으로 보호하고 안전한 운영을 유지하는 Python 애플리케이션을 구축할 수 있습니다.



