Sherlock21
Sherlock21

Reputation: 3

Audience validation failed while listing azure storage account containers from app registrations

I am trying to get the details of azure storage account containers from rest API using the credentials of a an registered application in azure. I have built the authentication header parameter and when I am calling the rest API I get this error which says Audience validation failed. Audience did not match.

19:47:16.826 [main] INFO - <-- 403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. https://shagundonotdeleteaccount.blob.core.windows.net/?comp=list (288 ms, 426-byte body) 19:47:16.828 [main] INFO - 426-byte body: ?<?xml version="1.0" encoding="utf-8"?><Error><Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:deb7a983-401e-008d-30e7-0c884e000000 Time:2020-04-07T14:17:14.0081901Z</Message><AuthenticationErrorDetail>Audience validation failed. Audience did not match.</AuthenticationErrorDetail></Error> 19:47:16.828 [main] INFO - <-- END HTTP

This is my code:

        ApplicationTokenCredentials credentials = new ApplicationTokenCredentials("clientID",
                "domain", "secret", AzureEnvironment.AZURE);
RestClient restClient = new RestClient.Builder()
                .withBaseUrl(AzureEnvironment.AZURE, AzureEnvironment.Endpoint.RESOURCE_MANAGER)
                .withSerializerAdapter(new AzureJacksonAdapter())
                .withReadTimeout(150, TimeUnit.SECONDS)
                .withLogLevel(LogLevel.BODY)
                .withResponseBuilderFactory(new AzureResponseBuilder.Factory())
                .withCredentials(credentials).build();
   Azure azure = Azure.authenticate(restClient, credentials.domain()).withDefaultSubscription();
             Base64 base64 = new Base64();

  for (StorageAccount account : azure.storageAccounts().list()) {

            URL url = new URL("https://" + account.name() + ".blob.core.windows.net/?comp=list");
            StorageAccountKey key = account.getKeys().get(0);
            SimpleDateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss");
            fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
            String date = fmt.format(Calendar.getInstance().getTime()) + " GMT";

            StringBuilder sb = new StringBuilder();
            sb.append("GET\n"); // method
            sb.append('\n'); // md5 (optional)
            sb.append('\n'); // content type
            sb.append('\n'); // legacy date
            sb.append("x-ms-date:" + date + '\n'); // headers
            sb.append("x-ms-version:2017-11-09\n");
            sb.append("/" + account.name() + url.getPath() + "?comp=list");

            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(base64.decode(key.value()), "HmacSHA256"));

            String authKey = new String(base64.encode(mac.doFinal(sb.toString().getBytes("UTF-8"))));
            String auth = "SharedKeyLite " + account.name() + ":" + authKey;
         OkHttpClient httpClient = restClient.httpClient();
         Request request = new Request.Builder()
                    .url(url)
                    //                    .addHeader("content-type", "application/json")
                    //                    .addHeader("cache-control", "no-cache")
                    .addHeader("x-ms-version", "2017-11-09")
                    .addHeader("x-ms-date", date)
                    .addHeader("Authorization", auth)
                    .get()
                    .build();

            okhttp3.Response response = httpClient.newCall(request).execute();
            httpClient.newCall(request);

            if (!response.isSuccessful()) {
                throw new RuntimeException("Request Failed" + response.code() + "\n" + response.message());
            }
            JSONParser parser = new JSONParser();
            String apiResponse = response.body().string();

            if (apiResponse != null) {
                org.json.simple.JSONObject responseJSON = (org.json.simple.JSONObject) parser.parse(apiResponse);
                System.out.println(responseJSON);

            }

        }

This is the authsignstring

GET



x-ms-date:Tue, 07 Apr 2020 14:17:13 GMT
x-ms-version:2017-11-09
/mystorageaccount/?comp=list

I have tried this as well:

StringBuilder sb = new StringBuilder();
        sb.append("GET\n"); // method
        sb.append('\n'); // content encoding
        sb.append('\n'); // content language
        sb.append('\n'); // content length
        sb.append('\n'); // md5 (optional)
        sb.append('\n'); // content type
        sb.append('\n'); // legacy date
        sb.append('\n'); // if-modified-since
        sb.append('\n'); // if-match
        sb.append('\n'); // if-none-match
        sb.append('\n'); // if-unmodified-since
        sb.append('\n'); // range
        sb.append("x-ms-date:" + date + '\n'); // headers
        sb.append("x-ms-version:2017-11-09\n");

        sb.append("/" + account.name() + "/" + "\ncomp:list");

