首页 > 解决方案 > How to login to Flask App when using Locust

问题描述

First time using Locust. I have a Flask App that requires user to login to access most routes. I cant get Locust to successfully login to my Flask App.

Here is my Locust.py file:

from locust import HttpLocust, TaskSet, task
import re


class UserBehavior(TaskSet):
    def on_start(self):
        """ on_start is called when a Locust start before any task is scheduled """
        self.client.verify = False
        self.get_token()
        self.login()

    def on_stop(self):
        """ on_stop is called when the TaskSet is stopping """
        self.logout()

    def get_token(self):
        response = self.client.get("/login")
        # Sample string from response:
        # <input id="csrf_token" name="csrf_token" type="hidden" value="REDACTED">
        self.csrftoken = re.search(' name="csrf_token" .* value="(.+?)"', response.text).group(1)
        print(f"DEBUG: self.csrftoken = {self.csrftoken}")

    def login(self):
        response = self.client.post("/login",
                                    {"email": "REDACTED", "password": "REDACTED"},
                                    headers={"X-CSRFToken": self.csrftoken})
        print(f"DEBUG: login response.status_code = {response.status_code}")

    def logout(self):
        self.client.get("/logout")

    @task(5)
    def list_domains(self):
        response = self.client.get("/domains", headers={"X-CSRFToken": self.csrftoken})
        print(f"DEBUG list: response.status_code = {response.status_code}")


class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 5000
    max_wait = 9000

Here is the login function of my Flask App: (with a few debug statements added)

@users.route('/login', methods=['GET', 'POST'])
def login():

    if request.method == 'POST':                                                        ##DEBUG
        logging.debug(f"debug0: inside login func with method == POST")                 ##DEBUG

    if current_user.is_authenticated:
        return redirect(url_for('main.home'))
    form = LoginForm()
    if form.validate_on_submit():
        logging.debug(f"debug0.1: inside validate_on_submit")                          ##DEBUG
        user = Users.query.filter_by(email=form.email.data).first()
        if user and user.check_password(form.password.data):
            login_user(user, remember=form.remember.data)
            next_page = request.args.get('next')
            if not next_page or url_parse(next_page).netloc != '':
                next_page = url_for('main.home')
            logging.debug(f"debug1: Login was successful")                              ##DEBUG
            return redirect(next_page)
        else:
            logging.debug(f"debug2: Login failed")                                      ##DEBUG
            flash(f'Login unsuccessful. Please check email and password!', 'danger')
    logging.debug(f"debug3: the end of login func")                                     ##DEBUG
    return render_template('login.html', title='Login', form=form)

When i run Locust, I get this output:

[2019-09-16 18:03:06,598] Mac-mini-3.local/INFO/locust.main: Starting web monitor at *:8089
[2019-09-16 18:03:06,598] Mac-mini-3.local/INFO/locust.main: Starting Locust 0.11.0
[2019-09-16 18:03:14,069] Mac-mini-3.local/INFO/locust.runners: Hatching and swarming 2 clients at the rate 1 clients/s...
[2019-09-16 18:03:14,138] Mac-mini-3.local/ERROR/stderr: /Users/myuser/.local/share/virtualenvs/locustio-gB1-mbqd/lib/python3.7/site-packages/urllib3/connectionpool.py:851: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings InsecureRequestWarning)
[2019-09-16 18:03:14,162] Mac-mini-3.local/INFO/stdout: DEBUG: self.csrftoken = REDACTED
[2019-09-16 18:03:14,183] Mac-mini-3.local/INFO/stdout: DEBUG: login response.status_code = 200
[2019-09-16 18:03:14,213] Mac-mini-3.local/INFO/stdout: DEBUG list: response.status_code = 200
[2019-09-16 18:03:15,112] Mac-mini-3.local/INFO/stdout: DEBUG: self.csrftoken = REDACTED
[2019-09-16 18:03:15,137] Mac-mini-3.local/INFO/stdout: DEBUG: login response.status_code = 200

I'm not concerned about the 'InsecureRequestWarning' as this is because I am using a self signed cert and i have disabled verification with 'self.client.verify = False' The csrftoken looks correct.

From the Flask App itself, I get this output:

DEBUG:user:debug0: inside login func with method == POST
INFO:flask_wtf.csrf:The CSRF token is missing.
DEBUG:user:debug3: the end of login func
DEBUG:user:debug3: the end of login func
DEBUG:user:debug3: the end of login func
DEBUG:user:debug3: the end of login func

