用 Flask 来写个轻博客 (21) — 结合 reCAPTCHA 验证码实现用户注册与登录

Blog 项目源码:https://github.com/JmilkFan/JmilkFan-s-Blog

目录

前文列表

用 Flask 来写个轻博客 (1) — 创建项目
用 Flask 来写个轻博客 (2) — Hello World!
用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy
用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表
用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解
用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)
用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)
用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级
用 Flask 来写个轻博客 (9) — M(V)C_Jinja 语法基础快速概览
用 Flask 来写个轻博客 (10) — M(V)C_Jinja 常用过滤器与 Flask 特殊变量及方法
用 Flask 来写个轻博客 (11) — M(V)C_创建视图函数
用 Flask 来写个轻博客 (12) — M(V)C_编写和继承 Jinja 模板
用 Flask 来写个轻博客 (13) — M(V)C_WTForms 服务端表单检验
用 Flask 来写个轻博客 (14) — M(V)C_实现项目首页的模板
用 Flask 来写个轻博客 (15) — M(V)C_实现博文页面评论表单
用 Flask 来写个轻博客 (16) — MV(C)_Flask Blueprint 蓝图
用 Flask 来写个轻博客 (17) — MV(C)_应用蓝图来重构项目
用 Flask 来写个轻博客 (18) — 使用工厂模式来生成应用对象
用 Flask 来写个轻博客 (19) — 以 Bcrypt 密文存储账户信息与实现用户登陆表单
用 Flask 来写个轻博客 (20) — 实现注册表单与应用 reCAPTCHA 来实现验证码

扩展阅读

flash 消息闪现

添加账户管理蓝图

网站的账户管理功能一般包括最基本的 登录 /注册 /权限, 在本篇中, 我们主要实现前两个功能模块.

在当前的项目中, 对 URL: http://localhost:5000/ 的路由在 jmilkfansblog.__init__:create_app() 被定义且重定向到蓝图 blog 的视图函数 home() 中. 如果一个 URL /只被一个视图函数处理的话, 这样做是没有问题的, 但现在我们需要在 / 下添加新的视图函数来为项目提供账户管理功能, 因此我们需要添加一个新的 controller, 并把该控制器注册到 app 对象中.

新建控制器(蓝图)

  • jmilkfansblog/controller/main.py
from os import path
from uuid import uuid4

from flask import flash, url_for, redirect, render_template, Blueprint
from jmilkfansblog.forms import LoginForm, RegisterForm

from jmilkfansblog.models import db, User


main_blueprint = Blueprint(
    'main',
    __name__,
    template_folder=path.join(path.pardir, 'templates', 'main'))

@main_blueprint.route('/')
def index():
    return redirect(url_for('blog.home'))

NOTE : 当 HTTP Request method 为 GET 的时候, 路由函数 index() 将会被优先调用.

  • jmilkfansblog/__init__.py
from flask import Flask, redirect, url_for

from jmilkfansblog.models import db
from jmilkfansblog.controllers import blog, main
from jmilkfansblog.extensions import bcrypt


def create_app(object_name):
    """Create the app instance via `Factory Method`"""

    app = Flask(__name__)
    # Set the config for app instance
    app.config.from_object(object_name)

    # Will be load the SQLALCHEMY_DATABASE_URL from config.py to db object
    db.init_app(app)
    # Init the Flask-Bcrypt via app object
    bcrypt.init_app(app)

    # Register the Blueprint into app object
    app.register_blueprint(blog.blog_blueprint)
    app.register_blueprint(main.main_blueprint)

    return app

NOTE 1: 这样的话, app 对象就拥有了两个蓝图, 其中 blog 提供博客内容的管理和展示功能, main 提供了网站的账户管理功能.
NOTE 2: 因为在 URL /下包含了博客首页和用户登录两个视图, 所以在两个蓝图中都必须含有对 / 进行处理的视图函数.

新建表单

表单主要用于输入登录和注册信息.

  • jmilkfansblog/forms.py
class LoginForm(Form):
    """Login Form"""

    username = StringField('Usermame', [DataRequired(), Length(max=255)])
    password = PasswordField('Password', [DataRequired()])

    def validate(self):
        """Validator for check the account information."""
        check_validata = super(LoginForm, self).validate()

        # If validator no pass
        if not check_validata:
            return False

        # Check the user whether exist.
        user = User.query.filter_by(username=self.username.data).first()
        if not user:
            self.username.errors.append('Invalid username or password.')
            return False

        # Check the password whether right.
        if not user.check_password(self.password.data):
            self.username.errors.append('Invalid username or password.')
            return False

        return True


class RegisterForm(Form):
    """Register Form."""

    username = StringField('Username', [DataRequired(), Length(max=255)])
    password = PasswordField('Password', [DataRequired(), Length(min=8)])
    comfirm = PasswordField('Confirm Password', [DataRequired(), EqualTo('password')])
    recaptcha = RecaptchaField()

    def validate(self):
        check_validate = super(RegisterForm, self).validate()

        # If validator no pass
        if not check_validate:
            return False

        # Check the user whether exist.
        user = User.query.filter_by(username=self.username.data).first()
        if user:
            self.username.errors.append('User with that name already exists.')
            return False
        return True

