perf: organize oauth2_provider urls, add .well-known API

This commit is contained in:
Bai
2025-12-02 14:50:20 +08:00
committed by feng626
parent 0aba9ba120
commit 427fd3f72c
8 changed files with 114 additions and 28 deletions

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
#
from django.urls import path
from oauth2_provider import views as op_views
from . import views
urlpatterns = [
path("authorize/", op_views.AuthorizationView.as_view(), name="authorize"),
path("token/", op_views.TokenView.as_view(), name="token"),
path("revoke_token/", op_views.RevokeTokenView.as_view(), name="revoke-token"),
path(".well-known/oauth-authorization-server", views.OAuthAuthorizationServerView.as_view(), name="oauth-authorization-server"),
]

View File

@@ -0,0 +1,17 @@
from django.conf import settings
from oauth2_provider.models import get_application_model
def get_or_create_jumpserver_client_application():
"""Auto get or create OAuth2 JumpServer Client application."""
Application = get_application_model()
application, created = Application.objects.get_or_create(
name='JumpServer Client',
defaults={
'client_type': Application.CLIENT_PUBLIC,
'authorization_grant_type': Application.GRANT_AUTHORIZATION_CODE,
'redirect_uris': settings.OAUTH2_PROVIDER_CLIENT_REDIRECT_URI,
'skip_authorization': True,
}
)
return application

View File

@@ -0,0 +1,77 @@
from django.views.generic import View
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from django.urls import reverse
from oauth2_provider.settings import oauth2_settings
from typing import List, Dict, Any
from .utils import get_or_create_jumpserver_client_application
@method_decorator(csrf_exempt, name='dispatch')
@method_decorator(cache_page(60 * 60 * 24), name='dispatch')
class OAuthAuthorizationServerView(View):
"""
OAuth 2.0 Authorization Server Metadata Endpoint
RFC 8414: https://datatracker.ietf.org/doc/html/rfc8414
This endpoint provides machine-readable information about the
OAuth 2.0 authorization server's configuration.
"""
def get_base_url(self, request) -> str:
scheme = 'https' if request.is_secure() else 'http'
host = request.get_host()
return f"{scheme}://{host}"
def get_supported_scopes(self) -> List[str]:
scopes_config = oauth2_settings.SCOPES
if isinstance(scopes_config, dict):
return list(scopes_config.keys())
return []
def get_metadata(self, request) -> Dict[str, Any]:
base_url = self.get_base_url(request)
application = get_or_create_jumpserver_client_application()
metadata = {
"issuer": base_url,
"client_id": application.client_id if application else "Not found any application.",
"authorization_endpoint": base_url + reverse('authentication:oauth2-provider:authorize'),
"token_endpoint": base_url + reverse('authentication:oauth2-provider:token'),
"revocation_endpoint": base_url + reverse('authentication:oauth2-provider:revoke-token'),
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"scopes_supported": self.get_supported_scopes(),
"token_endpoint_auth_methods_supported": ["none"],
"revocation_endpoint_auth_methods_supported": ["none"],
"code_challenge_methods_supported": ["S256"],
"response_modes_supported": ["query"],
}
if hasattr(oauth2_settings, 'ACCESS_TOKEN_EXPIRE_SECONDS'):
metadata["token_expires_in"] = oauth2_settings.ACCESS_TOKEN_EXPIRE_SECONDS
if hasattr(oauth2_settings, 'REFRESH_TOKEN_EXPIRE_SECONDS'):
if oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS:
metadata["refresh_token_expires_in"] = oauth2_settings.REFRESH_TOKEN_EXPIRE_SECONDS
return metadata
def get(self, request, *args, **kwargs):
metadata = self.get_metadata(request)
response = JsonResponse(metadata)
self.add_cors_headers(response)
return response
def options(self, request, *args, **kwargs):
response = JsonResponse({})
self.add_cors_headers(response)
return response
@staticmethod
def add_cors_headers(response):
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response['Access-Control-Max-Age'] = '3600'

View File

@@ -83,4 +83,6 @@ urlpatterns = [
path('oauth2/', include(('authentication.backends.oauth2.urls', 'authentication'), namespace='oauth2')), path('oauth2/', include(('authentication.backends.oauth2.urls', 'authentication'), namespace='oauth2')),
path('captcha/', include('captcha.urls')), path('captcha/', include('captcha.urls')),
path('oauth2-provider/', include(('authentication.backends.oauth2_provider.urls', 'authentication'), namespace='oauth2-provider'))
] ]

View File

