我正在使用Apache Cloudstack API。 我通常使用org.apache.commons.codec.binary.Base64 base64编码器。 在生成命令时,我使用以下代码(来自给出的示例):
private String generateUrl(String commands) throws Exception {
//Signature: This is the hashed signature of the Base URL that is generated using a combination of the user’s Secret Key and the HMAC SHA-1 hashing algorithm.
/*
1. For each field-value pair (as separated by a '&') in the Command String, URL encode each value so that it can be safely sent via HTTP GET.
2. Lower case the entire Command String and sort it alphabetically via the field for each field-value pair. The result of this step would look like the following.
3. Take the sorted Command String and run it through the HMAC SHA-1 hashing algorithm (most programming languages offer a utility method to do this) with the user’s Secret Key. Base64 encode the resulting byte array in UTF-8 so that it can be safely transmitted via HTTP. The final string produced after Base64 encoding should be "Lxx1DM40AjcXU%2FcaiK8RAP0O1hU%3D".
By reconstructing the final URL in the format Base URL+API Path+Command String+Signature, the final URL should look like:
*/
commands += "&id=" + apiReferenceId;
commands += "&response=json";
// Step 1: Make sure your APIKey is toLowerCased and URL encoded
String encodedApiKey = URLEncoder.encode(apiKey.toLowerCase(), "UTF-8");
// Step 2: toLowerCase all the parameters, URL encode each parameter value, and the sort the parameters in alphabetical order
// Please note that if any parameters with a '&' as a value will cause this test client to fail since we are using '&' to delimit
// the string
List<String> sortedParams = new ArrayList<String>();
sortedParams.add("apikey="+encodedApiKey);
StringTokenizer st = new StringTokenizer(commands, "&");
while (st.hasMoreTokens()) {
String paramValue = st.nextToken().toLowerCase();
String param = paramValue.substring(0, paramValue.indexOf("="));
String value = URLEncoder.encode(paramValue.substring(paramValue.indexOf("=")+1, paramValue.length()), "UTF-8");
sortedParams.add(param + "=" + value);
}
Collections.sort(sortedParams);
System.out.println("Sorted Parameters: " + sortedParams);
// Step 3: Construct the sorted URL and sign and URL encode the sorted URL with your secret key
String sortedUrl = null;
boolean first = true;
for (String param : sortedParams) {
if (first) {
sortedUrl = param;
first = false;
} else {
sortedUrl = sortedUrl + "&" + param;
}
}
Logger.debug("sorted URL : " + sortedUrl);
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
mac.init(keySpec);
mac.update(sortedUrl.getBytes());
byte[] encryptedBytes = mac.doFinal();
String encodedSignature = URLEncoder.encode(Base64.encodeBase64String(encryptedBytes), "UTF-8");
// Step 4: Construct the final URL we want to send to the CloudStack Management Server
// Final result should look like:
// http(s)://://client/api?&apiKey=&signature=
String finalUrl = apiUrl + "?" + commands + "&apiKey=" + apiKey + "&signature=" + encodedSignature;
return finalUrl;
}
然而,这会产生401错误。
<?xml version="1.0" encoding="UTF-8"?><stopvirtualmachineresponse cloud-stack-version="4.2.1"><errorcode>401</errorcode><errortext>unable to verify user credentials and/or request signature</errortext></stopvirtualmachineresponse>
当我使用时:
String encodedSignature = URLEncoder.encode(org.postgresql.util.Base64.encodeBytes(encryptedBytes), "UTF-8");
它工作正常。有没有办法可以在Apache Commons上进行标准化,或者在使用该库时是否有特定原因导致它失败?
答案 0 :(得分:0)
一个猜测。
我确实看到secretKey.getBytes()
和其他getBytes()
。假设secretKey
是一个字符串,它应该是:
secretKey.getBytes(StandardCharsets.UTF_8)
默认charset是当前操作系统的charset。而且你可能在不同的机器上运行,Windows用于开发,Linux用于服务器。
唯一的其他变化在于换行和终止填充(破折号)。 Apache commons编解码器Base64有一些控制参数。
顺便说一下,在JavaEE的几个地方还有一些其他(非Sun!)Base64 API,虽然apache commons是一个不错的选择。示例:javax.xml.bind.DatatypeConverter.parseBase64Binary
。