ORM e SQLAlchemy no Flask: Teoria e Boas Práticas de Segurança
Trabalho de Engenharia de Software
Nome: João Vitor Medeiros Araujo 3°C
1. Pesquisa Teórica
1.1 O que é ORM e qual seu objetivo?
ORM (Object-Relational Mapping, ou Mapeamento Objeto-Relacional) é uma técnica de programação que
faz a ponte entre o paradigma orientado a objetos, usado nas linguagens de programação, e o modelo
relacional, usado pelos bancos de dados. Na prática, uma biblioteca ORM permite representar tabelas do
banco como classes e linhas como instâncias (objetos) dessas classes, de modo que o desenvolvedor
manipula dados por meio de atributos e métodos em vez de escrever comandos SQL diretamente.
O objetivo principal do ORM é abstrair a camada de persistência: o código da aplicação passa a interagir
com objetos Python (ou de outra linguagem), enquanto o próprio ORM se encarrega de gerar e executar as
instruções SQL correspondentes (SELECT, INSERT, UPDATE, DELETE) no banco de dados.
1.2 Vantagens e desvantagens do uso de ORM
Vantagens:
● Produtividade e legibilidade: o desenvolvedor escreve consultas em código orientado a objetos
(ex.: [Link].filter_by(email=email).first()), o que costuma ser mais legível e rápido de
escrever do que SQL puro, além de facilitar a manutenção do código.
● Portabilidade e segurança: o ORM abstrai as diferenças entre bancos de dados (SQLite, PostgreSQL,
MySQL etc.), facilitando a troca do banco sem reescrever consultas, e por padrão utiliza consultas
parametrizadas, o que reduz o risco de SQL Injection.
Desvantagens:
● Desempenho: em consultas muito complexas ou que envolvem grandes volumes de dados, o SQL
gerado automaticamente pode ser menos eficiente do que uma consulta SQL escrita e otimizada
manualmente por um especialista.
● Curva de aprendizado e "abstração vazante": entender bem um ORM (relacionamentos,
carregamento de dados relacionados, transações) exige tempo, e em alguns casos é necessário
conhecer o SQL gerado por trás para depurar problemas de performance ou comportamento
inesperado.
1.3 O que é o SQLAlchemy e como ele se integra ao Flask?
SQLAlchemy é uma biblioteca Python para acesso a bancos de dados relacionais, composta por duas
camadas principais: o Core, que oferece um conjunto de ferramentas para construir e executar SQL de
forma programática, e o ORM, que permite mapear classes Python a tabelas do banco de dados, tratando
cada instância da classe como um registro.
No ecossistema Flask, a integração mais comum é feita por meio da extensão Flask-SQLAlchemy, que
configura automaticamente a conexão com o banco a partir das configurações da aplicação (como
SQLALCHEMY_DATABASE_URI), disponibiliza um objeto db (instância de SQLAlchemy) usado para declarar
modelos ([Link]) e gerencia o ciclo de vida das sessões de banco de dados de acordo com o contexto de
cada requisição HTTP do Flask.
1.4 Boas práticas de segurança em Flask e SQLAlchemy
● Nunca armazenar senhas em texto puro: sempre gerar um hash da senha (por exemplo, com
Werkzeug Security ou bcrypt) antes de salvar no banco, e comparar hashes no login em vez de
comparar a senha diretamente.
● Manter dados sensíveis fora do código-fonte: a string de conexão do banco, chaves secretas
(SECRET_KEY) e credenciais não devem ficar hard-coded no repositório; devem vir de variáveis de
ambiente ou de um gerenciador de segredos, e o arquivo com essas variáveis (ex.: .env) não deve
ser versionado no Git.
Outras práticas recomendadas incluem validar e sanitizar todas as entradas do usuário antes de usá-las em
consultas, usar sempre os métodos do ORM (ou consultas parametrizadas do Core) em vez de concatenar
strings SQL manualmente, limitar os privilégios do usuário do banco de dados usado pela aplicação e
manter as bibliotecas (Flask, SQLAlchemy, drivers de banco) atualizadas para receber correções de
segurança.
1.5 Como o ORM ajuda a prevenir SQL Injection
O ataque de SQL Injection ocorre quando uma entrada fornecida pelo usuário é inserida diretamente em
uma string SQL, permitindo que um atacante altere a lógica da consulta original (por exemplo, enviando um
valor como "' OR '1'='1" em um campo de login). Isso costuma acontecer quando o SQL é montado por
concatenação de strings.
Quando se utiliza um ORM como o SQLAlchemy, as consultas são construídas de forma programática (ex.:
[Link].filter_by(email=email)), e os valores fornecidos pelo usuário são tratados internamente
como parâmetros de uma consulta preparada (prepared statement), e não como parte literal do texto SQL.
O driver do banco de dados envia a consulta e os parâmetros separadamente, de modo que o conteúdo
digitado pelo usuário nunca é interpretado como comando SQL, apenas como dado. Isso elimina, na
prática, o vetor de ataque mais comum de SQL Injection — desde que o desenvolvedor não volte a montar
SQL manualmente por concatenação de strings dentro do próprio código que usa o ORM.
2. Aplicação Prática
2.1 Modelo Usuário
Abaixo está um modelo simples de usuário, com os campos id, nome, email e senha. O campo de senha
armazena apenas o hash, nunca o valor original.
[Link]
from flask_sqlalchemy import SQLAlchemy
from [Link] import generate_password_hash, check_password_hash
db = SQLAlchemy()
class Usuario([Link]):
__tablename__ = "usuarios"
id = [Link]([Link], primary_key=True)
nome = [Link]([Link](120), nullable=False)
email = [Link]([Link](120), unique=True, nullable=False)
senha_hash = [Link]([Link](255), nullable=False)
def set_senha(self, senha_texto_puro):
# Gera um hash seguro (com salt) a partir da senha em texto puro.
self.senha_hash = generate_password_hash(senha_texto_puro)
def verificar_senha(self, senha_texto_puro):
# Compara a senha informada com o hash armazenado.
return check_password_hash(self.senha_hash, senha_texto_puro)
def __repr__(self):
return f"<Usuario {[Link]} {[Link]}>"
2.2 Inserindo e listando usuários com segurança
O trecho a seguir mostra como criar (inserir) e listar usuários usando o SQLAlchemy dentro de rotas Flask.
Observe que a senha nunca é gravada em texto puro: o método set_senha() gera o hash antes de o objeto
ser adicionado à sessão do banco.
[Link]
from flask import Flask, request, jsonify
from models import db, Usuario
import os
app = Flask(__name__)
[Link]["SQLALCHEMY_DATABASE_URI"] = [Link]["DATABASE_URL"]
[Link]["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(app)
@[Link]("/usuarios", methods=["POST"])
def criar_usuario():
dados = request.get_json()
novo_usuario = Usuario(
nome=dados["nome"],
email=dados["email"],
)
# A senha é transformada em hash antes de ser persistida.
novo_usuario.set_senha(dados["senha"])
[Link](novo_usuario)
[Link]()
return jsonify({"id": novo_usuario.id, "nome": novo_usuario.nome}), 201
@[Link]("/usuarios", methods=["GET"])
def listar_usuarios():
usuarios = [Link]()
return jsonify([
{"id": [Link], "nome": [Link], "email": [Link]}
for u in usuarios
# Note: senha_hash nunca é incluído na resposta da API.
])
if __name__ == "__main__":
with app.app_context():
db.create_all()
[Link](debug=True)
2.3 Configuração segura da string de conexão
A string de conexão do banco de dados (usuário, senha, host e nome do banco) não deve ficar escrita
diretamente no código-fonte. A prática recomendada é carregá-la a partir de uma variável de ambiente,
mantendo o valor real fora do repositório de código.
.env (arquivo local, não deve ser commitado no Git)
DATABASE_URL=postgresql://usuario_app:senha_super_secreta@localhost:5432/
meu_banco
SECRET_KEY=uma-chave-secreta-gerada-aleatoriamente
[Link]
import os
from dotenv import load_dotenv
load_dotenv() # carrega as variáveis definidas no arquivo .env
class Config:
SQLALCHEMY_DATABASE_URI = [Link]["DATABASE_URL"]
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = [Link]["SECRET_KEY"]