perf: Client login

pull/14684/head
feng 2024-12-18 17:25:40 +08:00 committed by feng626
parent 3cd68ba0a9
commit 7a9a71197a
17 changed files with 83 additions and 29 deletions

View File

@ -1,12 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.urls import path
import django_cas_ng.views import django_cas_ng.views
from django.urls import path
from .views import CASLoginView from .views import CASLoginView, CASCallbackClientView
urlpatterns = [ urlpatterns = [
path('login/', CASLoginView.as_view(), name='cas-login'), path('login/', CASLoginView.as_view(), name='cas-login'),
path('logout/', django_cas_ng.views.LogoutView.as_view(), name='cas-logout'), path('logout/', django_cas_ng.views.LogoutView.as_view(), name='cas-logout'),
path('callback/', django_cas_ng.views.CallbackView.as_view(), name='cas-proxy-callback'), path('callback/', django_cas_ng.views.CallbackView.as_view(), name='cas-proxy-callback'),
path('login/client', CASCallbackClientView.as_view(), name='cas-proxy-callback-client'),
] ]

View File

@ -1,9 +1,12 @@
from django_cas_ng.views import LoginView
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.generic import View
from django_cas_ng.views import LoginView
__all__ = ['LoginView'] __all__ = ['LoginView']
from authentication.views.utils import redirect_to_guard_view
class CASLoginView(LoginView): class CASLoginView(LoginView):
def get(self, request): def get(self, request):
@ -13,3 +16,8 @@ class CASLoginView(LoginView):
return HttpResponseRedirect('/') return HttpResponseRedirect('/')
class CASCallbackClientView(View):
http_method_names = ['get', ]
def get(self, request):
return redirect_to_guard_view(query_string='next=client')

View File

@ -4,9 +4,9 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('login/', views.OAuth2AuthRequestView.as_view(), name='login'), path('login/', views.OAuth2AuthRequestView.as_view(), name='login'),
path('callback/', views.OAuth2AuthCallbackView.as_view(), name='login-callback'), path('callback/', views.OAuth2AuthCallbackView.as_view(), name='login-callback'),
path('callback/client/', views.OAuth2AuthCallbackClientView.as_view(), name='login-callback-client'),
path('logout/', views.OAuth2EndSessionView.as_view(), name='logout') path('logout/', views.OAuth2EndSessionView.as_view(), name='logout')
] ]

View File

@ -1,16 +1,16 @@
from django.views import View
from django.conf import settings from django.conf import settings
from django.contrib import auth from django.contrib import auth
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse from django.urls import reverse
from django.utils.http import urlencode from django.utils.http import urlencode
from django.views import View
from authentication.mixins import authenticate
from authentication.utils import build_absolute_uri from authentication.utils import build_absolute_uri
from authentication.views.mixins import FlashMessageMixin from authentication.views.mixins import FlashMessageMixin
from authentication.mixins import authenticate from authentication.views.utils import redirect_to_guard_view
from common.utils import get_logger from common.utils import get_logger
logger = get_logger(__file__) logger = get_logger(__file__)
@ -67,6 +67,13 @@ class OAuth2AuthCallbackView(View, FlashMessageMixin):
return HttpResponseRedirect(redirect_url) return HttpResponseRedirect(redirect_url)
class OAuth2AuthCallbackClientView(View):
http_method_names = ['get', ]
def get(self, request):
return redirect_to_guard_view(query_string='next=client')
class OAuth2EndSessionView(View): class OAuth2EndSessionView(View):
http_method_names = ['get', 'post', ] http_method_names = ['get', 'post', ]

View File

@ -12,9 +12,9 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('login/', views.OIDCAuthRequestView.as_view(), name='login'), path('login/', views.OIDCAuthRequestView.as_view(), name='login'),
path('callback/', views.OIDCAuthCallbackView.as_view(), name='login-callback'), path('callback/', views.OIDCAuthCallbackView.as_view(), name='login-callback'),
path('callback/client/', views.OIDCAuthCallbackClientView.as_view(), name='login-callback-client'),
path('logout/', views.OIDCEndSessionView.as_view(), name='logout'), path('logout/', views.OIDCEndSessionView.as_view(), name='logout'),
] ]

