在 Java 中生成 SAS 令牌以下载 Azure 数据存储容器中的文件

Posted

技术标签:

【中文标题】在 Java 中生成 SAS 令牌以下载 Azure 数据存储容器中的文件【英文标题】:Generating an SAS Token in Java to download a file in an Azure Data Storage container 【发布时间】:2019-05-08 06:32:00 【问题描述】:

尝试生成 SAS 令牌以访问存储帐户中的某些文件。我正在使用此处列出的方法:

https://docs.microsoft.com/en-us/rest/api/eventhub/generate-sas-token

现在,我遇到的问题是,我一辈子都无法让 sasToken 字符串工作。如果我通过门户(存储帐户中的共享访问签名)生成令牌,我可以使用提供的令牌通过 URL 访问这些文件。

但是,我还不能使用上面链接的方法通过 Java 以编程方式生成 SAS 令牌。我认为我的问题是正在加密的 StringToSign。在构造要加密的字符串时,我一直在关注这个例子:

https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas

我所有的努力都导致了:

<AuthenticationErrorDetail>Signature fields not well formed.</AuthenticationErrorDetail>

<AuthenticationErrorDetail>Signature did not match. String to sign used was <insert string details here>

查看 Portal 生成的适合我的 sasToken:

?sv=2017-11-09&ss=f&srt=o&sp=r&se=2018-12-06T22:15:20Z&st=2018-12-06T14:15:20Z&spr=https&sig=%2Bi1TWv5D80U%2BoaIeoBh1wjaO1p4xVFYz%3nRZt%2Fzwi p>

看来我需要这样的字符串:

            String stringToSign = accountName + "\n" +
                "r\n" +
                "f\n" +
                "o\n" +
                URLEncoder.encode(start, "UTF-8") + "\n" +
                URLEncoder.encode(expiry, "UTF-8") + "\n" +
                "\n" +
                "https\n" +
                azureApiVersion;

其中 accountName 是 Azure 的存储帐户名称,start/expiry 是开始和到期字符串(即 2018-12-06T22:15:20Z),azureApiVersion 是“2017-11-09”。

然后我尝试在构造字符串后返回令牌,如下所示:

        String signature = getHMAC256(key, stringToSign);
        sasToken = "sv=" + azureApiVersion +
                "&ss=f" +
                "&srt=o" +
                "&sp=r" +
                "&se=" +URLEncoder.encode(expiry, "UTF-8") +
                "&st=" + URLEncoder.encode(start, "UTF-8") +
                "&spr=https" +
                "&sig=" + URLEncoder.encode(signature, "UTF-8");

我也尝试了 URL 编码,而不是 URL 编码开始/到期日期,以防万一搞砸了。我错过了什么?

【问题讨论】:

您使用的密钥的值是多少?是你从portal得到的字符串还是base64解码账户密钥字符串得到的字节数组? 我的密钥的值是 Azure 中存储帐户上的访问密钥之一。 我相信问题出在您的getHMAC256 方法中。我查找了它的代码,它只是从字符串值中获取字节。实际上,您需要对密钥字符串进行 base64 解码并在此方法中使用该字节数组。 好的,我尝试更改基本 getHMAC256 方法以使用: SecretKeySpec secret_key = new SecretKeySpec(Base64.getDecoder().decode(key), "HmacSHA256");不幸的是,收到了“签名不匹配。使用的签名字符串是 ”消息。 @seanDematic 我遇到了同样的错误?你解决了吗? 【参考方案1】:

三点修复

    @Gaurav 提到的getHMAC256 方法问题

    不要在stringToSign 中编码expirystart,否则签名将不匹配。因为 url 中的编码部分会被 Azure Storage Service 解码,计算出预期的签名。

    stringToSign 中,在azureApiVersion 之后错过一个\n

