更新登录逻辑,等待数据库进一步完善
This commit is contained in:
20
accounts/crypto.py
Normal file
20
accounts/crypto.py
Normal 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()
|
||||
@@ -1,14 +1,6 @@
|
||||
import base64
|
||||
import hashlib
|
||||
from elastic.es_connect import get_user_by_username as es_get_user_by_username
|
||||
|
||||
|
||||
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)
|
||||
from .crypto import salt_for_username, derive_password
|
||||
|
||||
|
||||
def get_user_by_username(username: str):
|
||||
@@ -16,18 +8,27 @@ def get_user_by_username(username: str):
|
||||
从Elasticsearch获取用户数据
|
||||
"""
|
||||
# 首先尝试从ES获取用户数据
|
||||
es_user = es_get_user_by_username(username)
|
||||
salt = _salt_for_username(username)
|
||||
derived = _derive_password(es_user.get('password', ''), salt)
|
||||
if es_user:
|
||||
# 如果ES中有用户数据,使用ES中的密码
|
||||
return {
|
||||
'user_id': es_user.get('user_id', 0),
|
||||
'username': es_user.get('username', ''),
|
||||
'password': base64.b64encode(derived).decode('ascii'),
|
||||
'premission': es_user.get('permission', 1),
|
||||
'salt': base64.b64encode(salt).decode('ascii'),
|
||||
}
|
||||
# es_user = es_get_user_by_username(username)
|
||||
# if es_user:
|
||||
# salt = salt_for_username(username)
|
||||
# derived = derive_password(es_user.get('password', ''), salt)
|
||||
# # 如果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 {
|
||||
'user_id': 0,
|
||||
'username': 'admin',
|
||||
'password': base64.b64encode(derived).decode('ascii'),
|
||||
'permission': 0,
|
||||
}
|
||||
|
||||
|
||||
|
||||
return None
|
||||
@@ -81,6 +81,7 @@ document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||
const csrftoken = getCookie('csrftoken');
|
||||
const chalResp = await fetch('/accounts/challenge/', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken || ''
|
||||
@@ -102,6 +103,7 @@ document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||
// Step 3: submit login with username and hmac
|
||||
const submitResp = await fetch('/accounts/login/submit/', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken || ''
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import hmac
|
||||
|
||||
from django.http import JsonResponse, HttpResponseBadRequest
|
||||
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.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"])
|
||||
@@ -30,7 +32,7 @@ def challenge(request):
|
||||
|
||||
# Generate nonce and compute per-username salt
|
||||
nonce = os.urandom(16)
|
||||
salt = _salt_for_username(username)
|
||||
salt = salt_for_username(username)
|
||||
|
||||
# Persist challenge in session to prevent replay with mismatched user
|
||||
request.session["challenge_nonce"] = base64.b64encode(nonce).decode("ascii")
|
||||
@@ -71,10 +73,7 @@ def login_submit(request):
|
||||
nonce = base64.b64decode(nonce_b64)
|
||||
stored_derived_b64 = user.get("password", "")
|
||||
stored_derived = base64.b64decode(stored_derived_b64)
|
||||
# HMAC-SHA256: server computes with stored derived secret
|
||||
import hmac, hashlib
|
||||
server_hmac = hmac.new(stored_derived, nonce, hashlib.sha256).digest()
|
||||
server_hmac_b64 = base64.b64encode(server_hmac).decode("ascii")
|
||||
server_hmac_b64 = base64.b64encode(hmac_sha256(stored_derived, nonce)).decode("ascii")
|
||||
except Exception:
|
||||
return HttpResponseBadRequest("Verification error")
|
||||
|
||||
@@ -89,7 +88,7 @@ def login_submit(request):
|
||||
|
||||
request.session["user_id"] = user["user_id"]
|
||||
request.session["username"] = user["username"]
|
||||
request.session["premission"] = user["premission"]
|
||||
request.session["permission"] = user["permission"]
|
||||
|
||||
# Clear challenge to prevent reuse
|
||||
for k in ("challenge_username", "challenge_nonce"):
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
try {
|
||||
const resp = await fetch('/accounts/logout/', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken || ''
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.views.decorators.http import require_http_methods
|
||||
def home(request):
|
||||
# Enforce login: require session 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/")
|
||||
|
||||
# Show user_id (prefer query param if present, but don't trust it)
|
||||
|
||||
Reference in New Issue
Block a user