committed by
GitHub
1 changed files with 274 additions and 0 deletions
@ -0,0 +1,274 @@ |
|||
# OAuth2 com Senha (e hashing), Bearer com tokens JWT |
|||
|
|||
Agora que temos todo o fluxo de segurança, vamos tornar a aplicação realmente segura, usando tokens <abbr title="JSON Web Tokens">JWT</abbr> e hashing de senhas seguras. |
|||
|
|||
Este código é algo que você pode realmente usar na sua aplicação, salvar os hashes das senhas no seu banco de dados, etc. |
|||
|
|||
Vamos começar de onde paramos no capítulo anterior e incrementá-lo. |
|||
|
|||
## Sobre o JWT |
|||
|
|||
JWT significa "JSON Web Tokens". |
|||
|
|||
É um padrão para codificar um objeto JSON em uma string longa e densa sem espaços. Ele se parece com isso: |
|||
|
|||
``` |
|||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
|||
``` |
|||
|
|||
Ele não é criptografado, então qualquer pessoa pode recuperar as informações do seu conteúdo. |
|||
|
|||
Mas ele é assinado. Assim, quando você recebe um token que você emitiu, você pode verificar que foi realmente você quem o emitiu. |
|||
|
|||
Dessa forma, você pode criar um token com um prazo de expiração, digamos, de 1 semana. E então, quando o usuário voltar no dia seguinte com o token, você sabe que ele ainda está logado no seu sistema. |
|||
|
|||
Depois de uma semana, o token expirará e o usuário não estará autorizado, precisando fazer login novamente para obter um novo token. E se o usuário (ou uma terceira parte) tentar modificar o token para alterar a expiração, você seria capaz de descobrir isso, pois as assinaturas não iriam corresponder. |
|||
|
|||
Se você quiser brincar com tokens JWT e ver como eles funcionam, visite <a href="https://jwt.io/" class="external-link" target="_blank">https://jwt.io</a>. |
|||
|
|||
## Instalar `PyJWT` |
|||
|
|||
Nós precisamos instalar o `PyJWT` para criar e verificar os tokens JWT em Python. |
|||
|
|||
Certifique-se de criar um [ambiente virtual](../../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar o `pyjwt`: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install pyjwt |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// info | Informação |
|||
|
|||
Se você pretente utilizar algoritmos de assinatura digital como o RSA ou o ECDSA, você deve instalar a dependência da biblioteca de criptografia `pyjwt[crypto]`. |
|||
|
|||
Você pode ler mais sobre isso na <a href="https://pyjwt.readthedocs.io/en/latest/installation.html" class="external-link" target="_blank">documentação de instalação do PyJWT</a>. |
|||
|
|||
/// |
|||
|
|||
## Hashing de senhas |
|||
|
|||
"Hashing" significa converter algum conteúdo (uma senha neste caso) em uma sequência de bytes (apenas uma string) que parece um monte de caracteres sem sentido. |
|||
|
|||
Sempre que você passar exatamente o mesmo conteúdo (exatamente a mesma senha), você obterá exatamente o mesmo resultado. |
|||
|
|||
Mas não é possível converter os caracteres sem sentido de volta para a senha original. |
|||
|
|||
### Por que usar hashing de senhas |
|||
|
|||
Se o seu banco de dados for roubado, o invasor não terá as senhas em texto puro dos seus usuários, apenas os hashes. |
|||
|
|||
Então, o invasor não poderá tentar usar essas senhas em outro sistema (como muitos usuários utilizam a mesma senha em vários lugares, isso seria perigoso). |
|||
|
|||
## Instalar o `passlib` |
|||
|
|||
O PassLib é uma excelente biblioteca Python para lidar com hashes de senhas. |
|||
|
|||
Ele suporta muitos algoritmos de hashing seguros e utilitários para trabalhar com eles. |
|||
|
|||
O algoritmo recomendado é o "Bcrypt". |
|||
|
|||
Certifique-se de criar um [ambiente virtual](../../virtual-environments.md){.internal-link target=_blank}, ativá-lo e então instalar o PassLib com Bcrypt: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ pip install "passlib[bcrypt]" |
|||
|
|||
---> 100% |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
/// tip | Dica |
|||
|
|||
Com o `passlib`, você poderia até configurá-lo para ser capaz de ler senhas criadas pelo **Django**, um plug-in de segurança do **Flask** ou muitos outros. |
|||
|
|||
Assim, você poderia, por exemplo, compartilhar os mesmos dados de um aplicativo Django em um banco de dados com um aplicativo FastAPI. Ou migrar gradualmente uma aplicação Django usando o mesmo banco de dados. |
|||
|
|||
E seus usuários poderiam fazer login tanto pela sua aplicação Django quanto pela sua aplicação **FastAPI**, ao mesmo tempo. |
|||
|
|||
/// |
|||
|
|||
## Criar o hash e verificar as senhas |
|||
|
|||
Importe as ferramentas que nós precisamos de `passlib`. |
|||
|
|||
Crie um "contexto" do PassLib. Este será usado para criar o hash e verificar as senhas. |
|||
|
|||
/// tip | Dica |
|||
|
|||
O contexto do PassLib também possui funcionalidades para usar diferentes algoritmos de hashing, incluindo algoritmos antigos que estão obsoletos, apenas para permitir verificá-los, etc. |
|||
|
|||
Por exemplo, você poderia usá-lo para ler e verificar senhas geradas por outro sistema (como Django), mas criar o hash de novas senhas com um algoritmo diferente, como o Bcrypt. |
|||
|
|||
E ser compatível com todos eles ao mesmo tempo. |
|||
|
|||
/// |
|||
|
|||
Crie uma função utilitária para criar o hash de uma senha fornecida pelo usuário. |
|||
|
|||
E outra função utilitária para verificar se uma senha recebida corresponde ao hash armazenado. |
|||
|
|||
E outra para autenticar e retornar um usuário. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[8,49,56:57,60:61,70:76] *} |
|||
|
|||
/// note | Nota |
|||
|
|||
Se você verificar o novo banco de dados (falso) `fake_users_db`, você verá como o hash da senha se parece agora: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`. |
|||
|
|||
/// |
|||
|
|||
## Manipular tokens JWT |
|||
|
|||
Importe os módulos instalados. |
|||
|
|||
Crie uma chave secreta aleatória que será usada para assinar os tokens JWT. |
|||
|
|||
Para gerar uma chave secreta aleatória e segura, use o comando: |
|||
|
|||
<div class="termy"> |
|||
|
|||
```console |
|||
$ openssl rand -hex 32 |
|||
|
|||
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 |
|||
``` |
|||
|
|||
</div> |
|||
|
|||
E copie a saída para a variável `SECRET_KEY` (não use a do exemplo). |
|||
|
|||
Crie uma variável `ALGORITHM` com o algoritmo usado para assinar o token JWT e defina como `"HS256"`. |
|||
|
|||
Crie uma variável para a expiração do token. |
|||
|
|||
Defina um modelo Pydantic que será usado no endpoint de token para a resposta. |
|||
|
|||
Crie uma função utilitária para gerar um novo token de acesso. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[4,7,13:15,29:31,79:87] *} |
|||
|
|||
## Atualize as dependências |
|||
|
|||
Atualize `get_current_user` para receber o mesmo token de antes, mas desta vez, usando tokens JWT. |
|||
|
|||
Decodifique o token recebido, verifique-o e retorne o usuário atual. |
|||
|
|||
Se o token for inválido, retorne um erro HTTP imediatamente. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[90:107] *} |
|||
|
|||
## Atualize a *operação de rota* `/token` |
|||
|
|||
Crie um `timedelta` com o tempo de expiração do token. |
|||
|
|||
Crie um token de acesso JWT real e o retorne. |
|||
|
|||
{* ../../docs_src/security/tutorial004_an_py310.py hl[118:133] *} |
|||
|
|||
### Detalhes técnicos sobre o "sujeito" `sub` do JWT |
|||
|
|||
A especificação JWT diz que existe uma chave `sub`, com o sujeito do token. |
|||
|
|||
É opcional usá-la, mas é onde você colocaria a identificação do usuário, então nós estamos usando aqui. |
|||
|
|||
O JWT pode ser usado para outras coisas além de identificar um usuário e permitir que ele execute operações diretamente na sua API. |
|||
|
|||
Por exemplo, você poderia identificar um "carro" ou uma "postagem de blog". |
|||
|
|||
Depois, você poderia adicionar permissões sobre essa entidade, como "dirigir" (para o carro) ou "editar" (para o blog). |
|||
|
|||
E então, poderia dar esse token JWT para um usuário (ou bot), e ele poderia usá-lo para realizar essas ações (dirigir o carro ou editar o blog) sem sequer precisar ter uma conta, apenas com o token JWT que sua API gerou para isso. |
|||
|
|||
Usando essas ideias, o JWT pode ser usado para cenários muito mais sofisticados. |
|||
|
|||
Nesses casos, várias dessas entidades poderiam ter o mesmo ID, digamos `foo` (um usuário `foo`, um carro `foo` e uma postagem de blog `foo`). |
|||
|
|||
Então, para evitar colisões de ID, ao criar o token JWT para o usuário, você poderia prefixar o valor da chave `sub`, por exemplo, com `username:`. Assim, neste exemplo, o valor de `sub` poderia ser: `username:johndoe`. |
|||
|
|||
O importante a se lembrar é que a chave `sub` deve ter um identificador único em toda a aplicação e deve ser uma string. |
|||
|
|||
## Testando |
|||
|
|||
Execute o servidor e vá para a documentação: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. |
|||
|
|||
Você verá a interface de usuário assim: |
|||
|
|||
<img src="/img/tutorial/security/image07.png"> |
|||
|
|||
Autorize a aplicação da mesma maneira que antes. |
|||
|
|||
Usando as credenciais: |
|||
|
|||
Username: `johndoe` |
|||
Password: `secret` |
|||
|
|||
/// check | Verifique |
|||
|
|||
Observe que em nenhuma parte do código está a senha em texto puro "`secret`", nós temos apenas o hash. |
|||
|
|||
/// |
|||
|
|||
<img src="/img/tutorial/security/image08.png"> |
|||
|
|||
Chame o endpoint `/users/me/`, você receberá o retorno como: |
|||
|
|||
```JSON |
|||
{ |
|||
"username": "johndoe", |
|||
"email": "[email protected]", |
|||
"full_name": "John Doe", |
|||
"disabled": false |
|||
} |
|||
``` |
|||
|
|||
<img src="/img/tutorial/security/image09.png"> |
|||
|
|||
Se você abrir as ferramentas de desenvolvedor, poderá ver que os dados enviados incluem apenas o token. A senha é enviada apenas na primeira requisição para autenticar o usuário e obter o token de acesso, mas não é enviada nas próximas requisições: |
|||
|
|||
<img src="/img/tutorial/security/image10.png"> |
|||
|
|||
/// note | Nota |
|||
|
|||
Perceba que o cabeçalho `Authorization`, com o valor que começa com `Bearer `. |
|||
|
|||
/// |
|||
|
|||
## Uso avançado com `scopes` |
|||
|
|||
O OAuth2 tem a noção de "scopes" (escopos). |
|||
|
|||
Você pode usá-los para adicionar um conjunto específico de permissões a um token JWT. |
|||
|
|||
Então, você pode dar este token diretamente a um usuário ou a uma terceira parte para interagir com sua API com um conjunto de restrições. |
|||
|
|||
Você pode aprender como usá-los e como eles são integrados ao **FastAPI** mais adiante no **Guia Avançado do Usuário**. |
|||
|
|||
|
|||
## Recapitulação |
|||
|
|||
Com o que você viu até agora, você pode configurar uma aplicação **FastAPI** segura usando padrões como OAuth2 e JWT. |
|||
|
|||
Em quase qualquer framework, lidar com a segurança se torna rapidamente um assunto bastante complexo. |
|||
|
|||
Muitos pacotes que simplificam bastante isso precisam fazer muitas concessões com o modelo de dados, o banco de dados e os recursos disponíveis. E alguns desses pacotes que simplificam demais na verdade têm falhas de segurança subjacentes. |
|||
|
|||
--- |
|||
|
|||
O **FastAPI** não faz nenhuma concessão com nenhum banco de dados, modelo de dados ou ferramenta. |
|||
|
|||
Ele oferece toda a flexibilidade para você escolher as opções que melhor se ajustam ao seu projeto. |
|||
|
|||
E você pode usar diretamente muitos pacotes bem mantidos e amplamente utilizados, como `passlib` e `PyJWT`, porque o **FastAPI** não exige mecanismos complexos para integrar pacotes externos. |
|||
|
|||
Mas ele fornece as ferramentas para simplificar o processo o máximo possível, sem comprometer a flexibilidade, robustez ou segurança. |
|||
|
|||
E você pode usar e implementar protocolos padrão seguros, como o OAuth2, de uma maneira relativamente simples. |
|||
|
|||
Você pode aprender mais no **Guia Avançado do Usuário** sobre como usar os "scopes" do OAuth2 para um sistema de permissões mais refinado, seguindo esses mesmos padrões. O OAuth2 com scopes é o mecanismo usado por muitos provedores grandes de autenticação, como o Facebook, Google, GitHub, Microsoft, Twitter, etc. para autorizar aplicativos de terceiros a interagir com suas APIs em nome de seus usuários. |
Loading…
Reference in new issue