Django: autenticação e autorização com DRF e JWT
Use a aplicação de autenticação do Django com o Django Rest Framework para cadastrar usuários com validação de senha e fazer login com tokens JWT (access e refresh), usando um PostgreSQL na nuvem (Aiven).
1. Visão geral
Django é um framework "batteries-included". Ele traz diversas funcionalidades comuns prontas para uso. Uma delas é uma aplicação capaz de lidar com autenticação e autorização. Veja as suas principais características.
- Modelos predefinidos: inclui uma classe de modelo que representa usuários, contendo muitos dos campos comuns necessários, como username, password e email.
- Formulários de autenticação: inclui templates HTML básicos para as operações mais elementares como login, logout e troca de senha, os quais podem ser personalizados.
- Views e URLs associadas: complementando os formulários, o Django já possui views (funções ou classes que determinam o que mostrar ao usuário) e URLs relacionadas para processar pedidos de autenticação. Por exemplo, há views para fazer login, fazer logout, mudar a senha e assim por diante.
- Sistema de permissões: além da autenticação básica, o Django oferece um sistema de autorização que permite definir permissões específicas para diferentes tipos de usuários.
- Segurança: as senhas são armazenadas criptografadas. Além disso, ele oferece proteção contra ataques de força bruta (tentativas repetidas de adivinhar uma senha, por exemplo).
Neste material, veremos como utilizar a aplicação com
- API REST com o Django Rest Framework (DRF)
- templates (páginas HTML já predefinidas)
O que você vai aprender
- Como escolher o interpretador Python do ambiente virtual no VS Code
- Como criar um PostgreSQL gratuito na nuvem com o Aiven e acessá-lo pelo VS Code
- Como isolar os dados de acesso à base com o django-environ
- Como usar a classe de modelo
Userdo Django e escrever uma serializadora para ela - Como cadastrar usuários com uma
APIViewe validar senhas com expressões regulares - O que são access token e refresh token no mecanismo JWT
- Como fazer login com o djangorestframework-simplejwt
O que você vai precisar
- Python 3 instalado
- VS Code com as extensões Python e Thunder Client
- Uma instância do PostgreSQL (local ou no Aiven)
2. Ambiente virtual e novo projeto Django
Crie uma pasta para abrigar seus projetos Django. No Windows, uma sugestão é
C:\Users\usuario\Documents\dev\django
Em sistemas Unix-like, use
/home/usuario/dev/django
Abra um terminal (CMD no Windows, Bash em sistemas Unix-like) e navegue até o diretório recém-criado.
Terminal (Windows)
cd C:\Users\usuario\Documents\dev\django
Terminal (Unix-like)
cd /home/usuario/dev/django
Use
Terminal
python -m venv venv
para criar um ambiente virtual Python chamado venv utilizando o módulo venv. Após a sua execução, uma pasta chamada venv deve ser criada na raiz de seu projeto.
Depois da criação do ambiente virtual, é preciso ativá-lo. Usuários Windows podem utilizar um terminal "cmd" ou um terminal "Powershell". A tabela a seguir resume a forma como o ambiente virtual Python pode ser ativado em qualquer caso.
| Terminal | Comando(s) |
|---|---|
| Windows cmd | venv\Scripts\activate.bat |
| Windows Powershell | Set-ExecutionPolicy -Scope CurrentUser unrestricted e, depois, venv\Scripts\Activate.ps1 |
| Unix-like terminals | . venv/bin/activate |
Em qualquer caso, você pode verificar se o ambiente foi ativado com sucesso com
Terminal
pip -V
A pasta de seu ambiente virtual deve ser exibida.

O pip em uso é o do ambiente virtual.
O nome do ambiente virtual que você criou também deve aparecer.