So, it's hitting the login function (proven by debug0) but it's not getting into the 'form.validate_on_submit()' conditional.

So far I have spent all day on this, reading articles and trying a lot of things, ie adding the X-CSRFToken headers. I feel I am missing something fundamental, and would really appreciate some help.

thanks, WJ

标签: authenticationflaskcsrfflask-loginlocust

解决方案


好的,我解决了它,所以我想我会为遇到此问题的其他人分享答案。正如@user10788336 所建议的,这不是蝗虫问题。

问题是当发布到烧瓶路线时,表单没有被验证(即 form.validate() 没有被设置)。

所以,我做了两个改变。

1) 将 POST 更改为有一个额外的表单项,我称之为“test-mode”,并将值设置为“locust-test”

这是新的 Locust.py 文件:

from locust import HttpLocust, TaskSet, task
import re

class UserBehavior(TaskSet):
    def on_start(self):
        """ on_start is called when a Locust start before any task is scheduled """
        self.client.verify = False
        self.get_token()
        self.login()

    def on_stop(self):
        """ on_stop is called when the TaskSet is stopping """
        self.logout()

    def get_token(self):
        response = self.client.get("/login")
        # Sample string from response:
        # <input id="csrf_token" name="csrf_token" type="hidden" value="REDACTED">
        self.csrftoken = re.search(' name="csrf_token" .* value="(.+?)"', response.text).group(1)
        print(f"DEBUG: self.csrftoken = {self.csrftoken}")

    def login(self):
        response = self.client.post("/login",
                                    {"email": "REDACTED",
                                     "password": "REDACTED",
                                     "test-mode": "locust-test"
                                     },
                                    headers={"X-CSRFToken": self.csrftoken})
        print(f"DEBUG: login response.status_code = {response.status_code}")

    def logout(self):
        self.client.get("/logout")

    @task(5)
    def list_domains(self):
        response = self.client.get("/domains", headers={"X-CSRFToken": self.csrftoken})
        print(f"DEBUG list: response.status_code = {response.status_code}")


class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 5000
    max_wait = 9000

新旧的区别是:

<                                     {"email": "REDACTED", "password": "REDACTED"},
---
>                                     {"email": "REDACTED",
>                                      "password": "REDACTED",
>                                      "test-mode": "locust-test"
>                                      },

2)我更改了 Flask 应用程序的登录功能:更改是我不需要验证表单,所以只有当我检测到我在测试模式下运行时才跳过它。

这是新的登录功能:

@users.route('/login', methods=['GET', 'POST'])
def login():

    if current_user.is_authenticated:
        return redirect(url_for('main.home'))

    form = LoginForm()

    # shortcut for Locust testing - need to avoid form.validate() (which is within form.validate_on_submit())
    form_is_ok = False
    if request.method == 'POST':
        if request.form.get('test-mode') == 'locust-test':
            form_is_ok = True
        else:
            form_is_ok = form.validate_on_submit()

    if form_is_ok:
        logging.debug(f"debug0.1: inside validate_on_submit")  # DEBUG
        user = Users.query.filter_by(email=form.email.data).first()
        if user and user.check_password(form.password.data):
            login_user(user, remember=form.remember.data)
            next_page = request.args.get('next')
            if not next_page or url_parse(next_page).netloc != '':
                next_page = url_for('main.home')
            logging.debug(f"debug1: Login was successful")  # DEBUG
            return redirect(next_page)
        else:
            logging.debug(f"debug2: Login failed")  # DEBUG
            flash(f'Login unsuccessful. Please check email and password!', 'danger')
    logging.debug(f"debug3: the end of login func")  # DEBUG
    return render_template('login.html', title='Login', form=form)

新旧的区别是:

<     if form.validate_on_submit():
---
>
>     # shortcut for Locust testing - need to avoid form.validate() (which is within form.validate_on_submit())
>     form_is_ok = False
>     if request.method == 'POST':
>         if request.form.get('test-mode') == 'locust-test':
>             form_is_ok = True
>         else:
>             form_is_ok = form.validate_on_submit()
>
>     if form_is_ok:

我认为这仍然是安全的......对此有何想法?我可能会添加一个配置变量来禁用/启用此功能。

它有效!!!!

顺便说一句,蝗虫太棒了!

希望这可以帮助。

干杯,WJ


推荐阅读