NOTE 1: 在 class Form 中提供了检验函数 validate(), 该函数会在表单对象调用 validate_on_submit() 的时候被调用, 所以在 LoginForm 和 RegisterForm 子类中都重载了 Form.validate() 并扩展了个性化的检验需求. EG. LoginForm 的 validate() 还需要检验用户是否存在以及用户密码是否正确.

NOTE 2: validate() 应该返回 boolean 类型.

NOTE 3: 我们会在注册页面上使用验证码, 所以 RegisterForm 中定义了类属性 recaptcha = RecaptchaField() .

新建蓝图 main 的视图函数

在创建了表单之后, 我们需要新建为登录和注册模板提供数据对象的视图函数, 并且将表单对象应用到其中.

  • jmilkfansblog/controller/main.py
@main_blueprint.route('/login', methods=['GET', 'POST'])
def login():
    """View function for login."""

    # Will be check the account whether rigjt.
    form = LoginForm()

    if form.validate_on_submit():
        flash("You have been logged in.", category="success")
        return redirect(url_for('blog.home'))

    return render_template('login.html',
                           form=form)


@main_blueprint.route('/logout', methods=['GET', 'POST'])
def logout():
    """View function for logout."""

    flash("You have been logged out.", category="success")
    return redirect(url_for('blog.home'))

@main_blueprint.route('/register', methods=['GET', 'POST'])
def register():
    """View function for Register."""

    # Will be check the username whether exist.
    form = RegisterForm()

    if form.validate_on_submit():
        new_user = User(id=str(uuid4()),
                        username=form.username.data,
                        password=form.password.data)

        db.session.add(new_user)
        db.session.commit()

        flash('Your user has been created, please login.',
              category="success")

        return redirect(url_for('main.login'))
    return render_template('register.html',
                           form=form)

NOTE 1: 当用户注册成功之后会将用户信息写入到数据库中, 然后直接跳转到登录页面.

NOTE 2: 在登录成功之后会返回 [(‘success’, ‘You have been logged in.’)] 的信息, 这是由 flash() 来实现的. 详见扩展阅读.

新建模板

最后我们在创建登录和注册页面的模板.

  • jmilkfansblog/template/base.html: 首先我们需要在 base 模板中加入对 flash() 和 reCAPTCHA 的支持.
...

  {% block captcha %}                                                      
  {% endblock %}                                                                            
</head> 

<body>
  <div class="container">
    <div class="jumbotron">
      <!-- Replace the route function to URL: `/` -->
      <h1><a href="{{ url_for('blog.home')}} ">JmilkFan's Blog</a></h1>
        <p>Welcome to the blog!</p>
    </div>
    {% with messages = get_flashed_messages(with_categories=true) %}
      {% if messages %}
        {% for category, message in messages %}
          <div class="button" class="close" data-dismiss="alert" aria-label="Close">
            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
              <span aria-hidden="true">&times;</span>
            </button>
            {{ messages }}
          </div>
        {% endfor%}
      {% endif %}
    {% endwith %}
    {% block body %}
      body_content
    {% endblock %}
...

NOTE 1: get_flashed_messages(with_categories=true) 逐一取出并消费掉之前在应用中通过 flash() 传入的信息列表,类似实现一个队列。

NOTE 2: 一般来说 flash() 的消息是全局的, 只要在任意页面中定义了 get_flashed_messages() 就会被获取.

  • jmilkfansblog/template/login.html
{% extends "base.html" %}
{% block body %}
<div class="col-lg-3">
  <!-- Set the form -->
  <form method="POST" action="{{ url_for('main.login')
    }}">
    {{ form.hidden_tag() }}
    <div>
      {{ form.username.label }}
      {% if form.username.errors %}
        {% for e in form.username.errors %}
          <p class="help-block">{{ e }}</p>
        {% endfor %}
      {% endif %}
      {{ form.username(class_="form-control") }}
    </div>
    <div class="form-group">
      {{ form.password.label }}
      {% if form.password.errors %}
        {% for e in form.password.errors %}
          <p class="help-block">{{ e }}</p>
        {% endfor %}
      {% endif %}
      {{ form.password(class_='form-control') }}
    </div>
    <input class="btn btn-primary" type="submit" value="Login">
  </form>
</div>
{% endblock %}
  • register.html
{% extends "base.html" %}                                                  

{% block title %}                                                          
  Register                                                                 
{% endblock %}                                                             

{% block captcha %}                                                        
  <script src='https://www.google.com/recaptcha/api.js'></script>          
{% endblock %}