O nome do ambiente virtual ativo aparece no prompt.
A seguir, podemos instalar o pacote Django. Essa instalação será válida apenas para o ambiente virtual Python ativo no momento.
Terminal
pip install django
Assim, temos um ambiente virtual Python que já inclui o Django, que poderemos utilizar sempre que necessário.
O próximo passo é criar um projeto Django. Um projeto Django é uma coleção de aplicações Django e configurações. Em geral, ele possui
- um "CLI" (command line interface) que nos permite interagir com seu conteúdo
- arquivos de configurações
- arquivos de definição de URLs
Podemos criar o projeto com
Terminal
django-admin startproject exemplo_autenticacao_autorizacao
Uma pasta deve ter sido criada, ao lado da pasta que representa o seu ambiente virtual.

A pasta do novo projeto.
Abra o VS Code e clique em File >> Open Folder. Navegue para encontrar a pasta de seu projeto e vincule o VS Code a ela. Veja o resultado esperado.

O projeto aberto no VS Code.

Ative o Auto Save.
No VS Code, clique em Terminal >> New terminal para abrir um terminal interno do VS Code. Lembre-se de ativar o ambiente virtual que criamos anteriormente, assim ele será válido para essa instância de terminal que acabamos de abrir.
Veja o resultado esperado. Use o comando apropriado para o seu sistema operacional.

O ambiente virtual ativado no terminal do VS Code.
3. O interpretador Python no VS Code
Caso ainda não tenha feito, instale a extensão Python no VS Code.

A extensão Python para o VS Code.
A extensão Python para o VS Code inclui diversas outras. Uma delas se chama PyLance. Ela oferece dicas para completar código, permite a "navegação no código" (clicar num nome e ir até a sua definição), entre outras coisas. A PyLance baseia o seu funcionamento no ambiente Python ativo no momento. Embora tenhamos ativado o ambiente que inclui o Django em nossos terminais, pode ser que o VS Code esteja utilizando outro ambiente. Se for o caso, a PyLance não será capaz de dar dicas envolvendo os pacotes específicos do nosso ambiente virtual. Abra, por exemplo, o arquivo urls.py do projeto. É possível que os imports do Django apareçam sublinhados em amarelo.

Imports do Django sublinhados: o VS Code não está usando o venv.
Se for o caso, você pode trocar o ambiente Python utilizado pelo VS Code. Ele aparece na barra azul de status, na parte inferior do VS Code. No exemplo a seguir, não estamos usando o venv.

O interpretador em uso aparece na barra de status.
Clique nesta região. Você poderá escolher o seu ambiente.

Escolhendo o interpretador.
Clique em Find.

Clique em Find.
Navegue no seu sistema de arquivos para encontrar o executável Python de seu ambiente. Observe que ele fica na sua pasta venv. A sua versão pode ser diferente.

O executável Python do venv.
Veja o resultado depois da troca.

O VS Code agora usa o ambiente virtual.
4. Dependências e PostgreSQL no Aiven
Como comentamos, as funcionalidades de autenticação/autorização serão oferecidas por meio de
- templates
- API REST
Por conta da API Rest, vamos instalar o Django Rest Framework (DRF). Veja o site do DRF: https://www.django-rest-framework.org/.
Além disso, nossa aplicação se conectará a uma instância do PostgreSQL e, para tal, ela precisa ser capaz de se comunicar utilizando o protocolo do PostgreSQL. Ele é implementado por diferentes pacotes, que geralmente levam o nome de "driver". Neste material, vamos utilizar o pacote psycopg2. No terminal do VS Code, use
Terminal
pip install djangorestframework psycopg2
para instalar ambos.
Um PostgreSQL na nuvem com o Aiven
Clique em Get Started for Free.

A página do Aiven.
Depois de criar a conta, você receberá um e-mail de confirmação. É preciso realizar esta confirmação antes de utilizar o ambiente.

O e-mail de confirmação da conta.
Depois de fazer login, você precisa criar um serviço.

Criando um serviço.
Escolha o PostgreSQL.

Escolha o PostgreSQL.
Escolha o plano grátis e clique para criar um serviço. Se desejar, na parte inferior da tela, altere o nome do serviço. Use um nome que te ajude a lembrar a razão de ser do serviço, seu propósito, sua finalidade.

