修复了在实际部署环境中,请求可能命中不同进程导致的登录报错
This commit is contained in:
@@ -90,4 +90,26 @@ def verify_password(password_plain: str, salt_b64: str, hash_b64: str) -> bool:
|
||||
actual = hash_password_with_salt(password_plain, salt)
|
||||
return hmac.compare_digest(actual, expected)
|
||||
except Exception:
|
||||
return False
|
||||
return False
|
||||
|
||||
def generate_rsa_private_pem_b64() -> str:
|
||||
if rsa is None or serialization is None:
|
||||
raise RuntimeError("cryptography library is required for RSA operations")
|
||||
priv = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
pem = priv.private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption())
|
||||
return base64.b64encode(pem).decode('ascii')
|
||||
|
||||
def public_spki_b64_from_private_pem_b64(private_pem_b64: str) -> str:
|
||||
if serialization is None:
|
||||
raise RuntimeError("cryptography library is required for RSA operations")
|
||||
priv = serialization.load_pem_private_key(base64.b64decode(private_pem_b64), password=None)
|
||||
pub = priv.public_key()
|
||||
spki = pub.public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)
|
||||
return base64.b64encode(spki).decode('ascii')
|
||||
|
||||
def rsa_oaep_decrypt_b64_with_private_pem(private_pem_b64: str, ciphertext_b64: str) -> bytes:
|
||||
if serialization is None or padding is None or hashes is None:
|
||||
raise RuntimeError("cryptography library is required for RSA operations")
|
||||
priv = serialization.load_pem_private_key(base64.b64decode(private_pem_b64), password=None)
|
||||
ct = base64.b64decode(ciphertext_b64)
|
||||
return priv.decrypt(ct, padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None))
|
||||
@@ -77,8 +77,20 @@ document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||
const setKeyResp = await fetch('/accounts/session-key/', {
|
||||
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken || '' }, body: JSON.stringify({ encrypted_key: encAesKeyB64 })
|
||||
});
|
||||
const setKeyJson = await setKeyResp.json();
|
||||
if (!setKeyResp.ok || !setKeyJson.ok) throw new Error('设置会话密钥失败');
|
||||
const setKeySnapshot = await (async () => {
|
||||
const clone = setKeyResp.clone();
|
||||
const txt = await clone.text();
|
||||
let parsed = null;
|
||||
try { parsed = await setKeyResp.json(); } catch (_) {}
|
||||
return { txt, parsed };
|
||||
})();
|
||||
if (!setKeySnapshot.parsed) {
|
||||
const msg = (setKeySnapshot.txt || '').trim();
|
||||
const mapped = msg.toLowerCase().includes('decrypt error') ? '会话密钥解密失败,请刷新页面后重试' : (msg || '设置会话密钥失败');
|
||||
throw new Error(mapped);
|
||||
}
|
||||
const setKeyJson = setKeySnapshot.parsed;
|
||||
if (!setKeyResp.ok || !setKeyJson.ok) throw new Error(setKeyJson.message || '设置会话密钥失败');
|
||||
|
||||
const aesKey = await importAesKey(aesKeyRaw);
|
||||
const iv = new Uint8Array(12); window.crypto.getRandomValues(iv);
|
||||
@@ -92,7 +104,19 @@ document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||
const submitResp = await fetch('/accounts/login/secure-submit/', {
|
||||
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken || '' }, body: JSON.stringify({ iv: ivB64, ciphertext: ctB64 })
|
||||
});
|
||||
const submitJson = await submitResp.json();
|
||||
const submitSnapshot = await (async () => {
|
||||
const clone = submitResp.clone();
|
||||
const txt = await clone.text();
|
||||
let parsed = null;
|
||||
try { parsed = await submitResp.json(); } catch (_) {}
|
||||
return { txt, parsed };
|
||||
})();
|
||||
if (!submitSnapshot.parsed) {
|
||||
const msg = (submitSnapshot.txt || '').trim();
|
||||
const mapped = msg.toLowerCase().includes('decrypt error') ? '解密失败,请刷新页面后重试' : (msg || '服务器响应异常');
|
||||
throw new Error(mapped);
|
||||
}
|
||||
const submitJson = submitSnapshot.parsed;
|
||||
if (!submitResp.ok || !submitJson.ok) {
|
||||
if (submitJson && submitJson.captcha_required) { needCaptcha = true; await loadCaptcha(); }
|
||||
throw new Error(submitJson.message || '登录失败');
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie
|
||||
from django.conf import settings
|
||||
|
||||
from .es_client import get_user_by_username
|
||||
from .crypto import get_public_key_spki_b64, rsa_oaep_decrypt_b64, aes_gcm_decrypt_b64, verify_password
|
||||
from .crypto import get_public_key_spki_b64, rsa_oaep_decrypt_b64, aes_gcm_decrypt_b64, verify_password, generate_rsa_private_pem_b64, public_spki_b64_from_private_pem_b64, rsa_oaep_decrypt_b64_with_private_pem
|
||||
from elastic.es_connect import get_registration_code, get_user_by_username as es_get_user_by_username, get_all_users as es_get_all_users, write_user_data
|
||||
|
||||
|
||||
@@ -25,7 +25,11 @@ def login_page(request):
|
||||
@require_http_methods(["GET"])
|
||||
@ensure_csrf_cookie
|
||||
def pubkey(request):
|
||||
pk_b64 = get_public_key_spki_b64()
|
||||
pem_b64 = request.session.get("rsa_private_pem_b64")
|
||||
if not pem_b64:
|
||||
pem_b64 = generate_rsa_private_pem_b64()
|
||||
request.session["rsa_private_pem_b64"] = pem_b64
|
||||
pk_b64 = public_spki_b64_from_private_pem_b64(pem_b64)
|
||||
return JsonResponse({"public_key_spki": pk_b64})
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -56,7 +60,10 @@ def set_session_key(request):
|
||||
if not enc_key_b64:
|
||||
return HttpResponseBadRequest("Missing fields")
|
||||
try:
|
||||
key_bytes = rsa_oaep_decrypt_b64(enc_key_b64)
|
||||
pem_b64 = request.session.get("rsa_private_pem_b64")
|
||||
if not pem_b64:
|
||||
return HttpResponseBadRequest("Decrypt error")
|
||||
key_bytes = rsa_oaep_decrypt_b64_with_private_pem(pem_b64, enc_key_b64)
|
||||
except Exception:
|
||||
return HttpResponseBadRequest("Decrypt error")
|
||||
request.session["session_enc_key_b64"] = base64.b64encode(key_bytes).decode("ascii")
|
||||
@@ -110,6 +117,8 @@ def secure_login_submit(request):
|
||||
request.session["permission"] = 1
|
||||
if "session_enc_key_b64" in request.session:
|
||||
del request.session["session_enc_key_b64"]
|
||||
if "rsa_private_pem_b64" in request.session:
|
||||
del request.session["rsa_private_pem_b64"]
|
||||
if "login_failed_once" in request.session:
|
||||
del request.session["login_failed_once"]
|
||||
if "captcha_code" in request.session:
|
||||
|
||||
Reference in New Issue
Block a user