问题
I am trying to implement Azure Put Blob in Salesforce APEX but getting following error. I have read almost all Microsoft documentation and tried many way but in the end stuck in same point.
I would like someone to have a look please.
Error
<?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:e41a64e6-301e-0047-5cbd-fa2206000000
Time:2020-03-15T11:36:39.3690495Z</Message>
<AuthenticationErrorDetail>The MAC signature found in the HTTP request '7XBxAbAYZNkrx6mWol+RAYieDNZm9/JN6/4IQ+ygxhk=' is not the same as any computed signature. Server used following string to sign: 'PUT
1002
x-ms-blob-type:BlockBlob
x-ms-date:Sun, 15 Mar 2020 11:31:59 GMT
x-ms-version:2015-02-21
/zaindevtesting/zaindevblob/test.txt
blockid:YmxvY2stMQ==
comp:block'.</AuthenticationErrorDetail>
</Error>
Following is the Sample code the key information is for my test env so noting important, you can use same key to execute the example.
public static void putAzureBlob(Blob fileBody)
{
String blobName = '/zaindevtesting/zaindevblob/test.txt';
String urlQueue = 'https://zaindevtesting.blob.core.windows.net/zaindevblob/test.txt';
string storageKey = 'xxxx==';
Datetime dt = Datetime.now();
string formattedDate = dt.formatGMT('EEE, dd MMM yyyy HH:mm:ss') + ' GMT';
System.debug('formattedDate--->'+formattedDate);
System.debug('fileLength--->'+ fileLength);
System.debug('fileLength--->'+ fileType);
string sts = 'PUT\n\n\n'+fileLength+'\n\n'+fileType+'\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:' + formattedDate + '\nx-ms-version:2015-02-21\n' + blobName;
String signature = '';
Blob unicodeKey = EncodingUtil.base64Decode(storageKey);
Blob data = Crypto.generateMac('HMACSHA256', Blob.valueOf(sts), unicodeKey);
signature = EncodingUtil.base64Encode(data);
string authHeader = 'SharedKey zaindevtesting:' + signature;
System.debug('authHeader--->'+authHeader);
HttpRequest req = new HttpRequest();
req.setMethod('PUT');
req.setHeader('x-ms-blob-type', 'BlockBlob');
req.setHeader('x-ms-date', formattedDate);
req.setHeader('x-ms-version', '2015-02-21');
req.setHeader('Authorization', authHeader);
req.setEndpoint(urlQueue);
req.setBodyAsBlob(fileBody);
Http http = new Http();
try
{
HTTPResponse res = http.send(req);
// in the response body you have your blob
System.debug('Status--->'+res.getStatus());
// Let's save it as attachment
System.debug('Body---->'+res.getBody());
}catch(Exception exp)
{
System.debug('Exception --->'+exp);
}
}
Second Approach After editing my code for generating Signature I am getting now this Error
<?xml version="1.0" encoding="utf-8"?>
<Error>
<Code>InvalidAuthenticationInfo</Code>
<Message>Authentication information is not given in the correct format. Check the value of Authorization header.
RequestId:c869405b-501e-0013-6328-fbc88c000000
Time:2020-03-16T00:16:06.8896380Z</Message>
</Error>
Following is my Code
public static void putAzureBlob(Blob fileBody, Integer fileLength, String fileType)
{
String blobName = '/zaindevtesting/zaindevblob/test.txt';
String urlQueue = 'https://zaindevtesting.blob.core.windows.net/zaindevblob/test.txt';
string storageKey = 'XXXXXXXXXXXX==';
Datetime dt = Datetime.now();
string formattedDate = dt.formatGMT('EEE, dd MMM yyyy HH:mm:ss') + ' GMT';
System.debug('formattedDate--->'+formattedDate);
System.debug('fileLength--->'+ fileLength);
System.debug('fileLength--->'+ fileType);
string sts = 'PUT\n\n\n'+fileLength+'\n\n'+fileType+'\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:' + formattedDate + '\nx-ms-version:2015-02-21\n' + blobName;
String signature = '';
Blob unicodeKey = EncodingUtil.base64Decode(storageKey);
Blob data = Crypto.generateMac('HMACSHA256', Blob.valueOf(sts), unicodeKey);
signature = EncodingUtil.base64Encode(data);
System.debug('signature--->1'+ signature);
signature = EncodingUtil.urlEncode(signature, 'UTF-8');
System.debug('signature--->2'+ signature);
string authHeader = 'SharedKey zaindevtesting:' + signature;
System.debug('authHeader--->'+authHeader);
HttpRequest req = new HttpRequest();
req.setMethod('PUT');
req.setHeader('x-ms-blob-type', 'BlockBlob');
req.setHeader('x-ms-date', formattedDate);
req.setHeader('x-ms-version', '2015-02-21');
req.setHeader('Authorization', authHeader);
req.setHeader('Content-Length', String.valueof(fileLength));
req.setEndpoint(urlQueue);
req.setBodyAsBlob(fileBody);
Http http = new Http();
try
{
HTTPResponse res = http.send(req);
// in the response body you have your blob
System.debug('Status--->'+res.getStatus());
// Let's save it as attachment
System.debug('Body---->'+res.getBody());
}catch(Exception exp)
{
System.debug('Exception --->'+exp);
}
}
Third Approach These are the three different way I am using for Signature
string sts = 'PUT\n\n\n'+fileLength+'\n\n'+fileType+'\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:' + formattedDate + '\nx-ms-version:2015-02-21\n' + blobName;
//string sts = 'PUT\n\n\n'+fileLength+'\n\n'+fileType+'\n\n\n\n\n\n\nx-ms-date:' + formattedDate + '\nx-ms-version:2015-02-21\n' + blobName;
// string sts = 'PUT\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:' + formattedDate + '\nx-ms-version:2015-02-21\n' + blobName+'\nrestype:container\ntimeout:30';
I am getting this error still, one thing I have notice that my content length is 996 as per debug but in error it is showing 1002?
<?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:f7ccfaf6-a01e-0060-113d-fbb84f000000
Time:2020-03-16T02:52:05.0980775Z</Message>
<AuthenticationErrorDetail>The MAC signature found in the HTTP request 'R+k2bivFW2AH3UriO8m4z8RxPJRDC8uujRc6FCZMEs8=' is not the same as any computed signature. Server used following string to sign: 'PUT
1002
text/plain;charset=UTF-8
x-ms-blob-type:BlockBlob
x-ms-date:Mon, 16 Mar 2020 02:52:04 GMT
x-ms-version:2015-02-21
/zaindevtesting/zaindevblob/test.txt'.</AuthenticationErrorDetail>
</Error>
回答1:
One issue I see in your code is that you have not included the content-length
and content-type
in your signature string (sts):
string sts = 'PUT\n\n\n\n\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:' + formattedDate + '\nx-ms-version:2015-02-21\n' + blobName+'\nblockid:YmxvY2stMQ==\ncomp:block';
Also, considering you're performing Put Blob operation and not Put Block operation, you don't need to include blockid:YmxvY2stMQ==\ncomp:block
in sts
.
It should be:
string sts = 'PUT\n\n\n' + content-length-here (745 based on the file you're trying to upload) + '\n\n' + content-type-here (text/plain based on the file you're trying to upload + '\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:' + formattedDate + '\nx-ms-version:2015-02-21\n' + blobName;
For more details, please see this: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key.
回答2:
After spending so many days, I was able to figure out the issue. It was with the length of the file.
Following is the complete working implementation of "Upload to Azure Blob with Salesforce APEX"
Lightning Component
<aura:component controller="FileUploadController" implements="flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" access="global" >
<aura:attribute name="azureservice" type="Object" />
<aura:attribute name="accept" type="List" default="['.jpg', '.jpeg', '.pdf', '.Docx', '.Doc']"/>
<aura:attribute name="multiselect" type="Boolean" default="true"/>
<aura:attribute name="disabled" type="Boolean" default="false"/>
<aura:attribute name="fileName" type="String" default="No File Selected.." />
<lightning:card variant="Narrow" title="File Upload">
<!-- Lightning Input with file type and on file change call the 'handleFilesChange' controller -->
<div style="padding 10px">
<lightning:input aura:id="fileId" onchange="{!c.handleFiles}" type="file" name="file" label="Upload File" multiple="true"/>
<div class="slds-text-body_small slds-text-color_error">{!v.fileName} </div>
<br/>
<div class="slds-is-relative">
<lightning:spinner aura:id="Spinner" alternativeText="Loading..." size="small" class="slds-hide"/>
</div>
</div>
<lightning:buttonGroup>
<div class="slds-m-top_medium">
<lightning:button label="Cancel" onclick="{!c.handleCancel}" class="slds-m-top--medium"/>
</div>
<div class="slds-m-top_medium">
<lightning:button label="Save" onclick="{!c.handleUpload}" class="slds-m-top--medium" />
</div>
</lightning:buttonGroup>
</lightning:card>
</aura:component>
FileUploadController Javascript
({
handleFiles: function(component, event, helper) {
var fileName = 'No File Selected..';
if (event.getSource().get("v.files").length > 0) {
console.log('fileName--->'+fileName)
fileName = event.getSource().get("v.files")[0]['name'];
}
component.set("v.fileName", fileName);
},
handleUpload: function(component, event, helper) {
if (component.find("fileId").get("v.files").length > 0) {
console.log('No of files-->'+component.find("fileId").get("v.files").length);
//Calling helper method to upload the file
//Show Spinner while request is in process
helper.showSpinner(component);
helper.uploadHelper(component, event);
helper.hideSpinner(component);
} else {
alert('Please Select a Valid File');
}
}
})
Helper Javascript
({
uploadHelper: function(component, event) {
// start/show the loading spinner
component.set("v.showLoadingSpinner", true);
// get the selected files using aura:id [return array of files]
var fileInput = component.find("fileId").get("v.files");
// get the first file using array index[0]
var file = fileInput[0];
var fileType = file.type;
var self = this;
// check the selected file size, if select file size greter then MAX_FILE_SIZE,
// then show a alert msg to user,hide the loading spinner and return from function
if (file.size > self.MAX_FILE_SIZE) {
// component.set("v.showLoadingSpinner", false);
component.set("v.fileName", 'Alert : File size cannot exceed ' + self.MAX_FILE_SIZE + ' bytes.\n' + ' Selected file size: ' + file.size);
return;
}
var objFileReader = new FileReader();
objFileReader.onload = $A.getCallback(function() {
var fileContents = objFileReader.result;
var base64 = 'base64,';
var dataStart = fileContents.indexOf(base64) + base64.length;
fileContents = fileContents.substring(dataStart);
// call the uploadProcess method
self.uploadProcess(component, file, fileContents);
});
objFileReader.readAsDataURL(file);
},
uploadProcess: function(component, file, fileContents) {
var action = component.get("c.upload");
action.setParams({
base64Data: fileContents,
filelength: file.size,
fileType: file.type,
fileName: file.name
});
// set call back
action.setCallback(this, function(response) {
var isUploaded = response.getReturnValue();
if(isUploaded==true)
{
var state = response.getState();
if (state === "SUCCESS") {
console.log('your File is uploaded successfully');
// handel the response errors
} else if (state === "INCOMPLETE") {
alert("From server: " + response.getReturnValue());
} else if (state === "ERROR") {
var errors = response.getError();
if (errors) {
if (errors[0] && errors[0].message) {
console.log("Error message: " + errors[0].message);
}
} else {
console.log("Unknown error");
}
}
console.log('Your File is uploaded successfully');
}else{
console.log("Your File is has not uploaded successfully");
}
});
$A.enqueueAction(action);
},
showSpinner:function(component){
var spinnerMain = component.find("Spinner");
$A.util.removeClass(spinnerMain, "slds-hide");
},
hideSpinner:function(component){
var spinnerMain = component.find("Spinner");
$A.util.addClass(spinnerMain, "slds-hide");
}
})
FileUploadController
public class FileUploadController {
@AuraEnabled
public static Boolean upload(String base64Data, Integer filelength, String fileType, String fileName ){
Blob blobData = EncodingUtil.base64Decode(base64Data);
AzureService service = new AzureService();
Boolean isUploaded = service.uploadBlob(blobData, filelength, fileType, fileName);
// AzureService.generateAuthorizationHeader(blobData, filelength, fileType);
return isUploaded;
}
}
AzureService
public class AzureService {
private String storageKey;
private String storageName;
private String storageContainer;
private String storageUrl;
private String blobName;
private String requestURL;
private String fileLength;
private String formattedDate ;
private String fileType;
private String fileName;
private final string DATEFORMAT = 'EEE, dd MMM yyyy HH:mm:ss z';
private final string VERSION = '2015-12-11';
private final string BLOB_TYPE = 'BlockBlob';
public Boolean uploadBlob( Blob fileBody, Integer intFileLength, String strFileType, String strFileName)
{
Boolean isUploaded= false;
this.fileName = EncodingUtil.urlEncode(strFileName, 'UTF-8');
this.fileType = strFileType;
this.storageName = 'YourStorageName';
this.storageContainer = 'YourContainerName';
this.storageKey = 'YourStorageAccountKet';
this.storageUrl ='https://YourStorageName.blob.core.windows.net';
this.blobName = '/'+storageName+'/'+storageContainer+'/'+fileName;
System.debug('blobName--->'+blobName);
this.requestURL = storageUrl+'/'+storageContainer+'/'+fileName;
System.debug('requestURL--->'+requestURL);
this.fileLength = String.valueof(intFileLength);
String strSharedKey = getBlobSharedKey();
try
{
this.uploadBlob(fileBody, strSharedKey);
isUploaded = true;
}catch(Exception exp)
{
System.debug('Exception occur while uploading the Blob-->'+exp.getMessage());
isUploaded = false;
}
return isUploaded;
}
public String getBlobSharedKey()
{
System.debug('getBlobSharedKey--->Start');
String sharedKey;
String signature;
Datetime dt = Datetime.now();
this.formattedDate = dt.formatGMT(DATEFORMAT);
String stringToSign = 'PUT\n\n\n'+fileLength+'\n\n'+fileType+'\n\n\n\n\n\n\nx-ms-blob-type:BlockBlob\nx-ms-date:'+formattedDate+'\nx-ms-version:2015-12-11\n'+blobName;
System.debug('stringToSign--->'+stringToSign);
Blob unicodeKey = EncodingUtil.base64Decode(storageKey);
Blob data = Crypto.generateMac('HMACSHA256', Blob.valueOf(stringToSign), unicodeKey);
signature = EncodingUtil.base64Encode(data);
sharedKey = 'SharedKey '+storageName+':' + signature;
return sharedKey;
}
public void uploadBlob(Blob fileBody, String sharedKey)
{
HttpRequest req = new HttpRequest();
req.setMethod('PUT');
req.setHeader('x-ms-blob-type', BLOB_TYPE);
req.setHeader('x-ms-version', VERSION);
req.setHeader('x-ms-date', formattedDate);
req.setHeader('Authorization', sharedKey);
req.setHeader('Content-Type', fileType);
req.setHeader('Content-Length', fileLength);
req.setEndpoint(this.requestURL);
req.setBodyAsBlob(fileBody);
Http http = new Http();
HTTPResponse res = http.send(req);
// in the response body you have your blob
System.debug('Response Body--->'+res.getBody());
System.debug('Status--->'+res.getStatus());
}
}
I would be happy if someone this post redo the code in more clean manner.
Hope this will find useful to you guys
Thanks, Zain
来源:https://stackoverflow.com/questions/60692538/implementing-azure-put-blob-in-salesforce-apex