Hello\"\nsanitized = sanitize_user_input(input_text)\nprint(sanitized) # \"Hello\"\n\n# Для SQL: используй параметризованные запросы (никогда f-string!)\nfrom sqlalchemy import text\n\n# ПРАВИЛЬНО\nuser = session.execute(\n text(\"SELECT * FROM users WHERE email = :email\"),\n {\"email\": user_email}\n).first()\n\n# НЕПРАВИЛЬНО — SQL injection!\nuser = session.execute(\n f\"SELECT * FROM users WHERE email = '{user_email}'\"\n).first()\n```\n\n### Способ 6: Валидация файлов\n\n```python\nimport os\nfrom pathlib import Path\n\ndef validate_file_upload(file, max_size_mb: int = 10, allowed_extensions: list = None):\n \"\"\"Валидация загруженного файла\"\"\"\n if allowed_extensions is None:\n allowed_extensions = ['jpg', 'png', 'pdf']\n \n # Проверка расширения\n file_ext = Path(file.filename).suffix.lower()\n if file_ext not in [f'.{ext}' for ext in allowed_extensions]:\n raise ValueError(f'Расширение {file_ext} не допускается')\n \n # Проверка размера\n file.seek(0, os.SEEK_END)\n file_size = file.tell()\n if file_size > max_size_mb * 1024 * 1024:\n raise ValueError(f'Файл слишком большой (макс {max_size_mb}MB)')\n \n # Проверка магических чисел (реальный тип файла)\n file.seek(0)\n file_header = file.read(8)\n magic_numbers = {\n b'\\xFF\\xD8\\xFF': 'jpg',\n b'\\x89PNG': 'png',\n b'%PDF': 'pdf'\n }\n \n detected_type = None\n for magic, ftype in magic_numbers.items():\n if file_header.startswith(magic):\n detected_type = ftype\n break\n \n if detected_type not in allowed_extensions:\n raise ValueError('Файл имеет другой тип, чем указанное расширение')\n \n file.seek(0)\n return True\n```\n\n### Способ 7: JSON Schema валидация\n\n```python\nfrom jsonschema import validate, ValidationError\n\nschema = {\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\"type\": \"string\", \"minLength\": 1, \"maxLength\": 100},\n \"age\": {\"type\": \"integer\", \"minimum\": 0, \"maximum\": 150},\n \"email\": {\"type\": \"string\", \"format\": \"email\"},\n \"tags\": {\n \"type\": \"array\",\n \"items\": {\"type\": \"string\"},\n \"maxItems\": 10\n }\n },\n \"required\": [\"name\", \"email\"]\n}\n\ndata = {\n \"name\": \"John\",\n \"age\": 30,\n \"email\": \"john@example.com\",\n \"tags\": [\"python\", \"web\"]\n}\n\ntry:\n validate(instance=data, schema=schema)\n print(\"Данные валидны\")\nexcept ValidationError as e:\n print(f\"Ошибка: {e.message}\")\n```\n\n### Чеклист валидации\n\n- [ ] **Тип данных** — число, строка, boolean\n- [ ] **Длина** — минимум/максимум символов или элементов\n- [ ] **Формат** — email, URL, phone, дата\n- [ ] **Диапазон** — от/до для чисел\n- [ ] **Обязательность** — required или optional\n- [ ] **Кастомные правила** — по бизнес-логике\n- [ ] **Санитизация** — удалить опасный код\n- [ ] **Rate limiting** — защита от brute-force\n- [ ] **HTTPS** — передача данных в шифрованном виде\n\n### Итог\n\n**Используй Pydantic (FastAPI)** — это лучший выбор для современных Python приложений. Она обеспечивает:\n- Автоматическую валидацию\n- Кастомные валидаторы\n- Отличные сообщения об ошибках\n- Типизацию\n- Документацию API (OpenAPI)\n\n**Никогда не доверяй пользовательским данным. Валидируй ВСЁ на сервере.**","dateCreated":"2026-03-22T16:01:57.012451","upvoteCount":0,"author":{"@type":"Person","name":"claude-haiku-4.5"}}}}
← Назад к вопросам

Как валидировать значение полученное от пользователя?

1.3 Junior🔥 241 комментариев
#REST API и HTTP#Безопасность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Валидация пользовательского ввода в Python

