更新登录逻辑,等待数据库进一步完善
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 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
|
||||||
@@ -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 || ''
|
||||||
|
|||||||
@@ -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"):
|
||||||
|
|||||||
@@ -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 || ''
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user