java - Amazon Alexa Web 服务总是给出 401

标签 java c# amazon-web-services

问题已解决 - 请参阅说明末尾的更新 2。下面的代码很好

在这里撕扯我的头发......但事情就是这样:

我正在尝试从 API 连接 Amazon Alexa API ( http://docs.aws.amazon.com/AlexaWebInfoService/latest/index.html?ApiReference_UrlInfoAction.html ) 数据...我需要使用 C#。

我已经用我用来查看是否是我的代码问题或 AWIS 问题的 Java 代码更新了下面的这篇文章。

关于 C#,这篇文章末尾的人声称它可以工作:https://forums.aws.amazon.com/message.jspa?messageID=476573#476573

这是调用该类的 C# 代码:

  var awis = new AmazonAWIS
                {
                    AWSAccessKeyId = "ABCDRFGHIJKLMNOP",
                    AWSSecret = "GpC0PcXnnzG/TCpoi9r7RxBtqCzdKaHeEkq7Mfs6"    
                };

  awis.UrlInfo("bbc.co.uk");

这是直接从上面发布的链接中获取的类代码...我没有更改它:

public class AmazonAWIS
{
    public string AWSAccessKeyId { get; set; }
    public string AWSSecret { get; set; }

    protected string GenerateSignature(string param)
    {
        var sign = "GET\n" + "awis.amazonaws.com" + "\n/\n" + param;

        // create the hash object
        var shaiSignature = new HMACSHA256(Encoding.UTF8.GetBytes(AWSSecret));

        // calculate the hash
        var binSig = shaiSignature.ComputeHash(Encoding.UTF8.GetBytes(sign));

        // convert to hex
        var signature = Convert.ToBase64String(binSig);

        return signature;
    }

    // this is one of the key problems with the Amazon code and C#.. C# by default returns excaped values in lower case
    // for example %3a but Amazon expects them in upper case i.e. %3A, this function changes them to upper case..
    //
    public static string UpperCaseUrlEncode(string s)
    {
        char[] temp = HttpUtility.UrlEncode(s).ToCharArray();
        for (int i = 0; i < temp.Length - 2; i++)
        {
            if (temp[i] == '%')
            {
                temp[i + 1] = char.ToUpper(temp[i + 1]);
                temp[i + 2] = char.ToUpper(temp[i + 2]);
            }
        }
        return new string(temp);
    }

    string GetQueryParams(string action, Dictionary<string, string> extra)
    {
        var time = DateTime.UtcNow;

        // set the correct format for the date string
        var timestamp = time.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", System.Globalization.CultureInfo.InvariantCulture);

        // create a sortable dict
        var vals = new Dictionary<string, string>();

        vals.Add("AWSAccessKeyId", AWSAccessKeyId);
        vals.Add("Action", action);
        vals.Add("ResponseGroup", "Rank,ContactInfo,LinksInCount");
        vals.Add("Timestamp", timestamp);
        vals.Add("Count", "10");
        vals.Add("Start", "1");
        vals.Add("SignatureVersion", "2");
        vals.Add("SignatureMethod", "HmacSHA256");

        // add any extra values
        foreach (var v in extra)
        {
            if (vals.ContainsKey(v.Key) == false)
                vals.Add(v.Key, v.Value);
        }

        // sort the values by ordinal.. important!
        var sorted = vals.OrderBy(p => p.Key, StringComparer.Ordinal).ToArray();

        var url = new StringBuilder();

        foreach (var v in sorted)
        {
            if (url.Length > 0)
                url.Append("&");

            url.Append(v.Key + "=" + UpperCaseUrlEncode(v.Value));
        }

        return url.ToString();
    }

    public void UrlInfo(string domain)
    {
        string request = "UrlInfo";

        // add the extra values
        var extra = new Dictionary<string, string>();
        extra.Add("Url", domain);

        // run the request with amazon
        try
        {
            var res = RunRequest(request, extra);

            // process the results...
            Console.WriteLine(res);
        }
        catch (Exception ex)
        {
            throw;
        }
    }

    private string RunRequest(string request, Dictionary<string, string> extra)
    {
        // generate the query params
        var queryParams = GetQueryParams(request, extra);

        // calculate the signature
        var sig = GenerateSignature(queryParams);

        // generate the url
        var url = new StringBuilder();
        url.Append("http://awis.amazonaws.com?");
        url.Append(queryParams);
        url.Append("&Signature=" + UpperCaseUrlEncode(sig));

        // get the request

        var c = new WebClient();
        var res = c.DownloadString(url.ToString());
        return res;
    }
}

线路失败:

var res = c.DownloadString(url.ToString());

我总是收到 401 Unauthorized...

知道我做错了什么吗?

更新

我可以使用他们的 Java 示例应用程序重现同样的问题。我已修改他们的应用程序,仅对 AWSAccessId 和 SecretKey 进行硬编码,而且我也不使用他们应用程序中的 sun.misc.BASE64Encoder。

确切的代码如下...再次,如果我从 makeRequest(uri) 语句中获取 uri,并将其粘贴到 Fiddler 中,我可以看到它是相同的 401 响应:

<?xml version="1.0"?>

AuthFailureAWS 无法验证提供的访问凭证ff8f1853-b816-47a0-2283-be9941e7f2a9

以及导致上述情况的代码(我已经更改了accessKey和secretKey):

package urlinfo.com;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.security.SignatureException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
* Makes a request to the Alexa Web Information Service UrlInfo action.
*/
public class UrlInfo {

private static final String ACTION_NAME = "UrlInfo";
private static final String RESPONSE_GROUP_NAME = "Rank,ContactInfo,LinksInCount";
private static final String SERVICE_HOST = "awis.amazonaws.com";
private static final String AWS_BASE_URL = "http://" + SERVICE_HOST + "/?";
private static final String HASH_ALGORITHM = "HmacSHA256";

private static final String DATEFORMAT_AWS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";

private String accessKeyId;
private String secretAccessKey;
private String site;

public UrlInfo(String accessKeyId, String secretAccessKey, String site) {
    this.accessKeyId = accessKeyId;
    this.secretAccessKey = secretAccessKey;
    this.site = site;
}

/**
 * Generates a timestamp for use with AWS request signing
 *
 * @param date current date
 * @return timestamp
 */
protected static String getTimestampFromLocalTime(Date date) {
    SimpleDateFormat format = new SimpleDateFormat(DATEFORMAT_AWS);
    format.setTimeZone(TimeZone.getTimeZone("GMT"));
    return format.format(date);
}

/**
 * Computes RFC 2104-compliant HMAC signature.
 *
 * @param data The data to be signed.
 * @return The base64-encoded RFC 2104-compliant HMAC signature.
 * @throws java.security.SignatureException
 *          when signature generation fails
 */
protected String generateSignature(String data)
        throws java.security.SignatureException {
    String result;
    try {
        // get a hash key from the raw key bytes
        SecretKeySpec signingKey = new SecretKeySpec(
                secretAccessKey.getBytes(), HASH_ALGORITHM);

        // get a hasher instance and initialize with the signing key
        Mac mac = Mac.getInstance(HASH_ALGORITHM);
        mac.init(signingKey);

        // compute the hmac on input data bytes
        byte[] rawHmac = mac.doFinal(data.getBytes());

        // base64-encode the hmac
        // result = Encoding.EncodeBase64(rawHmac);
        // result = new BASE64Encoder().encode(rawHmac);
        result = javax.xml.bind.DatatypeConverter.printBase64Binary(rawHmac);

    } catch (Exception e) {
        throw new SignatureException("Failed to generate HMAC : "
                + e.getMessage());
    }
    return result;
}

/**
 * Makes a request to the specified Url and return the results as a String
 *
 * @param requestUrl url to make request to
 * @return the XML document as a String
 * @throws IOException
 */
public static String makeRequest(String requestUrl) throws IOException {
    URL url = new URL(requestUrl);
    URLConnection conn = url.openConnection();
    InputStream in = conn.getInputStream();

    // Read the response
    StringBuffer sb = new StringBuffer();
    int c;
    int lastChar = 0;
    while ((c = in.read()) != -1) {
        if (c == '<' && (lastChar == '>'))
            sb.append('\n');
        sb.append((char) c);
        lastChar = c;
    }
    in.close();
    return sb.toString();
}


/**
 * Builds the query string
 */
protected String buildQuery()
        throws UnsupportedEncodingException {
    String timestamp = getTimestampFromLocalTime(Calendar.getInstance().getTime());

    Map<String, String> queryParams = new TreeMap<String, String>();
    queryParams.put("Action", ACTION_NAME);
    queryParams.put("ResponseGroup", RESPONSE_GROUP_NAME);
    queryParams.put("AWSAccessKeyId", accessKeyId);
    queryParams.put("Timestamp", timestamp);
    queryParams.put("Url", site);
    queryParams.put("SignatureVersion", "2");
    queryParams.put("SignatureMethod", HASH_ALGORITHM);

    String query = "";
    boolean first = true;
    for (String name : queryParams.keySet()) {
        if (first)
            first = false;
        else
            query += "&";

        query += name + "=" + URLEncoder.encode(queryParams.get(name), "UTF-8");
    }

    return query;
}

/**
 * Makes a request to the Alexa Web Information Service UrlInfo action
 */
public static void main(String[] args) throws Exception {

    String accessKey = "REMOVED";
    String secretKey = "REMOVED";
    // String site = args[2];
    String site = "www.google.com";

    UrlInfo urlInfo = new UrlInfo(accessKey, secretKey, site);

    String query = urlInfo.buildQuery();

    String toSign = "GET\n" + SERVICE_HOST + "\n/\n" + query;

    System.out.println("String to sign:\n" + toSign + "\n");

    String signature = urlInfo.generateSignature(toSign);

    String uri = AWS_BASE_URL + query + "&Signature=" +
            URLEncoder.encode(signature, "UTF-8");

    System.out.println("Making request to:\n");
    System.out.println(uri + "\n");

    // Make the Request

    String xmlResponse = makeRequest(uri);

    // Print out the XML Response

    System.out.println("Response:\n");
    System.out.println(xmlResponse);
  }
}

更新2:

这段代码没有问题。问题出在 AWIS 注册按钮上。使用页面底部的那个...而不是顶部的那个(这是大多数人会点击的)。亚马逊确认顶部按钮当前不起作用。

最佳答案

即使在处理功能失调的按钮错误之后,我也通过我的 php 代码收到了相同的错误。 See here

对我来说,错误是因为我使用的 IAM 凭证仍然与 AWIS 不兼容,并且尚未记录here 。应使用 AWIS 的根帐户详细信息。 See here

关于java - Amazon Alexa Web 服务总是给出 401,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21684516/

相关文章:

java - Spring 启动验证注释 @Valid 和 @NotBlank 不起作用

java - Apache Commons Net FTPClient 中的文件名编码

c# - Windows Phone 8 中的自动扩展列表 - xaml

c# - 填充数据 GridView 组合框

c# - 分析 MongoDB 数据库以查看执行的查询

mysql - Google Data Studio 和 AWS MySQL SSL 连接

amazon-web-services - aws 基于 IP 的路由 - Route53

java - 双字符串格式

java - 新手关于maven的问题

amazon-web-services - 不使用 route53 将 Cloudflare 指向 AWS EC2 服务器