mirror of https://github.com/jumpserver/jumpserver
perf(users): 优化用户认证来源
parent
1216f15e45
commit
ea325f6e52
|
@ -1,13 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
|
import inspect
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.conf import settings
|
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.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 common.utils import get_object_or_none, get_request_ip, get_logger, bulk_get
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
@ -22,6 +26,59 @@ from .const import RSA_PRIVATE_KEY
|
||||||
logger = get_logger(__name__)
|
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:
|
class AuthMixin:
|
||||||
request = None
|
request = None
|
||||||
partial_credential_error = None
|
partial_credential_error = None
|
||||||
|
@ -121,13 +178,6 @@ class AuthMixin:
|
||||||
self.raise_credential_error(errors.reason_user_inactive)
|
self.raise_credential_error(errors.reason_user_inactive)
|
||||||
return user
|
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):
|
def _check_login_acl(self, user, ip):
|
||||||
# ACL 限制用户登录
|
# ACL 限制用户登录
|
||||||
from acls.models import LoginACL
|
from acls.models import LoginACL
|
||||||
|
@ -144,9 +194,6 @@ class AuthMixin:
|
||||||
user = self._check_auth_user_is_valid(username, password, public_key)
|
user = self._check_auth_user_is_valid(username, password, public_key)
|
||||||
# 校验login-acl规则
|
# 校验login-acl规则
|
||||||
self._check_login_acl(user, ip)
|
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_password_require_reset_or_not(user)
|
||||||
self._check_passwd_is_too_simple(user, password)
|
self._check_passwd_is_too_simple(user, password)
|
||||||
|
|
||||||
|
@ -154,7 +201,7 @@ class AuthMixin:
|
||||||
request.session['auth_password'] = 1
|
request.session['auth_password'] = 1
|
||||||
request.session['user_id'] = str(user.id)
|
request.session['user_id'] = str(user.id)
|
||||||
request.session['auto_login'] = auto_login
|
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
|
return user
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -679,6 +679,21 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
||||||
return
|
return
|
||||||
return super(User, self).delete()
|
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:
|
class Meta:
|
||||||
ordering = ['username']
|
ordering = ['username']
|
||||||
verbose_name = _("User")
|
verbose_name = _("User")
|
||||||
|
|
Loading…
Reference in New Issue