使用 JwT 令牌身份验证对 HTTP Rest API 的 Python 发布请求会生成重复的帖子

Posted

技术标签:

【中文标题】使用 JwT 令牌身份验证对 HTTP Rest API 的 Python 发布请求会生成重复的帖子【英文标题】:Python Post Requests to HTTP RestAPI with JwToken authentication generates duplicate posts 【发布时间】:2018-01-22 00:26:41 【问题描述】:

我一直在编写一个 API 程序来测试发布到具有 JwToken 身份验证的 http RestAPI。在这种情况下,它用于患者管理系统,我正在生成一个约会。 API 业务规则不允许同时进行重复预订。

我正在使用 python 3.5.3 虚拟环境(在 pycharm IDE 中编写)并使用 Pytest 框架运行我的测试。 也使用 PyJWT 1.5.2。 , Requests 2.18.3, simplejson 3.11.1 和 urllib3 1.22 已安装(我假设 Requests 正在使用 urllib3)。我使用的是 simplejson.dumps 而不是普通的 json.dumps,因为虚拟环境没有那个库,而且我在添加它时遇到了麻烦。据我所知,simplejson 具有与转储过程相同的功能。

使用下面的代码,我发现我可以运行 requests.post 调用以成功传递 Json 数据有效负载并生成一个帖子,但随后它似乎执行了第二个帖子,这会产生 409 冲突错误。我可以访问相应 api 服务器上的日志数据库,并且可以看到它实际上已尝试发布两次,但我无法弄清楚为什么会发生这种情况,我认为请求库中有一些东西被调用了两次.可能是由于我发布的 Json。

输出如下所示:

https://targerserver.url.com.au/API/Core/v2.1/appointment/
200
'statusMessages': [], 'appointment': 'startDateTime': '2017-08-15T11:00:00 +10:00', 'appointmentReferenceNumber': '39960337', 'notes': '', 'clients': ['clientId': 'abeff2be-ce6e-4324-9b57-e28ab7967b6c'], 'status': 'Booked', 'locationId': 'd8d4fe7c-765a-46a3-a389-54ce298a27e9', 'notifyPractitioner': False, 'endDateTime': '2017-08-15T11:30:00 +10:00', 'subject': 'Jim Beam ', 'appointmentId': '08b37ce3-25e1-4e2a-9bb7-9ec2d716f83b', 'practitioner': 'practitionerId': 'a630f4ad-8b4b-4e06-8cee-7db56ba8b9bf'
collected 1 item

test_PMSAPI_availability.py https://targerserver.url.com.au/API/Core/v2.1/appointment/
409

我的 Json 需要一个对象(它是一个字典)以及另一个字段的键列表(其中有一个条目),我想知道请求库是否没有处理这个问题。这是 json 的样例

payload_str = "startDateTime":"2017-08-15T11:00+10:00","endDateTime":"2017-08-15T11:30+10:00","practitioner": "practitionerId":"a630f4ad-8b4b-4e06-8cee-7db56ba8b9bf", "locationId":"d8d4fe7c-765a-46a3-a389-54ce298a27e9","clients":["clientId":"abeff2be-ce6e-4324-9b57-e28ab7967b6c"]

我有类似的代码可用于同一系统上的 Get 调用,但发布 Json 似乎确实有问题。我们有其他工具可以调用相同的 API 端点,它们似乎没有这个问题。日志表明我提供的 JSON 数据与来自其他工具的具有相同数据的数据相同。

我从输出中可以看到,在初始响应中收到了成功的 200 代码,但是如果查询 response.status_code 它已变成 409 响应。我还尝试不对响应做任何事情,以防导致请求重新查询并产生冲突。

我的代码如下所示:

import jwt
import _datetime
import requests
import simplejson
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from string import Template

def app_undertest_credentials(keyname):
    app_credentials = 'app_consumer_id': 'MyTestApp',
                       'app_consumer_secret': 'where my secret goes',
                       'app_access_token': 'where my access token goes',
                       'base_url': 'https://targerserver.url.com.au'
                       

    return app_credentials.get(keyname)
def end_points_dict(keynameStr, versionStr):
    end_points = 'location': '/API/Core/$version/location/',
                  'practitioner': '/API/Core/$version/practitioner/',
                  'availabilityslot': '/API/Core/$version/AvailabilitySlot/',
                  'client': '/API/Core/$version/client/',
                  'healthfundproviderlist': '/API/Core/$version/healthfundproviderlist/',
                  'timezone': '/API/Core/$version/timezone/',
                  'clientgroup': '/API/Core/$version/clientgroup/',
                  'appointment': '/API/Core/$version/appointment/'
                  
    lower_keynameStr = keynameStr.lower()
    url_extension_no_version = Template(end_points.get(lower_keynameStr))
    url_extension_with_version = url_extension_no_version.safe_substitute(version=versionStr)
    return url_extension_with_version

