mirror of https://github.com/jumpserver/jumpserver
Merge pull request #8803 from jumpserver/pr@dev@feat_customauthbackend
feat: 支持自定义认证 backend;统一其他认证方式的信号触发逻辑;pull/8810/head
commit
bdeec0d3cb
|
@ -0,0 +1,64 @@
|
|||
from django.conf import settings
|
||||
from django.utils.module_loading import import_string
|
||||
from common.utils import get_logger
|
||||
from django.contrib.auth import get_user_model
|
||||
from authentication.signals import user_auth_failed, user_auth_success
|
||||
|
||||
|
||||
from .base import JMSModelBackend
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class CustomAuthBackend(JMSModelBackend):
|
||||
custom_auth_method_path = 'data.auth.main.authenticate'
|
||||
|
||||
def load_authenticate_method(self):
|
||||
return import_string(self.custom_auth_method_path)
|
||||
|
||||
def is_enabled(self):
|
||||
if not settings.AUTH_CUSTOM:
|
||||
return False
|
||||
try:
|
||||
self.load_authenticate_method()
|
||||
except Exception as e:
|
||||
logger.warning('Not enabled custom auth backend: {}'.format(e))
|
||||
return False
|
||||
else:
|
||||
logger.info('Enabled custom auth backend')
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def get_or_create_user_from_userinfo(userinfo: dict):
|
||||
username = userinfo['username']
|
||||
attrs = ['name', 'username', 'email', 'is_active']
|
||||
defaults = {attr: userinfo[attr] for attr in attrs}
|
||||
user, created = get_user_model().objects.get_or_create(
|
||||
username=username, defaults=defaults
|
||||
)
|
||||
return user, created
|
||||
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
try:
|
||||
authenticate = self.load_authenticate_method()
|
||||
userinfo: dict = authenticate(username=username, password=password, **kwargs)
|
||||
user, created = self.get_or_create_user_from_userinfo(userinfo)
|
||||
except Exception as e:
|
||||
logger.error('Custom authenticate error: {}'.format(e))
|
||||
return None
|
||||
|
||||
if self.user_can_authenticate(user):
|
||||
logger.info(f'Custom authenticate success: {user.username}')
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_CUSTOM
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.info(f'Custom authenticate failed: {user.username}')
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=user.username,
|
||||
reason=_('User invalid, disabled or expired'),
|
||||
backend=settings.AUTH_BACKEND_CUSTOM
|
||||
)
|
||||
return None
|
|
@ -10,11 +10,11 @@ from django.urls import reverse
|
|||
from common.utils import get_logger
|
||||
from users.utils import construct_user_email
|
||||
from authentication.utils import build_absolute_uri
|
||||
from authentication.signals import user_auth_failed, user_auth_success
|
||||
from common.exceptions import JMSException
|
||||
|
||||
from .signals import (
|
||||
oauth2_create_or_update_user, oauth2_user_login_failed,
|
||||
oauth2_user_login_success
|
||||
oauth2_create_or_update_user
|
||||
)
|
||||
from ..base import JMSModelBackend
|
||||
|
||||
|
@ -145,13 +145,17 @@ class OAuth2Backend(JMSModelBackend):
|
|||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('OAuth2 user login success'))
|
||||
logger.debug(log_prompt.format('Send signal => oauth2 user login success'))
|
||||
oauth2_user_login_success.send(sender=self.__class__, request=request, user=user)
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_OAUTH2
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('OAuth2 user login failed'))
|
||||
logger.debug(log_prompt.format('Send signal => oauth2 user login failed'))
|
||||
oauth2_user_login_failed.send(
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=user.username,
|
||||
reason=_('User invalid, disabled or expired')
|
||||
reason=_('User invalid, disabled or expired'),
|
||||
backend=settings.AUTH_BACKEND_OAUTH2
|
||||
)
|
||||
return None
|
||||
|
|
|
@ -4,6 +4,4 @@ from django.dispatch import Signal
|
|||
oauth2_create_or_update_user = Signal(
|
||||
providing_args=['request', 'user', 'created', 'name', 'username', 'email']
|
||||
)
|
||||
oauth2_user_login_success = Signal(providing_args=['request', 'user'])
|
||||
oauth2_user_login_failed = Signal(providing_args=['request', 'username', 'reason'])
|
||||
|
||||
|
|
|
@ -26,8 +26,9 @@ from ..base import JMSBaseAuthBackend
|
|||
from .utils import validate_and_return_id_token
|
||||
from .decorator import ssl_verification
|
||||
from .signals import (
|
||||
openid_create_or_update_user, openid_user_login_failed, openid_user_login_success
|
||||
openid_create_or_update_user
|
||||
)
|
||||
from authentication.signals import user_auth_success, user_auth_failed
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
@ -213,14 +214,18 @@ class OIDCAuthCodeBackend(OIDCBaseBackend):
|
|||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('OpenID user login success'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login success'))
|
||||
openid_user_login_success.send(sender=self.__class__, request=request, user=user)
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_OIDC_CODE
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('OpenID user login failed'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
openid_user_login_failed.send(
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=user.username,
|
||||
reason="User is invalid"
|
||||
reason="User is invalid", backend=settings.AUTH_BACKEND_OIDC_CODE
|
||||
|
||||
)
|
||||
return None
|
||||
|
||||
|
@ -271,8 +276,9 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend):
|
|||
"content is: {}, error is: {}".format(token_response.content, str(e))
|
||||
logger.debug(log_prompt.format(error))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
openid_user_login_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason=error
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason=error,
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -299,8 +305,9 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend):
|
|||
"content is: {}, error is: {}".format(claims_response.content, str(e))
|
||||
logger.debug(log_prompt.format(error))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
openid_user_login_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason=error
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason=error,
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -312,13 +319,16 @@ class OIDCAuthPasswordBackend(OIDCBaseBackend):
|
|||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('OpenID user login success'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login success'))
|
||||
openid_user_login_success.send(
|
||||
sender=self.__class__, request=request, user=user
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user,
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('OpenID user login failed'))
|
||||
logger.debug(log_prompt.format('Send signal => openid user login failed'))
|
||||
openid_user_login_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason="User is invalid"
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username, reason="User is invalid",
|
||||
backend=settings.AUTH_BACKEND_OIDC_PASSWORD
|
||||
)
|
||||
return None
|
||||
|
|
|
@ -13,6 +13,4 @@ from django.dispatch import Signal
|
|||
openid_create_or_update_user = Signal(
|
||||
providing_args=['request', 'user', 'created', 'name', 'username', 'email']
|
||||
)
|
||||
openid_user_login_success = Signal(providing_args=['request', 'user'])
|
||||
openid_user_login_failed = Signal(providing_args=['request', 'username', 'reason'])
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@ from django.db import transaction
|
|||
from common.utils import get_logger
|
||||
from authentication.errors import reason_choices, reason_user_invalid
|
||||
from .signals import (
|
||||
saml2_user_authenticated, saml2_user_authentication_failed,
|
||||
saml2_create_or_update_user
|
||||
)
|
||||
from authentication.signals import user_auth_failed, user_auth_success
|
||||
from ..base import JMSModelBackend
|
||||
|
||||
__all__ = ['SAML2Backend']
|
||||
|
@ -55,14 +55,16 @@ class SAML2Backend(JMSModelBackend):
|
|||
|
||||
if self.user_can_authenticate(user):
|
||||
logger.debug(log_prompt.format('SAML2 user login success'))
|
||||
saml2_user_authenticated.send(
|
||||
sender=self, request=request, user=user, created=created
|
||||
user_auth_success.send(
|
||||
sender=self.__class__, request=request, user=user, created=created,
|
||||
backend=settings.AUTH_BACKEND_SAML2
|
||||
)
|
||||
return user
|
||||
else:
|
||||
logger.debug(log_prompt.format('SAML2 user login failed'))
|
||||
saml2_user_authentication_failed.send(
|
||||
sender=self, request=request, username=username,
|
||||
reason=reason_choices.get(reason_user_invalid)
|
||||
user_auth_failed.send(
|
||||
sender=self.__class__, request=request, username=username,
|
||||
reason=reason_choices.get(reason_user_invalid),
|
||||
backend=settings.AUTH_BACKEND_SAML2
|
||||
)
|
||||
return None
|
||||
|
|
|
@ -2,5 +2,3 @@ from django.dispatch import Signal
|
|||
|
||||
|
||||
saml2_create_or_update_user = Signal(providing_args=('user', 'created', 'request', 'attrs'))
|
||||
saml2_user_authenticated = Signal(providing_args=('user', 'created', 'request'))
|
||||
saml2_user_authentication_failed = Signal(providing_args=('request', 'username', 'reason'))
|
||||
|
|
|
@ -7,16 +7,7 @@ from django.dispatch import receiver
|
|||
from django_cas_ng.signals import cas_user_authenticated
|
||||
|
||||
from apps.jumpserver.settings.auth import AUTHENTICATION_BACKENDS_THIRD_PARTY
|
||||
from authentication.backends.oidc.signals import (
|
||||
openid_user_login_failed, openid_user_login_success
|
||||
)
|
||||
from authentication.backends.saml2.signals import (
|
||||
saml2_user_authenticated, saml2_user_authentication_failed
|
||||
)
|
||||
from authentication.backends.oauth2.signals import (
|
||||
oauth2_user_login_failed, oauth2_user_login_success
|
||||
)
|
||||
from .signals import post_auth_success, post_auth_failed
|
||||
from .signals import post_auth_success, post_auth_failed, user_auth_failed, user_auth_success
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
|
@ -29,7 +20,8 @@ def on_user_auth_login_success(sender, user, request, **kwargs):
|
|||
and user.mfa_enabled \
|
||||
and not request.session.get('auth_mfa'):
|
||||
request.session['auth_mfa_required'] = 1
|
||||
if not request.session.get("auth_third_party_done") and request.session.get('auth_backend') in AUTHENTICATION_BACKENDS_THIRD_PARTY:
|
||||
if not request.session.get("auth_third_party_done") and \
|
||||
request.session.get('auth_backend') in AUTHENTICATION_BACKENDS_THIRD_PARTY:
|
||||
request.session['auth_third_party_required'] = 1
|
||||
# 单点登录,超过了自动退出
|
||||
if settings.USER_LOGIN_SINGLE_MACHINE_ENABLED:
|
||||
|
@ -44,43 +36,19 @@ def on_user_auth_login_success(sender, user, request, **kwargs):
|
|||
request.session['auth_session_expiration_required'] = 1
|
||||
|
||||
|
||||
@receiver(openid_user_login_success)
|
||||
def on_oidc_user_login_success(sender, request, user, create=False, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_OIDC_CODE
|
||||
post_auth_success.send(sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(openid_user_login_failed)
|
||||
def on_oidc_user_login_failed(sender, username, request, reason, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_OIDC_CODE
|
||||
post_auth_failed.send(sender, username=username, request=request, reason=reason)
|
||||
|
||||
|
||||
@receiver(cas_user_authenticated)
|
||||
def on_cas_user_login_success(sender, request, user, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_CAS
|
||||
post_auth_success.send(sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(saml2_user_authenticated)
|
||||
def on_saml2_user_login_success(sender, request, user, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_SAML2
|
||||
@receiver(user_auth_success)
|
||||
def on_user_login_success(sender, request, user, backend, create=False, **kwargs):
|
||||
request.session['auth_backend'] = backend
|
||||
post_auth_success.send(sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(saml2_user_authentication_failed)
|
||||
def on_saml2_user_login_failed(sender, request, username, reason, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_SAML2
|
||||
post_auth_failed.send(sender, username=username, request=request, reason=reason)
|
||||
|
||||
|
||||
@receiver(oauth2_user_login_success)
|
||||
def on_oauth2_user_login_success(sender, request, user, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_OAUTH2
|
||||
post_auth_success.send(sender, user=user, request=request)
|
||||
|
||||
|
||||
@receiver(oauth2_user_login_failed)
|
||||
def on_oauth2_user_login_failed(sender, username, request, reason, **kwargs):
|
||||
request.session['auth_backend'] = settings.AUTH_BACKEND_OAUTH2
|
||||
@receiver(user_auth_failed)
|
||||
def on_user_login_failed(sender, username, request, reason, backend, **kwargs):
|
||||
request.session['auth_backend'] = backend
|
||||
post_auth_failed.send(sender, username=username, request=request, reason=reason)
|
||||
|
|
|
@ -3,3 +3,7 @@ from django.dispatch import Signal
|
|||
|
||||
post_auth_success = Signal(providing_args=('user', 'request'))
|
||||
post_auth_failed = Signal(providing_args=('username', 'request', 'reason'))
|
||||
|
||||
|
||||
user_auth_success = Signal(providing_args=('user', 'request', 'backend', 'create'))
|
||||
user_auth_failed = Signal(providing_args=('username', 'request', 'reason', 'backend'))
|
||||
|
|
|
@ -224,6 +224,8 @@ class Config(dict):
|
|||
'CONNECTION_TOKEN_EXPIRATION': 5 * 60,
|
||||
|
||||
# Custom Config
|
||||
'AUTH_CUSTOM': False,
|
||||
|
||||
# Auth LDAP settings
|
||||
'AUTH_LDAP': False,
|
||||
'AUTH_LDAP_SERVER_URI': 'ldap://localhost:389',
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#
|
||||
import os
|
||||
import ldap
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from ..const import CONFIG, PROJECT_DIR, BASE_DIR
|
||||
|
||||
|
@ -195,7 +194,7 @@ AUTH_BACKEND_AUTH_TOKEN = 'authentication.backends.sso.AuthorizationTokenAuthent
|
|||
AUTH_BACKEND_SAML2 = 'authentication.backends.saml2.SAML2Backend'
|
||||
AUTH_BACKEND_OAUTH2 = 'authentication.backends.oauth2.OAuth2Backend'
|
||||
AUTH_BACKEND_TEMP_TOKEN = 'authentication.backends.token.TempTokenAuthBackend'
|
||||
|
||||
AUTH_BACKEND_CUSTOM = 'authentication.backends.custom.CustomAuthBackend'
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
# 只做权限校验
|
||||
|
@ -208,9 +207,14 @@ AUTHENTICATION_BACKENDS = [
|
|||
# 扫码模式
|
||||
AUTH_BACKEND_WECOM, AUTH_BACKEND_DINGTALK, AUTH_BACKEND_FEISHU,
|
||||
# Token模式
|
||||
AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_SSO, AUTH_BACKEND_TEMP_TOKEN
|
||||
AUTH_BACKEND_AUTH_TOKEN, AUTH_BACKEND_SSO, AUTH_BACKEND_TEMP_TOKEN,
|
||||
]
|
||||
|
||||
AUTH_CUSTOM = CONFIG.AUTH_CUSTOM
|
||||
if AUTH_CUSTOM:
|
||||
# 自定义认证模块
|
||||
AUTHENTICATION_BACKENDS.append(AUTH_BACKEND_CUSTOM)
|
||||
|
||||
AUTHENTICATION_BACKENDS_THIRD_PARTY = [AUTH_BACKEND_OIDC_CODE, AUTH_BACKEND_CAS, AUTH_BACKEND_SAML2, AUTH_BACKEND_OAUTH2]
|
||||
ONLY_ALLOW_EXIST_USER_AUTH = CONFIG.ONLY_ALLOW_EXIST_USER_AUTH
|
||||
ONLY_ALLOW_AUTH_FROM_SOURCE = CONFIG.ONLY_ALLOW_AUTH_FROM_SOURCE
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.13 on 2022-08-24 02:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0039_auto_20211229_1852'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='source',
|
||||
field=models.CharField(choices=[('local', 'Local'), ('ldap', 'LDAP/AD'), ('openid', 'OpenID'), ('radius', 'Radius'), ('cas', 'CAS'), ('saml2', 'SAML2'), ('oauth2', 'OAuth2'), ('custom', 'Custom')], default='local', max_length=30, verbose_name='Source'),
|
||||
),
|
||||
]
|
|
@ -629,6 +629,7 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
|||
cas = 'cas', 'CAS'
|
||||
saml2 = 'saml2', 'SAML2'
|
||||
oauth2 = 'oauth2', 'OAuth2'
|
||||
custom = 'custom', 'Custom'
|
||||
|
||||
SOURCE_BACKEND_MAPPING = {
|
||||
Source.local: [
|
||||
|
@ -656,6 +657,9 @@ class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
|
|||
Source.oauth2: [
|
||||
settings.AUTH_BACKEND_OAUTH2
|
||||
],
|
||||
Source.custom: [
|
||||
settings.AUTH_BACKEND_CUSTOM
|
||||
]
|
||||
}
|
||||
|
||||
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
|
|
Loading…
Reference in New Issue