Xử lý đăng nhập và đăng xuất trong ứng dụng thuê nhà với Flask

Xử lý phiên đăng nhập/dăng xuất

Triển khai logic phía backend

Các thao tác đăng nhập, đăng xuất và kiểm tra trạng thái đăng nhập đều thao tác trên session, do đó có thể định nghĩa chung một endpoint là /sessions. Trong file ihome/api_1_0/users.py, tạo view xử lý các phương thức HTTP tương ứng:

# ihome/api_1_0/users.py
from flask import request, jsonify, current_app, session

@api.route('/sessions', methods=['GET', 'POST', 'DELETE'])
def manage_session():
    if request.method == 'POST':
        return perform_login()
    elif request.method == 'DELETE':
        return perform_logout()
    else:
        return fetch_current_user()

Mỗi phương thức tương ứng với một hành động cụ thể:

  • POST: Đăng nhập
  • DELETE: Đăng xuất
  • GET: Lấy thông tin người dùng hiện tại

Triển khai các hàm xử lý riêng biệt như sau:

def perform_login():
    """Xử lý đăng nhập người dùng"""
    data = request.get_json()
    if not data:
        return jsonify(errno=RET.PARAMERR, errmsg='Dữ liệu không hợp lệ')

    phone_num = data.get('phone')
    pwd = data.get('password')
    if not all([phone_num, pwd]):
        return jsonify(errno=RET.PARAMERR, errmsg='Thiếu thông tin đăng nhập')

    # Giới hạn số lần thử đăng nhập trong 1 phút
    login_attempt_key = f'login_attempts_{phone_num}'
    try:
        attempts = redis_connect.incr(login_attempt_key)
        if attempts > constants.MAX_LOGIN_ATTEMPTS:
            return jsonify(errno=RET.REQERR, errmsg='Quá số lần đăng nhập cho phép trong 1 phút')
        redis_connect.expire(login_attempt_key, constants.LOGIN_LOCKOUT_DURATION)
    except Exception as ex:
        current_app.logger.error(f"Lỗi Redis khi kiểm tra số lần đăng nhập: {ex}")
        return jsonify(errno=RET.DBERR, errmsg='Lỗi hệ thống')

    # Kiểm tra xem đã đăng nhập chưa (tránh đăng nhập trùng)
    if session.get('phone') == phone_num:
        return jsonify(errno=RET.OK)

    # Tìm người dùng theo số điện thoại
    try:
        user = Users.query.filter_by(phone=phone_num).first()
    except Exception as ex:
        current_app.logger.error(f"Lỗi truy vấn CSDL: {ex}")
        return jsonify(errno=RET.DBERR, errmsg='Lỗi truy vấn người dùng')

    # Xác thực mật khẩu
    if not user or not user.verify_password(pwd):
        return jsonify(errno=RET.PWDERR, errmsg='Sai tên đăng nhập hoặc mật khẩu')

    # Lưu thông tin vào session
    session['user_id'] = user.id
    session['name'] = user.name
    session['phone'] = phone_num

    return jsonify(errno=RET.OK)


def perform_logout():
    """Xử lý đăng xuất"""
    if not session.get('user_id'):
        return jsonify(errno=RET.NODATA, errmsg='Chưa đăng nhập')

    session.pop('user_id', None)
    session.pop('name', None)
    session.pop('phone', None)

    return jsonify(errno=RET.OK)


def fetch_current_user():
    """Lấy thông tin người dùng đang đăng nhập"""
    username = session.get('name')
    if username:
        return jsonify(errno=RET.OK, data={'name': username})
    return jsonify(errno=RET.SESSIONERR, errmsg='Chưa đăng nhập')

Triển khai logic phía frontend – Đăng nhập

Tại file login.js, ghi đè hành vi submit mặc định của form và gửi yêu cầu AJAX đến API đăng nhập:

$(document).ready(function() {
    $(".form-login").submit(function(e) {
        e.preventDefault();

        const mobile = $("#mobile").val().trim();
        const passwd = $("#password").val().trim();

        if (!mobile) {
            $("#mobile-err span").text("Vui lòng nhập số điện thoại!");
            $("#mobile-err").show();
            return;
        }
        if (!passwd) {
            $("#password-err span").text("Vui lòng nhập mật khẩu!");
            $("#password-err").show();
            return;
        }

        $.ajax({
            url: '/api/v1.0/sessions',
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({ phone: mobile, password: passwd }),
            headers: { 'X-CSRFToken': getCookie('csrf_token') },
            dataType: 'json',
            success: function(resp) {
                if (resp.errno === '0') {
                    window.location.href = '/index.html';
                } else {
                    alert(resp.errmsg);
                }
            },
            error: function() {
                alert('Có lỗi xảy ra khi đăng nhập');
            }
        });
    });
});

Hiển thị thông tin người dùng sau khi đăng nhập

Khi trang chủ được tải, gọi API để kiểm tra trạng thái đăng nhập và hiển thị tên người dùng nếu đã đăng nhập:

// index.js
$(document).ready(function() {
    $.get('/api/v1.0/sessions', function(resp) {
        if (resp.errno === '0') {
            $('.user-info > .user-name').text(resp.data.name);
            $('.user-info').show();
        } else {
            $('.top-bar > .register-login').show();
        }
    }, 'json');
});

Triển khai chức năng đăng xuất

Tại trang cá nhân (my.js), gắn sự kiện click cho nút "Đăng xuất":

function logout() {
    $.ajax({
        url: '/api/v1.0/sessions',
        type: 'DELETE',
        dataType: 'json',
        headers: { 'X-CSRFToken': getCookie('csrf_token') },
        success: function(resp) {
            if (resp.errno === '0') {
                window.location.href = '/';
            } else {
                alert(resp.errmsg);
            }
        },
        error: function() {
            alert('Đăng xuất thất bại');
        }
    });
}

Decorator kiểm tra trạng thái đăng nhập

Để bảo vệ các route yêu cầu người dùng phải đăng nhập (ví dụ: trang cá nhân), tạo decorator login_required trong ihome/utils/commons.py:

# ihome/utils/commons.py
from flask import session, jsonify, g, current_app
from functools import wraps
from .response_codes import RET

def login_required(view_func):
    @wraps(view_func)
    def decorated_function(*args, **kwargs):
        user_id = session.get('user_id')
        if not user_id:
            return jsonify(errno=RET.SESSIONERR, errmsg='Yêu cầu đăng nhập')

        try:
            user = Users.query.get(user_id)
            if not user:
                return jsonify(errno=RET.DBERR, errmsg='Người dùng không tồn tại')
            g.current_user = user
        except Exception as e:
            current_app.logger.error(f"Lỗi khi lấy thông tin người dùng: {e}")
            return jsonify(errno=RET.DBERR, errmsg='Lỗi hệ thống')

        return view_func(*args, **kwargs)
    return decorated_function

Decorator này:

  • Kiểm tra user_id trong session
  • Nếu chưa đăng nhập → trả về lỗi
  • Nếu đã đăng nhập → truy vấn đối tượng Users và lưu vào g.current_user để sử dụng trong view
  • Sử dụng @wraps để giữ nguyên metadata của hàm gốc

Thẻ: Flask python Web Development Session Management authentication

Đăng vào ngày 31 tháng 7 lúc 02:18