Повышение безопасности с помощью хэширования паролей
В наших предыдущих реализациях мы хранили пароли в виде обычного текста, что представляет собой значительный риск для безопасности. На этом этапе мы улучшим нашу систему аутентификации, внедрив хэширование паролей с использованием алгоритма bcrypt.
Зачем хэшировать пароли?
Хранение паролей в виде обычного текста создает несколько рисков для безопасности:
- Если ваша база данных взломана, злоумышленники могут немедленно использовать пароли
- Пользователи часто повторно используют пароли на нескольких сайтах, поэтому взлом может повлиять на другие сервисы
- Это нарушает лучшие практики безопасности и может нарушать правила защиты данных
Хэширование паролей решает эти проблемы путем:
- Преобразования паролей в строки фиксированной длины, которые нельзя обратить
- Добавления «соли» (salt), чтобы помешать злоумышленникам использовать предварительно вычисленные таблицы подстановки (радужные таблицы)
- Обеспечения того, чтобы даже если у двух пользователей один и тот же пароль, их хэши будут разными
Установка необходимых пакетов
Сначала нам нужно установить пакет 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
Вы должны увидеть вывод, демонстрирующий:
- Успешную регистрацию пользователя
- Неудачную регистрацию с существующим именем пользователя
- Вход обычного пользователя в систему и доступ к защищенным ресурсам
- Неудачную попытку обычного пользователя получить доступ к панели администратора
- Вход администратора в систему и доступ как к защищенным ресурсам, так и к панели администратора
Этот вывод подтверждает, что наша улучшенная система аутентификации с хэшированием паролей и контролем доступа на основе ролей работает правильно.
Преимущества безопасности
Наша улучшенная система теперь предоставляет:
- Безопасное хранение паролей: Пароли хэшируются с использованием bcrypt, медленной хэш-функции, разработанной для защиты от атак методом перебора.
- Контроль доступа на основе ролей: Разные пользователи могут иметь разные уровни доступа в зависимости от их ролей.
- Аутентификация на основе токенов: Продолжает предоставлять преимущества токенов JWT, которые мы реализовали ранее.
- Регистрация пользователей: Позволяет динамически создавать пользователей с безопасным хранением паролей.
Эти улучшения делают нашу систему аутентификации гораздо более надежной и подходящей для реальных приложений.