One place for hosting & domains

      FlaskLogin

      Como adicionar uma autenticação ao seu aplicativo com o Flask-Login


      Introdução

      Permitir que usuários façam login em seu aplicativo é um dos recursos mais comuns a ser adicionado ao seu aplicativo Web. Este artigo irá abordar como adicionar uma autenticação ao seu aplicativo Flask com o pacote Flask-Login.

      Gif animado do aplicativo Flask e caixa de login

      Construiremos algumas páginas de inscrição e login que permitem que os usuários façam login e acessem páginas protegidas que usuários não autenticados são incapazes de visualizar. Coletaremos informações do modelo de usuário e as exibiremos em nossas páginas protegidas quando o usuário fizer login, de forma a simular como seria um perfil.

      Iremos abordar os seguintes assuntes neste artigo:

      • Como usar a biblioteca Flask-Login para o gerenciamento de sessão
      • Como usar o utilitário integrado do Flask para o hash de senhas
      • Como adicionar páginas protegidas ao nosso aplicativo acessíveis somente por usuários autenticados
      • Como usar o Flask-SQLAlchemy para criar um modelo de usuário
      • Como criar formulários de inscrição e login para que nossos usuários criem contas e façam login
      • Como exibir mensagens de erro aos usuários quando algo der errado
      • Como usar informações da conta do usuário para exibi-las na página do perfil

      O código fonte para este projeto está disponível no GitHub.

      Pré-requisitos

      Para concluir este tutorial, você precisará do seguinte:

      Nosso aplicativo irá utilizar os padrões de fábrica do aplicativo Flask com blueprints. Teremos um blueprint que processa tudo que está relacionado à autenticação e outro para nossas rotas regulares, que incluem o índice e a página de perfil protegida. Em um aplicativo real, é possível desmembrar as funcionalidades da maneira que achar melhor, mas a solução abordada aqui irá funcionar bem para este tutorial.

      Aqui está um diagrama para entendermos melhor como ficará a estrutura de arquivos do seu projeto após completar o tutorial:

      .
      └── flask_auth_app
          └── project
              ├── __init__.py       # setup our app
              ├── auth.py           # the auth routes for our app
              ├── db.sqlite         # our database
              ├── main.py           # the non-auth routes for our app
              ├── models.py         # our user model
              └── templates
                  ├── base.html     # contains common layout and links
                  ├── index.html    # show the home page
                  ├── login.html    # show the login form
                  ├── profile.html  # show the profile page
                  └── signup.html   # show the signup form
      

      À medida que avançarmos neste tutorial, iremos criar esses diretórios e arquivos.

      Passo 1 — Instalando os pacotes

      Existem três pacotes principais que precisamos para o nosso projeto:

      • Flask
      • Flask-Login: para processar as sessões de usuário após a autenticação
      • Flask-SQLAlchemy: para representar o modelo de usuário e interagir com nosso banco de dados

      Iremos utilizar o SQLite para evitar a instalação de dependências extras para o banco de dados.

      Primeiro, começaremos criando o diretório de projeto:

      Em seguida, precisamos navegar até o diretório de projeto:

      Crie um ambiente Python caso não tenha um. Dependendo da forma como o Python foi instalado em sua máquina, seus comandos serão semelhantes a:

      • python3 -m venv auth
      • source auth/bin/activate

      Nota: consulte o tutorial relevante ao seu ambiente de trabalho para configurar o venv.

      Execute os comandos a seguir a partir do seu ambiente virtual para instalar os pacotes necessários:

      • pip install flask flask-sqlalchemy flask-login

      Agora que você instalou os pacotes, tudo está pronto para a criação do arquivo principal do aplicativo.

      Passo 2 — Criando o arquivo principal do aplicativo

      Vamos começar criando um diretório project:

      O primeiro arquivo no qual trabalharemos será o arquivo __init__.py para o nosso projeto:

      Esse arquivo será responsável pela criação do nosso aplicativo, inicializando o banco de dados e registrando nossos blueprints. Neste momento, aparentemente nada irá mudar. No entanto, isso será necessário para o resto do nosso aplicativo. Precisamos inicializar o SQLAlachemy, definir alguns valores de configuração e registrar nossos blueprints aqui.

      project/__init__.py

      from flask import Flask
      from flask_sqlalchemy import SQLAlchemy
      
      # init SQLAlchemy so we can use it later in our models
      db = SQLAlchemy()
      
      def create_app():
          app = Flask(__name__)
      
          app.config['SECRET_KEY'] = 'secret-key-goes-here'
          app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
      
          db.init_app(app)
      
          # blueprint for auth routes in our app
          from .auth import auth as auth_blueprint
          app.register_blueprint(auth_blueprint)
      
          # blueprint for non-auth parts of app
          from .main import main as main_blueprint
          app.register_blueprint(main_blueprint)
      
          return app
      

      Agora que temos o arquivo principal do aplicativo, podemos começar a adicionar nossas rotas.

      Passo 3 — Adicionando rotas

      Para nossas rotas, iremos utilizar dois blueprints. Para o nosso blueprint principal, teremos uma página inicial (/) e uma página de perfil (/profile) para depois da sessão ser iniciada. Se o usuário tentar acessar a página de perfil sem estar autenticado, ele será enviado para a rota de login.

      Para o nosso blueprint de autenticação, teremos rotas para recuperar tanto a página de login (/login) quanto a página de inscrição (/sign-up). Também teremos rotas para lidar com as solicitações POST de ambas as rotas. Por fim, teremos uma rota de logoff (/logout) para efetuar o logoff de um usário ativo.

      Por enquanto, iremos definir login, signup e logout com devoluções simples. Iremos revisitá-los em um passo posterior e atualizá-los com a funcionalidade desejada.

      Primeiro, crie o main.py para o seu main_blueprint:

      project/main.py

      from flask import Blueprint
      from . import db
      
      main = Blueprint('main', __name__)
      
      @main.route('/')
      def index():
          return 'Index'
      
      @main.route('/profile')
      def profile():
          return 'Profile'
      

      Em seguida, crie o auth.py para o seu auth_blueprint:

      project/auth.py

      from flask import Blueprint
      from . import db
      
      auth = Blueprint('auth', __name__)
      
      @auth.route('/login')
      def login():
          return 'Login'
      
      @auth.route('/signup')
      def signup():
          return 'Signup'
      
      @auth.route('/logout')
      def logout():
          return 'Logout'
      

      Em um terminal, defina os valores FLASK_APP e FLASK_DEBUG:

      • export FLASK_APP=project
      • export FLASK_DEBUG=1

      A variável de ambiente FLASK_APP instrui o Flask sobre como carregar o aplicativo. Ela deve apontar para o local onde o create_app está localizado. Para o nosso caso, iremos apontar para o diretório project.

      A variável de ambiente FLASK_DEBUG é habilitada quando definida em 1. Isso irá habilitar um depurador que exibirá erros do aplicativo no navegador.

      Certifique-se de estar no diretório flask_auth_app e então execute o projeto:

      Agora, em um navegador, deve ser possível navegar até as cinco URLs possíveis e ver o texto retornado que foi definido em auth.py e main.py.

      Por exemplo, visitar o localhost:5000/profile exibe Profile:

      Captura de tela do projeto em localhost porta 5000 no navegador

      Agora que verificamos que nossas rotas estão se comportando conforme esperado, podemos seguir em frente para a criação de modelos.

      Passo 4 — Criando modelos

      Vamos continuar e criar os modelos usados em nosso aplicativo. Este é o primeiro passo antes de podermos implementar de fato a funcionalidade de login. Nosso aplicativo irá utilizar quatro modelos:

      • index.html
      • profile.html
      • login.html
      • signup.html

      Além disso, teremos um modelo base que terá partes de código comuns a cada uma das páginas. Neste caso, o modelo base terá links de navegação e o layout geral da página. Vamos criá-los agora.

      Primeiro, crie um diretório templates no seu diretório project:

      • mkdir -p project/templates

      Em seguida, crie o base.html:

      • nano project/templates/base.html

      Depois, adicione o código a seguir ao arquivo base.html:

      project/templates/base.html

      <!DOCTYPE html>
      <html>
      
      <head>
          <meta charset="utf-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Flask Auth Example</title>
          <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" />
      </head>
      
      <body>
          <section class="hero is-primary is-fullheight">
      
              <div class="hero-head">
                  <nav class="navbar">
                      <div class="container">
      
                          <div id="navbarMenuHeroA" class="navbar-menu">
                              <div class="navbar-end">
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.index') }}" class="navbar-item">
                                      Home
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.profile') }}" class="navbar-item">
                                      Profile
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}" class="navbar-item">
                                      Login
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.signup') }}" class="navbar-item">
                                      Sign Up
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.logout') }}" class="navbar-item">
                                      Logout
                                  </a>
                              </div>
                          </div>
                      </div>
                  </nav>
              </div>
      
              <div class="hero-body">
                  <div class="container has-text-centered">
                     {% block content %}
                     {% endblock %}
                  </div>
              </div>
          </section>
      </body>
      
      </html>
      

      Esse código irá criar uma série de links de menu para cada página do aplicativo e uma área onde o conteúdo irá aparecer.

      Nota: nos bastidores, estamos usando o Bulma para lidar com a estilização e o layout. Para entender mais sobre o Bulma, leia a documentação oficial do Bulma.

      Em seguida, crie o templates/index.html:

      • nano project/templates/index.html

      Adicione o código a seguir ao arquivo recém-criado para adicionar conteúdo à página:

      project/templates/index.html

      {% extends "base.html" %}
      
      {% block content %}
      <h1 class="title">
        Flask Login Example
      </h1>
      <h2 class="subtitle">
        Easy authentication and authorization in Flask.
      </h2>
      {% endblock %}
      

      Esse código irá criar um uma página básica de índice com um título e um subtítulo.

      Em seguida, crie o templates/login.html:

      • nano project/templates/login.html

      Esse código gera uma página de login com campos para E-mail e Senha. Há também uma caixa de seleção para “lembrar” uma sessão iniciada.

      project/templates/login.html

      {% extends "base.html" %}
      
      {% block content %}
      <div class="column is-4 is-offset-4">
          <h3 class="title">Login</h3>
          <div class="box">
              <form method="POST" action="/login">
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="password" name="password" placeholder="Your Password">
                      </div>
                  </div>
                  <div class="field">
                      <label class="checkbox">
                          <input type="checkbox">
                          Remember me
                      </label>
                  </div>
                  <button class="button is-block is-info is-large is-fullwidth">Login</button>
              </form>
          </div>
      </div>
      {% endblock %}
      

      Em seguida, crie o templates/signup.html:

      • nano project/templates/signup.html

      Adicione o código a seguir para criar uma página de inscrição com campos para e-mail, nome e senha:

      project/templates/signup.html

      {% extends "base.html" %}
      
      {% block content %}
      <div class="column is-4 is-offset-4">
          <h3 class="title">Sign Up</h3>
          <div class="box">
              <form method="POST" action="/signup">
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="email" name="email" placeholder="Email" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="text" name="name" placeholder="Name" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="password" name="password" placeholder="Password">
                      </div>
                  </div>
      
                  <button class="button is-block is-info is-large is-fullwidth">Sign Up</button>
              </form>
          </div>
      </div>
      {% endblock %}
      

      Em seguida, crie o templates/profile.html:

      • nano project/templates/profile.html

      Adicione este código para criar uma página simples com um título embutido em código para dar boas vindas ao Anthony:

      project/templates/profile.html

      {% extends "base.html" %}
      
      {% block content %}
      <h1 class="title">
        Welcome, Anthony!
      </h1>
      {% endblock %}
      

      Posteriormente, iremos adicionar um código para saudar dinamicamente qualquer usuário.

      Assim que os modelos forem adicionados, podemos atualizar as declarações de retorno em cada uma de nossas rotas para que retornem os modelos ao invés do texto.

      Em seguida, atualize o main.py alterando a linha de importação e as rotas para o index e profile:

      project/main.py

      from flask import Blueprint, render_template
      ...
      @main.route('/')
      def index():
          return render_template('index.html')
      
      @main.route('/profile')
      def profile():
          return render_template('profile.html')
      

      Agora, você irá atualizar o auth.py modificando a linha de importação e rotas para login e signup:

      project/auth.py

      from flask import Blueprint, render_template
      ...
      @auth.route('/login')
      def login():
          return render_template('login.html')
      
      @auth.route('/signup')
      def signup():
          return render_template('signup.html')
      

      Assim que as alterações forem feitas, a página de inscrição ficará desta forma, caso navegue até /sign-up:

      Página de inscrição em /signup

      Também deve ser possível ver as páginas para /, /login e /profile.

      Não iremos alterar /logout por enquanto, pois ela não irá exibir um modelo quando for finalizada.

      Passo 5 — Criando modelos de usuário

      Nosso modelo de usuário representa o que significa para o nosso aplicativo ter um usuário. Teremos campos para um endereço de e-mail, senha e nome. Em seu aplicativo, fique a vontade para decidir se quer que mais informações sejam armazenadas por usuário. É possível adicionar coisas como aniversário, imagem de perfil, localização ou qualquer preferência do usuário.

      Os modelos criados no Flask-SQLAlchemy são representados por classes que então se traduzem em tabelas em um banco de dados. Os atributos dessas classes transformam-se então em colunas para essas tabelas.

      Vamos continuar e criar o modelo de usuário:

      Este código cria um modelo de usuário com colunas para um id, email, password e name:

      project/models.py

      from . import db
      
      class User(db.Model):
          id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
          email = db.Column(db.String(100), unique=True)
          password = db.Column(db.String(100))
          name = db.Column(db.String(1000))
      

      Agora que um modelo de usuário foi criado, continue para a configuração do seu banco de dados.

      Passo 6 — Configurando o banco de dados

      Como mencionado nos pré-requisitos, iremos utilizar um banco de dados SQLite. Poderíamos criar um banco de dados SQLite por conta própria, mas vamos deixar o Flask-SQLAlchemy fazer isso por nós. Nós já temos o caminho do banco de dados especificado no arquivo __init__.py. Portanto, precisamos apenas dizer ao Flask-SQLAlchemy para criar o banco de dados no REPL do Python.

      Se você parar o aplicativo e abrir um REPL do Python, seremos capazes de criar o banco de dados usando o método create_all no objeto db. Certifique-se de que você ainda esteja no ambiente virtual e no diretório flask_auth_app.

      • from project import db, create_app
      • db.create_all(app=create_app()) # pass the create_app result so Flask-SQLAlchemy gets the configuration.

      Nota: caso usar o interpretador do Python seja algo novo para você, consulte a documentação oficial.

      Agora, você verá um arquivo db.sqlite em seu diretório de projeto. Esse banco de dados terá a nossa tabela de usuário em seu interior.

      Passo 7 — Configurando a função de autorização

      Para a nossa função de inscrição, enviaremos os dados que o usuário digita no formulário ao nosso banco de dados. Antes de adicioná-la, precisamos garantir que o usuário não exista no banco de dados. Se ele não existir, então precisamos nos certificar que a senha passará pelo hash antes de colocá-la no banco de dados, pois não queremos que nossas senhas sejam armazenadas em texto simples.

      Vamos começar adicionando uma segunda função para lidar com os dados do formulário POST. Nesta função, coletaremos dados passados pelo usuário primeiro.

      Crie a função e adicione um redirecionamento no final. Isso proporcionará uma experiência de usuário de uma inscrição bem-sucedida e direcionamento à Página de login.

      Atualize o auth.py alterando a linha de importação e implementando o signup_post:

      project/auth.py

      from flask import Blueprint, render_template, redirect, url_for
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          # code to validate and add user to database goes here
          return redirect(url_for('auth.login'))
      

      Agora, vamos adicionar o resto do código necessário para a inscrição de um usuário.

      Para começar, será necessário usar o objeto de solicitação para obter os dados do formulário.

      Continue atualizando o auth.py adicionando as importações e implementando o signup_post:

      auth.py

      from flask import Blueprint, render_template, redirect, url_for, request
      from werkzeug.security import generate_password_hash, check_password_hash
      from .models import User
      from . import db
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          email = request.form.get('email')
          name = request.form.get('name')
          password = request.form.get('password')
      
          user = User.query.filter_by(email=email).first() # if this returns a user, then the email already exists in database
      
          if user: # if a user is found, we want to redirect back to signup page so user can try again
              return redirect(url_for('auth.signup'))
      
          # create a new user with the form data. Hash the password so the plaintext version isn't saved.
          new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'))
      
          # add the new user to the database
          db.session.add(new_user)
          db.session.commit()
      
          return redirect(url_for('auth.login'))
      

      Nota: armazenar senhas em texto simples é considerada uma prática de segurança ruim. De maneira geral, é vantajoso utilizar um algoritmo de hash complexo e um valor de sal de senha para manter as senhas em segurança.

      Passo 8 — Testando o método de inscrição

      Agora que o método de inscrição está finalizado, devemos ser capazes de criar um novo usuário. Use o formulário para criar um usuário.

      Existem duas maneiras de verificar se a inscrição foi bem-sucedida: usando o visualizador do banco de dados para observar a linha que foi adicionada à sua tabela, ou tentando inscrever-se com o mesmo e-mail novamente e, caso receba um erro, saberá que o primeiro e-mail foi salvo corretamente. Portanto, vamos tomar esta segunda abordagem.

      Podemos adicionar um código para informar ao usuário que o e-mail já existe e pedir para que vá à página de login. Ao chamar a função flash, iremos enviar uma mensagem para a próxima solicitação, que, neste caso, é o redirecionamento. Dessa forma, a página em que chegarmos terá acesso a essa mensagem no modelo.

      Primeiro, adicionamos o flash antes de redirecionarmos o processo de volta à nossa página de inscrição.

      project/auth.py

      from flask import Blueprint, render_template, redirect, url_for, request, flash
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          ...
          if user: # if a user is found, we want to redirect back to signup page so user can try again
              flash('Email address already exists')
              return redirect(url_for('auth.signup'))
      

      Para o recebimento da mensagem de flash no modelo, podemos adicionar este código acima do formulário. Isso fará com que a mensagem seja exibida diretamente acima do formulário.

      project/templates/signup.html

      ...
      {% with messages = get_flashed_messages() %}
      {% if messages %}
          <div class="notification is-danger">
              {{ messages[0] }}. Go to <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}">login page</a>.
          </div>
      {% endif %}
      {% endwith %}
      <form method="POST" action="/signup">
      

      Caixa de inscrição exibindo uma mensagem de que o

      Passo 9 — Adicionando o método de login

      O método de login é semelhante à função de inscrição, já que também iremos pegar informações do usuário para fazer algo com elas. Neste caso, iremos analisar o endereço de e-mail inserido para checar se ele está presente no banco de dados. Caso esteja, iremos testar a senha fornecida pelo usuário utilizando o hash na senha passada pelo usuário e comparando-a com a senha já com hash no banco de dados. Sabemos que o usuário digitou a senha correta quando ambas as senhas com hash correspondem.

      Assim que o usuário passar pela checagem de senha, sabemos que ele possui as credenciais corretas e podemos autenticá-los usando o Flask-Login. Ao chamar o login_user, o Flask-Login irá criar uma sessão para aquele usuário que irá persistir enquanto o usuário permanecer conectado. Isso permitirá que o usuário visualize páginas protegidas.

      Podemos começar com uma nova rota para o manuseio dos dados em POST. Redirecionaremos o usuário para a página de perfil quando o login for realizado com sucesso:

      project/auth.py

      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          # login code goes here
          return redirect(url_for('main.profile'))
      

      Agora, é necessário verificar se o usuário possui as credenciais corretas:

      project/auth.py

      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          email = request.form.get('email')
          password = request.form.get('password')
          remember = True if request.form.get('remember') else False
      
          user = User.query.filter_by(email=email).first()
      
          # check if the user actually exists
          # take the user-supplied password, hash it, and compare it to the hashed password in the database
          if not user or not check_password_hash(user.password, password):
              flash('Please check your login details and try again.')
              return redirect(url_for('auth.login')) # if the user doesn't exist or password is wrong, reload the page
      
          # if the above check passes, then we know the user has the right credentials
          return redirect(url_for('main.profile'))
      

      Vamos adicionar o bloco de código no modelo para que o usuário possa ver a mensagem em flash. Assim como no formulário de inscrição, vamos adicionar a mensagem de erro em potencial diretamente acima do formulário:

      project/templates/login.html

      ...
      {% with messages = get_flashed_messages() %}
      {% if messages %}
          <div class="notification is-danger">
              {{ messages[0] }}
          </div>
      {% endif %}
      {% endwith %}
      <form method="POST" action="/login">
      

      Agora, somos capazes de dizer que um usuário foi conectado com sucesso, mas não há nada para fazer o login do usuário. É aqui onde trazemos o Flask-Login para o gerenciamento de sessões de usuário.

      Antes de iniciarmos, precisamos de algumas coisas para que o Flask-Login funcione. Comece adicionando o UserMixin ao seu Modelo de usuário. O UserMixin irá adicionar atributos do Flask-Login ao modelo para que o Flask-Login seja capaz de trabalhar com ele.

      models.py

      from flask_login import UserMixin
      from . import db
      
      class User(UserMixin, db.Model):
          id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
          email = db.Column(db.String(100), unique=True)
          password = db.Column(db.String(100))
          name = db.Column(db.String(1000))
      

      Em seguida, é necessário especificar nosso carregador de usuário. Um carregador de usuário informa ao Flask-Login como encontrar um usuário específico a partir do ID armazenado em seu cookie de sessão. Podemos adicionar isso em nossa função create_app, juntamente com o código init para o Flask-Login:

      project/__init__.py

      ...
      from flask_login import LoginManager
      ...
      def create_app():
          ...
          db.init_app(app)
      
          login_manager = LoginManager()
          login_manager.login_view = 'auth.login'
          login_manager.init_app(app)
      
          from .models import User
      
          @login_manager.user_loader
          def load_user(user_id):
              # since the user_id is just the primary key of our user table, use it in the query for the user
              return User.query.get(int(user_id))
      

      Por fim, adicionamos a função login_user um pouco antes de redirecionarmos o usuário para a página de perfil para criar a sessão:

      project/auth.py

      from flask_login import login_user
      from .models import User
      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          ...
          # if the above check passes, then we know the user has the right credentials
          login_user(user, remember=remember)
          return redirect(url_for('main.profile'))
      

      Com o Flask-Login configurado, podemos usar a rota /login. Quando tudo estiver no lugar, você verá a página de perfil.

      Página de perfil com

      Passo 10 — Protegendo as páginas

      Se o seu nome não é Anthony, então verá que a mensagem está errada. O que queremos é que o perfil exiba o nome presente no banco de dados. Portanto, primeiro, é necessário proteger a página e então acessar os dados do usuário para obter o seu nome.

      Para proteger uma página ao usar o Flask-Login, adicionamos o decorador @login_requried entre a rota e a função. Isso impede que um usuário que não esteja conectado veja a rota. Se o usuário não estiver conectado, ele será redirecionado para a página de login, conforme a configuração do Flask-Login.

      Com as rotas decoradas com o decorador @login_required, somos capazes de usar o objeto current_user dentro da função. O current_user representa o usuário do banco de dados e podemos acessar todos os atributos desse usuário com uma notação de ponto. Por exemplo, current_user.email, current_user.password, current_user.name e current_user.id irão retornar os valores reais armazenados no banco de dados para o usuário conectado.

      Vamos usar o nome do usuário atual e enviá-lo ao modelo. Em seguida, usaremos esse nome e exibiremos seu valor.

      project/main.py

      from flask_login import login_required, current_user
      ...
      @main.route('/profile')
      @login_required
      def profile():
          return render_template('profile.html', name=current_user.name)
      

      Depois disso, no arquivo profile.html, atualize a página para exibir o valor name:

      project/templates/profile.html

      ...
      <h1 class="title">
        Welcome, {{ name }}!
      </h1>
      

      Ao acessarmos nossa página de perfil, vemos agora que o nome do usuário aparece.

      Página de boas-vindas com o nome do usuário atualmente conectado

      A última coisa que podemos fazer é atualizar a visualização de logoff. Podemos chamar a função logout_user em uma rota para o logoff. Adicionamos o decorador @login_required porque não faz sentido fazer logoff de um usuário que não esteja conectado em primeiro lugar.

      project/auth.py

      from flask_login import login_user, logout_user, login_required
      ...
      @auth.route('/logout')
      @login_required
      def logout():
          logout_user()
          return redirect(url_for('main.index'))
      

      Depois de fazer o logoff, se tentarmos visualizar a página de perfil novamente, vemos que uma mensagem de erro aparece. Isso ocorre porque o Flask-Login exibe uma mensagem quando o usuário não é autorizado a acessar uma página.

      Página de login com uma mensagem mostrando que o usuário deve fazer login para acessar a página

      Uma última coisa que podemos fazer é colocar declarações if nos modelos para que apenas os links relevantes ao usuário sejam exibidos. Assim, antes do usuário fazer login, haverá a opção de fazer login ou se inscrever. Após ter feito o login, é possível ir ao perfil ou fazer logoff:

      templates/base.html

      ...
      <div class="navbar-end">
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.index') }}" class="navbar-item">
              Home
          </a>
          {% if current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.profile') }}" class="navbar-item">
              Profile
          </a>
          {% endif %}
          {% if not current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}" class="navbar-item">
              Login
          </a>
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.signup') }}" class="navbar-item">
              Sign Up
          </a>
          {% endif %}
          {% if current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.logout') }}" class="navbar-item">
              Logout
          </a>
          {% endif %}
      </div>
      

      Página inicial com as opções Home, Login e Sign Up no topo da tela

      Com isso, você terminou de construir seu aplicativo de autenticação com sucesso.

      Conclusão

      Utilizamos neste tutorial o Flask-Login e o Flask-SQLAlchemy para construir um sistema de login para o nosso aplicativo e abordamos como autenticar um usuário. Primeiro, criamos um modelo de usuário e armazenamos suas informações. Em seguida, foi necessário verificar se a senha do usuário estava correta utilizando o hash na senha do formulário e comparando-a com a armazenada no banco de dados. Por fim, adicionamos a autorização ao nosso aplicativo usando o decorador @login_required em uma página de perfil para que apenas usuários conectados possam ver essa página.

      O que criamos neste tutorial será o suficiente para aplicativos menores, mas se você quiser ter mais funcionalidades logo no início, considere usar as bibliotecas Flask-User ou Flask-Security, que são desenvolvidas com base na biblioteca Flask-Login.



      Source link

      Добавление аутентификации в ваше приложение с помощью Flask-Login


      Введение

      Предоставление пользователям возможности ввода учетных данных для входа — одна из самых распространенных возможностей, добавляемых в веб-приложения. В этой статье мы расскажем, как добавить аутентификацию в ваше приложение Flask с помощью пакета Flask-Login.

      Анимированный файл gif с изображением приложения Flask и поля входа

      Мы построим несколько образцов страниц регистрации и входа, которые позволят пользователям входить в приложение и видеть защищенные страницы, невидимые для пользователей, не выполнивших вход. Мы соберем информацию пользовательской модели и будем выводить ее на наших защищенных страницах при входе пользователя, чтобы смоделировать вид профиля.

      В этой статье мы затронем следующие вопросы:

      • Использование библиотеки Flask-Login для управления сеансами
      • Использование встроенной утилиты Flask для хэширования паролей
      • Добавление в приложение защищенных страниц для пользователей, не выполнивших вход
      • Использование Flask-SQLAlchemy для создания пользовательской модели
      • Создание форм регистрации и входа для создания учетных записей пользователей и входа
      • Вывод пользователям сообщений об ошибках, если что-то идет не так
      • Использование информации учетной записи пользователя для отображения на странице профиля

      Исходный код этого проекта доступен на GitHub.

      Предварительные требования

      Для этого обучающего модуля вам потребуется следующее:

      Наше приложение будет использовать шаблон фабрики приложений Flask с готовыми проектами. У нас будет один проект, отвечающий за все связанное с аутентификацией, и еще один проект для обычных маршрутов, включая указатель и страницу защищенного профиля. В реальном приложении функции можно распределять любым удобным образом, однако описанное здесь решение хорошо подходит для этого учебного модуля.

      Здесь имеется схема, которая поможет понять, как будет выглядеть структура файлов проекта после завершения прохождения учебного модуля:

      .
      └── flask_auth_app
          └── project
              ├── __init__.py       # setup our app
              ├── auth.py           # the auth routes for our app
              ├── db.sqlite         # our database
              ├── main.py           # the non-auth routes for our app
              ├── models.py         # our user model
              └── templates
                  ├── base.html     # contains common layout and links
                  ├── index.html    # show the home page
                  ├── login.html    # show the login form
                  ├── profile.html  # show the profile page
                  └── signup.html   # show the signup form
      

      По мере выполнения этого учебного модуля мы будем создавать эти каталоги и файлы.

      Шаг 1 — Установка пакетов

      Для нашего проекта нам потребуется три основных проекта:

      • Flask
      • Flask-Login: для обработки пользовательских сеансов после аутентификации
      • Flask-SQLAlchemy: для представления пользовательской модели и интерфейса с нашей базой данных

      Мы будем использовать SQLite, чтобы не устанавливать дополнительные зависимости для базы данных.

      Для начала мы создадим каталог проекта:

      Затем нам нужно будет перейти в каталог проекта:

      Если у нас нет среды Python, нам нужно будет ее создать. В зависимости от способа установки Python на вашем компьютере команды будут выглядеть примерно так:

      • python3 -m venv auth
      • source auth/bin/activate

      Примечание. Вы можете использовать учебный модуль по вашей локальной среде для получения информации по настройке venv.

      Выполните в своей виртуальной среде следующие команды для установки необходимых пакетов:

      • pip install flask flask-sqlalchemy flask-login

      Мы установили пакеты и теперь можем создать основной файл приложения.

      Шаг 2 — Создание главного файла приложения

      Для начала мы создадим каталог проекта:

      В первую очередь мы начнем работать над файлом __init__.py для нашего проекта:

      Этот файл будет содержать функцию создания нашего приложения, которая инициализирует базу данных и зарегистрирует наши проекты. На данный момент мы не увидим изменений, но это потребуется для создания остальных частей приложения. Нам нужно будет инициализировать SQLAlchemy, настроить определенные значения конфигурации и зарегистрировать здесь наши проекты.

      project/__init__.py

      from flask import Flask
      from flask_sqlalchemy import SQLAlchemy
      
      # init SQLAlchemy so we can use it later in our models
      db = SQLAlchemy()
      
      def create_app():
          app = Flask(__name__)
      
          app.config['SECRET_KEY'] = 'secret-key-goes-here'
          app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'
      
          db.init_app(app)
      
          # blueprint for auth routes in our app
          from .auth import auth as auth_blueprint
          app.register_blueprint(auth_blueprint)
      
          # blueprint for non-auth parts of app
          from .main import main as main_blueprint
          app.register_blueprint(main_blueprint)
      
          return app
      

      Теперь у нас готов основной файл приложения, и мы можем начать добавление маршрутов.

      Шаг 3 — Добавление маршрутов

      Для наших маршрутов мы будем использовать два проекта. В основном проекте у нас будет главная страница (/) и страница профиля (/profile), открываемая после входа. Если пользователь попытается получить доступ к странице профиля без входа в систему, он будет направлен на маршрут входа.

      В нашем проекте auth у нас будут маршруты для получения страницы входа (/login) и страницы регистрации (/sign-up). Также у нас имеются маршруты для обработки запросов POST от обоих этих маршрутов. Наконец, у нас имеется маршрут выхода (/logout) для выхода активного пользователя из системы.

      Пока что мы определим login, signup и logout простыми возвратами. Мы вернемся к ним немного позднее и обновим их, добавив желаемые функции.

      Вначале создайте файл main.py для main_blueprint:

      project/main.py

      from flask import Blueprint
      from . import db
      
      main = Blueprint('main', __name__)
      
      @main.route('/')
      def index():
          return 'Index'
      
      @main.route('/profile')
      def profile():
          return 'Profile'
      

      Затем создайте файл auth.py для auth_blueprint:

      project/auth.py

      from flask import Blueprint
      from . import db
      
      auth = Blueprint('auth', __name__)
      
      @auth.route('/login')
      def login():
          return 'Login'
      
      @auth.route('/signup')
      def signup():
          return 'Signup'
      
      @auth.route('/logout')
      def logout():
          return 'Logout'
      

      В терминале вы можете задать значения FLASK_APP и FLASK_DEBUG:

      • export FLASK_APP=project
      • export FLASK_DEBUG=1

      Переменная среды FLASK_APP сообщает Flask, как загружать приложение. Она должна указывать на место создания create_app. Для наших целей мы будем использовать указатели на каталог project.

      Переменная среды FLASK_DEBUG активируется посредством присвоения ей значения 1. Это активирует отладчик, который будет отображать в браузере ошибки приложения.

      Убедитесь, что вы находитесь в каталоге flask_auth_app и запустите проект:

      Теперь у вас должна появиться возможность открыть в браузере пять возможных URL-адресов и увидеть возвращаемый текст, определенный в файлах auth.py и main.py.

      Например, если открыть адрес localhost:5000/profile, появится: Profile:

      Снимок экрана проекта с открытым адресом localhost:5000 в браузере

      Мы проверили поведение наших маршрутов и можем перейти к созданию шаблонов.

      Шаг 4 — Создание шаблонов

      Давайте продолжим и создадим шаблоны, которые используются в нашем приложении. Это будет первый шаг, прежде чем мы сможем реализовать реальную функцию входа. Наше приложение будет использовать четыре шаблона:

      • index.html
      • profile.html
      • login.html
      • signup.html

      Также у нас будет базовый шаблон, который будет содержать общий код для каждой из страниц. В данном случае базовый шаблон будет содержать ссылки навигации и общий макет страницы. Давайте создадим их сейчас.

      Для начала создайте каталог templates в каталоге project:

      • mkdir -p project/templates

      Затем создайте base.html:

      • nano project/templates/base.html

      Добавьте следующий код в файл base.html:

      project/templates/base.html

      <!DOCTYPE html>
      <html>
      
      <head>
          <meta charset="utf-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Flask Auth Example</title>
          <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" />
      </head>
      
      <body>
          <section class="hero is-primary is-fullheight">
      
              <div class="hero-head">
                  <nav class="navbar">
                      <div class="container">
      
                          <div id="navbarMenuHeroA" class="navbar-menu">
                              <div class="navbar-end">
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.index') }}" class="navbar-item">
                                      Home
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.profile') }}" class="navbar-item">
                                      Profile
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}" class="navbar-item">
                                      Login
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.signup') }}" class="navbar-item">
                                      Sign Up
                                  </a>
                                  <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.logout') }}" class="navbar-item">
                                      Logout
                                  </a>
                              </div>
                          </div>
                      </div>
                  </nav>
              </div>
      
              <div class="hero-body">
                  <div class="container has-text-centered">
                     {% block content %}
                     {% endblock %}
                  </div>
              </div>
          </section>
      </body>
      
      </html>
      

      Этот код создаст серию ссылок меню на каждую страницу приложения, а также область, где будет отображаться контент.

      Примечание. За кулисами мы используем Bulma для работы со стилями и макетом. Чтобы узнать больше о Bulma, ознакомьтесь с официальной документацией Bulma.

      Затем создайте файл templates/index.html:

      • nano project/templates/index.html

      Добавьте в созданный файл следующий код, чтобы заполнить страницу содержанием:

      project/templates/index.html

      {% extends "base.html" %}
      
      {% block content %}
      <h1 class="title">
        Flask Login Example
      </h1>
      <h2 class="subtitle">
        Easy authentication and authorization in Flask.
      </h2>
      {% endblock %}
      

      Этот код создаст базовую страницу указателя с заголовком и подзаголовком.

      Затем создайте страницу templates/login.html:

      • nano project/templates/login.html

      Этот код генерирует страницу входа с полями Email и Password. Также имеется поле для отметки запоминания сеанса входа.

      project/templates/login.html

      {% extends "base.html" %}
      
      {% block content %}
      <div class="column is-4 is-offset-4">
          <h3 class="title">Login</h3>
          <div class="box">
              <form method="POST" action="/login">
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="password" name="password" placeholder="Your Password">
                      </div>
                  </div>
                  <div class="field">
                      <label class="checkbox">
                          <input type="checkbox">
                          Remember me
                      </label>
                  </div>
                  <button class="button is-block is-info is-large is-fullwidth">Login</button>
              </form>
          </div>
      </div>
      {% endblock %}
      

      Создайте шаблон templates/signup.html:

      • nano project/templates/signup.html

      Добавьте в файл следующий код, чтобы создать страницу регистрации с полями адреса электронной почты, имени и пароля:

      project/templates/signup.html

      {% extends "base.html" %}
      
      {% block content %}
      <div class="column is-4 is-offset-4">
          <h3 class="title">Sign Up</h3>
          <div class="box">
              <form method="POST" action="/signup">
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="email" name="email" placeholder="Email" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="text" name="name" placeholder="Name" autofocus="">
                      </div>
                  </div>
      
                  <div class="field">
                      <div class="control">
                          <input class="input is-large" type="password" name="password" placeholder="Password">
                      </div>
                  </div>
      
                  <button class="button is-block is-info is-large is-fullwidth">Sign Up</button>
              </form>
          </div>
      </div>
      {% endblock %}
      

      Затем создайте шаблон templates/profile.html:

      • nano project/templates/profile.html

      Добавьте этот код, чтобы создать простую страницу с закодированным заголовком, welcome Anthony:

      project/templates/profile.html

      {% extends "base.html" %}
      
      {% block content %}
      <h1 class="title">
        Welcome, Anthony!
      </h1>
      {% endblock %}
      

      Позднее мы добавим код для динамического приветствия пользователя.

      После добавления шаблонов мы можем обновить выражения возврата в каждом из маршрутов, чтобы вместо текста возвращались шаблоны.

      Обновите файл main.py, изменив строку импорта и маршруты index и profile:

      project/main.py

      from flask import Blueprint, render_template
      ...
      @main.route('/')
      def index():
          return render_template('index.html')
      
      @main.route('/profile')
      def profile():
          return render_template('profile.html')
      

      Теперь мы обновим auth.py, изменив строку импорта и маршруты login и signup:

      project/auth.py

      from flask import Blueprint, render_template
      ...
      @auth.route('/login')
      def login():
          return render_template('login.html')
      
      @auth.route('/signup')
      def signup():
          return render_template('signup.html')
      

      После внесения этих изменений страница регистрации будет следующим образом при переходе в /sign-up:

      Страница регистрации в /signup

      Теперь вы должны видеть страницы /, /login и /profile.

      Пока что мы оставим /logout отдельно, потому что эта страница не отображает шаблон.

      Шаг 5 — Создание пользовательской модели

      Наша пользовательская модель отражает, что означает наличие пользователя для нашего приложения. У нас есть поля адреса эл. почты, пароля и имени. В приложении вы можете указать, хотите ли хранить больше информации для каждого пользователя. Вы можете добавлять такие вещи как день рождения, изображение профиля или предпочтения пользователя.

      Модели, созданные в Flask-SQLAlchemy, представляются классами, которые преобразуются в таблицу в базе данных. Атрибуты этих классов превратятся в столбцы этих таблиц.

      Давайте вместе создадим эту пользовательскую модель:

      Этот код создает пользовательскую модель со столбцами id, email, password и name:

      project/models.py

      from . import db
      
      class User(db.Model):
          id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
          email = db.Column(db.String(100), unique=True)
          password = db.Column(db.String(100))
          name = db.Column(db.String(1000))
      

      Мы создали пользовательскую модель и теперь можем перейти к настройке базы данных.

      Шаг 6 — Настройка базы данных

      Как указано в разделе «Предварительные требования», мы будем использовать базу данных SQLite. Мы можем создать базу данных SQLite самостоятельно, но сейчас используем для этого Flask-SQLAlchemy. Мы уже указали путь к базе данных в файле __init__.py, так что нам нужно просто указать Flask-SQLAlchemy создать базу данных на Python REPL.

      Если вы остановите приложение и откроете Python REPL, мы сможем создать базу данных, используя метод create_all для объекта db. Убедитесь, что вы все еще находитесь в виртуальной среде и в каталоге flask_auth_app.

      • from project import db, create_app
      • db.create_all(app=create_app()) # pass the create_app result so Flask-SQLAlchemy gets the configuration.

      Примечание. Если вы незнакомы с использованием интерпретатора Python, вы можете проконсультироваться с официальной документацией.

      Теперь вы видите файл db.sqlite в каталоге проекта. В этой базе данных будет наша пользовательская таблица.

      Шаг 7 — Настройка функции авторизации

      Для нашей функции регистрации мы возьмем данные, вводимые пользователем в форму, и добавим их в нашу базу данных. Прежде чем добавить это, нам нужно убедиться, что пользователя еще нет в базе данных. Если его нет, нам нужно хэшировать пароль, прежде чем поместить его в базу данных, потому что мы не хотим хранить пароли в формате обычного текста.

      Для начала давайте добавим вторую функцию для обработки данных формы POST. В этой функции мы вначале соберем данные, которые были переданы пользователем.

      Создайте функцию и добавьте в ее конец переадресацию. В результате после успешной регистрации пользователь будет переадресован на страницу входа.

      Обновите файл auth.py, изменив строку import и реализовав signup_post:

      project/auth.py

      from flask import Blueprint, render_template, redirect, url_for
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          # code to validate and add user to database goes here
          return redirect(url_for('auth.login'))
      

      Теперь добавим остальной код, необходимый для регистрации пользователя.

      Для начала нам нужно будет использовать объект запроса для получения данных формы.

      Продолжим обновлять файл auth.py, добавляя элементы импорта и реализуя signup_post:

      auth.py

      from flask import Blueprint, render_template, redirect, url_for, request
      from werkzeug.security import generate_password_hash, check_password_hash
      from .models import User
      from . import db
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          email = request.form.get('email')
          name = request.form.get('name')
          password = request.form.get('password')
      
          user = User.query.filter_by(email=email).first() # if this returns a user, then the email already exists in database
      
          if user: # if a user is found, we want to redirect back to signup page so user can try again
              return redirect(url_for('auth.signup'))
      
          # create a new user with the form data. Hash the password so the plaintext version isn't saved.
          new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'))
      
          # add the new user to the database
          db.session.add(new_user)
          db.session.commit()
      
          return redirect(url_for('auth.login'))
      

      Примечание. Хранение паролей в формате обычного текста считается плохой практикой с точки зрения безопасности. Обычно для защиты паролей нужно использовать сложный алгоритм хэширования и шифрование паролей.

      Шаг 8 — Тестирование метода регистрации

      У нас готов метод регистрации, и теперь мы можем создать нового пользователя. Используйте форму для создания пользователя.

      Существует два способа проверить регистрацию: использовать инструмент просмотра БД для поиска строки, добавленную в таблицу, или попробовать зарегистрироваться с тем же адресом электронной почты, и если вы получите сообщение об ошибке, это будет означать, что первый адрес электронной почты был сохранен правильно. Используем этот подход.

      Мы можем добавить код, чтобы сообщить пользователю, что адрес электронной почты уже существует, а затем отправить пользователя на страницу входа. Вызывая функцию flash, мы отправим сообщение для следующего запроса, в данном случае это запрос переадресации. На итоговой странице мы получим доступ к этому сообщение в шаблоне.

      Вначале мы добавим элемент flash, выводимый до возврата на страницу регистрации.

      project/auth.py

      from flask import Blueprint, render_template, redirect, url_for, request, flash
      ...
      @auth.route('/signup', methods=['POST'])
      def signup_post():
          ...
          if user: # if a user is found, we want to redirect back to signup page so user can try again
              flash('Email address already exists')
              return redirect(url_for('auth.signup'))
      

      Чтобы добавить в шаблон мигающее сообщение, нужно добавить над формой этот код. Так сообщение будет выводиться непосредственно над формой.

      project/templates/signup.html

      ...
      {% with messages = get_flashed_messages() %}
      {% if messages %}
          <div class="notification is-danger">
              {{ messages[0] }}. Go to <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}">login page</a>.
          </div>
      {% endif %}
      {% endwith %}
      <form method="POST" action="/signup">
      

      Поле регистрации с сообщением «Email address already exists. Go to login page» в темно-розовом поле

      Шаг 9 — Добавление метода входа

      Метод входа похож на функцию регистрации тем, что мы берем информацию пользователя и что-то делаем с ней. В данном случае мы сравниваем введенный адрес электронной почты, чтобы проверить его наличие в базе данных. Если он там есть, мы протестируем указанный пользователем пароль, выполнив хэширование введенного пользователем пароля и сравнив его с хэшированным паролем в базе данных. Если оба хэшированных пароля совпадают, мы понимаем, что пользователь ввел правильный пароль.

      После успешной проверки пароля мы знаем, что учетные данные пользователя верны, и мы можем разрешить ему вход в систему, используя Flask-Login. Вызывая login_user, Flask-Login создает сеанс этого пользователя, который сохраняется все время, пока пользователь остается в системе, и позволяет пользователю просматривать защищенные страницы.

      Мы можем начать с нового маршрута обработки данных, переданных через метод POST. Мы будем выполнять переадресацию на страницу профиля после успешного входа пользователя:

      project/auth.py

      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          # login code goes here
          return redirect(url_for('main.profile'))
      

      Теперь нам нужно убедиться, что учетные данные пользователя введены верно:

      project/auth.py

      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          email = request.form.get('email')
          password = request.form.get('password')
          remember = True if request.form.get('remember') else False
      
          user = User.query.filter_by(email=email).first()
      
          # check if the user actually exists
          # take the user-supplied password, hash it, and compare it to the hashed password in the database
          if not user or not check_password_hash(user.password, password):
              flash('Please check your login details and try again.')
              return redirect(url_for('auth.login')) # if the user doesn't exist or password is wrong, reload the page
      
          # if the above check passes, then we know the user has the right credentials
          return redirect(url_for('main.profile'))
      

      Добавим в шаблон блок, чтобы пользователь мог видеть мигающее сообщение. Как и в случае с формой регистрации, добавим потенциальное сообщение об ошибке непосредственно над формой:

      project/templates/login.html

      ...
      {% with messages = get_flashed_messages() %}
      {% if messages %}
          <div class="notification is-danger">
              {{ messages[0] }}
          </div>
      {% endif %}
      {% endwith %}
      <form method="POST" action="/login">
      

      Теперь мы можем указать, что пользователь успешно выполнил вход, но пользователю пока некуда входить. На этом этапе мы используем Flask-Login для управления сеансами пользователя.

      Прежде чем начать, нам потребуется несколько вещей для работы Flask-Login. Для начала добавьте UserMixin в пользовательскую модель. UserMixin добавит в модель атрибуты Flask-Login, чтобы Flask-Login мог с ней работать.

      models.py

      from flask_login import UserMixin
      from . import db
      
      class User(UserMixin, db.Model):
          id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy
          email = db.Column(db.String(100), unique=True)
          password = db.Column(db.String(100))
          name = db.Column(db.String(1000))
      

      Затем нам нужно указать загрузчик пользователя. Загрузчик пользователя сообщает Flask-Login, как найти определенного пользователя по идентификатору, сохраненному в файле cookie сеанса. Мы можем добавить его в функцию create_app вместе с кодом init для Flask-Login:

      project/__init__.py

      ...
      from flask_login import LoginManager
      ...
      def create_app():
          ...
          db.init_app(app)
      
          login_manager = LoginManager()
          login_manager.login_view = 'auth.login'
          login_manager.init_app(app)
      
          from .models import User
      
          @login_manager.user_loader
          def load_user(user_id):
              # since the user_id is just the primary key of our user table, use it in the query for the user
              return User.query.get(int(user_id))
      

      В заключение мы можем добавить функцию login_user перед переадресацией на страницу профиля для создания сеанса:

      project/auth.py

      from flask_login import login_user
      from .models import User
      ...
      @auth.route('/login', methods=['POST'])
      def login_post():
          ...
          # if the above check passes, then we know the user has the right credentials
          login_user(user, remember=remember)
          return redirect(url_for('main.profile'))
      

      С помощью Flask-Login мы можем использовать маршрут /login. Когда все размещено правильно, вы увидите страницу профиля.

      Страница профиля с текстом «Welcome, Anthony!»

      Шаг 10 — Защита страниц

      Если ваше имя не Anthony, вы увидите, что ваше имя указано неправильно. Нам нужно, чтобы профиль отображал имя в базе данных. Вначале следует защитить страницу, а затем получить доступ к данным пользователя для получения его имени.

      Чтобы защитить страницу при использовании Flask-Login, мы добавим декоратор @login_requried между маршрутом и функцией. Это не даст пользователю, не выполнившему вход в систему, увидеть этот маршрут. Если пользователь не выполнил вход, он будет переадресован на страницу входа согласно конфигурации Flask-Login.

      Используя маршруты с декоратором @login_required, мы можем использовать объект current_user внутри функций. Этот объект current_user представляет пользователя из базы данных, и мы можем получить доступ ко всем атрибутам этого пользователя, используя точечную нотацию. Например, current_user.email, current_user.password, current_user.name и current_user.id будут возвращать реальные значения, хранящиеся в базе данных для пользователя, который выполнил вход в систему.

      Давайте используем имя текущего пользователя и отправим его в шаблон. Затем мы используем это имя и выведем его значение.

      project/main.py

      from flask_login import login_required, current_user
      ...
      @main.route('/profile')
      @login_required
      def profile():
          return render_template('profile.html', name=current_user.name)
      

      Затем в файле profile.html мы обновим страницу для отображения значения name:

      project/templates/profile.html

      ...
      <h1 class="title">
        Welcome, {{ name }}!
      </h1>
      

      Когда мы перейдем на страницу профиля, мы увидим, что там отображается имя пользователя.

      Страница приветствия пользователя с именем пользователя, выполнившего вход в систему

      В заключение мы можем обновить представление выхода из системы. Мы можем вызвать функцию logout_user в маршруте выхода. Мы используем декоратор @login_required, потому что не имеет смысла выполнять выход для пользователя, который не выполнил вход.

      project/auth.py

      from flask_login import login_user, logout_user, login_required
      ...
      @auth.route('/logout')
      @login_required
      def logout():
          logout_user()
          return redirect(url_for('main.index'))
      

      После выхода и повторной попытки просмотра страницы профиля мы должны увидеть сообщение об ошибке. Это связано с тем, что Flask-Login выводит мигающее сообщение, когда пользователю не разрешен доступ к странице.

      Страница входа с сообщением, показывающим, что для доступа пользователь должен выполнить вход

      В заключение мы поместим в шаблоны выражения if, чтобы отображать только ссылки, актуальные для пользователя. До входа в систему у пользователя должна быть возможность выполнить вход или зарегистрироваться. После входа у него должна быть возможность перейти в свой профиль или выйти из системы:

      templates/base.html

      ...
      <div class="navbar-end">
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.index') }}" class="navbar-item">
              Home
          </a>
          {% if current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("main.profile') }}" class="navbar-item">
              Profile
          </a>
          {% endif %}
          {% if not current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.login') }}" class="navbar-item">
              Login
          </a>
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.signup') }}" class="navbar-item">
              Sign Up
          </a>
          {% endif %}
          {% if current_user.is_authenticated %}
          <a href="https://www.digitalocean.com/community/tutorials/{{ url_for("auth.logout') }}" class="navbar-item">
              Logout
          </a>
          {% endif %}
      </div>
      

      Главная страница с элементами навигации Home (Главная), Login (Вход) и Sign Up (Регистрация) в верхней части экрана

      Мы успешно создали приложение с аутентификацией.

      Заключение

      Мы использовали Flask-Login и Flask-SQLAlchemy для создания системы входа в наше приложение. Мы рассказали о том, как организовать аутентификацию пользователей посредством создания пользовательской модели и сохранения данных пользователя. Затем нам нужно было проверить правильность пароля пользователя, выполнив хэширование пароля из формы, и сравнив его с сохраненным в базе данных. В заключение мы добавили в приложение авторизацию, используя декоратор @login_required на странице профиля, чтобы пользователи могли видеть ее только после входа.

      Того, что мы создали в этом учебном модуле, будет достаточно для небольших приложений, но если вы хотите с самого начала использовать больше функций, подумайте об использовании библиотек Flask-User или Flask-Security, которые построены на базе библиотеки Flask-Login.



      Source link