O plano grátis.
No próximo passo, você pode adicionar os endereços IP a partir dos quais seu serviço aceitará conexões. É uma medida de segurança. No momento, não precisamos nos preocupar com isso. Clique Next.

Endereços IP permitidos.
A seguir, você também pode adicionar dados de bases de dados que eventualmente já possua. Também não estamos interessados no momento. Clique Next.

Migração de dados existentes (opcional).
Depois disso, você pode escolher extensões que eventualmente deseje instalar.

Extensões do PostgreSQL.
Apenas instale aquelas de que realmente precisa. Se você estiver na dúvida, não instale nenhuma, por enquanto. Clique para finalizar quando terminar.
Observe que, agora, você pode obter os dados para acesso à sua base de dados remota.

Os dados de acesso à base remota.
Você pode utilizar diferentes clientes para se conectar à sua base. Neste exemplo, vamos utilizar a seguinte extensão do VS Code.

A extensão Database Client.
Depois da instalação, você pode criar uma conexão.

Criando uma conexão.
A seguir, preencha os campos, clique em Save e Connect.

Os dados da conexão.
Se tudo deu certo, você deverá ver a mensagem Connect success.

Conexão bem-sucedida.
Agora é preciso expandir a conexão e o banco de dados, além de clicar no ícone destacado a seguir.

Abrindo um editor de consultas.
Você terá acesso a um editor em que poderá digitar seus comandos SQL. Faça o seguinte teste.
Editor SQL
CREATE TABLE tb_teste(
cod_teste SERIAL PRIMARY KEY,
nome VARCHAR(50) NOT NULL
);
INSERT INTO tb_teste(nome) VALUES('teste1');
SELECT * FROM tb_teste;
Selecione o código e aperte CTRL + Enter para executar.

O teste executado no PostgreSQL remoto.
5. Aplicação e variáveis de ambiente
Nova aplicação
Até então, temos apenas um projeto Django. Agora precisamos criar uma aplicação que fará parte dele. Para isso, use
Terminal
python manage.py startapp exemplo_autenticacao_autorizacao_app
Veja o resultado esperado.

A pasta da nova aplicação.
Adicionando aplicações ao projeto
No arquivo settings.py do projeto, precisamos adicionar a nossa aplicação como parte dele, na lista de INSTALLED_APPS. Como vamos usar o DRF, também precisamos adicionar uma aplicação chamada rest_framework. Veja.
exemplo_autenticacao_autorizacao/settings.py
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"exemplo_autenticacao_autorizacao_app"
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
# ...
]
Isolando os dados de acesso à base com django-environ
Desejamos isolar os dados de acesso à base de dados por duas razões, pelo menos.
- controle de versão em repositórios públicos
- facilidade de alteração dos valores em função do ambiente (desenvolvimento, produção etc.)
Para defini-los, vamos usar o pacote django-environ. Faça a sua instalação com
Terminal
pip install django-environ
A seguir, crie um arquivo chamado .env na raiz do projeto, fora de qualquer pasta, lado a lado com o arquivo manage.py.

O arquivo .env na raiz do projeto.
Veja seu conteúdo. Neste exemplo, estamos utilizando um banco de dados gerenciado pelo PostgreSQL por meio do serviço Aiven. Você também pode usar uma base local, se desejar.
.env
SECRET_KEY=django-insecure-es!1$*fjkcfm7j_c=tyy-it9r#w9+eh6f0v2by80cpi6jf4^u!
DEBUG=True
DATABASE_DEFAULT_NAME=defaultdb
DATABASE_DEFAULT_USER=avnadmin
DATABASE_DEFAULT_PASSWORD=sua senha do Aiven aqui
DATABASE_DEFAULT_HOST=pg-1bd68abd-professorbossini.aivencloud.com
DATABASE_DEFAULT_PORT=12956
Já no arquivo settings.py do projeto, faça a leitura do seu arquivo .env e, então, passe a utilizar os valores nele definidos, por meio da função env.
exemplo_autenticacao_autorizacao/settings.py
# ...
from pathlib import Path
import environ
env = environ.Env(
#deixamos False por padrão
#caso o .env não defina, por segurança, é melhor, já que o ambiente pode ser o de produção
DEBUG = (bool, False)
)
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
environ.Env.read_env(BASE_DIR / Path(".env"))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG')
ALLOWED_HOSTS = []
# Application definition
# INSTALLED_APPS = [
# ...
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('DATABASE_DEFAULT_NAME'),
'USER': env('DATABASE_DEFAULT_USER'),
'PASSWORD': env('DATABASE_DEFAULT_PASSWORD'),
'HOST': env('DATABASE_DEFAULT_HOST'),
'PORT': env('DATABASE_DEFAULT_PORT'),
}
}
# ...
Faça uma primeira execução de migração, atualizando as bases referentes às aplicações já existentes no projeto.
Terminal
python manage.py migrate
Se tudo deu certo, você deverá visualizar as tabelas criadas pelo Django.

