如何将密码保存为 postgressql 数据库中的哈希密码

问题描述 投票:0回答:1

我在 Django 中创建了一个扩展 AbstractBaseUser 的自定义用户模型 Employee。 Employee 模型有一个 CharField 类型的密码字段。我使用 PostgreSQL 作为数据库。

使用 EmployeeManager.create_employee 方法创建新的 Employee 实例并提供明文密码时,该密码不会作为哈希值保存在 PostgreSQL 数据库中。相反,它被存储为明文。

相关代码如下:

模型.py

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.hashers import make_password, check_password


class OrganizationManager(BaseUserManager):
    def create_organization(self, email, name, **extra_fields):
        if not email:
            raise ValueError(_('The Email field must be set'))
        email = self.normalize_email(email)
        organization = self.model(email=email, name=name, **extra_fields)
        return organization


class Organization(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_('email address'), unique=True)
    name = models.CharField(_('name'), max_length=150)
    is_active = models.BooleanField(_('active'), default=True)
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
    groups = models.ManyToManyField(
        'auth.Group',
        related_name='org_groups',  # Custom related name for Organization
        blank=True,
        help_text=_(
            'The groups this organization belongs to. A user will gain all permissions granted to each group.'),
    )
    user_permissions = models.ManyToManyField(
        'auth.Permission',
        related_name='org_user_permissions',  # Custom related name for Organization
        blank=True,
        help_text=_('Specific permissions for this organization.'),
    )

    password = None  # Override the password field
    last_login = None  # Override the last_login field

    class Meta:
        verbose_name = _('organization')
        verbose_name_plural = _('organizations')

    def __str__(self):
        return self.name


class EmployeeManager(BaseUserManager):
    def create_employee(self, email, name, organization, password=None, **extra_fields):
        if not email:
            raise ValueError(_('The Email field must be set'))
        email = self.normalize_email(email)
        employee = self.model(email=email, name=name,
                              organization=organization, **extra_fields)
        employee.password = make_password(password)
        employee.is_employee = True
        employee.save(using=self._db)
        return employee


class Employee(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_('email address'), unique=True)
    name = models.CharField(_('name'), max_length=150)
    organization = models.ForeignKey(
        'Organization', on_delete=models.CASCADE, null=False, blank=False, related_name='employees')
    password = models.TextField(_('password'))
    is_employee = models.BooleanField(_('employee status'), default=False)
    is_staff = models.BooleanField(_('staff status'), default=True)
    is_admin_staff = models.BooleanField(
        _('admin staff status'), default=False)
    is_active = models.BooleanField(_('active'), default=True)
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
    groups = models.ManyToManyField(
        'auth.Group',
        related_name='user_groups',  # Custom related name for User
        blank=True,
        help_text=_(
            'The groups this user belongs to. A user will gain all permissions granted to each group.'),
    )
    user_permissions = models.ManyToManyField(
        'auth.Permission',
        # You can keep the default here (or use a custom name)
        related_name='user_permissions',
        blank=True,
        help_text=_('Specific permissions for this user.'),
    )

    objects = EmployeeManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name', 'organization', 'password']

    class Meta:
        verbose_name = _('employee')
        verbose_name_plural = _('employees')

    def __str__(self):
        return self.email

    def get_full_name(self):
        return self.name

    def get_short_name(self):
        return self.name

    def check_password(self, raw_password):
        """Use in login view"""
        self.password = check_password(raw_password)

views.py

from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from django.contrib.auth import authenticate
from .serializers import OrganizationSerializer, EmployeeSerializer
from .models import Employee, Organization


class OrganizationRegisterView(generics.CreateAPIView):
    queryset = Organization.objects.all()
    serializer_class = OrganizationSerializer


class EmployeeRegisterView(generics.CreateAPIView):
    queryset = Employee.objects.filter(is_employee=True)
    serializer_class = EmployeeSerializer


class LoginView(generics.GenericAPIView):

    def post(self, request):
        email = request.data.get('email')
        password = request.data.get('password')
        user = authenticate(request, email=email, password=password)

        if user is not None:
            token, _ = Token.objects.get_or_create(user=user)
            if user.is_employee:
                user_data = {
                    'id': user.id,
                    'email': user.email,
                    'name': user.name,
                    'token': token.key,
                    'organization': user.organization.name
                }
            else:
                user_data = {
                    'id': user.id,
                    'email': user.email,
                    'name': user.name,
                    'token': token.key
                }
            return Response(user_data, status=status.HTTP_200_OK)
        else:
            return Response({'error': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)

序列化器.py

from rest_framework import serializers
from .models import Employee, Organization
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError


class OrganizationSerializer(serializers.ModelSerializer):
    class Meta:
        model = Organization
        fields = ('id', 'email', 'name')


class EmployeeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Employee
        fields = ('id', 'email', 'name', 'password', 'organization')
        extra_kwargs = {'password': {'write_only': True}}

    def validate_password(self, value):
        try:
            validate_password(value)
        except ValidationError as e:
            raise serializers.ValidationError(e.messages)
        return value

设置.py

"""
Django settings for core project.

Generated by 'django-admin startproject' using Django 4.2.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

import os
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-wz@e7odl56wk(=ot%e1_$&x4#ll9r_0ol*dhya43dvj)adk5*7'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'authentication',
    'core_manage'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'core.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

PASSWORD_HASHERS = [
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
    "django.contrib.auth.hashers.Argon2PasswordHasher",
    "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
    "django.contrib.auth.hashers.BCryptPasswordHasher",
    "django.contrib.auth.hashers.ScryptPasswordHasher",
    "django.contrib.auth.hashers.MD5PasswordHasher",
]

WSGI_APPLICATION = 'core.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'HOST': os.environ.get('DB_HOST'),
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASS'),
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        "OPTIONS": {
            "min_length": 9,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://redis:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
AUTH_USER_MODEL = 'authentication.Employee'

如你所见,我尝试了不同类型的PASSWORD_HASHERS,但我认为我编码错误,因为我刚刚开始使用Django。

采取的步骤:

  • 我尝试使用 make_password 和 set_password 方法在保存之前对密码进行哈希处理。 (经历了不同的 stackoverflow 解决方案,但没有帮助。)
  • 遵循此无法将哈希密码存储在django的数据库中仍然没有帮助
  • 我已将密码字段更改为 TextField,以防 PostgreSQL 出现长度问题。
  • 对模型进行更改后,我已经运行了必要的 Django 迁移。

问题: 尽管采取了这些步骤,密码仍然以明文形式保存在 PostgreSQL 数据库中。这可能是什么原因?在 Django 中使用带有 PostgreSQL 的自定义用户模型时,如何确保密码保存为哈希值?

django django-models django-rest-framework
1个回答
0
投票

我相信您已经使用普通的

Employee.objects.create(...)
来创建
Employee
实例。您可以尝试使用
create_employee
创建
Employee
实例:

Employee.objects.create_employee(email='[email protected]', name='Employee 1', organization='X', password='plain-text')
© www.soinside.com 2019 - 2024. All rights reserved.