Валидация — это критически важная часть безопасности приложения. Я расскажу про все основные техники, от простых до продвинутых.

Принцип: "Trust nothing from users"

Никогда не доверяй пользовательским данным. Валидируй ВСЁ — на сервере, независимо от клиентской валидации.

Способ 1: Ручная валидация (базовый уровень)

def validate_email(email: str) -> bool:
    """Простая проверка email"""
    if not email or not isinstance(email, str):
        return False
    
    if len(email) > 255:  # Слишком длинный
        return False
    
    if '@' not in email:
        return False
    
    parts = email.split('@')
    if len(parts) != 2:
        return False
    
    local, domain = parts
    if not local or not domain:
        return False
    
    return True

# Использование
if validate_email(user_input):
    print("Email валиден")
else:
    print("Некорректный email")

Проблема: громоздко, легко ошибиться, недостаточно строго.

Способ 2: Регулярные выражения

import re

def validate_email_regex(email: str) -> bool:
    """Email валидация через regex"""
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))

def validate_phone(phone: str) -> bool:
    """Номер телефона: только цифры и +"""
    pattern = r'^\+?[0-9\s\-()]{10,}$'
    return bool(re.match(pattern, phone))

def validate_username(username: str) -> bool:
    """Username: букв, цифр, подчеркивание, дефис"""
    pattern = r'^[a-zA-Z0-9_-]{3,20}$'
    return bool(re.match(pattern, username))

# Использование
if validate_email_regex(user_email):
    print("Email валиден")

Проблема: regex сложно читать и поддерживать. Email валидация на самом деле очень сложная.

Способ 3: Специализированные библиотеки (рекомендуется)

Pydantic — лучший выбор для валидации:

from pydantic import BaseModel, EmailStr, Field, validator, conint
from typing import Optional
from datetime import datetime

class UserCreate(BaseModel):
    # Основные типы с автоматической валидацией
    email: EmailStr  # Проверяет что это валидный email
    username: str = Field(..., min_length=3, max_length=20, regex=r'^[a-zA-Z0-9_-]+$')
    age: conint(ge=0, le=150)  # Число от 0 до 150
    password: str = Field(..., min_length=8)
    phone: Optional[str] = None
    is_premium: bool = False
    
    @validator('password')
    def password_strong(cls, v):
        """Кастомная валидация пароля"""
        if not any(char.isupper() for char in v):
            raise ValueError('Пароль должен содержать заглавную букву')
        if not any(char.isdigit() for char in v):
            raise ValueError('Пароль должен содержать цифру')
        return v
    
    @validator('username')
    def username_not_reserved(cls, v):
        """Запретить зарезервированные имена"""
        reserved = ['admin', 'root', 'system', 'api']
        if v.lower() in reserved:
            raise ValueError(f'Username {v} зарезервирован')
        return v

# Использование в FastAPI (автоматически)
from fastapi import FastAPI

app = FastAPI()

@app.post('/register')
async def register(user: UserCreate):
    # user автоматически валидирован и типизирован
    # Если валидация не пройдёт, FastAPI вернёт 422 с ошибками
    return {"email": user.email, "username": user.username}

# Ручное использование
try:
    user_data = UserCreate(
        email="user@example.com",
        username="john_doe",
        age=25,
        password="SecurePass123",
        phone="+79991234567"
    )
    print(f"Пользователь создан: {user_data}")
except ValueError as e:
    print(f"Ошибка валидации: {e}")

Способ 4: Flask с WTForms

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, IntegerField
from wtforms.validators import DataRequired, Length, Email, NumberRange, EqualTo

class RegistrationForm(FlaskForm):
    email = StringField('Email', validators=[
        DataRequired(),
        Email(message='Некорректный email')
    ])
    username = StringField('Username', validators=[
        DataRequired(),
        Length(min=3, max=20)
    ])
    age = IntegerField('Age', validators=[
        DataRequired(),
        NumberRange(min=0, max=150)
    ])
    password = PasswordField('Password', validators=[
        DataRequired(),
        Length(min=8)
    ])
    confirm = PasswordField('Confirm Password', validators=[
        DataRequired(),
        EqualTo('password')
    ])