As tabelas criadas pelas migrações.
6. A classe User e a serializadora
A aplicação de autenticação/autorização do Django possui uma classe de modelo chamada User. Como o nome sugere, ela serve para representar possíveis usuários do sistema. Se desejar, você pode averiguar seu código fonte. Ele pode ser encontrado em seu diretório venv. Algo como
venv/lib/python3.11/site-packages/django/contrib/auth/
Se desejar, você também pode encontrar o código fonte no Github: https://github.com/django/django/blob/main/django/contrib/auth/models.py.
Veja um trecho da classe User.
django/contrib/auth/models.py (trecho)
class User(AbstractUser):
"""
Users within the Django authentication system are represented by this
model.
Username and password are required. Other fields are optional.
"""
class Meta(AbstractUser.Meta):
swappable = "AUTH_USER_MODEL"
Observe como ela herda de AbstractUser. Veja um trecho dela.
django/contrib/auth/models.py (trecho)
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_("username"),
max_length=150,
unique=True,
help_text=_(
"Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
),
validators=[username_validator],
error_messages={
"unique": _("A user with that username already exists."),
},
)
first_name = models.CharField(_("first name"), max_length=150, blank=True)
last_name = models.CharField(_("last name"), max_length=150, blank=True)
email = models.EmailField(_("email address"), blank=True)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
is_active = models.BooleanField(
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Unselect this instead of deleting accounts."
),
)
Veja uma descrição de alguns campos que a classe User possui.
- username: o nome de usuário, único, usado para autenticar o usuário. É um campo obrigatório.
- password: senha do usuário, criptografada pelo Django antes de ser armazenada.
- email: endereço de email do usuário.
- first_name e last_name: primeiro e último nome do usuário.
- groups: um relacionamento muitos para muitos (
ManyToManyField) com o modeloGroup, permitindo que você agrupe usuários e atribua permissões a esses grupos. - user_permissions: um relacionamento muitos para muitos (
ManyToManyField) com o modeloPermission, permitindo que você atribua permissões específicas a um usuário específico, independentemente dos grupos.
A serializadora de usuários
Como a classe de modelo já está pronta, vamos criar uma classe serializadora para escolher os campos que nos são de interesse. Para isso, crie uma pasta chamada serializers na raiz da aplicação e, dentro dela, um arquivo chamado __init__.py, vazio num primeiro momento. Crie também um arquivo chamado user_serializer.py.

A pasta serializers.
No arquivo user_serializer.py, escreva a classe UserSerializer. Vamos escolher quais campos farão parte das operações da aplicação (farão parte do objeto JSON recebido e produzido e, por consequência, do objeto Python envolvido nas conversões).
Exemplo: classe Meta num modelo
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=100)
class Meta:
ordering = ['title']
Veja o código da serializadora de usuários.
exemplo_autenticacao_autorizacao_app/serializers/user_serializer.py
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
A fim de simplificar o import a ser realizado em outros módulos externos, importe a classe UserSerializer no arquivo __init__.py que criamos há pouco.
exemplo_autenticacao_autorizacao_app/serializers/__init__.py
#arquivo serializers/__init__.py
from .user_serializer import UserSerializer
7. Cadastro de novos usuários
A seguir, definimos uma View que viabiliza o cadastro de novos usuários. Para defini-la, crie uma pasta chamada views na raiz da aplicação. A seguir, crie um arquivo chamado __init__.py e outro chamado usuario_views.py.