and

String auth = "SharedKey " + account.name() + ":" + authKey;

Can you help me with this? I am stuck here from a long time. I want the JSON response for the list of containers.

Upvotes: 0

Views: 3457

Answers (2)

Jim Xu
Jim Xu

Reputation: 23161

If you want to use SharedKeyLite auth to call Azur blob rest api, please refer to the following code

 String StorageAccountName = "blobstorage0516";
         String StorageAccountKey = "";
        URL url = new URL("https://" + StorageAccountName + ".blob.core.windows.net/?comp=list");
        SimpleDateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss");
        fmt.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = fmt.format(Calendar.getInstance().getTime()) + " GMT";
        StringBuilder sb = new StringBuilder();
        sb.append("GET\n"); // method
        sb.append("\n") ;//Content-MD5
        sb.append("\n") ;//Content-Type
        sb.append("\n") ;//data
        sb.append("x-ms-date:" + date + '\n'); // headers
        sb.append("x-ms-version:2017-11-09\n");
        sb.append("/" + StorageAccountName + url.getPath() + "?comp=list");
 System.out.println(sb.toString()); // print stringtosign
SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(StorageAccountKey), "HmacSHA256");
        Mac sha256HMAC = Mac.getInstance("HmacSHA256");
        sha256HMAC.init(secretKey);
        String authKey=Base64.getEncoder().encodeToString(sha256HMAC.doFinal(sb.toString().getBytes("UTF-8")));
        String auth = "SharedKeyLite " + StorageAccountName + ":" + authKey;
        System.out.println(auth);
        OkHttpClient client = new OkHttpClient().newBuilder().build();
        Request request = new Request.Builder()
                             .url(url)
                             .method("GET", null)
                             .addHeader("x-ms-version", "2017-11-09")
                             .addHeader("x-ms-date", date)
                             .addHeader("Authorization", auth)
                             .build();
        Response response = client.newCall(request).execute();
        if(response.isSuccessful()){

            System.out.println(response.body().string());
        }

enter image description here


Update

When we use RestClient to call the Azure rest api, it will use Azure AD access token to do auth. But the restclient use wrong resource to get Azure AD access token. It uses https://management.core.windows.net/ but we needs https:\\storage.azure.com\

My test code

 OkHttpClient httpClient = restClient.httpClient().newBuilder().build();
        //OkHttpClient client = new OkHttpClient().newBuilder().build();
        Request request = new Request.Builder()
                .url(url)
                .method("GET", null)
                .addHeader("x-ms-version", "2017-11-09")
                //.addHeader("x-ms-date", date)
                .build();
        //Response response = client.newCall(request).execute();

        okhttp3.Response response1 = httpClient.newCall(request).execute();



            System.out.println(response1.body().string());

enter image description here

Analyze access token via the link enter image description here

So if you want to use sharekey to call the api, please create a new httoclient.

Upvotes: 1

Jack Jia
Jack Jia

Reputation: 5549

I see that you want to use the credentials of a registered application in azure. So, you may want to Authorize access to blobs and queues using Azure Active Directory

  1. You need to grant your application access to your storage account

enter image description here

To be able to read your storage account, you need to at least add Storage Blob Data Reader role.

enter image description here

  1. Acquire a token in your code, and use the token to access your storage account:
    public static void main(String[] args) throws IOException {
        String clientId = "your application id here";
        String clientSecret = "application secret here";
        String tenantId = "your tenant id";
        String storageResource = "https://storage.azure.com/";
        String url = "https://{your_storage_account_name}.core.windows.net/?comp=list";

        ApplicationTokenCredentials credentials = new ApplicationTokenCredentials(clientId,
                tenantId, clientSecret, AzureEnvironment.AZURE);

        String token = credentials.getToken(storageResource);

        OkHttpClient client = new OkHttpClient().newBuilder().build();
        Request request = new Request.Builder()
                .url(url)
                .get()
                .addHeader("x-ms-version", "2017-11-09")
                .addHeader("Authorization", "Bearer "+token)
                .build();
        Response response = client.newCall(request).execute();
        if(response.isSuccessful()){
            System.out.println(response.body().string());
        }
    }

Output

enter image description here

Upvotes: 0

Related Questions