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ậpDELETE: Đăng xuấtGET: 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_idtrong session - Nếu chưa đăng nhập → trả về lỗi
- Nếu đã đăng nhập → truy vấn đối tượng
Usersvà lưu vàog.current_userđể sử dụng trong view - Sử dụng
@wrapsđể giữ nguyên metadata của hàm gốc