@@ -25,20 +25,5 @@ class CommonConfig(AppConfig):
django_ready.send(CommonConfig) django_ready.send(CommonConfig)
close_old_connections() close_old_connections()
self._auto_register_jumpserver_client_if_not_exists() from authentication.backends.oauth2_provider import utils
utils.get_or_create_jumpserver_client_application()
def _auto_register_jumpserver_client_if_not_exists(self):
""" Auto register JumpServer Client application if not exists. """
from oauth2_provider.models import get_application_model
Application = get_application_model()
client_id = settings.OAUTH2_PROVIDER_CLIENT_ID
if Application.objects.filter(client_id=client_id).exists():
return
Application.objects.create(
name='JumpServer Client',
client_id=client_id,
client_type=Application.CLIENT_PUBLIC,
authorization_grant_type=Application.GRANT_AUTHORIZATION_CODE,
redirect_uris=settings.OAUTH2_PROVIDER_CLIENT_REDIRECT_URI,
skip_authorization=True,
)

View File

@@ -130,6 +130,7 @@ INSTALLED_APPS = [
'settings.apps.SettingsConfig', 'settings.apps.SettingsConfig',
'terminal.apps.TerminalConfig', 'terminal.apps.TerminalConfig',
'audits.apps.AuditsConfig', 'audits.apps.AuditsConfig',
'oauth2_provider',
'authentication.apps.AuthenticationConfig', # authentication 'authentication.apps.AuthenticationConfig', # authentication
'tickets.apps.TicketsConfig', 'tickets.apps.TicketsConfig',
'acls.apps.AclsConfig', 'acls.apps.AclsConfig',
@@ -141,7 +142,6 @@ INSTALLED_APPS = [
'drf_spectacular', 'drf_spectacular',
'drf_spectacular_sidecar', 'drf_spectacular_sidecar',
'django_cas_ng', 'django_cas_ng',
'oauth2_provider',
'channels', 'channels',
'django_filters', 'django_filters',
'bootstrap3', 'bootstrap3',

View File

@@ -228,8 +228,8 @@ JUMPSERVER_UPTIME = int(time.time())
OAUTH2_PROVIDER = { OAUTH2_PROVIDER = {
'ALLOWED_REDIRECT_URI_SCHEMES': ['https', 'jms'], 'ALLOWED_REDIRECT_URI_SCHEMES': ['https', 'jms'],
'PKCE_REQUIRED': True, 'PKCE_REQUIRED': True,
'OIDC_ENABLED': True,
'ACCESS_TOKEN_EXPIRE_SECONDS': CONFIG.OAUTH2_PROVIDER_ACCESS_TOKEN_EXPIRE_SECONDS, 'ACCESS_TOKEN_EXPIRE_SECONDS': CONFIG.OAUTH2_PROVIDER_ACCESS_TOKEN_EXPIRE_SECONDS,
'REFRESH_TOKEN_EXPIRE_SECONDS': CONFIG.OAUTH2_PROVIDER_REFRESH_TOKEN_EXPIRE_SECONDS, 'REFRESH_TOKEN_EXPIRE_SECONDS': CONFIG.OAUTH2_PROVIDER_REFRESH_TOKEN_EXPIRE_SECONDS,
} }
OAUTH2_PROVIDER_CLIENT_ID = 'FkkXFf0wPelYPIbvf0VElkZtyrw8TWIcyqakDgni'
OAUTH2_PROVIDER_CLIENT_REDIRECT_URI = 'jms://auth/callback' OAUTH2_PROVIDER_CLIENT_REDIRECT_URI = 'jms://auth/callback'

View File

@@ -9,7 +9,6 @@ from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.urls import path, include, re_path from django.urls import path, include, re_path
from django.views.i18n import JavaScriptCatalog from django.views.i18n import JavaScriptCatalog
from oauth2_provider import views as oauth2_provider_views
from . import views, api from . import views, api
@@ -45,13 +44,6 @@ if settings.MCP_ENABLED:
path('resources/<str:resource>/<str:pk>/', api.ResourceDetailApi.as_view()), path('resources/<str:resource>/<str:pk>/', api.ResourceDetailApi.as_view()),
]) ])
oauth2_provider_patterns = [
path("authorize/", oauth2_provider_views.AuthorizationView.as_view(), name="authorize"),
path("token/", oauth2_provider_views.TokenView.as_view(), name="token"),
path("revoke_token/", oauth2_provider_views.RevokeTokenView.as_view(), name="revoke-token"),
]
app_view_patterns = [ app_view_patterns = [
path('auth/', include('authentication.urls.view_urls'), name='auth'), path('auth/', include('authentication.urls.view_urls'), name='auth'),
path('ops/', include('ops.urls.view_urls'), name='ops'), path('ops/', include('ops.urls.view_urls'), name='ops'),
@@ -62,7 +54,6 @@ app_view_patterns = [
path('download/', views.ResourceDownload.as_view(), name='download'), path('download/', views.ResourceDownload.as_view(), name='download'),
path('redirect/confirm/', views.RedirectConfirm.as_view(), name='redirect-confirm'), path('redirect/confirm/', views.RedirectConfirm.as_view(), name='redirect-confirm'),
path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'), path('i18n/<str:lang>/', views.I18NView.as_view(), name='i18n-switch'),
path('oauth2-provider/', include(oauth2_provider_patterns), name='oauth2-provider')
] ]
if settings.XPACK_ENABLED: if settings.XPACK_ENABLED: