我正在尝试获取一个使用 okhttp 正确使用 Optum API 进行身份验证的 Java 应用程序,如此处所述。 虽然我可以从测试工具获得正确的响应,但无法从 Java 应用程序获得正确的响应。
使用 API 文档中API 权限和范围部分中的信息,我能够在 Postman(用于测试 http 请求的应用程序)中进行调用。 当我在 Java 中复制调用时,当我期望 302 重定向时,我收到 500 服务器错误。 Optum 的 API 支持台无法解释为什么来自 Postman 的相同调用在 Java 中不起作用,所以我想我应该在这里问,以防有人发现如何解决这个问题,或者有我可以尝试的事情。
我得出的结论是,来自 Postman 的调用和来自 Java 的调用是等效的,即将请求发送到 https://httpbin.org,它会回显它所看到的内容。
我的 Postman 导出(包括
TestAuthHttpBin
和 TestAuthOptumSandbox
)可以在我的 github 项目上找到。如果您想自己测试的话,可以将其导入 Postman。导入集合,单击 Authorization
选项卡,然后针对每个请求单击 Get New Access Token
。这是邮递员屏幕:
这是我正在运行的代码(可在此处获取),其设计相当于 Postman 调用:
public class FirstGetOptum {
//============================= URL PARAMETERS ================================================================
public static final ArrayList<Pair<String, String>> URL_PARAM_LIST = new ArrayList<>();
{
URL_PARAM_LIST.add(new Pair<>("response_type", "code"));
URL_PARAM_LIST.add(new Pair<>("client_id", "55796a71-8104-4625-b259-bb91e9f13a60"));
URL_PARAM_LIST.add(new Pair<>("state", "0124"));
URL_PARAM_LIST.add(new Pair<>("scope", "patient/Patient.read"));
URL_PARAM_LIST.add(new Pair<>("redirect_uri", "https://sites.google.com/sengsational.com/privacy/privacypolicy"));
URL_PARAM_LIST.add(new Pair<>("code_challenge", "s6kElxScJMXGilr1VTwZYsjlq5XexWCUn94rmO7Y29o")); // optionally replaced in main()
URL_PARAM_LIST.add(new Pair<>("code_challenge_method", "S256"));
}
//============================= HEADERS ================================================================
public static final ArrayList<Pair<String, String>> HEADER_LIST = new ArrayList<>();
{
HEADER_LIST.add(new Pair<>("Upgrade-Insecure-Requests", "1"));
HEADER_LIST.add(new Pair<>("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) PostmanCanary/11.2.14-canary240621-0734 Electron/20.3.11 Safari/537.36"));
HEADER_LIST.add(new Pair<>("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"));
HEADER_LIST.add(new Pair<>("Sec-Fetch-Site", "none"));
HEADER_LIST.add(new Pair<>("Sec-Fetch-Mode", "navigate"));
HEADER_LIST.add(new Pair<>("Sec-Fetch-User", "?1"));
HEADER_LIST.add(new Pair<>("Sec-Fetch-Dest", "document"));
HEADER_LIST.add(new Pair<>("Accept-Encoding", "gzip, deflate, br"));
HEADER_LIST.add(new Pair<>("Accept-Language", "en-US"));
}
private static String generateCodeChallenge() throws Exception {
// Construct the code challenge url parameter
StringBuilder sb = new StringBuilder ();
String characters = "01234567890abcde";
Random random = new Random ();
for (int i = 0; i < 56; i ++) {
sb.append (characters.charAt (random.nextInt (characters.length ())));
}
String randomText = sb.toString();
// Temporarily override the random text with the same text each time
randomText = "6b890b254542c9de4603278153e1b127d21730d46ac2620e6e35514c";
byte[] binaryData = null;
try {
binaryData = MessageDigest.getInstance("SHA-256").digest(randomText.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
throw new Exception("Failed SHA-256");
}
Base64.Encoder encoder = Base64.getUrlEncoder();
String codeChallenge = encoder.encodeToString(binaryData);
codeChallenge = codeChallenge.replaceAll("=", ""); // remove pad
return codeChallenge;
}
public static void main(String[] args) throws Exception {
FirstGetOptum fgo = new FirstGetOptum(); // for static initializers
// Use this boolean to run the test or the actual call
HttpUrl.Builder urlBuilder = null;
boolean getHttpBinDump = false;
if (getHttpBinDump) {
urlBuilder = HttpUrl.parse("https://www.httpbin.org/get").newBuilder();
} else {
urlBuilder = HttpUrl.parse("https://sandbox.authz.flex.optum.com/oauth/authorize").newBuilder();
}
for (Pair<String, String> pair : URL_PARAM_LIST) {
urlBuilder.addQueryParameter(pair.getFirst(), pair.getSecond());
}
boolean replaceCodeChallengeValue = true;
if (replaceCodeChallengeValue) {
String codeChallenge = generateCodeChallenge();
System.out.println("generated codeChallenge [" + codeChallenge + "]");
urlBuilder.setQueryParameter("code_challenge", codeChallenge);
urlBuilder.setQueryParameter("code_challenge_method", "S256");
}
// It's not supposed to make a difference, but this bit rearranges to match the order that Postman has it
String url = urlBuilder.build().toString();
url = url.replaceAll("&code_challenge_method=S256", "") + "&code_challenge_method=S256";
System.out.println("Constructed the URL: [" + url + "]");
Request.Builder requestBuilder = new Request.Builder();
requestBuilder.url(url);
for (Pair<String, String> pair : HEADER_LIST) {
requestBuilder.addHeader(pair.getFirst(), pair.getSecond());
}
List<String> headerDebug = requestBuilder.getHeaders$okhttp().getNamesAndValues$okhttp();
for (int i = 0; i < headerDebug.size(); i = i+2) {
System.out.println("Header item: " + headerDebug.get(i) + ":" + headerDebug.get(i + 1));
}
Request request = requestBuilder.build();
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
// There seems to be no difference if requestBuilder.addHeader() is used or if the HeaderInterceptor is used. I left this here in case it's needed later.
okHttpBuilder.addInterceptor(fgo.new HeaderInterceptor("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) PostmanCanary/11.2.14-canary240621-0734 Electron/20.3.11 Safari/537.36"));
OkHttpClient client = okHttpBuilder.build();
Call call = client.newCall(request);
Response response = call.execute();
System.out.println("response " + response.code() + " (should be 302)");
System.out.println("response body\n" + response.body().string());
}
class HeaderInterceptor implements Interceptor {
private String mVariableValue;
private String mVariableName;
public HeaderInterceptor(String variableName, String variableValue) {
mVariableName = variableName;
mVariableValue = variableValue;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.header(mVariableName, mVariableValue)
.build();
return chain.proceed(request);
}
}
}
该代码需要
okhhtp
库。
当我将请求发送到 URL
https://www.httpbin.org/get
时,我在 Postman 和 Java 中得到相同的输出,但是当我将请求发送到 URL https://sandbox.authz.flex.optum.com/oauth/authorize
时,我从 httpbin.org
获得成功,但 Optum 给了我一个 500 服务器错误“E0020000”。
比较 httpbin 的输出,我们发现它们是相同的,无论发起者是 Postman 还是 Java(Postman 输出,Java 输出)。 唯一的区别是跟踪 ID,它每次都会改变:
那么,调用 Optum 沙箱 URL 的两种方法之间还有什么不同:
https://sandbox.authz.flex.optum.com/oauth/authorize
? 为什么 Postman 的一个调用有效,而 Java 的另一个调用无效? 我还能尝试什么?
发现问题了。这不是标题问题。
您需要禁用
followRedirects
中的 OkHttpClient
。默认情况下,OkHttpClient
遵循重定向,这在您的情况下导致 500
错误。
修复: 在您的代码中添加此
okHttpBuilder.followRedirects(false)
。
您将得到预期结果 302
。
更新代码:
public class FirstGetOptum {
//============================= URL PARAMETERS ================================================================
public static final ArrayList<Pair<String, String>> URL_PARAM_LIST = new ArrayList<>();
static {
URL_PARAM_LIST.add(new Pair<>("response_type", "code"));
URL_PARAM_LIST.add(new Pair<>("client_id", "55796a71-8104-4625-b259-bb91e9f13a60"));
URL_PARAM_LIST.add(new Pair<>("state", "0124"));
URL_PARAM_LIST.add(new Pair<>("scope", "patient/Patient.read"));
URL_PARAM_LIST.add(new Pair<>("redirect_uri", "https://sites.google.com/sengsational.com/privacy/privacypolicy"));
URL_PARAM_LIST.add(new Pair<>("code_challenge", "s6kElxScJMXGilr1VTwZYsjlq5XexWCUn94rmO7Y29o")); // optionally replaced in main()
URL_PARAM_LIST.add(new Pair<>("code_challenge_method", "S256"));
}
private static String generateCodeChallenge() throws Exception {
// Construct the code challenge url parameter
StringBuilder sb = new StringBuilder();
String characters = "01234567890abcde";
Random random = new Random();
for (int i = 0; i < 56; i++) {
sb.append(characters.charAt(random.nextInt(characters.length())));
}
String randomText = sb.toString();
// Temporarily override the random text with the same text each time
randomText = "6b890b254542c9de4603278153e1b127d21730d46ac2620e6e35514c";
byte[] binaryData = null;
try {
binaryData = MessageDigest.getInstance("SHA-256").digest(randomText.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
throw new Exception("Failed SHA-256");
}
Base64.Encoder encoder = Base64.getUrlEncoder();
String codeChallenge = encoder.encodeToString(binaryData);
codeChallenge = codeChallenge.replaceAll("=", ""); // remove pad
return codeChallenge;
}
public static void main(String[] args) throws Exception {
FirstGetOptum fgo = new FirstGetOptum(); // for static initializers
// Use this boolean to run the test or the actual call
HttpUrl.Builder urlBuilder = null;
boolean getHttpBinDump = false;
if (getHttpBinDump) {
urlBuilder = HttpUrl.parse("https://www.httpbin.org/get").newBuilder();
} else {
urlBuilder = HttpUrl.parse("https://sandbox.authz.flex.optum.com/oauth/authorize").newBuilder();
}
for (Pair<String, String> pair : URL_PARAM_LIST) {
urlBuilder.addQueryParameter(pair.getFirst(), pair.getSecond());
}
boolean replaceCodeChallengeValue = true;
if (replaceCodeChallengeValue) {
String codeChallenge = generateCodeChallenge();
System.out.println("generated codeChallenge [" + codeChallenge + "]");
urlBuilder.setQueryParameter("code_challenge", codeChallenge);
urlBuilder.setQueryParameter("code_challenge_method", "S256");
}
// It's not supposed to make a difference, but this bit rearranges to match the order that Postman has it
String url = urlBuilder.build().toString();
url = url.replaceAll("&code_challenge_method=S256", "") + "&code_challenge_method=S256";
System.out.println("Constructed the URL: [" + url + "]");
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
// There seems to be no difference if requestBuilder.addHeader() is used or if the HeaderInterceptor is used. I left this here in case it's needed later.
okHttpBuilder.addInterceptor(fgo.new HeaderInterceptor("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"));
okHttpBuilder.addInterceptor(fgo.new HeaderInterceptor("Upgrade-Insecure-Requests", "1"));
okHttpBuilder.addInterceptor(fgo.new HeaderInterceptor("Sec-Fetch-Site", "none"));
okHttpBuilder.addInterceptor(fgo.new HeaderInterceptor("Sec-Fetch-Mode", "navigate"));
okHttpBuilder.addInterceptor(fgo.new HeaderInterceptor("Sec-Fetch-User", "?1"));
okHttpBuilder.addInterceptor(fgo.new HeaderInterceptor("Sec-Fetch-Dest", "document"));
okHttpBuilder.addInterceptor(fgo.new HeaderInterceptor("Accept-Encoding", "gzip, deflate, br"));
okHttpBuilder.addInterceptor(fgo.new HeaderInterceptor("Accept-Language", "en-US"));
okHttpBuilder.addInterceptor(fgo.new HeaderInterceptor("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"));
okHttpBuilder.followRedirects(false);
Call call = okHttpBuilder.build().newCall(new Request.Builder().url(url).build());
Response response = call.execute();
System.out.println("response " + response.code() + " (should be 302)");
System.out.println("response body\n" + response.body().string());
}
class HeaderInterceptor implements Interceptor {
private String mVariableValue;
private String mVariableName;
public HeaderInterceptor(String variableName, String variableValue) {
mVariableName = variableName;
mVariableValue = variableValue;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.header(mVariableName, mVariableValue)
.build();
return chain.proceed(request);
}
}
}
输出:
getHttpBinDump
为 false
(TestAuthOptumSandbox
) 时:generated codeChallenge [EKhdok_1ZKtBrevixgZqXxEHxn5pQjKkFA4bTVOmyH4]
Constructed the URL: [https://sandbox.authz.flex.optum.com/oauth/authorize?response_type=code&client_id=55796a71-8104-4625-b259-bb91e9f13a60&state=0124&scope=patient%2FPatient.read&redirect_uri=https%3A%2F%2Fsites.google.com%2Fsengsational.com%2Fprivacy%2Fprivacypolicy&code_challenge=EKhdok_1ZKtBrevixgZqXxEHxn5pQjKkFA4bTVOmyH4&code_challenge_method=S256]
response 302 (should be 302)
response body
getHttpBinDump
为 true
(TestAuthHttpBin
) 时:generated codeChallenge [EKhdok_1ZKtBrevixgZqXxEHxn5pQjKkFA4bTVOmyH4]
Constructed the URL: [https://www.httpbin.org/get?response_type=code&client_id=55796a71-8104-4625-b259-bb91e9f13a60&state=0124&scope=patient%2FPatient.read&redirect_uri=https%3A%2F%2Fsites.google.com%2Fsengsational.com%2Fprivacy%2Fprivacypolicy&code_challenge=EKhdok_1ZKtBrevixgZqXxEHxn5pQjKkFA4bTVOmyH4&code_challenge_method=S256]
response 200 (should be 302)
response body
{
"args": {
"client_id": "55796a71-8104-4625-b259-bb91e9f13a60",
"code_challenge": "EKhdok_1ZKtBrevixgZqXxEHxn5pQjKkFA4bTVOmyH4",
"code_challenge_method": "S256",
"redirect_uri": "https://sites.google.com/sengsational.com/privacy/privacypolicy",
"response_type": "code",
"scope": "patient/Patient.read",
"state": "0124"
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US",
"Host": "www.httpbin.org",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
"X-Amzn-Trace-Id": "Root=1-66daa962-4a7c521c2b0e18a461abed67"
},
"origin": "49.37.250.89",
"url": "https://www.httpbin.org/get?response_type=code&client_id=55796a71-8104-4625-b259-bb91e9f13a60&state=0124&scope=patient%2FPatient.read&redirect_uri=https%3A%2F%2Fsites.google.com%2Fsengsational.com%2Fprivacy%2Fprivacypolicy&code_challenge=EKhdok_1ZKtBrevixgZqXxEHxn5pQjKkFA4bTVOmyH4&code_challenge_method=S256"
}
看看这是否有帮助。