View File

@ -22,13 +22,14 @@ from django.http import HttpResponseRedirect, QueryDict
from django.urls import reverse from django.urls import reverse
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.http import urlencode from django.utils.http import urlencode
from django.views.generic import View
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import View
from authentication.utils import build_absolute_uri_for_oidc from authentication.utils import build_absolute_uri_for_oidc
from authentication.views.mixins import FlashMessageMixin from authentication.views.mixins import FlashMessageMixin
from common.utils import safe_next_url from common.utils import safe_next_url
from .utils import get_logger from .utils import get_logger
from ...views.utils import redirect_to_guard_view
logger = get_logger(__file__) logger = get_logger(__file__)
@ -208,6 +209,13 @@ class OIDCAuthCallbackView(View, FlashMessageMixin):
return HttpResponseRedirect(settings.AUTH_OPENID_AUTHENTICATION_FAILURE_REDIRECT_URI) return HttpResponseRedirect(settings.AUTH_OPENID_AUTHENTICATION_FAILURE_REDIRECT_URI)
class OIDCAuthCallbackClientView(View):
http_method_names = ['get', ]
def get(self, request):
return redirect_to_guard_view(query_string='next=client')
class OIDCEndSessionView(View): class OIDCEndSessionView(View):
""" Allows to end the session of any user authenticated using OpenID Connect. """ Allows to end the session of any user authenticated using OpenID Connect.

View File

@ -4,10 +4,10 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('login/', views.Saml2AuthRequestView.as_view(), name='saml2-login'), path('login/', views.Saml2AuthRequestView.as_view(), name='saml2-login'),
path('logout/', views.Saml2EndSessionView.as_view(), name='saml2-logout'), path('logout/', views.Saml2EndSessionView.as_view(), name='saml2-logout'),
path('callback/', views.Saml2AuthCallbackView.as_view(), name='saml2-callback'), path('callback/', views.Saml2AuthCallbackView.as_view(), name='saml2-callback'),
path('callback/client/', views.Saml2AuthCallbackClientView.as_view(), name='saml2-callback-client'),
path('metadata/', views.Saml2AuthMetadataView.as_view(), name='saml2-metadata'), path('metadata/', views.Saml2AuthMetadataView.as_view(), name='saml2-metadata'),
] ]

View File