def test_api_appointment_post():
    # Set Client app credentials
    app_consumer_id = app_undertest_credentials('app_consumer_id')
    app_consumer_secret = app_undertest_credentials('app_consumer_secret')
    app_access_token = app_undertest_credentials('app_access_token')
    base_url = app_undertest_credentials('base_url')
    end_point_url_sfx_str = end_points_dict('Appointment', 'v2.1')
    httpmethod = 'POST'

    # Create dictionary for json post payload
    data_payload = 'startDateTime':'2017-08-15T11:00+10:00',
                'endDateTime':'2017-08-15T11:30+10:00',
                'practitioner': 'practitionerId':'a630f4ad-8b4b-4e06-8cee-7db56ba8b9bf',
                'locationId': 'd8d4fe7c-765a-46a3-a389-54ce298a27e9',
                'clients': ['clientId':'abeff2be-ce6e-4324-9b57-e28ab7967b6c']

    # Create claims dictionary payload for generation of JwToken
    claims = 
        'iss': 'http://myappsdomain.com.au',
        'aud': 'https://targetservers.domain.com.au',
        'nbf': _datetime.datetime.utcnow(),
        'exp': _datetime.datetime.utcnow() + _datetime.timedelta(seconds=60),
        'consumerId': app_consumer_id,
        'accessToken': app_access_token,
        'url': base_url + end_point_url_sfx_str,
        'httpMethod': http_method
    

    #create jwtoken and then convert to string
    encoded_jwt_byte = jwt.encode(claim_payload, app_consumer_secret, algorithm='HS256')
    jwt_str = encoded_jwt_byte.decode()

    #Create authentication header
    headers = 'Authorization': 'JwToken' + ' ' + jwt_str, 'content-type': 'application/json'

    uri = base_url + end_point_url_sfx_str
    response = requests.post(uri, headers=headers, json=datapayload)
    print(response.status)
    print(response.json())
    response.close()

我正在考虑使用 wireshark 来确定我的呼叫实际发送的内容,但我怀疑这只会告诉我呼叫被发送了两次

【问题讨论】:

Edit 你的问题是你正在使用哪个 Python 模块和版本,python_jwtpyjwt 我在其他模块版本中添加了使用 PyJWT 1.5.2。 , 请求 2.18.3、simplejson 3.11.1 和 urllib3 1.22 【参考方案1】:

好的,找到了我重复发布问题的原因。这是我自己愚蠢的错。在屏幕底部的 python 文件的底部,我调用了测试函数并且没有注意到这一行(因此我错过了在上面的代码中发布它)。它将测试功能置于一个完全重复的循环中。 .... :( 如此愚蠢的新手错误。 感谢 stovfl 关于处理编码的 JwToken 的建议。

事后看来,我在 *** 上发现的每篇关于 API 重复帖子的帖子都是因为用户代码中有一个循环,我只是找不到它。

【讨论】:

【参考方案2】:

评论:但是我需要一个 base64 编码的字符串来发送 api 请求。

关于来源

segments.append(base64url_encode(signature))
    return base64.urlsafe_b64encode(input).replace(b'=', b'')
return b'.'.join(segments)

全部为 base64 并返回为 bytes。所以你应该没问题使用

jwt_str = str(encoded_jwt_byte)

抱歉,不能使用PyJWT。 尝试使用python_jwt,它按预期工作。

使用 Python:3.4.2 测试 - 请求:2.11.1

你真的需要先encode(...,然后再decode()吗?

#create jwtoken and then convert to string
encoded_jwt_byte = jwt.encode(claim_payload, app_consumer_secret, algorithm='HS256')
jwt_str = encoded_jwt_byte.decode()

jwt_str 会不会和claim_payload 一样?

【讨论】:

由于某种原因,当我使用 jwt.encode 创建 jwtoken 时,我的系统会创建字节码,尽管文档说它应该返回一个字符串。例如它看起来像 b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIU.... 但我需要一个 base64 编码的字符串来发送 api 请求。通过再次解码,它会创建一个 base64 编码的字符串,我可以在 jwt.io 进行验证,并且似乎适用于我的 get 请求。如果您有任何建议,我会收到一个字节字符串而不是 base64 编码的字符串,我会感兴趣 刚刚意识到我可以使用 jwt_str = str(encoded_jwt_byte,'utf-8') 而不是解码,当它没有解码的秘密时,它似乎在做同样的事情。 @Roochiedoor:更新了我的答案

以上是关于使用 JwT 令牌身份验证对 HTTP Rest API 的 Python 发布请求会生成重复的帖子的主要内容,如果未能解决你的问题,请参考以下文章

Docusign REST API:使用 Microsoft Flow 的 JWT Grant 实现 OAuth 身份验证

使用 otp 获取 api 令牌的 Django REST 框架的 JWT 身份验证

使用 Django Rest 框架进行 JWT 令牌身份验证

Django Rest Framework 不接受 JWT 身份验证令牌

带有 jwt 身份验证的 django rest api 要求 csrf 令牌

如何在 vue 中存储、管理 REST API JWT 身份验证令牌?