From 7a9a71197a5ffd0a4ae42a18013029ec10ddfa79 Mon Sep 17 00:00:00 2001 From: feng <1304903146@qq.com> Date: Wed, 18 Dec 2024 17:25:40 +0800 Subject: [PATCH] perf: Client login --- apps/authentication/backends/cas/urls.py | 5 ++-- apps/authentication/backends/cas/views.py | 10 +++++++- apps/authentication/backends/oauth2/urls.py | 2 +- apps/authentication/backends/oauth2/views.py | 13 ++++++++--- apps/authentication/backends/oidc/urls.py | 2 +- apps/authentication/backends/oidc/views.py | 10 +++++++- apps/authentication/backends/saml2/urls.py | 2 +- apps/authentication/backends/saml2/views.py | 8 +++++++ apps/authentication/views/base.py | 3 +++ apps/authentication/views/dingtalk.py | 2 ++ apps/authentication/views/feishu.py | 2 ++ apps/authentication/views/login.py | 24 +++++++++++--------- apps/authentication/views/mfa.py | 8 ++++--- apps/authentication/views/slack.py | 2 ++ apps/authentication/views/utils.py | 14 ++++++++++-- apps/authentication/views/wecom.py | 3 ++- apps/users/utils.py | 2 -- 17 files changed, 83 insertions(+), 29 deletions(-) diff --git a/apps/authentication/backends/cas/urls.py b/apps/authentication/backends/cas/urls.py index 376ce2332..fe0779300 100644 --- a/apps/authentication/backends/cas/urls.py +++ b/apps/authentication/backends/cas/urls.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- # -from django.urls import path import django_cas_ng.views +from django.urls import path -from .views import CASLoginView +from .views import CASLoginView, CASCallbackClientView urlpatterns = [ path('login/', CASLoginView.as_view(), name='cas-login'), 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('login/client', CASCallbackClientView.as_view(), name='cas-proxy-callback-client'), ] diff --git a/apps/authentication/backends/cas/views.py b/apps/authentication/backends/cas/views.py index f74e46e9c..0e150008a 100644 --- a/apps/authentication/backends/cas/views.py +++ b/apps/authentication/backends/cas/views.py @@ -1,9 +1,12 @@ -from django_cas_ng.views import LoginView from django.core.exceptions import PermissionDenied from django.http import HttpResponseRedirect +from django.views.generic import View +from django_cas_ng.views import LoginView __all__ = ['LoginView'] +from authentication.views.utils import redirect_to_guard_view + class CASLoginView(LoginView): def get(self, request): @@ -13,3 +16,8 @@ class CASLoginView(LoginView): return HttpResponseRedirect('/') +class CASCallbackClientView(View): + http_method_names = ['get', ] + + def get(self, request): + return redirect_to_guard_view(query_string='next=client') diff --git a/apps/authentication/backends/oauth2/urls.py b/apps/authentication/backends/oauth2/urls.py index c44dd7c74..a9c5e4ff0 100644 --- a/apps/authentication/backends/oauth2/urls.py +++ b/apps/authentication/backends/oauth2/urls.py @@ -4,9 +4,9 @@ from django.urls import path from . import views - urlpatterns = [ path('login/', views.OAuth2AuthRequestView.as_view(), name='login'), 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') ] diff --git a/apps/authentication/backends/oauth2/views.py b/apps/authentication/backends/oauth2/views.py index eaad1e1b0..6eb324863 100644 --- a/apps/authentication/backends/oauth2/views.py +++ b/apps/authentication/backends/oauth2/views.py @@ -1,16 +1,16 @@ -from django.views import View from django.conf import settings from django.contrib import auth from django.http import HttpResponseRedirect from django.urls import reverse 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.views.mixins import FlashMessageMixin -from authentication.mixins import authenticate +from authentication.views.utils import redirect_to_guard_view from common.utils import get_logger - logger = get_logger(__file__) @@ -67,6 +67,13 @@ class OAuth2AuthCallbackView(View, FlashMessageMixin): 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): http_method_names = ['get', 'post', ] diff --git a/apps/authentication/backends/oidc/urls.py b/apps/authentication/backends/oidc/urls.py index a79ebe0c2..2b5634f0b 100644 --- a/apps/authentication/backends/oidc/urls.py +++ b/apps/authentication/backends/oidc/urls.py @@ -12,9 +12,9 @@ from django.urls import path from . import views - urlpatterns = [ path('login/', views.OIDCAuthRequestView.as_view(), name='login'), 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'), ] diff --git a/apps/authentication/backends/oidc/views.py b/apps/authentication/backends/oidc/views.py index 6d3df51a4..613836d84 100644 --- a/apps/authentication/backends/oidc/views.py +++ b/apps/authentication/backends/oidc/views.py @@ -22,13 +22,14 @@ from django.http import HttpResponseRedirect, QueryDict from django.urls import reverse from django.utils.crypto import get_random_string from django.utils.http import urlencode -from django.views.generic import View 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.views.mixins import FlashMessageMixin from common.utils import safe_next_url from .utils import get_logger +from ...views.utils import redirect_to_guard_view logger = get_logger(__file__) @@ -208,6 +209,13 @@ class OIDCAuthCallbackView(View, FlashMessageMixin): 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): """ Allows to end the session of any user authenticated using OpenID Connect. diff --git a/apps/authentication/backends/saml2/urls.py b/apps/authentication/backends/saml2/urls.py index d354fff58..73fe5e8b5 100644 --- a/apps/authentication/backends/saml2/urls.py +++ b/apps/authentication/backends/saml2/urls.py @@ -4,10 +4,10 @@ from django.urls import path from . import views - urlpatterns = [ path('login/', views.Saml2AuthRequestView.as_view(), name='saml2-login'), path('logout/', views.Saml2EndSessionView.as_view(), name='saml2-logout'), 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'), ] diff --git a/apps/authentication/backends/saml2/views.py b/apps/authentication/backends/saml2/views.py index abc79cf68..3cc2e3c0f 100644 --- a/apps/authentication/backends/saml2/views.py +++ b/apps/authentication/backends/saml2/views.py @@ -19,6 +19,7 @@ from onelogin.saml2.idp_metadata_parser import ( from authentication.views.mixins import FlashMessageMixin from common.utils import get_logger from .settings import JmsSaml2Settings +from ...views.utils import redirect_to_guard_view logger = get_logger(__file__) @@ -298,6 +299,13 @@ class Saml2AuthCallbackView(View, PrepareRequestMixin, FlashMessageMixin): 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): def get(self, request): diff --git a/apps/authentication/views/base.py b/apps/authentication/views/base.py index 70424e8f2..17fe703d7 100644 --- a/apps/authentication/views/base.py +++ b/apps/authentication/views/base.py @@ -110,6 +110,9 @@ class BaseLoginCallbackView(AuthMixin, FlashMessageMixin, IMClientMixin, View): msg = e.msg response = self.get_failed_response(login_url, title=msg, msg=msg) return response + + if 'next=client' in redirect_url: + self.request.META['QUERY_STRING'] += '&next=client' return self.redirect_to_guard_view() diff --git a/apps/authentication/views/dingtalk.py b/apps/authentication/views/dingtalk.py index 85680dde1..e45c6951f 100644 --- a/apps/authentication/views/dingtalk.py +++ b/apps/authentication/views/dingtalk.py @@ -175,6 +175,8 @@ class DingTalkQRLoginView(DingTalkQRMixin, METAMixin, View): def get(self, request: HttpRequest): 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 = safe_next_url(next_url, request=request) diff --git a/apps/authentication/views/feishu.py b/apps/authentication/views/feishu.py index 67e0040d5..624d97e77 100644 --- a/apps/authentication/views/feishu.py +++ b/apps/authentication/views/feishu.py @@ -110,6 +110,8 @@ class FeiShuQRLoginView(FeiShuQRMixin, View): def get(self, request: HttpRequest): 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 += '?' + urlencode({ 'redirect_url': redirect_url, diff --git a/apps/authentication/views/login.py b/apps/authentication/views/login.py index e43969ca3..ee8bf6dfd 100644 --- a/apps/authentication/views/login.py +++ b/apps/authentication/views/login.py @@ -45,69 +45,70 @@ class UserLoginContextMixin: error_origin: str def get_support_auth_methods(self): + query_string = self.request.GET.urlencode() auth_methods = [ { 'name': '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'), 'auto_redirect': True # 是否支持自动重定向 }, { 'name': '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'), 'auto_redirect': True }, { 'name': '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'), 'auto_redirect': True }, { 'name': settings.AUTH_OAUTH2_PROVIDER, '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), 'auto_redirect': True }, { 'name': _('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'), }, { 'name': _('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') }, { 'name': _('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') }, { 'name': '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') }, { 'name': _('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') }, { 'name': _("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') } ] @@ -220,7 +221,7 @@ class UserLoginView(mixins.AuthMixin, UserLoginContextMixin, FormView): 'redirect_url': redirect_url, 'interval': 3, '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) return redirect_url @@ -306,6 +307,7 @@ class UserLoginGuardView(mixins.AuthMixin, RedirectView): login_url = reverse_lazy('authentication:login') login_mfa_url = reverse_lazy('authentication:login-mfa') login_confirm_url = reverse_lazy('authentication:login-wait-confirm') + query_string = True def format_redirect_url(self, url): args = self.request.META.get('QUERY_STRING', '') diff --git a/apps/authentication/views/mfa.py b/apps/authentication/views/mfa.py index a043b3861..df3137814 100644 --- a/apps/authentication/views/mfa.py +++ b/apps/authentication/views/mfa.py @@ -2,13 +2,14 @@ # from __future__ import unicode_literals -from django.views.generic.edit import FormView + from django.shortcuts import redirect, reverse +from django.views.generic.edit import FormView from common.utils import get_logger from users.views import UserFaceCaptureView -from .. import forms, errors, mixins from .utils import redirect_to_guard_view +from .. import forms, errors, mixins from ..const import MFAType logger = get_logger(__name__) @@ -48,7 +49,8 @@ class UserLoginMFAView(mixins.AuthMixin, FormView): self._do_check_user_mfa(code, mfa_type) user, ip = self.get_user_from_session(), self.get_request_ip() 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: form.add_error('code', e.msg) return super().form_invalid(form) diff --git a/apps/authentication/views/slack.py b/apps/authentication/views/slack.py index 912fe89ef..3ca844552 100644 --- a/apps/authentication/views/slack.py +++ b/apps/authentication/views/slack.py @@ -98,6 +98,8 @@ class SlackQRLoginView(SlackMixin, View): def get(self, request: Request): 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 += '?' + urlencode({ 'redirect_url': redirect_url, diff --git a/apps/authentication/views/utils.py b/apps/authentication/views/utils.py index 63a1d76c6..c17e7f337 100644 --- a/apps/authentication/views/utils.py +++ b/apps/authentication/views/utils.py @@ -1,8 +1,18 @@ # -*- coding: utf-8 -*- # +from urllib.parse import urlencode, parse_qsl + from django.shortcuts import reverse, redirect -def redirect_to_guard_view(comment=''): - continue_url = reverse('authentication:login-guard') + '?_=' + comment +def redirect_to_guard_view(comment='', query_string=None): + 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) diff --git a/apps/authentication/views/wecom.py b/apps/authentication/views/wecom.py index 753f08c6b..98b6f0a45 100644 --- a/apps/authentication/views/wecom.py +++ b/apps/authentication/views/wecom.py @@ -22,7 +22,6 @@ from users.views import UserVerifyPasswordView from .base import BaseLoginCallbackView, BaseBindCallbackView from .mixins import METAMixin, FlashMessageMixin - logger = get_logger(__file__) @@ -86,6 +85,8 @@ class WeComQRBindView(WeComQRMixin, View): def get(self, request: HttpRequest): 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 += '?' + urlencode({'redirect_url': redirect_url}) diff --git a/apps/users/utils.py b/apps/users/utils.py index 5e4d06c22..9893b331e 100644 --- a/apps/users/utils.py +++ b/apps/users/utils.py @@ -9,7 +9,6 @@ import time import pyotp from django.conf import settings -from django.contrib.auth import logout as auth_logout from django.core.cache import cache 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': url = get_redirect_client_url(request) - auth_logout(request) url = safe_next_url(url, request=request) # 防止 next 地址为 None