更新登录逻辑,等待数据库进一步完善

This commit is contained in:
2025-11-10 13:38:44 +08:00
parent f3aec9a18d
commit 1bbd777565
6 changed files with 52 additions and 29 deletions

20
accounts/crypto.py Normal file
View File

@@ -0,0 +1,20 @@
import hashlib
import hmac
def salt_for_username(username: str) -> bytes:
"""Derive a per-username salt using SHA-256(username).
The salt is deterministic for a given username and does not require storage.
"""
return hashlib.sha256(username.encode('utf-8')).digest()
def derive_password(password_plain: str, salt: bytes, iterations: int = 100_000, dklen: int = 32) -> bytes:
"""PBKDF2-SHA256 derive a fixed-length secret from a plaintext password and salt."""
return hashlib.pbkdf2_hmac('sha256', password_plain.encode('utf-8'), salt, iterations, dklen=dklen)
def hmac_sha256(key: bytes, message: bytes) -> bytes:
"""Compute HMAC-SHA256 signature for the given message using key bytes."""
return hmac.new(key, message, hashlib.sha256).digest()

View File

@@ -1,14 +1,6 @@
import base64 import base64
import hashlib
from elastic.es_connect import get_user_by_username as es_get_user_by_username from elastic.es_connect import get_user_by_username as es_get_user_by_username
from .crypto import salt_for_username, derive_password
def _salt_for_username(username: str) -> bytes:
return hashlib.sha256(username.encode('utf-8')).digest()
def _derive_password(password_plain: str, salt: bytes) -> bytes:
return hashlib.pbkdf2_hmac('sha256', password_plain.encode('utf-8'), salt, 100_000, dklen=32)
def get_user_by_username(username: str): def get_user_by_username(username: str):
@@ -16,18 +8,27 @@ def get_user_by_username(username: str):
从Elasticsearch获取用户数据 从Elasticsearch获取用户数据
""" """
# 首先尝试从ES获取用户数据 # 首先尝试从ES获取用户数据
es_user = es_get_user_by_username(username) # es_user = es_get_user_by_username(username)
salt = _salt_for_username(username) # if es_user:
derived = _derive_password(es_user.get('password', ''), salt) # salt = salt_for_username(username)
if es_user: # derived = derive_password(es_user.get('password', ''), salt)
# 如果ES中有用户数据使用ES中的密码 # # 如果ES中有用户数据使用ES中的密码
# return {
# 'user_id': es_user.get('user_id', 0),
# 'username': es_user.get('username', ''),
# 'password': base64.b64encode(derived).decode('ascii'),
# 'permission': es_user.get('permission', 1),
# }
salt = salt_for_username('admin')
derived = derive_password('admin', salt)
return { return {
'user_id': es_user.get('user_id', 0), 'user_id': 0,
'username': es_user.get('username', ''), 'username': 'admin',
'password': base64.b64encode(derived).decode('ascii'), 'password': base64.b64encode(derived).decode('ascii'),
'premission': es_user.get('permission', 1), 'permission': 0,
'salt': base64.b64encode(salt).decode('ascii'),
} }
return None return None

View File

@@ -81,6 +81,7 @@ document.getElementById('loginForm').addEventListener('submit', async (e) => {
const csrftoken = getCookie('csrftoken'); const csrftoken = getCookie('csrftoken');
const chalResp = await fetch('/accounts/challenge/', { const chalResp = await fetch('/accounts/challenge/', {
method: 'POST', method: 'POST',
credentials: 'same-origin',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRFToken': csrftoken || '' 'X-CSRFToken': csrftoken || ''
@@ -102,6 +103,7 @@ document.getElementById('loginForm').addEventListener('submit', async (e) => {
// Step 3: submit login with username and hmac // Step 3: submit login with username and hmac
const submitResp = await fetch('/accounts/login/submit/', { const submitResp = await fetch('/accounts/login/submit/', {
method: 'POST', method: 'POST',
credentials: 'same-origin',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRFToken': csrftoken || '' 'X-CSRFToken': csrftoken || ''

View File

@@ -1,6 +1,7 @@
import base64 import base64
import json import json
import os import os
import hmac
from django.http import JsonResponse, HttpResponseBadRequest from django.http import JsonResponse, HttpResponseBadRequest
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
@@ -8,7 +9,8 @@ from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
from django.conf import settings from django.conf import settings
from .es_client import get_user_by_username, _salt_for_username from .es_client import get_user_by_username
from .crypto import salt_for_username, hmac_sha256
@require_http_methods(["GET"]) @require_http_methods(["GET"])
@@ -30,7 +32,7 @@ def challenge(request):
# Generate nonce and compute per-username salt # Generate nonce and compute per-username salt
nonce = os.urandom(16) nonce = os.urandom(16)
salt = _salt_for_username(username) salt = salt_for_username(username)
# Persist challenge in session to prevent replay with mismatched user # Persist challenge in session to prevent replay with mismatched user
request.session["challenge_nonce"] = base64.b64encode(nonce).decode("ascii") request.session["challenge_nonce"] = base64.b64encode(nonce).decode("ascii")
@@ -71,10 +73,7 @@ def login_submit(request):
nonce = base64.b64decode(nonce_b64) nonce = base64.b64decode(nonce_b64)
stored_derived_b64 = user.get("password", "") stored_derived_b64 = user.get("password", "")
stored_derived = base64.b64decode(stored_derived_b64) stored_derived = base64.b64decode(stored_derived_b64)
# HMAC-SHA256: server computes with stored derived secret server_hmac_b64 = base64.b64encode(hmac_sha256(stored_derived, nonce)).decode("ascii")
import hmac, hashlib
server_hmac = hmac.new(stored_derived, nonce, hashlib.sha256).digest()
server_hmac_b64 = base64.b64encode(server_hmac).decode("ascii")
except Exception: except Exception:
return HttpResponseBadRequest("Verification error") return HttpResponseBadRequest("Verification error")
@@ -89,7 +88,7 @@ def login_submit(request):
request.session["user_id"] = user["user_id"] request.session["user_id"] = user["user_id"]
request.session["username"] = user["username"] request.session["username"] = user["username"]
request.session["premission"] = user["premission"] request.session["permission"] = user["permission"]
# Clear challenge to prevent reuse # Clear challenge to prevent reuse
for k in ("challenge_username", "challenge_nonce"): for k in ("challenge_username", "challenge_nonce"):

View File

@@ -38,6 +38,7 @@
try { try {
const resp = await fetch('/accounts/logout/', { const resp = await fetch('/accounts/logout/', {
method: 'POST', method: 'POST',
credentials: 'same-origin',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-CSRFToken': csrftoken || '' 'X-CSRFToken': csrftoken || ''

View File

@@ -6,7 +6,7 @@ from django.views.decorators.http import require_http_methods
def home(request): def home(request):
# Enforce login: require session user_id # Enforce login: require session user_id
session_user_id = request.session.get("user_id") session_user_id = request.session.get("user_id")
if not session_user_id: if session_user_id is None:
return redirect("/accounts/login/") return redirect("/accounts/login/")
# Show user_id (prefer query param if present, but don't trust it) # Show user_id (prefer query param if present, but don't trust it)