@ -19,6 +19,7 @@ from onelogin.saml2.idp_metadata_parser import (
from authentication.views.mixins import FlashMessageMixin from authentication.views.mixins import FlashMessageMixin
from common.utils import get_logger from common.utils import get_logger
from .settings import JmsSaml2Settings from .settings import JmsSaml2Settings
from ...views.utils import redirect_to_guard_view
logger = get_logger(__file__) logger = get_logger(__file__)
@ -298,6 +299,13 @@ class Saml2AuthCallbackView(View, PrepareRequestMixin, FlashMessageMixin):
return super().dispatch(*args, **kwargs) return super().dispatch(*args, **kwargs)
class Saml2AuthCallbackClientView(View):
http_method_names = ['get', ]
def get(self, request):
return redirect_to_guard_view(query_string='next=client')
class Saml2AuthMetadataView(View, PrepareRequestMixin): class Saml2AuthMetadataView(View, PrepareRequestMixin):
def get(self, request): def get(self, request):

View File

@ -110,6 +110,9 @@ class BaseLoginCallbackView(AuthMixin, FlashMessageMixin, IMClientMixin, View):
msg = e.msg msg = e.msg
response = self.get_failed_response(login_url, title=msg, msg=msg) response = self.get_failed_response(login_url, title=msg, msg=msg)
return response return response
if 'next=client' in redirect_url:
self.request.META['QUERY_STRING'] += '&next=client'
return self.redirect_to_guard_view() return self.redirect_to_guard_view()

View File

@ -175,6 +175,8 @@ class DingTalkQRLoginView(DingTalkQRMixin, METAMixin, View):
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
redirect_url = request.GET.get('redirect_url') or reverse('index') redirect_url = request.GET.get('redirect_url') or reverse('index')
query_string = request.GET.urlencode()
redirect_url = f'{redirect_url}?{query_string}'
next_url = self.get_next_url_from_meta() or reverse('index') next_url = self.get_next_url_from_meta() or reverse('index')
next_url = safe_next_url(next_url, request=request) next_url = safe_next_url(next_url, request=request)

View File

@ -110,6 +110,8 @@ class FeiShuQRLoginView(FeiShuQRMixin, View):
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
redirect_url = request.GET.get('redirect_url') or reverse('index') redirect_url = request.GET.get('redirect_url') or reverse('index')
query_string = request.GET.urlencode()
redirect_url = f'{redirect_url}?{query_string}'
redirect_uri = reverse(f'authentication:{self.category}-qr-login-callback', external=True) redirect_uri = reverse(f'authentication:{self.category}-qr-login-callback', external=True)
redirect_uri += '?' + urlencode({ redirect_uri += '?' + urlencode({
'redirect_url': redirect_url, 'redirect_url': redirect_url,

View File

@ -45,69 +45,70 @@ class UserLoginContextMixin:
error_origin: str error_origin: str
def get_support_auth_methods(self): def get_support_auth_methods(self):
query_string = self.request.GET.urlencode()
auth_methods = [ auth_methods = [
{ {
'name': 'OpenID', 'name': 'OpenID',
'enabled': settings.AUTH_OPENID, 'enabled': settings.AUTH_OPENID,
'url': reverse('authentication:openid:login'), 'url': f"{reverse('authentication:openid:login')}?{query_string}",
'logo': static('img/login_oidc_logo.png'), 'logo': static('img/login_oidc_logo.png'),
'auto_redirect': True # 是否支持自动重定向 'auto_redirect': True # 是否支持自动重定向
}, },
{ {
'name': 'CAS', 'name': 'CAS',
'enabled': settings.AUTH_CAS, 'enabled': settings.AUTH_CAS,
'url': reverse('authentication:cas:cas-login'), 'url': f"{reverse('authentication:cas:cas-login')}?{query_string}",
'logo': static('img/login_cas_logo.png'), 'logo': static('img/login_cas_logo.png'),
'auto_redirect': True 'auto_redirect': True
}, },
{ {
'name': 'SAML2', 'name': 'SAML2',
'enabled': settings.AUTH_SAML2, 'enabled': settings.AUTH_SAML2,
'url': reverse('authentication:saml2:saml2-login'), 'url': f"{reverse('authentication:saml2:saml2-login')}?{query_string}",
'logo': static('img/login_saml2_logo.png'), 'logo': static('img/login_saml2_logo.png'),
'auto_redirect': True 'auto_redirect': True
}, },
{ {
'name': settings.AUTH_OAUTH2_PROVIDER, 'name': settings.AUTH_OAUTH2_PROVIDER,
'enabled': settings.AUTH_OAUTH2, 'enabled': settings.AUTH_OAUTH2,
'url': reverse('authentication:oauth2:login'), 'url': f"{reverse('authentication:oauth2:login')}?{query_string}",
'logo': static_or_direct(settings.AUTH_OAUTH2_LOGO_PATH), 'logo': static_or_direct(settings.AUTH_OAUTH2_LOGO_PATH),
'auto_redirect': True 'auto_redirect': True
}, },
{ {
'name': _('WeCom'), 'name': _('WeCom'),
'enabled': settings.AUTH_WECOM, 'enabled': settings.AUTH_WECOM,
'url': reverse('authentication:wecom-qr-login'), 'url': f"{reverse('authentication:wecom-qr-login')}?{query_string}",
'logo': static('img/login_wecom_logo.png'), 'logo': static('img/login_wecom_logo.png'),
}, },
{ {
'name': _('DingTalk'), 'name': _('DingTalk'),
'enabled': settings.AUTH_DINGTALK, 'enabled': settings.AUTH_DINGTALK,
'url': reverse('authentication:dingtalk-qr-login'), 'url': f"{reverse('authentication:dingtalk-qr-login')}?{query_string}",
'logo': static('img/login_dingtalk_logo.png') 'logo': static('img/login_dingtalk_logo.png')
}, },
{ {
'name': _('FeiShu'), 'name': _('FeiShu'),
'enabled': settings.AUTH_FEISHU, 'enabled': settings.AUTH_FEISHU,
'url': reverse('authentication:feishu-qr-login'), 'url': f"{reverse('authentication:feishu-qr-login')}?{query_string}",
'logo': static('img/login_feishu_logo.png') 'logo': static('img/login_feishu_logo.png')
}, },
{ {
'name': 'Lark', 'name': 'Lark',
'enabled': settings.AUTH_LARK, 'enabled': settings.AUTH_LARK,
'url': reverse('authentication:lark-qr-login'), 'url': f"{reverse('authentication:lark-qr-login')}?{query_string}",
'logo': static('img/login_lark_logo.png') 'logo': static('img/login_lark_logo.png')
}, },
{ {
'name': _('Slack'), 'name': _('Slack'),
'enabled': settings.AUTH_SLACK, 'enabled': settings.AUTH_SLACK,
'url': reverse('authentication:slack-qr-login'), 'url': f"{reverse('authentication:slack-qr-login')}?{query_string}",
'logo': static('img/login_slack_logo.png') 'logo': static('img/login_slack_logo.png')
}, },
{ {
'name': _("Passkey"), 'name': _("Passkey"),
'enabled': settings.AUTH_PASSKEY, 'enabled': settings.AUTH_PASSKEY,
'url': reverse('api-auth:passkey-login'), 'url': f"{reverse('api-auth:passkey-login')}?{query_string}",
'logo': static('img/login_passkey.png') 'logo': static('img/login_passkey.png')
} }
] ]
@ -220,7 +221,7 @@ class UserLoginView(mixins.AuthMixin, UserLoginContextMixin, FormView):
'redirect_url': redirect_url, 'redirect_url': redirect_url,
'interval': 3, 'interval': 3,
'has_cancel': True, 'has_cancel': True,
'cancel_url': reverse('authentication:login') + '?admin=1' 'cancel_url': reverse('authentication:login') + f'?admin=1&{query_string}'
} }
redirect_url = FlashMessageUtil.gen_message_url(message_data) redirect_url = FlashMessageUtil.gen_message_url(message_data)
return redirect_url return redirect_url
@ -306,6 +307,7 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView):
login_url = reverse_lazy('authentication:login') login_url = reverse_lazy('authentication:login')
login_mfa_url = reverse_lazy('authentication:login-mfa') login_mfa_url = reverse_lazy('authentication:login-mfa')
login_confirm_url = reverse_lazy('authentication:login-wait-confirm') login_confirm_url = reverse_lazy('authentication:login-wait-confirm')
query_string = True
def format_redirect_url(self, url): def format_redirect_url(self, url):
args = self.request.META.get('QUERY_STRING', '') args = self.request.META.get('QUERY_STRING', '')

View File

@ -2,13 +2,14 @@
# #
from __future__ import unicode_literals from __future__ import unicode_literals
from django.views.generic.edit import FormView
from django.shortcuts import redirect, reverse from django.shortcuts import redirect, reverse
from django.views.generic.edit import FormView
from common.utils import get_logger from common.utils import get_logger
from users.views import UserFaceCaptureView from users.views import UserFaceCaptureView
from .. import forms, errors, mixins
from .utils import redirect_to_guard_view from .utils import redirect_to_guard_view
from .. import forms, errors, mixins
from ..const import MFAType from ..const import MFAType
logger = get_logger(__name__) logger = get_logger(__name__)
@ -48,7 +49,8 @@ class UserLoginMFAView(mixins.AuthMixin, FormView):
self._do_check_user_mfa(code, mfa_type) self._do_check_user_mfa(code, mfa_type)
user, ip = self.get_user_from_session(), self.get_request_ip() user, ip = self.get_user_from_session(), self.get_request_ip()
MFABlockUtils(user.username, ip).clean_failed_count() MFABlockUtils(user.username, ip).clean_failed_count()
return redirect_to_guard_view('mfa_ok') query_string = self.request.GET.urlencode()
return redirect_to_guard_view('mfa_ok', query_string)
except (errors.MFAFailedError, errors.BlockMFAError) as e: except (errors.MFAFailedError, errors.BlockMFAError) as e:
form.add_error('code', e.msg) form.add_error('code', e.msg)
return super().form_invalid(form) return super().form_invalid(form)

View File

@ -98,6 +98,8 @@ class SlackQRLoginView(SlackMixin, View):
def get(self, request: Request): def get(self, request: Request):
redirect_url = request.GET.get('redirect_url') or reverse('index') redirect_url = request.GET.get('redirect_url') or reverse('index')
query_string = request.GET.urlencode()
redirect_url = f'{redirect_url}?{query_string}'
redirect_uri = reverse('authentication:slack-qr-login-callback', external=True) redirect_uri = reverse('authentication:slack-qr-login-callback', external=True)
redirect_uri += '?' + urlencode({ redirect_uri += '?' + urlencode({
'redirect_url': redirect_url, 'redirect_url': redirect_url,

View File

@ -1,8 +1,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from urllib.parse import urlencode, parse_qsl
from django.shortcuts import reverse, redirect from django.shortcuts import reverse, redirect
def redirect_to_guard_view(comment=''): def redirect_to_guard_view(comment='', query_string=None):
continue_url = reverse('authentication:login-guard') + '?_=' + comment params = {'_': comment}
base_url = reverse('authentication:login-guard')
if query_string:
params.update(dict(parse_qsl(query_string)))
query_string = urlencode(params)
continue_url = f"{base_url}?{query_string}"
return redirect(continue_url) return redirect(continue_url)

View File

@ -22,7 +22,6 @@ from users.views import UserVerifyPasswordView
from .base import BaseLoginCallbackView, BaseBindCallbackView from .base import BaseLoginCallbackView, BaseBindCallbackView
from .mixins import METAMixin, FlashMessageMixin from .mixins import METAMixin, FlashMessageMixin
logger = get_logger(__file__) logger = get_logger(__file__)
@ -86,6 +85,8 @@ class WeComQRBindView(WeComQRMixin, View):
def get(self, request: HttpRequest): def get(self, request: HttpRequest):
redirect_url = request.GET.get('redirect_url') redirect_url = request.GET.get('redirect_url')
query_string = request.GET.urlencode()
redirect_url = f'{redirect_url}?{query_string}'
redirect_uri = reverse('authentication:wecom-qr-bind-callback', external=True) redirect_uri = reverse('authentication:wecom-qr-bind-callback', external=True)
redirect_uri += '?' + urlencode({'redirect_url': redirect_url}) redirect_uri += '?' + urlencode({'redirect_url': redirect_url})

View File

@ -9,7 +9,6 @@ import time
import pyotp import pyotp
from django.conf import settings from django.conf import settings
from django.contrib.auth import logout as auth_logout
from django.core.cache import cache from django.core.cache import cache
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -75,7 +74,6 @@ def redirect_user_first_login_or_index(request, redirect_field_name):
if url == 'client': if url == 'client':
url = get_redirect_client_url(request) url = get_redirect_client_url(request)
auth_logout(request)
url = safe_next_url(url, request=request) url = safe_next_url(url, request=request)
# 防止 next 地址为 None # 防止 next 地址为 None