@app.route('/register', methods=['POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        # Данные валидированы
        user = create_user(form.email.data, form.username.data)
        return redirect('/success')
    else:
        # form.errors содержит ошибки
        return render_template('register.html', form=form)

Способ 5: Санитизация (дополнение к валидации)

import bleach
from html import escape

def sanitize_user_input(text: str) -> str:
    """Удалить опасный HTML и скрипты"""
    # Способ 1: удалить HTML теги полностью
    cleaned = bleach.clean(text, tags=[], strip=True)
    return cleaned

def sanitize_html_safe(text: str) -> str:
    """Оставить безопасный HTML"""
    allowed_tags = ['b', 'i', 'u', 'em', 'strong', 'a', 'p', 'br']
    allowed_attrs = {'a': ['href']}
    return bleach.clean(text, tags=allowed_tags, attributes=allowed_attrs)

# Примеры
input_text = "<script>alert('XSS')</script>Hello"
sanitized = sanitize_user_input(input_text)
print(sanitized)  # "Hello"

# Для SQL: используй параметризованные запросы (никогда f-string!)
from sqlalchemy import text

# ПРАВИЛЬНО
user = session.execute(
    text("SELECT * FROM users WHERE email = :email"),
    {"email": user_email}
).first()

# НЕПРАВИЛЬНО — SQL injection!
user = session.execute(
    f"SELECT * FROM users WHERE email = '{user_email}'"
).first()

Способ 6: Валидация файлов

import os
from pathlib import Path

def validate_file_upload(file, max_size_mb: int = 10, allowed_extensions: list = None):
    """Валидация загруженного файла"""
    if allowed_extensions is None:
        allowed_extensions = ['jpg', 'png', 'pdf']
    
    # Проверка расширения
    file_ext = Path(file.filename).suffix.lower()
    if file_ext not in [f'.{ext}' for ext in allowed_extensions]:
        raise ValueError(f'Расширение {file_ext} не допускается')
    
    # Проверка размера
    file.seek(0, os.SEEK_END)
    file_size = file.tell()
    if file_size > max_size_mb * 1024 * 1024:
        raise ValueError(f'Файл слишком большой (макс {max_size_mb}MB)')
    
    # Проверка магических чисел (реальный тип файла)
    file.seek(0)
    file_header = file.read(8)
    magic_numbers = {
        b'\xFF\xD8\xFF': 'jpg',
        b'\x89PNG': 'png',
        b'%PDF': 'pdf'
    }
    
    detected_type = None
    for magic, ftype in magic_numbers.items():
        if file_header.startswith(magic):
            detected_type = ftype
            break
    
    if detected_type not in allowed_extensions:
        raise ValueError('Файл имеет другой тип, чем указанное расширение')
    
    file.seek(0)
    return True

Способ 7: JSON Schema валидация

from jsonschema import validate, ValidationError

schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string", "minLength": 1, "maxLength": 100},
        "age": {"type": "integer", "minimum": 0, "maximum": 150},
        "email": {"type": "string", "format": "email"},
        "tags": {
            "type": "array",
            "items": {"type": "string"},
            "maxItems": 10
        }
    },
    "required": ["name", "email"]
}

data = {
    "name": "John",
    "age": 30,
    "email": "john@example.com",
    "tags": ["python", "web"]
}

try:
    validate(instance=data, schema=schema)
    print("Данные валидны")
except ValidationError as e:
    print(f"Ошибка: {e.message}")

Чеклист валидации

  • Тип данных — число, строка, boolean
  • Длина — минимум/максимум символов или элементов
  • Формат — email, URL, phone, дата
  • Диапазон — от/до для чисел
  • Обязательность — required или optional
  • Кастомные правила — по бизнес-логике
  • Санитизация — удалить опасный код
  • Rate limiting — защита от brute-force
  • HTTPS — передача данных в шифрованном виде

Итог

Используй Pydantic (FastAPI) — это лучший выбор для современных Python приложений. Она обеспечивает:

  • Автоматическую валидацию
  • Кастомные валидаторы
  • Отличные сообщения об ошибках
  • Типизацию
  • Документацию API (OpenAPI)

Никогда не доверяй пользовательским данным. Валидируй ВСЁ на сервере.

Как валидировать значение полученное от пользователя? | PrepBro