Skip to content

Commit ede5798

Browse files
authored
fix(bkpaas-auth): 修复 apigw_manager 的 JWT 中间件新增 tenant_id 参数导致的 JWT 认证失效问题 (TencentBlueKing#213)
1 parent 7b0c59d commit ede5798

6 files changed

Lines changed: 76 additions & 49 deletions

File tree

sdks/bkpaas-auth/CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# 版本历史
22

3+
## 3.1.1
4+
- fix: 修复 apigw-manager 4.0.1 版本 JWT 中间件新增 tenant_id 参数导致的 JWT 认证失效问题
5+
36
## 3.1.0
47
- feat: UniversalAuthBackend 支持获取用户租户身份信息
58

sdks/bkpaas-auth/bkpaas_auth/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
__version__ = "3.1.0"
2+
__version__ = "3.1.1"
33

44

55
def get_user_by_user_id(user_id: str, username_only: bool = True):
@@ -18,7 +18,7 @@ def get_user_by_user_id(user_id: str, username_only: bool = True):
1818

1919
# 多租户模式下, 暂时没有根据用户名获取用户详细信息的接口
2020
if conf.ENABLE_MULTI_TENANT_MODE:
21-
raise ValueError('Multi-tenant mode only return username, please set username_only=True')
21+
raise ValueError("Multi-tenant mode only return username, please set username_only=True")
2222

2323
# Request third party service to get info other than username
2424
if provider_type == ProviderType.RTX:
@@ -30,4 +30,4 @@ def get_user_by_user_id(user_id: str, username_only: bool = True):
3030
user_info.provide(user)
3131
return user
3232
else:
33-
raise ValueError('ProviderType is not supported yet!')
33+
raise ValueError("ProviderType is not supported yet!")

sdks/bkpaas-auth/bkpaas_auth/backends.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -200,46 +200,55 @@ def configure_user(self, db_user, bk_user: User):
200200

201201

202202
class APIGatewayAuthBackend:
203-
"""This backend is to be used in conjunction with the ``ApiGatewayJWTUserMiddleware``
204-
found in the middleware module of ``apigw_manager`` package.
203+
"""Authentication backend for API Gateway JWT validation.
205204
205+
This backend works with `ApiGatewayJWTUserMiddleware` from the
206+
`apigw_manager` package to handle JWT-based authentication.
206207
"""
207208

208-
def authenticate_with_signature_v3(self, request, gateway_name, bk_username, verified, **credentials):
209-
"""authenticate function with signature required by ApiGatewayJWTUserMiddleware in apigw_manager == '^3.0.0'"""
210-
if not verified:
211-
return self.make_anonymous_user(bk_username)
209+
_TOKEN_EXPIRE_TIME = 86400 # 24 hours in seconds
212210

211+
def _create_authenticated_user(self, username: str, provider_type: ProviderType) -> User:
212+
"""Create a user object for authenticated requests."""
213213
return User(
214-
token=LoginToken("any_token", expires_in=86400),
215-
provider_type=self.get_provider_type(),
216-
username=bk_username,
214+
token=LoginToken("any_token", expires_in=self._TOKEN_EXPIRE_TIME),
215+
provider_type=provider_type,
216+
username=username,
217217
)
218218

219-
def authenticate_with_signature_v1(self, request, api_name, bk_username, verified, **credentials):
220-
"""authenticate function with signature required by ApiGatewayJWTUserMiddleware in apigw_manager == '^1.0.0'"""
221-
if not verified:
222-
return self.make_anonymous_user(bk_username)
219+
def _authenticate_common(self, verified: bool, username: Optional[str]) -> Union[User, AnonymousUser]:
220+
"""Common authentication logic for all versions."""
221+
if not verified or not username:
222+
return self.make_anonymous_user(username)
223223

224-
return User(
225-
token=LoginToken("any_token", expires_in=86400),
226-
provider_type=self.get_provider_type(),
227-
username=bk_username,
228-
)
224+
return self._create_authenticated_user(username=username, provider_type=self.get_provider_type())
225+
226+
def authenticate_with_signature_v3(
227+
self, request: HttpRequest, gateway_name: str, bk_username: str, verified: bool, **credentials: Dict
228+
) -> Union[User, AnonymousUser]:
229+
"""authenticate function with signature required by ApiGatewayJWTUserMiddleware in apigw_manager == '^3.0.0'"""
230+
return self._authenticate_common(verified, bk_username)
231+
232+
def authenticate_with_signature_v1(
233+
self, request: HttpRequest, api_name: str, bk_username: str, verified: bool, **credentials: Dict
234+
) -> Union[User, AnonymousUser]:
235+
"""authenticate function with signature required by ApiGatewayJWTUserMiddleware in apigw_manager == '^1.0.0'"""
236+
return self._authenticate_common(verified, bk_username)
229237

230238
try:
231239
from apigw_manager.apigw.authentication import ApiGatewayJWTUserMiddleware
232240

233-
get_user_parameters = sorted(inspect.signature(ApiGatewayJWTUserMiddleware.get_user).parameters.keys())
234-
v3_parameters = sorted(inspect.signature(authenticate_with_signature_v3).parameters.keys())
235-
if get_user_parameters == v3_parameters:
236-
authenticate = authenticate_with_signature_v3
237-
else:
241+
get_user_parameters = inspect.signature(ApiGatewayJWTUserMiddleware.get_user).parameters.keys()
242+
# django 的 authenticate 方法会保证向后兼容参数,调用方新增参数不会影响用户认证(认证只用到了 verified、bk_username 这 2 个参数)
243+
# apigw_manager 的 3.0.0 版本开始 将 api_name 修改为了 gateway_name,导致无法保证向后兼容,所以需要单独处理
244+
# https://github.com/django/django/blob/stable/4.2.x/django/contrib/auth/__init__.py#L69
245+
if "api_name" in get_user_parameters and "gateway_name" not in get_user_parameters:
238246
authenticate = authenticate_with_signature_v1 # type: ignore
247+
else:
248+
authenticate = authenticate_with_signature_v3 # type: ignore
239249
del get_user_parameters
240-
del v3_parameters
241250
except ImportError:
242-
authenticate = authenticate_with_signature_v1 # type: ignore
251+
authenticate = authenticate_with_signature_v3 # type: ignore
243252

244253
def get_user(self, user_id):
245254
raise NotImplementedError(

sdks/bkpaas-auth/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "bkpaas-auth"
3-
version = "3.1.0"
3+
version = "3.1.1"
44
description = "User authentication django app for blueking internal projects"
55
readme = "README.md"
66
authors = ["blueking <blueking@tencent.com>"]

sdks/bkpaas-auth/setup.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,27 @@
1212

1313
import os.path
1414

15-
readme = ''
15+
readme = ""
1616
here = os.path.abspath(os.path.dirname(__file__))
17-
readme_path = os.path.join(here, 'README.rst')
17+
readme_path = os.path.join(here, "README.rst")
1818
if os.path.exists(readme_path):
19-
with open(readme_path, 'rb') as stream:
20-
readme = stream.read().decode('utf8')
19+
with open(readme_path, "rb") as stream:
20+
readme = stream.read().decode("utf8")
2121

2222

2323
setup(
2424
long_description=readme,
25-
name='bkpaas-auth',
26-
version='3.1.0',
27-
description='User authentication django app for blueking internal projects',
28-
python_requires='<4.0,>=3.8',
29-
author='blueking',
30-
author_email='blueking@tencent.com',
31-
license='Apach License 2.0',
32-
packages=['bkpaas_auth', 'bkpaas_auth.core'],
25+
name="bkpaas-auth",
26+
version="3.1.1",
27+
description="User authentication django app for blueking internal projects",
28+
python_requires="<4.0,>=3.8",
29+
author="blueking",
30+
author_email="blueking@tencent.com",
31+
license="Apach License 2.0",
32+
packages=["bkpaas_auth", "bkpaas_auth.core"],
3333
package_dir={"": "."},
3434
package_data={},
35-
install_requires=['django<5.0,>=4.2', 'requests', 'six'],
35+
install_requires=["django<5.0,>=4.2", "requests", "six"],
3636
extras_require={
3737
"dev": [
3838
"flake8==3.*,>=3.8.4",

sdks/bkpaas-auth/tests/test_backends.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99

1010

1111
class TestUniversalAuthBackend:
12-
1312
@override_settings(BKAUTH_ENABLE_MULTI_TENANT_MODE=False, BKAUTH_BACKEND_TYPE="bk_ticket")
14-
@mock.patch('requests.Session.request')
13+
@mock.patch("requests.Session.request")
1514
def test_authenticate_bk_ticket(self, mock_request, mocker):
1615
mock_request.return_value = mock_json_response({"msg": "", "data": {"username": "foo"}, "ret": 0})
1716

@@ -26,7 +25,7 @@ def test_authenticate_bk_ticket(self, mock_request, mocker):
2625
assert getattr(user, "display_name") == "foo"
2726

2827
@override_settings(BKAUTH_ENABLE_MULTI_TENANT_MODE=False, BKAUTH_BACKEND_TYPE="bk_token")
29-
@mock.patch('requests.Session.request')
28+
@mock.patch("requests.Session.request")
3029
def test_authenticate_bk_token(self, mock_request, mocker):
3130
mock_request.return_value = mock_json_response(
3231
{"result": True, "code": 0, "message": "", "data": {"bk_username": "bar"}}
@@ -47,7 +46,7 @@ def test_authenticate_bk_token(self, mock_request, mocker):
4746
BKAUTH_BACKEND_TYPE="bk_token",
4847
BKAUTH_USER_INFO_APIGW_URL="fake_url",
4948
)
50-
@mock.patch('requests.Session.request')
49+
@mock.patch("requests.Session.request")
5150
def test_authenticate_bk_token_for_tenant_mode(self, mock_request, mocker):
5251
mock_request.return_value = mock_raw_response(
5352
{
@@ -74,7 +73,7 @@ def test_authenticate_bk_token_for_tenant_mode(self, mock_request, mocker):
7473

7574

7675
class TestAPIGatewayAuthBackend:
77-
@pytest.fixture
76+
@pytest.fixture()
7877
def backend(self):
7978
return APIGatewayAuthBackend()
8079

@@ -89,7 +88,7 @@ def test_get_provider_type(self, backend):
8988
def test_authenticate_not_verified(self, mocker, backend):
9089
user = backend.authenticate(
9190
request=mocker.MagicMock(),
92-
api_name="test",
91+
gateway_name="test",
9392
bk_username="admin",
9493
verified=False,
9594
)
@@ -101,11 +100,27 @@ def test_authenticate_not_verified(self, mocker, backend):
101100
def test_authenticate_verified(self, mocker, backend):
102101
user = backend.authenticate(
103102
request=mocker.MagicMock(),
104-
api_name="test",
103+
gateway_name="test",
105104
bk_username="admin",
106105
verified=True,
107106
)
108107

109108
assert not user.is_anonymous
110109
assert user.is_authenticated
111110
assert user.username == "admin"
111+
112+
def test_authenticate_with_additional_params(self, mocker, backend):
113+
"""测试带多个额外参数的情况"""
114+
user = backend.authenticate(
115+
request=mocker.MagicMock(),
116+
gateway_name="test",
117+
bk_username="multi_param_user",
118+
verified=True,
119+
param1="value1",
120+
param2=2,
121+
param3={"key": "value"},
122+
)
123+
124+
assert not user.is_anonymous
125+
assert user.is_authenticated
126+
assert user.username == "multi_param_user"

0 commit comments

Comments
 (0)