Keycloak UserInfo 端点403 Forbidden 错误解决
Intro
实习改一个接口的bug,方法内调用其他系统(A)提供的服务。
- 方法内通过请求 Keycloak
/auth/realms/momenta-prod/protocol/openid-connect/token
获取到Access Token - 将Access Token 放入构造的请求头中,调用其他系统(A)提供的服务,发送请求
返回的http状态码是 500
,看response的内容定位是没handle异常,好在对接的服务日志还是挺完整的。可以分析出,在服务内部使用mozilla_django_oidc
库来处理OpenID Connect
的认证时抛出了HTTPError
异常。可以具体到是请求 Keycloak 的 UserInfo 端点时返回了 403 Forbidden
错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
HTTPError at /api/v1/xxx
403 Client Error: Forbidden for url: https://{keycloak-server}/auth/realms/momenta-prod/protocol/openid-connect/userinfo
Request Method: GET
Request URL: https://{a-server}/api/v1/xxx?Vehiclename=L7-PMV304
Django Version: 4.2.6
Python Executable: /usr/local/bin/python3
Python Version: 3.11.4
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
response = get_response(request)
^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/views/decorators/csrf.py", line 56, in wrapper_view
return view_func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/rest_framework/viewsets.py", line 125, in view
return self.dispatch(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 515, in dispatch
response = self.handle_exception(exc)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 475, in handle_exception
self.raise_uncaught_exception(exc)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception
raise exc
^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 503, in dispatch
self.initial(request, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 420, in initial
self.perform_authentication(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/rest_framework/views.py", line 330, in perform_authentication
request.user
^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/rest_framework/request.py", line 232, in user
self._authenticate()
^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/rest_framework/request.py", line 385, in _authenticate
user_auth_tuple = authenticator.authenticate(self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/mozilla_django_oidc/contrib/drf.py", line 80, in authenticate
user = self.backend.get_or_create_user(access_token, None, None)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/mozilla_django_oidc/auth.py", line 347, in get_or_create_user
user_info = self.get_userinfo(access_token, id_token, payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/mozilla_django_oidc/auth.py", line 281, in get_userinfo
user_response.raise_for_status()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/requests/models.py", line 1024, in raise_for_status
raise HTTPError(http_error_msg, response=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Exception Type: HTTPError at /api/v1/VersionLaster
Exception Value: 403 Client Error: Forbidden for url: https://{keycloak-server}/auth/realms/momenta-prod/protocol/openid-connect/userinfo
按理说是一个很容易修复的bug,一方面由于是集成其他服务,debug起来不方便,只能对着服务返回不完整日志定位问题;OIDC疑似会在响应的Header里返回一些提示信息,告诉是缺少openid
scope导致的403 Forbidden
错误,但响应太长还真没注意到()另一方面就是OIDC
涉及到知识盲区了,没踩坑经验
Solution
Keycloak 的 UserInfo 端点要求 Access Token 必须包含 openid
scope,否则会返回 403 Forbidden
错误。这是 OpenID Connect 协议规范的要求,确保只有经过身份验证的令牌才能访问用户信息。
部分 Keycloak 配置下,Access Token 的 JWT payload 可能未直接包含 scope 字段,但只要申请 token 时带上了 openid
scope,UserInfo 端点即可正常使用。可以参考如下python代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
def get_access_token():
client_id = 'your client id'
client_secret = 'your client secret'
url = 'https://{your-keycloak-server}/auth/realms/{your-realm}/protocol/openid-connect/token'
headers = {"Content-Type": "application/x-www-form-urlencoded"}
payload = {
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
"scope": "openid" # ❗️ 确保包含 openid scope
}
resp = requests.request("POST", url, headers=headers, data=payload)
content = resp.json()
return content['access_token']
def request():
access_token = get_access_token()
url = 'your request url'
headers = {'Authorization': f'Bearer {access_token}'}
resp = requests.request(method, url, headers=headers)
在请求体中添加 scope=openid
,确保 Access Token 包含 openid
scope。在JSON Web Tokens - jwt.io解码 Access Token 后,可以看到新生成的 Access Token 在scope
这个Claim中包含了openid
左侧/右侧分别为未包含/包含 openid scope
的 token

Ref
Keycloak UserInfo 端点403 Forbidden 错误解决