这是完整的示例。

 public static void GetFileSAS()

    String accountName = "accountName";
    String key = "accountKey";
    String resourceUrl = "https://"+accountName+".file.core.windows.net/fileShare/fileName";

    String start = "startTime";
    String expiry = "expiry";
    String azureApiVersion = "2017-11-09";

    String stringToSign = accountName + "\n" +
                "r\n" +
                "f\n" +
                "o\n" +
                start + "\n" +
                expiry + "\n" +
                "\n" +
                "https\n" +
                azureApiVersion+"\n";

    String signature = getHMAC256(key, stringToSign);

    try

        String sasToken = "sv=" + azureApiVersion +
            "&ss=f" +
            "&srt=o" +
            "&sp=r" +
            "&se=" +URLEncoder.encode(expiry, "UTF-8") +
            "&st=" + URLEncoder.encode(start, "UTF-8") +
            "&spr=https" +
            "&sig=" + URLEncoder.encode(signature, "UTF-8");

    System.out.println(resourceUrl+"?"+sasToken);

     catch (UnsupportedEncodingException e) 
        e.printStackTrace();
    


private static String getHMAC256(String accountKey, String signStr) 
    String signature = null;
    try 
        SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(accountKey), "HmacSHA256");
        Mac sha256HMAC = Mac.getInstance("HmacSHA256");
        sha256HMAC.init(secretKey);
        signature = Base64.getEncoder().encodeToString(sha256HMAC.doFinal(signStr.getBytes("UTF-8")));
     catch (Exception e) 
        e.printStackTrace();
    
    return signature;

【讨论】:

噢!你对第 1 点和第 3 点是绝对正确的(我正在尝试使用编码和未编码的日期,只是碰巧在我的示例中粘贴了编码版本)谢谢! 除上述之外 - 我发现本节在描述 stringToSign: docs.microsoft.com/en-us/rest/api/storageservices/… 的内容时很有用 我遇到了下载链接不会过期的问题。原来,我的浏览器正在缓存请求的结果。在另一个浏览器中尝试该链接证明它确实已过期。看到这个:social.msdn.microsoft.com/Forums/en-US/…。只是把它放在那里......【参考方案2】:

我有一个更简单的方法

SharedAccessAccountPolicy sharedAccessAccountPolicy = new SharedAccessAccountPolicy();
sharedAccessAccountPolicy.setPermissionsFromString("racwdlup");
long date = new Date().getTime();
long expiryDate = new Date(date + 8640000).getTime();
sharedAccessAccountPolicy.setSharedAccessStartTime(new Date(date));
sharedAccessAccountPolicy.setSharedAccessExpiryTime(new Date(expiryDate));
sharedAccessAccountPolicy.setResourceTypeFromString("sco");
sharedAccessAccountPolicy.setServiceFromString("bfqt");
String sasToken = "?" + storageAccount.generateSharedAccessSignature(sharedAccessAccountPolicy);

您可以像这样获取存储帐户:

private String storageConnectionString = "DefaultEndpointsProtocol=https;AccountName=<storage name>;AccountKey=<your key>;EndpointSuffix=core.windows.net";
storageAccount = CloudStorageAccount.parse(storageConnectionString);

【讨论】:

嘿,我在哪里可以获得 mvn 中的 SharedAccessAccountPolicy 库? @Rodolfo Velasco 我想是这个。 mvnrepository.com/artifact/com.microsoft.azure/azure-storage 如果这不起作用,请告诉我,我会检查。 好吧,我还是检查了。这里是:编译组:'com.microsoft.azure',名称:'azure-storage',版本:'8.0.0' 我在 maven 上使用 v8.0.0 我看到他们有更新的版本。 此时我使用的是最新版本。我需要设置权限才能读取(下载),所以我将“racwdlup”更改为“r”。除此之外,您认为是否需要更改 setResourceTypeFromString("sco") 或 setServiceFromString("bfqt")? 中的任何内容?提前致谢。 @Rodolfo Velasco 老实说,我已经不记得这个权限在 Azure 上是如何工作的了,但他们的文档中有解释。

以上是关于在 Java 中生成 SAS 令牌以下载 Azure 数据存储容器中的文件的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 中生成 JWT 令牌以进行 hasura 授权

无法使用 Microsoft 示例在 Azure APIM 中生成 SAS

缓存/存储在 datapower 中生成的 LTPA 令牌

如何在 Python 中生成 Azure blob SAS URL?

如何在 Java 中基于 SAS 令牌访问 Azure WASB 容器?

在 Java 应用程序中生成 JavaScript 代码