{% block body %}                                                           
<div class="col-lg-3">
  <!-- Set the form -->                                                    
  <form method="POST" action="{{ url_for('main.register')                  
    }}">
    {{ form.hidden_tag() }}
    <div> 
      {{ form.username.label }}                                            
      {{ form.username(class_="form-control") }}                           
    </div>
    <div class="form-group">                                               
      {{ form.password.label }}                                            
      {{ form.password(class_='form-control') }}                           
    </div>
    <div class="form-group">
      {{ form.comfirm.label }}
      {{ form.comfirm(class_='form-control') }}                            
    </div>
    <input class="btn btn-primary" type="submit" value="Register">         
    <div class="g-recaptcha" data-sitekey="<Your public key>"></div>
  </form>
</div>
{% endblock %}           

NOTE 1: 在模板 register 中需要按照 reCAPTCHA 官档给出的方法将 <script src='https://www.google.com/recaptcha/api.js'></script><div class="g-recaptcha" data-sitekey="<Your public key>"></div> 应用到该模板中, 验证码才会生效.

NOTE 2: 因为要访问 google 所以在测试是部署的时候都需要使用 VPN 会反向代理来完成.

页面效果

注册页面:
这里写图片描述

reCHPTCHA 验证码:
这里写图片描述

通过验证:
这里写图片描述

成功注册并跳转到登录页面:
这里写图片描述

登录失败:
这里写图片描述

<think>好的,我现在需要帮助用户寻找注册登录页面的设计案例和代码示例,以及用户认证界面的实现方法。首先,用户提到的参考引用中有三个来源,我需要结合这些信息来构建答案。 引用[1]提到集成FIDO二次验证,说明用户可能对安全验证感兴趣,所以除了基本设计,可能需要提到增强安全性的方法,比如双因素认证。引用[2]讨论了防御措施,如防止SQL注入和CSRF攻击,这在设计登录注册时非常重要,需要提醒用户注意安全性。引用[3]涉及使用Bootstrap和Django实现用户注册页面,所以应该提供相关的前端和后端代码示例。 接下来,用户需要设计案例和示例代码。考虑到引用[3]中的Django和Bootstrap,我可以提供一个基于这两个技术的示例。首先,前端部分使用Bootstrap来设计响应式页面,后端用Django处理表单验证和用户认证。需要展示HTML模板的结构,包括表单字段、CSRF令牌、提交按钮,并加入Bootstrap的类来美化界面。同时,需要提到如何在后端处理POST请求,保存用户数据,并实现登录功能。 此外,安全性方面,必须强调使用Django内置的安全功能,比如CSRF中间件、密码哈希处理,以及如何防止常见攻击。比如,表单提交时自动验证CSRF令牌,使用参数化查询防止SQL注入,这些都可以引用[2]的内容。同时,集成FIDO验证可以作为扩展功能,提升安全性,引用[1]的内容可以作为补充建议。 用户可能还希望了解其他实现方式,比如使用Vue.js或React前端框架,或者不同的后端技术如Flask,所以生成相关问题时要包括这些选项。此外,安全性和用户体验的优化也是常见问题,比如密码强度检查、验证码、错误提示等,都需要在相关问题上体现。 最后,确保回答结构清晰,分步骤说明设计和实现过程,并附上代码示例。同时,引用标记要正确添加在对应的建议后面,比如提到防御措施时引用[^2],提到FIDO时引用[^1],提到Django表单处理时引用[^3]。</think>### 注册登录页面设计实现案例 #### 一、基础设计案例(Bootstrap + Django) 以下为基于Bootstrap前端框架和Django后端的注册/登录页面实现: **1. 注册页面模板 (register.html)** ```html <div class="container mt-5"> <h2 class="mb-4">用户注册</h2> <form method="post" enctype="multipart/form-data"> {% csrf_token %} <!-- CSRF防御集成[^2] --> <div class="form-group"> <label>用户名</label> {{ form.username }} <small class="form-text text-muted">6-20位字母数字组合</small> </div> <div class="form-group"> <label>密码</label> {{ form.password }} </div> <button type="submit" class="btn btn-primary">立即注册</button> </form> </div> ``` **2. Django表单处理 (views.py)** ```python from django.contrib.auth.forms import UserCreationForm def register(request): if request.method == "POST": form = UserCreationForm(request.POST) # 自动包含密码哈希处理 if form.is_valid(): user = form.save() login(request, user) # 自动登录实现 return redirect('/') else: form = UserCreationForm() return render(request, 'register.html', {'form': form}) ``` #### 二、安全增强特性 1. **防御措施集成**: - 自动生成CSRF令牌防御CSRF攻击 - 使用Django ORM防止SQL注入[^2] - 密码字段自动哈希存储 2. **双因素认证扩展**: ```python # FIDO验证集成示例[^1] def verify_fido(request): if request.user.is_authenticated: fido_device = FIDODevice.objects.get(user=request.user) return render(request, 'fido_verify.html', {'device': fido_device}) ``` #### 三、界面优化建议 1. 增加第三方登录按钮(微信/Google) 2. 实时密码强度检测 3. 错误提示浮动框 4. 验证码集成(reCAPTCHA
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

范桂飓

文章对您有帮助就请一键三连:)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值