A pasta views (o views.py original pode ser apagado).
Veja o código do arquivo views/__init__.py.
exemplo_autenticacao_autorizacao_app/views/__init__.py
from .usuario_views import CadastroNovoUsuarioView
Veja as características da nova view.
- Se chama
CadastroNovoUsuarioView. - Herda de
views.APIView. Essa é uma classe base para views do DRF. Ela oferece acesso mais direto aos dados da requisição e nos permite especificar explicitamente os métodos do protocolo HTTP desejados. - Possui um método chamado
post. Ele é acionado quando uma requisição do tipo post for recebida. Como parâmetro, ele tem um objeto chamadorequest. Ele dá acesso aos dados recebidos na requisição. - Instanciamos um
UserSerializere a ele entregamos os campos da request, acessíveis por meio do seu campodata. Internamente, cada serializadora tem seu próprio campo também chamadodata. Esse é um dicionário que contém todos os dados sobre os quais a serializadora opera. - Verificamos se os dados recebidos são "válidos" usando o método
is_valid. Neste momento, por exemplo, a serializadora verifica, no banco de dados, se o username recebido já existe por lá. Se for o caso, a requisição terá como resposta um erro da família 400, ou seja, causado pela aplicação cliente. Há diversas validações padrão e também podemos especificar outras que nos sejam de interesse. - Acessamos a coleção de usuários e criamos um novo usuário. Após a validação, os campos validados se encontram na propriedade
validated_datada classe serializadora. Observe que usamos o operador**para "desestruturar" o dicionáriovalidated_data. Isso acontece pois o métodocreate_userespera receber uma coleção de pares chave/valor "avulsos", não incluídos em um dicionário. - Se o cadastro foi feito corretamente, devolvemos um objeto
Response. Ele contém os dados de interesse (geralmente os dados do próprio objeto que foi criado) e o status do protocolo HTTP 201 Created, indicando que um recurso foi criado. - Se o cadastro falhou, devolvemos o objeto
errorsda classe serializadora. Ele contém as mensagens de erro que explicam os erros eventualmente encontrados. Além disso, o código de status do protocolo HTTP será 400 Bad Request, indicando que houve um erro causado pela requisição que foi enviada pelo cliente (e não foi um erro interno do servidor).
Exemplo: argumentos nomeados e o operador **
def exibe_nomes(**nomes):
for nome in nomes.values():
print(nome)
#passando valores como argumentos nomeados, ou seja, chave=valor, avulsos, sem precisar de um dicionário
exibe_nomes(nome1='João', nome2='Maria', nome3='José')
#construindo um dicionário
pessoa = {'nome':'João'}
#tentando passar o dicionário inteiro
#vai dar erro, pois, assim, o dicionário é um argumento posicional, e a função espera argumentos nomeados
exibe_nomes(pessoa)
#para passar os dados do dicionário como argumentos nomeados, usamos o operador **, desempacotando o dicionário
exibe_nomes(**pessoa)
Veja o código da view.
exemplo_autenticacao_autorizacao_app/views/usuario_views.py
from rest_framework import views, status
from rest_framework.response import Response
from exemplo_autenticacao_autorizacao_app.serializers import UserSerializer
from django.contrib.auth.models import User
class CadastroNovoUsuarioView(views.APIView):
def post(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = User.objects.create_user(**serializer.validated_data)
return Response(UserSerializer(user).data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
A seguir, criamos um arquivo urls.py para definir os mapeamentos específicos da aplicação.

O urls.py da aplicação.
Veja seu conteúdo. Fazemos um primeiro mapeamento.
exemplo_autenticacao_autorizacao_app/urls.py
from django.urls import path
from exemplo_autenticacao_autorizacao_app.views import CadastroNovoUsuarioView
urlpatterns = [
path('signup/', CadastroNovoUsuarioView.as_view(), name='signup'),
]
A seguir, precisamos incluir as urls da aplicação ao arquivo urls.py do projeto. Lembre-se que ele fica na pasta do projeto.

O urls.py do projeto.
exemplo_autenticacao_autorizacao/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path('', include('exemplo_autenticacao_autorizacao_app.urls'))
]
Certifique-se de que o servidor está em execução.
Terminal
python manage.py runserver

O servidor em execução.
Na Thunder Client, faça um teste.

Cadastro de um usuário: a senha não aparece na resposta.
Faça um SELECT no banco e verifique se o usuário foi, de fato, cadastrado. Observe que a senha foi criptografada antes de ser armazenada. Inspecione também os demais campos.

A senha armazenada criptografada.
De volta à Thunder Client, tente cadastrar um novo usuário com username igual ao do anterior. Veja o erro causado.

Username repetido.
Altere o username. Ao mesmo tempo, especifique um e-mail inválido. Veja o erro.

E-mail inválido.
8. Validando a senha
Observe que estamos admitindo senhas não muito seguras. Caso queiramos especificar uma validação específica para um determinado campo, temos diferentes alternativas.
- escrever um método
validate_nome_do_campona classe serializadora. - sobrescrever o método
validateda classe serializadora (isso nos permite validar diversos campos de uma só vez). Ele recebe um parâmetrodatae pegamos um campo assim:data.get('nome_do_campo'). - escrever uma função num arquivo à parte, próprio para armazenar validadores, e depois utilizá-la numa classe de modelo. Veja um exemplo. Apenas observe, não vamos utilizar este modo ainda.
Exemplo (apenas observe)
#arquivo qualquer
def validar_password(password):
#logica de validacao
pass
#numa classe de modelo
password = models.CharField(validators=[validar_password])
Neste momento, vamos utilizar a primeira estratégia. Na classe serializadora, escreva o seguinte método. Esta implementação informa que toda senha é inválida. Claro, vamos aprimorar a seguir. Estamos no arquivo user_serializer.py.
exemplo_autenticacao_autorizacao_app/serializers/user_serializer.py
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
#tem que ser esse nome: validate_nome_do_campo
def validate_password(self, password):
raise serializers.ValidationError("Senha inválida")
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
Vamos validar utilizando expressões regulares. As regras serão as seguintes:
- Pelo menos uma letra maiúscula
- Pelo menos uma letra minúscula
- Pelo menos um número
- Pelo menos um símbolo especial
- Pelo menos oito caracteres
Se desejar, estude mais sobre expressões regulares em Python em https://docs.python.org/3/library/re.html.
Exemplo: raw strings
#sem raw string
path1 = 'C:\\Users\\rodrigo\\Documents'
#com raw string
path2 = r'C:\Users\rodrigo\Documents'
#sem raw string
digito_qualquer = '\\d'
#com raw string
digito_qualquer = r'\d'
Veja a implementação do validador.
exemplo_autenticacao_autorizacao_app/serializers/user_serializer.py
from django.contrib.auth.models import User
from rest_framework import serializers
import re
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
#tem que ser esse nome: validate_nome_do_campo
def validate_password(self, password):
#pelo menos uma letra maiuscula
if not re.search('[A-Z]', password):
raise serializers.ValidationError("A senha deve conter pelo menos uma letra maiúscula")
#pelo menos uma letra minuscula
if not re.search('[a-z]', password):
raise serializers.ValidationError("A senha deve conter pelo menos uma letra minúscula")
#pelo menos um numero
if not re.search('[0-9]', password):
raise serializers.ValidationError("A senha deve conter pelo menos um número")
#pelo menos um caracter especial (o ^ é para negar)
if not re.search('[^a-zA-Z0-9]', password):
raise serializers.ValidationError("A senha deve conter pelo menos um caracter especial")
#pelo menos oito caracteres
if len(password) < 8:
raise serializers.ValidationError("A senha deve conter pelo menos oito caracteres")
return password
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
Na Thunder Client, faça novos testes. Veja alguns exemplos.

Senha sem letra maiúscula.

Senha sem letra minúscula.

Senha sem número.

Senha sem caractere especial.

Senha com menos de oito caracteres.

Senha válida: usuário cadastrado.
9. Login com JWT
Para fazer login, o usuário precisa mostrar-se autêntico. Há diferentes mecanismos de autenticação, como Bearer Token Authentication, Basic, Digest, entre outros. Neste material, vamos utilizar o mecanismo Bearer Token Authentication com JWT (JSON Web Token).
Como funciona: após o login bem-sucedido, o servidor envia um token que o cliente deve incluir nas solicitações subsequentes para acessar recursos protegidos. Esse token é geralmente incluído no cabeçalho da requisição, precedido pela palavra "Bearer".
Em geral, quando utilizamos o mecanismo JWT, dois tipos de tokens estão envolvidos:
Access Token
O token de acesso (access token) é usado para acessar e autenticar requisições em endpoints protegidos. Ele tem todas as informações necessárias para identificar e autorizar um usuário.
- Duração: ele tem uma duração curta, o que significa que expira rapidamente. Isso é intencional, pois se um token de acesso for comprometido, ele poderá ser utilizado com más intenções por pouco tempo.
- Informações: normalmente, carrega informações sobre o usuário e/ou suas permissões. Isso permite que o servidor saiba quem está fazendo a requisição e o que essa pessoa tem permissão para acessar.
Refresh Token
O token de atualização (refresh token) não é usado para acessar endpoints diretamente. Ele é usado para obter um novo token de acesso quando o anterior expira.
- Duração: geralmente tem uma duração muito mais longa do que o token de acesso. Pode durar dias, semanas ou até mais, dependendo da implementação.
- Segurança: em muitas implementações, o refresh token é guardado de maneira segura no lado do servidor. Se um refresh token for comprometido, o impacto pode ser significativo, já que ele pode ser usado para obter tokens de acesso continuamente até sua expiração.
- Rotação: algumas implementações usam a técnica de rotação de token. Nela, cada vez que um refresh token é usado para obter um novo access token, um novo refresh token é também gerado e o anterior é invalidado. Isso garante que, se um refresh token for comprometido, ele só possa ser usado uma vez.
djangorestframework-simplejwt
Para utilizar este mecanismo com Django, vamos usar o pacote djangorestframework-simplejwt. Faça a sua instalação com
Terminal
pip install djangorestframework-simplejwt
A seguir, no arquivo settings.py, vamos fazer os seguintes ajustes.
- Adicionar a nova aplicação à lista de aplicações instaladas no projeto.
- Apontar qual a classe responsável pela autenticação.
- Fazer configurações quanto ao funcionamento do mecanismo de autenticação JWT.
exemplo_autenticacao_autorizacao/settings.py
# ...
from pathlib import Path
import environ
#representa uma duração de tempo
from datetime import timedelta
# ...
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"exemplo_autenticacao_autorizacao_app",
"rest_framework_simplejwt.token_blacklist"
]
# ...
SIMPLE_JWT = {
#quanto tempo o token vai durar
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
#quanto tempo o refresh token vai durar
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
#se o refresh token vai ser rotacionado
'ROTATE_REFRESH_TOKENS': False,
#algoritmo de criptografia
'ALGORITHM': 'HS256',
#chave de criptografia
'SIGNING_KEY': env('SECRET_KEY'),
#tipos de autenticação, vamos usar apenas o Bearer. Tem que ser uma tupla, por isso a virgula no final
'AUTH_HEADER_TYPES': ('Bearer',),
}
# ...
O próximo passo é dar acesso às funcionalidades de obtenção dos tokens de acesso e de atualização. Para tal, podemos usar views definidas pelo próprio rest_framework_simplejwt que instalamos. No arquivo urls.py do projeto, faça o ajuste a seguir.
exemplo_autenticacao_autorizacao/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path("admin/", admin.site.urls),
#para obter um access token e um refresh token em função de um username e password
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
#para obter um novo access token em função de um refresh token
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('', include('exemplo_autenticacao_autorizacao_app.urls'))
]
Reinicie a aplicação com
Terminal
python manage.py runserver
Observe que a aplicação que instalamos (token_blacklist) possui migrações necessárias para seu correto funcionamento.

