Invalid_client при попытке аутентификации с client_id и client_secret с помощью django oauth toolkit и rest framework
Я запускаю Django-сервис, который выставляет конечные точки через Django REST Framework. Я хочу защитить эти конечные точки, используя Django OAuth Toolkit для аутентификации.
Когда я создаю приложение из панели администратора, я использую следующие настройки:
Как показано на скриншоте, я отключил хеширование client_secret. При такой конфигурации все работает отлично, и я могу получить токен доступа без каких-либо проблем.
* Preparing request to http://localhost:8000/o/token/
* Current time is 2024-12-19T10:47:03.573Z
* Enable automatic URL encoding
* Using default HTTP version
* Enable timeout of 30000ms
* Enable SSL validation
* Found bundle for host localhost: 0x159f21ed0 [serially]
* Can not multiplex, even if we wanted to!
* Re-using existing connection! (#106) with host localhost
* Connected to localhost (127.0.0.1) port 8000 (#106)
> POST /o/token/ HTTP/1.1
> Host: localhost:8000
> User-Agent: insomnia/0.2.2
> Content-Type: application/x-www-form-urlencoded
> Accept: application/x-www-form-urlencoded, application/json
> Authorization: Basic MkVDdzAwWkN1cm1CRFc4TEFrSmpjSGt1bnh1OW9WZlRpY09DaGU5bDpKVTl6SURzd0Zob09JMzJhRjhUMVI2WnhYZDVVTU84TWwwbHdiZldNWFNxcHVuTGdsaXBLT2xLTFBNMTBublV1TGp3WGFWOVBPR2ZxYUpURzF5Smx2VGRMWHVPRzN0SVg4bE9tQ1N6U09lbTV4Z2ExaWZrNWRUdjVOYWdFV2djQQ==
> Content-Length: 29
| grant_type=client_credentials
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Thu, 19 Dec 2024 10:47:03 GMT
< Server: WSGIServer/0.2 CPython/3.12.8
< Content-Type: application/json
< Cache-Control: no-store
< Pragma: no-cache
< djdt-store-id: b377d48fbdae4db989aabb760af12619
< Server-Timing: TimerPanel_utime;dur=28.13699999999919;desc="User CPU time", TimerPanel_stime;dur=2.7200000000000557;desc="System CPU time", TimerPanel_total;dur=30.856999999999246;desc="Total CPU time", TimerPanel_total_time;dur=56.63733399705961;desc="Elapsed time", SQLPanel_sql_time;dur=5.738208987168036;desc="SQL 3 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls"
< Vary: Accept-Language, Cookie
< Content-Language: en
< X-Frame-Options: DENY
< Content-Length: 118
< X-Content-Type-Options: nosniff
< Referrer-Policy: same-origin
< Cross-Origin-Opener-Policy: same-origin
* Received 118 B chunk
* Connection #106 to host localhost left intact
| {"access_token": "C5RbvIhZIp3y5zLnC7xrezfuzTGNwe", "expires_in": 36000, "token_type": "Bearer", "scope": "read write"}
Однако, когда я включаю хеширование client_secret, я получаю ошибку: {«error»: «invalid_client"}.
* Preparing request to http://localhost:8000/o/token/
* Current time is 2024-12-19T10:50:41.587Z
* Enable automatic URL encoding
* Using default HTTP version
* Enable timeout of 30000ms
* Enable SSL validation
* Too old connection (217 seconds), disconnect it
* Connection 106 seems to be dead!
* Closing connection 106
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#107)
> POST /o/token/ HTTP/1.1
> Host: localhost:8000
> User-Agent: insomnia/0.2.2
> Content-Type: application/x-www-form-urlencoded
> Accept: application/x-www-form-urlencoded, application/json
> Authorization: Basic eHp0cktWNmh1bDJjdFJNWWMxNW82ZHhWRk4wZHJTNzkyMjNldTQ3VTp6cUJwbW1zVHQ4dU1PaEk4Y29FZ3U3eW9rUUNRYnpoVE1rN2Y3bkVYclFlYnl5cEdYU1JpOWNFSGlIUXk1UnJ4blpvNTFOWVBldWhoTERkeWF3VkNrYkJGN05KVzNKZjRua1Z4OEI0a3p1V2tHRU9XdHNOOUo0NWFQRTIybHkzNw==
> Content-Length: 29
| grant_type=client_credentials
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 Unauthorized
< Date: Thu, 19 Dec 2024 10:50:41 GMT
< Server: WSGIServer/0.2 CPython/3.12.8
< Content-Type: application/json
< Cache-Control: no-store
< Pragma: no-cache
< WWW-Authenticate: Bearer error="invalid_client"
< djdt-store-id: fc37d8dc93364fa1ab78cd392ddcfe20
< Server-Timing: TimerPanel_utime;dur=20.312999999998027;desc="User CPU time", TimerPanel_stime;dur=3.6159999999991754;desc="System CPU time", TimerPanel_total;dur=23.928999999997203;desc="Total CPU time", TimerPanel_total_time;dur=44.46612499305047;desc="Elapsed time", SQLPanel_sql_time;dur=2.3827080003684387;desc="SQL 2 queries", CachePanel_total_time;dur=0;desc="Cache 0 Calls"
< Vary: Accept-Language, Cookie
< Content-Language: en
< X-Frame-Options: DENY
< Content-Length: 27
< X-Content-Type-Options: nosniff
< Referrer-Policy: same-origin
< Cross-Origin-Opener-Policy: same-origin
* Received 27 B chunk
* Connection #107 to host localhost left intact
| {"error": "invalid_client"}
Вот мой пользовательский класс аутентификации, который я скопировал из https://stackoverflow.com/a/65718714/15136864:
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
from oauth2_provider.models import Application
class OAuth2ClientCredentialAuthentication(OAuth2Authentication):
"""OAuth2Authentication doesn't allows credentials to belong to an application (client).
This override authenticates server-to-server requests, using client_credential authentication.
"""
def authenticate(self, request):
authentication = super().authenticate(request)
if authentication is not None:
_, access_token = authentication
if self._grant_type_is_client_credentials(access_token):
authentication = access_token.application.user, access_token
return authentication
def _grant_type_is_client_credentials(self, access_token):
return access_token.application.authorization_grant_type == Application.GRANT_CLIENT_CREDENTIALS
Я использовал этот класс, потому что, как упоминалось в упомянутом посте, вход в систему с использованием учетных данных клиента не поддерживается из коробки, что не указано в документации.
Вот моя конфигурация
REST_FRAMEWORK = {
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"DEFAULT_AUTHENTICATION_CLASSES": ("authentication.rest_framework.OAuth2ClientCredentialAuthentication",),
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
}
OAUTH2_PROVIDER_APPLICATION_MODEL = "authentication.Application"
OAUTH_2_PROVIDER = {
"SCOPES": {"read": "Read scope", "write": "Write scope", "groups": "Access to your groups"},
}
Редактирование: Я обнаружил, что наше приложение использует собственный хэшер паролей:
PASSWORD_HASHERS = [
"authentication.password.PBKDF2SHA256PasswordHasher",
]
Вероятно, Django OAuth Toolkit использует другой хэшер, что может быть причиной проблемы.