使用JwToken身份验证对HTTP RestAPI的Python Post请求会生成重复的Post

使用JwToken身份验证对HTTP RestAPI的Python Post请求会生成重复的Post,python,rest,api,python-requests,json-web-token,Python,Rest,Api,Python Requests,Json Web Token,我一直在编写一个API过程,用JwToken身份验证测试向http RestAPI的发布。在这个场景中,它是针对患者管理系统的,我正在生成一个预约。API业务规则不允许同时进行重复预订 我正在使用Python3.5.3虚拟环境(用pycharm IDE编写),并使用Pytest框架运行测试。 同样使用PyJWT 1.5.2,已安装请求2.18.3、simplejson 3.11.1和urllib3 1.22(我假设请求使用的是urllib3)。我使用simplejson.dumps而不是普通的j

我一直在编写一个API过程,用JwToken身份验证测试向http RestAPI的发布。在这个场景中,它是针对患者管理系统的,我正在生成一个预约。API业务规则不允许同时进行重复预订

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

使用下面的代码,我发现我可以运行requests.post调用来成功交付Json数据负载并生成post,但随后它似乎会执行第二次post,从而生成409冲突错误。我可以访问相应api服务器上的a日志数据库,可以看到它实际上已经尝试发布了两次,但我不知道为什么会发生这种情况,我认为请求库中有东西被调用了两次。可能是因为我发布的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
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()
我的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数据与另一个具有相同数据的工具的JSON数据相同

我可以从输出中看到,在初始响应中成功接收到200个代码,但如果查询response.status\u代码,则它已成为409个响应。我还尝试不处理响应,以防导致请求重新查询并产生冲突

我的代码如下所示:

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
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来计算我的电话到底发送了什么,但我怀疑这只会告诉我电话被发送了两次

注释:但是我需要一个base64编码的字符串来发送api请求列表

关于来源

segments.append(base64url_encode(signature))
    return base64.urlsafe_b64encode(input).replace(b'=', b'')
return b'.'.join(segments)
所有内容都是
base64
,并作为
字节返回。所以你应该可以用它

jwt_str = str(encoded_jwt_byte)

抱歉,无法使用
PyJWT

使用
python\u jwt
进行了尝试,效果如预期

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

你真的需要
编码(…
然后
解码()


Will
jwt\u str
claim\u payload
不一样吗?

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


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

你的问题是,你使用的Python模块和版本是
Python\u jwt
还是
pyjwt
?我已经在使用pyjwt 1.5的其他模块版本中添加了它。2.请求2.18.3、simplejson 3.11.1和urllib3 1.22出于某种原因,当我使用jwt.encode创建jwtoken时,它会创建字节码,尽管文档中说它应该返回一个字符串。例如,它看起来像b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIU…但我需要一个base64编码的字符串,用于通过解码发送api请求它再次创建了一个base64编码的字符串,我可以在那里验证它,并且似乎对我的get请求有效。如果您有任何建议,为什么我会收到一个字节字符串而不是base64编码的字符串,我会感兴趣的。我刚刚意识到我可以使用jwt_str=str(encoded_jwt_byte,'utf-8')而不是解码,它似乎在做同样的事情,当它没有解码的秘密时。@roociedoor:更新了我的答案