mirror of https://github.com/jumpserver/jumpserver
perf(users): 优化用户认证来源
parent
1216f15e45
commit
ea325f6e52
|
@ -1,13 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
import inspect
|
||||
from urllib.parse import urlencode
|
||||
from functools import partial
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth import (
|
||||
BACKEND_SESSION_KEY, _get_backends,
|
||||
PermissionDenied, user_login_failed, _clean_credentials
|
||||
)
|
||||
from django.shortcuts import reverse
|
||||
from django.contrib.auth import BACKEND_SESSION_KEY
|
||||
|
||||
from common.utils import get_object_or_none, get_request_ip, get_logger, bulk_get
|
||||
from users.models import User
|
||||
|
@ -22,6 +26,59 @@ from .const import RSA_PRIVATE_KEY
|
|||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def check_backend_can_auth(username, backend_path, allowed_auth_backends):
|
||||
if allowed_auth_backends is not None and backend_path not in allowed_auth_backends:
|
||||
logger.debug('Skip user auth backend: {}, {} not in'.format(
|
||||
username, backend_path, ','.join(allowed_auth_backends)
|
||||
)
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def authenticate(request=None, **credentials):
|
||||
"""
|
||||
If the given credentials are valid, return a User object.
|
||||
"""
|
||||
username = credentials.get('username')
|
||||
allowed_auth_backends = User.get_user_allowed_auth_backends(username)
|
||||
|
||||
for backend, backend_path in _get_backends(return_tuples=True):
|
||||
# 预先检查,不浪费认证时间
|
||||
if not check_backend_can_auth(username, backend_path, allowed_auth_backends):
|
||||
continue
|
||||
|
||||
backend_signature = inspect.signature(backend.authenticate)
|
||||
try:
|
||||
backend_signature.bind(request, **credentials)
|
||||
except TypeError:
|
||||
# This backend doesn't accept these credentials as arguments. Try the next one.
|
||||
continue
|
||||
try:
|
||||
user = backend.authenticate(request, **credentials)
|
||||
except PermissionDenied:
|
||||
# This backend says to stop in our tracks - this user should not be allowed in at all.
|
||||
break
|
||||
if user is None:
|
||||
continue
|
||||
# 如果是 None, 证明没有检查过, 需要再次检查
|
||||
if allowed_auth_backends is None:
|
||||
# 有些 authentication 参数中不带 username, 之后还要再检查
|
||||
allowed_auth_backends = user.get_allowed_auth_backends()
|
||||
if not check_backend_can_auth(user.username, backend_path, allowed_auth_backends):
|
||||
continue
|
||||
|
||||
# Annotate the user object with the path of the backend.
|
||||
user.backend = backend_path
|
||||
return user
|
||||
|
||||
# The credentials supplied are invalid to all backends, fire signal
|
||||
user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request)
|
||||
|
||||
|
||||
auth.authenticate = authenticate
|
||||
|
||||
|
||||
class AuthMixin:
|
||||
request = None
|
||||
partial_credential_error = None
|
||||
|
@ -121,13 +178,6 @@ class AuthMixin:
|
|||
self.raise_credential_error(errors.reason_user_inactive)
|
||||
return user
|
||||
|
||||
def _check_auth_source_is_valid(self, user, auth_backend):
|
||||
# 限制只能从认证来源登录
|
||||
if settings.ONLY_ALLOW_AUTH_FROM_SOURCE:
|
||||
auth_backends_allowed = user.SOURCE_BACKEND_MAPPING.get(user.source)
|
||||
if auth_backend not in auth_backends_allowed:
|
||||
self.raise_credential_error(error=errors.reason_backend_not_match)
|
||||
|
||||
def _check_login_acl(self, user, ip):
|
||||
# ACL 限制用户登录
|
||||
from acls.models import LoginACL
|
||||
|
@ -144,9 +194,6 @@ class AuthMixin:
|
|||
user = self._check_auth_user_is_valid(username, password, public_key)
|
||||
# 校验login-acl规则
|
||||
self._check_login_acl(user, ip)
|
||||
# 限制只能从认证来源登录
|
||||
auth_backend = getattr(user, 'backend', 'django.contrib.auth.backends.ModelBackend')
|
||||
self._check_auth_source_is_valid(user, auth_backend)
|
||||
self._check_password_require_reset_or_not(user)
|
||||
self._check_passwd_is_too_simple(user, password)
|
||||
|
||||
|
@ -154,7 +201,7 @@ class AuthMixin:
|
|||
request.session['auth_password'] = 1
|
||||
request.session['user_id'] = str(user.id)
|
||||
request.session['auto_login'] = auto_login
|
||||
request.session['auth_backend'] = auth_backend
|
||||
request.session['auth_backend'] = getattr(user, 'backend', settings.AUTH_BACKEND_MODEL)
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -679,6 +679,21 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
|||
return
|
||||
return super(User, self).delete()
|
||||
|
||||
@classmethod
|
||||
def get_user_allowed_auth_backends(cls, username):
|
||||
if not settings.ONLY_ALLOW_AUTH_FROM_SOURCE or not username:
|
||||
# return settings.AUTHENTICATION_BACKENDS
|
||||
return None
|
||||
user = cls.objects.filter(username=username).first()
|
||||
if not user:
|
||||
return None
|
||||
return user.get_allowed_auth_backends()
|
||||
|
||||
def get_allowed_auth_backends(self):
|
||||
if not settings.ONLY_ALLOW_AUTH_FROM_SOURCE:
|
||||
return None
|
||||
return self.SOURCE_BACKEND_MAPPING.get(self.source, [])
|
||||
|
||||
class Meta:
|
||||
ordering = ['username']
|
||||
verbose_name = _("User")
|
||||
|
|
Loading…
Reference in New Issue