Простая система входа с использованием Python Flask и MySQL

Я сделал страницу входа, используя Python Flask, которая работает с MySQL. Я начал изучать Flask 2 дня назад, и это было весело, поэтому я придумал это:

from flask import Flask, session, redirect, url_for, escape, request, render_template
from hashlib import md5
import MySQLdb

app = Flask(__name__)

#######################
#   DATABASE CONFIG   #
#######################

db = MySQLdb.connect(host="localhost", user="root", passwd="", db="test")
cur = db.cursor()

@app.route('/')
def index():
    if 'username' in session:
        username_session = escape(session['username']).capitalize()
        return render_template('index.html', session_user_name=username_session)
    return redirect(url_for('login'))


@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if 'username' in session:
        return redirect(url_for('index'))
    if request.method == 'POST':
        username_form  = request.form['username']
        password_form  = request.form['password']
        cur.execute("SELECT COUNT(1) FROM users WHERE name = %s;", [username_form]) # CHECKS IF USERNAME EXSIST
        if cur.fetchone()[0]:
            cur.execute("SELECT pass FROM users WHERE name = %s;", [username_form]) # FETCH THE HASHED PASSWORD
            for row in cur.fetchall():
                if md5(password_form).hexdigest() == row[0]:
                    session['username'] = request.form['username']
                    return redirect(url_for('index'))
                else:
                    error = "Invalid Credential"
        else:
            error = "Invalid Credential"
    return render_template('login.html', error=error)


@app.route('/logout')
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))

app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

if __name__ == '__main__':
    app.run(debug=True)

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
    <script src="{{url_for('static', filename='js/bootstrap.min.js')}}"></script>
</head>
<body>
    <div class="container" style="margin-top:50px;">
        <div class="row">
            <div class="col-md-6 col-md-offset-3 text-center">
                {% if session_user_name %}
                    <p>Hello <b>{{ session_user_name }}</b></p>
                {% endif %}
                <a href="{{ url_for('logout') }}">Logout</a>
            </div>
        </div>
    </div>
</body>
</html>

login.html

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
    <script src="{{url_for('static', filename='js/bootstrap.min.js')}}"></script>
</head>
<body>
    <div class="container" style="margin-top:50px;">
        <div class="row">
            <div class="col-md-6 col-md-offset-3">
                <form action="" method="POST">
                    {% if error %}
                        <p class=error><strong>Error:</strong> {{ error }}
                    {% endif %}
                    <div class="input-group">
                        <span class="input-group-addon" id="basic-addon3">Your Username</span>
                        <input type="text" class="form-control" name="username" id="user" aria-describedby="basic-addon3">
                    </div>
                    <br>
                    <div class="input-group">
                        <span class="input-group-addon" id="basic-addon3">Your Password</span>
                        <input type="text" class="form-control" id="pass" name="password" aria-describedby="basic-addon3">
                    </div>
                    <br>
                    <input type="Submit" value="Login" class="btn btn-default btn-sm">
                </form>
            </div>
        </div>
    </div>
</body>
</html>
8 голосов | спросил Sakir 13 52015vEurope/Moscow11bEurope/MoscowFri, 13 Nov 2015 17:35:15 +0300 2015, 17:35:15

1 ответ


12

Стиль

У Python есть руководство по стилю. Он называется PEP8 , и обычно, если вы следуете ему, вам становится легче читать код.
Это довольно небольшое чтение, и вы можете узнать от него что-то новое.

  1. В PEP8 рекомендуется ограничить ширину символов скриптов до 79 символов. Он имеет несколько преимуществ:

    • Людям на StackExchange не нужно использовать горизонтальную прокрутку.

    • Легче читать. По той же причине, что газеты используют две или более колонки.

    • На широкоэкранном компьютере вы можете одновременно открыть 3 сценария. (В зависимости от размера шрифта)

    Вы можете подумать, что это глупо, но я и другие считают это приятным.

  2. Комментарии «Box», см. ниже, довольно уродливы и не добавляют много кода.

    #######################
    #   DATABASE CONFIG   #
    #######################
    
  3. Использование not для замены if и else может улучшить читаемость. login 'if cur.fetchone()[0]: довольно большой , и может привести к тому, что человек, читающий, забыл свое место, и нужно перечитать, если понимать их место в коде.

    В то время как это определенно не имеет большого значения здесь, он может легко стать одним.

    # An example of how to avoid this:
    if not cur.fetchone()[0]:
        error = "Invalid Credential"
    else:
        ...
    

В целом ваш стиль действительно хорош.


