eClinicalWorks、其他 FHIR 端点的身份验证/API 令牌检索

问题描述 投票:0回答:1

问题已被编辑以包含一些开发代码。

只是想了解通过各种 EMR 供应商进行 FHIR 身份验证的各种方法。 我在 AthenaHealth、eClinicalWorks 和 EPIC 拥有沙箱帐户。 我在 Athenahealth 中使用 BasicAuthentication 和 clientID/Secret 来检索令牌取得了一些成功,然后该令牌可用于通过对各种范围的适当授权来发出后续请求。我还获得了 EPIC FHIR 沙箱来与 JWT 配合使用。

我当前的问题是,迄今为止我无法通过 eClinicalWorks 沙箱环境进行身份验证。 类似的代码实际上适用于使用 JWT 方法的 EPIC 沙箱。

下面的 Python 函数适用于使用基本身份验证方法的 AtheneHealth,其中 TOKEN_URL='..../oauth2/v1/token'

def get_access_token():
    response = requests.post(
        TOKEN_URL,
        auth=HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET),
        headers={'Content-Type': 'application/x-www-form-urlencoded'},
        data={
        'grant_type': 'client_credentials',
        'scope': '....'
        }
    )
    response.raise_for_status()
    return response.json()['access_token']

ECW 文档位于此处:ECW API 文档

我使用以下方法生成了自己的公钥-私钥对:

# Generate a private key
openssl genrsa -out private_key.pem 2048
# Extract the public key
openssl req -new -x509 -key private_key.pem -out public.pem -subj '/CN=SandboxTester'
# Get the fingerprint
openssl x509 -noout -fingerprint -sha1 -inform pem -in public.pem
# e.g. sha1 Fingerprint=A9:18:FF:9E:A2:99:0F:24:C6:1A:CF:4C:B8:09:0C:0D:0E:5D:49:B3

然后我使用此 Python 代码生成一个 JWK 集:

import hashlib
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography import x509
from base64 import urlsafe_b64encode
import json

# Load your public key from the certificate
with open("public.pem", "rb") as cert_file:
    public_key = x509.load_pem_x509_certificate(cert_file.read(), default_backend()).public_key()

# Extract the modulus (n) and exponent (e) from the public key
numbers = public_key.public_numbers()
n = numbers.n
e = numbers.e

# Convert the modulus and exponent to URL-safe base64 encoding
n_b64 = urlsafe_b64encode(n.to_bytes((n.bit_length() + 7) // 8, byteorder='big')).decode('utf-8').rstrip("=")
e_b64 = urlsafe_b64encode(e.to_bytes((e.bit_length() + 7) // 8, byteorder='big')).decode('utf-8').rstrip("=")

# Generate the kid using a SHA-256 fingerprint
pub_key_bytes = public_key.public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)
kid = hashlib.sha256(pub_key_bytes).hexdigest()

# Create the JWK structure
jwk = {
    "kty": "RSA",
    "use": "sig",
    "alg": "RS384",
    "n": n_b64,
    "e": e_b64,
    "kid": kid,  # example Key ID, ensure this matches your requirements
    "key_ops": ["verify"],  # example key operations, adjust as needed
    "ext": True
}

# Create the JWK Set structure
jwk_set = {"keys": [jwk]}

# Print the JWK in a pretty JSON format
print(json.dumps(jwk_set, indent=4))

# Save the JWK to a file
with open("jwk.json", "w") as jwk_file:
    json.dump(jwk_set, jwk_file, indent=4)

print("JWK saved to jwk.json")

然后我的 Python 测试脚本是这样的,减去一些配置值。

import os
import json
import time
import jwt
import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
import uuid

CLIENT_ID = "omitted"  #Staging
# TOKEN_URL = 'https://oauthserver.eclinicalworks.com/oauth/oauth2/token'    #Production
TOKEN_URL = "https://staging-oauthserver.ecwcloud.com/oauth/oauth2/token"  #Staging
# AUTH_URL = 'https://oauthserver.eclinicalworks.com/oauth/oauth2/authorize' #Production
AUTH_URL = "https://staging-oauthserver.ecwcloud.com/oauth/oauth2/authorize" #Staging

JWKS_URL = "omitted"

# Path to the private key file
private_key_file = "private_key.pem"

# Read the private key from the file
with open(private_key_file, "rb") as key_file:
    private_key = serialization.load_pem_private_key(
        key_file.read(),
        password=None,
        backend=default_backend()
    )


# Generate a signed JWT with RS384
def generate_jwt():
    now = int(time.time())
    exp = now + 300  # Expiration time no more than five minutes in the future
    claims = {
        "iss": CLIENT_ID,
        "sub": CLIENT_ID,
        "aud": TOKEN_URL,
        "exp": exp,
        "iat": now,
        "jti": str(uuid.uuid4())  # Generate a unique JWT ID
    }
    headers = {
        "alg": "RS384",
        "kid": "omitted",
        "typ": "JWT",
        "jku": JWKS_URL  # Optional, if your JWK Set URL is available
    }
    token = jwt.encode(
        payload=claims,
        key=private_key,
        algorithm="RS384",
        headers=headers
    )
    print("Generated JWT:", token)  # Debugging line to check the JWT
    return token


# Function to get a new access token using JWT
def get_access_token():
    signed_jwt = generate_jwt()
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    data = {
        'grant_type': 'client_credentials',
        'scope': 'system/Patient.read system/Encounter.read system/Group.read',  # Adjust scope as needed
        'client_assertion_type': 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
        'client_assertion': signed_jwt
    }

    print("Request Headers:", headers)  # Debugging line to check headers
    print("Request Data:", data)  # Debugging line to check data

    response = requests.post(TOKEN_URL, headers=headers, data=data)
    print("Response Status Code:", response.status_code)
    print("Response Body:", response.text)
    response.raise_for_status()  # Raise an HTTPError for bad responses
    return response.json()['access_token']

# Attempt to get the access token
try:
    access_token = get_access_token()
    print("Access Token:", access_token)
except requests.exceptions.HTTPError as err:
    print(f"HTTP error occurred: {err}")
    print(f"Response content: {err.response.content}")
except Exception as err:
    print(f"Other error occurred: {err}")

我已经在他们的技术支持下经历过几次,他们还验证了一切似乎都设置正确,并且 JWT 至少进行了手动验证。

任何其他建议表示赞赏。

我得到的错误是:

Response Status Code: 401
Response Body: {"error":"invalid_client"}
HTTP error occurred: 401 Client Error:  for url: https://staging-oauthserver.ecwcloud.com/oauth/oauth2/token
Response content: b'{"error":"invalid_client"}'
python-3.x authentication oauth-2.0 hl7-fhir smart-on-fhir
1个回答
0
投票

@SScotti 您是否需要更改任何代码?尝试了解解决方案是站在您这边还是 eClinicalWorks 那边。

© www.soinside.com 2019 - 2024. All rights reserved.