Migrações pendentes do token_blacklist.
Por isso, execute
Terminal
python manage.py migrate

As migrações do token_blacklist aplicadas.
Coloque o servidor em execução uma vez mais com
Terminal
python manage.py runserver
Para testar, comece criando um usuário com a Thunder Client, caso ainda não tenha um ou não se lembre da senha daqueles que já foram cadastrados.

Criando um usuário para o teste de login.
Agora tente obter um token de acesso. Observe que o método a ser usado é o POST.

O login devolve um refresh token e um access token.
A resposta contém um access token e um refresh token. Copie o refresh token e faça novo teste, a fim de obter um novo access token.

Um novo access token obtido com o refresh token.
10. Exercícios
Neste exercício, você criará um endpoint para redefinição de senha. Para acessá-lo, o aplicativo cliente enviará um objeto JSON com um campo de email apenas. O servidor deve montar uma URL que, quando acessada, permite a redefinição de senha. A seguir, ele envia um email para o usuário, instruindo-o a utilizar o link para reconfigurar a sua senha. Para isso, utilize as seguintes dicas.
Configurações de e-mail
Faça as seguintes configurações no settings.py.
settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'seu_email@gmail.com'
EMAIL_HOST_PASSWORD = 'sua_senha'
Modelo e endpoints
Crie uma classe de modelo PasswordResetToken com
user(ForeignKey apontando paraUserdedjango.contrib.auth.models)token(UUIDField, importe o pacoteuuidpara gerar)created_at(DateTimeField)
Crie um arquivo para views chamado reset_password_views.py. Implemente o seguinte endpoint:
@api_view(['POST'])
def request_password_reset(request):
...
Ele deve:
- Obter o email da request.
- Verificar se, na base, há um usuário com aquele e-mail cadastrado (
User.objects.get). Devolver 404 se não existir. - Construir um
PasswordResetTokenvinculado ao usuário (reset_token = PasswordResetToken(user=user)) e salvar no banco comsave. - Montar um link para reset de password:
reset_link = f"https://localhost:8000/reset-password/{reset_token.token}" - Enviar o e-mail com a função
send_mail(from django.core.mail import send_mail): https://docs.djangoproject.com/en/3.2/topics/email/#send-mail - Devolver 200 e uma mensagem dizendo que o e-mail foi enviado com sucesso.
Ainda no arquivo reset_password_views.py, crie outro endpoint:
@api_view(['POST'])
def reset_password(request, token):
...
Ele deve
- Verificar se o token existe (
reset_token = PasswordResetToken.objects.get(token=token)). Se não existir, devolver 400. - Da requisição, pegar a senha do JSON enviado pelo cliente:
{"password": "nova-senha"}. - Configurar a nova senha do usuário com
reset_token.user.set_password(new_password)ereset_token.user.save(). - Apagar o token com
reset_token.delete(). - Devolver 200.
Configure as novas URLs.
urls.py da aplicação (trecho)
urlpatterns = [
# ... (mapeamentos já existentes)
path('request-password-reset/', request_password_reset, name='request_password_reset'),
path('reset-password/<uuid:token>/', reset_password, name='reset_password'),
]
Envie novas requisições POST a fim de
- obter novo token para nova senha
- configurar nova senha (token como path e password no corpo da requisição)
Referências
- Django Rest Framework: https://www.django-rest-framework.org/
- Código fonte da classe User: https://github.com/django/django/blob/main/django/contrib/auth/models.py
- Expressões regulares em Python: https://docs.python.org/3/library/re.html
- Aiven: https://aiven.io/