Улучшения

  1. Вы можете перенести все свои глобальные настройки в одну область. Если они растут больше, чем у немногих, у вас есть 3 варианта их хранения.

    1. Сделайте класс, определенный в верхней части вашего кода, чтобы удерживать их.

    2. Создайте модуль для их хранения. Осторожно помести свой секретный ключ! Как люди смогут его импортировать.

    3. Поместите их в базу данных, вы можете использовать свою базу данных MySQL или просто файл json или csv.

  2. Обычно вы хотите свести глобальные значения к минимуму. Также я бы сказал, что ваш db находится в опасном положении. Например, что делать, если я должен был сделать следующее, считая, что ваш файл python называется main.py:

    import main
    
    main.cur.execute('DROP DATABASE')
    
  3. Существует анти-шаблон, называемый «стрелка-анти-шаблон», это приводит к сильно отступовному коду, который имеет форму стрелы. Обычно это происходит из-за того, что люди не используют «break clauses».

    Например, в index:

    # Leads to an arrow
    def index():
        if 'username' in session:
            username_session = escape(session['username']).capitalize()
            # So is this the main page you're meant to go to?
            return render_template('index.html', session_user_name=username_session)
        return redirect(url_for('login'))
    
    # Avoids the arrow, and wondering what the main page is.
    def index():
        if 'username' not in session:
            return redirect(url_for('login'))
    
        username_session = escape(session['username']).capitalize()
        return render_template('index.html', session_user_name=username_session)
    
  4. Вы используете переменную 'error' в login. Python имеет ошибки и легко расширяется.

    Также Python следует EAFP больше, чем LBYL. И поэтому использование исключений является нормой.

    # Define a new error
    class ServerError(Exception):pass
    
    # Use error
    if not cur.fetchone()[0]:
        raise ServerError('Invalid Credential')
    
    # Catch and handle error
    error = None
    try:
        ...
    except ServerError as e:
        error = str(e)
    return render_template('login.html', error=error)
    
  5. Python отказывается от использования '%s' % 'yo' в пользу нового str.format. Хотя есть некоторые места, где вам нужно использовать %, это не один из них.

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

    'SELECT COUNT(1) FROM users WHERE name = {};'.format(username_form)
    

    Это открывает вам атаки SQL-инъекций, и поэтому это нежелательно. Текущий путь определенно предпочтительнее.

  6. Ваш цикл for, перераспределяет error для каждого сбойного пароля. Это эквивалентно созданию функции is prime и повторение того, что вы не доказали, что это не просто каждое число, которое вы проверяете.

    Вместо этого вы можете просто переместить его из цикла for.

    for row in cur.fetchall():
        if md5(password_form).hexdigest() == row[0]:
            session['username'] = request.form['username']
            return redirect(url_for(index))
    raise ServerError('Invalid password')
    
  7. Я думаю, ваши «ошибки» не очень описательны.
    Раньше сегодня /вчера я забыл пароль для SO. Поэтому я пытался пароль послепароль, где мысль перешла мне на ум, «возможно, я использую неправильный адрес электронной почты», на который я попробовал свое старое письмо. И было приятно сказано, что учетной записи не существует.

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

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

Кроме того, внедрение собственной схемы обработки паролей может быть очень плохой идеей. У вас не солевой пароль, а IIRC - старая техника. Я бы рекомендовал использовать такой стандарт, как OAuth или OpenID. (Я не специалист по безопасности, поэтому эта технология может быть устаревшей.)


Я не просмотрел ваш HTML-код, но с точки зрения колпачка это кажется прекрасным.


Если бы я переписал ваш код, я бы получил следующее:

from flask import Flask, session, redirect, url_for, escape, request, render_template
from hashlib import md5
import MySQLdb

app = Flask(__name__)

if __name__ == '__main__':
    db = MySQLdb.connect(host="localhost", user="root", passwd="", db="test")
    cur = db.cursor()
    app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

class ServerError(Exception):pass

@app.route('/')
def index():
    if 'username' in session:
        return redirect(url_for('login'))

    username_session = escape(session['username']).capitalize()
    return render_template('index.html', session_user_name=username_session)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if 'username' in session:
        return redirect(url_for('index'))

    error = None
    try:
        if request.method == 'POST':
            username_form  = request.form['username']
            cur.execute("SELECT COUNT(1) FROM users WHERE name = {};"
                        .format(username_form))

            if not cur.fetchone()[0]:
                raise ServerError('Invalid username')

            password_form  = request.form['password']
            cur.execute("SELECT pass FROM users WHERE name = {};"
                        .format(username_form))

            for row in cur.fetchall():
                if md5(password_form).hexdigest() == row[0]:
                    session['username'] = request.form['username']
                    return redirect(url_for('index'))

            raise ServerError('Invalid password')
    except ServerError as e:
        error = str(e)

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


@app.route('/logout')
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)
ответил Peilonrayz 18 32015vEurope/Moscow11bEurope/MoscowWed, 18 Nov 2015 01:40:17 +0300 2015, 01:40:17

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132