我成功地创建了一个独立的Java应用程序,可以为Google云端存储中的资产创建过期签名URL。但是,我没有成功通过AppEngine确定如何为这些相同的资产创建过期签名URL。
如何为Google云端存储资产创建一个过期的签名URL,我可以将其返回给客户端应用程序?
这是我的Java应用程序:
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.UnrecoverableKeyException;
import java.util.Calendar;
import org.apache.commons.codec.binary.Base64;
public class GCSSignedURL {
public static void main(String[] args) throws Exception {
final String googleAccessId = "XXXXXXXXXXXX@developer.gserviceaccount.com";
final String keyFile = "D:\\XXXXXXXXXXXXXXXXXXXXXXXXXXXXX-privatekey.p12";
final String keyPassword = "notasecret";
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, 30);
String httpVerb = "GET";
String contentMD5 = "";
String contentType = "";
long expiration = calendar.getTimeInMillis();
String canonicalizedExtensionHeaders = "";
String canonicalizedResource = "/myproj/foo.txt";
String stringToSign =
httpVerb + "\n" +
contentMD5 + "\n" +
contentType + "\n" +
expiration + "\n" +
canonicalizedExtensionHeaders +
canonicalizedResource;
PrivateKey pkcsKey = loadKeyFromPkcs12(keyFile, keyPassword.toCharArray());
String signature = signData(pkcsKey, stringToSign);
String baseURL = "https://storage.cloud.google.com/myproj/foo.txt";
String urlEncodedSignature = URLEncoder.encode(signature, "UTF-8");
String url = baseURL + "?GoogleAccessId=" + googleAccessId + "&Expires=" + expiration + "&Signature=" + urlEncodedSignature;
System.out.println(url);
}
private static PrivateKey loadKeyFromPkcs12(String filename, char[] password)
throws Exception {
FileInputStream fis = new FileInputStream(filename);
KeyStore ks = KeyStore.getInstance("PKCS12");
try {
ks.load(fis, password);
} catch (IOException e) {
if (e.getCause() != null
&& e.getCause() instanceof UnrecoverableKeyException) {
System.err.println("Incorrect password");
}
throw e;
}
return (PrivateKey) ks.getKey("privatekey", password);
}
private static String signData(PrivateKey key, String data)
throws Exception {
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(key);
signer.update(data.getBytes("UTF-8"));
byte[] rawSignature = signer.sign();
String encodedSignature = new String(Base64.encodeBase64(rawSignature,
false), "UTF-8");
return encodedSignature;
}
}
以下是我的尝试:
public String signUrl(Long _songId, String _format) throws ResourceNotFoundException
{
final String googleAccessId = "XXXXXXXXXXXXXXXXXX@developer.gserviceaccount.com";
AppIdentityService service = AppIdentityServiceFactory.getAppIdentityService();
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, 5);
String httpVerb = "GET";
String contentMD5 = "";
String contentType = "";
long expiration = calendar.getTimeInMillis();
String canonicalizedExtensionHeaders = "";
String canonicalizedResource = "/myproj/foo.txt";
String stringToSign =
httpVerb + "\n" +
contentMD5 + "\n" +
contentType + "\n" +
expiration + "\n" +
canonicalizedExtensionHeaders +
canonicalizedResource;
SigningResult key = service.signForApp(stringToSign.getBytes());
String baseURL = "https://storage.cloud.google.com/myproj/foo.txt";
String encodedUrl = baseURL + "?GoogleAccessId=" + googleAccessId + "&Expires=" + expiration
+ "&Signature=" + key.getKeyName();
return encodedUrl;
}
结果是网址过期,但要求我使用我的Google电子邮件/密码进行身份验证,以便签名无法正常运行。
我最终能够使用Fabio的建议生成一个编码的URL,但是,我现在得到:
<?xml version="1.0" encoding="UTF-8"?>
-<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated
does not match the signature you provided. Check your Google secret key and signing
method.</Message>
<StringToSign>
GET 1374729586 /[my_bucket]/[my_folder]/file.png</StringToSign>
</Error>
我用来生成网址的代码是:
AppIdentityService service = AppIdentityServiceFactory.getAppIdentityService();
final String googleAccessId = service.getServiceAccountName();
String url = songUrl(_songId, _format);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, 10);
String httpVerb = "GET";
String contentMD5 = "";
String contentType = "";
long expiration = calendar.getTimeInMillis()/1000L;
String canonicalizedExtensionHeaders = "";
String canonicalizedResource = "/[my_bucket]/[my_folder]/file.png";
String stringToSign =
httpVerb + "\n" +
contentMD5 + "\n" +
contentType + "\n" +
expiration + "\n" +
canonicalizedExtensionHeaders +
canonicalizedResource;
try
{
String baseURL = http://[my_bucket].commondatastorage.googleapis.com/[my_folder]/file.png;
SigningResult signingResult = service.signForApp(stringToSign.getBytes());
String encodedSignature = new String(Base64.encodeBase64(signingResult.getSignature(), false), "UTF-8");
String encodedUrl = baseURL + "?GoogleAccessId=" + googleAccessId + "&Expires=" + expiration
+ "&Signature=" + encodedSignature;
return encodedUrl;
}
catch (UnsupportedEncodingException e)
{
throw new ResourceNotFoundException("Unable to encode URL. Unsupported encoding exception.", e);
}
答案 0 :(得分:10)
两件事:
对于 googleAccessId ,请使用:
String googleAccessId = service.getServiceAccountName();
对于签名,请使用:
SigningResult signingResult = service
.signForApp(stringToSign.getBytes());
String encodedSignature = new String(Base64.encodeBase64(
signingResult.getSignature(), false), "UTF-8");
这对我有用。 请参阅下面的示例签名者类:
public class GcsAppIdentityServiceUrlSigner {
private static final int EXPIRATION_TIME = 5;
private static final String BASE_URL = "https://storage.googleapis.com";
private static final String BUCKET = "my_bucket";
private static final String FOLDER = "folder";
private final AppIdentityService identityService = AppIdentityServiceFactory.getAppIdentityService();
public String getSignedUrl(final String httpVerb, final String fileName) throws Exception {
final long expiration = expiration();
final String unsigned = stringToSign(expiration, fileName, httpVerb);
final String signature = sign(unsigned);
return new StringBuilder(BASE_URL).append("/")
.append(BUCKET)
.append("/")
.append(FOLDER)
.append("/")
.append(fileName)
.append("?GoogleAccessId=")
.append(clientId())
.append("&Expires=")
.append(expiration)
.append("&Signature=")
.append(URLEncoder.encode(signature, "UTF-8")).toString();
}
private static long expiration() {
final long unitMil = 1000l;
final Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, EXPIRATION_TIME);
final long expiration = calendar.getTimeInMillis() / unitMil;
return expiration;
}
private String stringToSign(final long expiration, String filename, String httpVerb) {
final String contentType = "";
final String contentMD5 = "";
final String canonicalizedExtensionHeaders = "";
final String canonicalizedResource = "/" + BUCKET + "/" + FOLDER + "/" + filename;
final String stringToSign = httpVerb + "\n" + contentMD5 + "\n" + contentType + "\n"
+ expiration + "\n" + canonicalizedExtensionHeaders + canonicalizedResource;
return stringToSign;
}
protected String sign(final String stringToSign) throws UnsupportedEncodingException {
final SigningResult signingResult = identityService
.signForApp(stringToSign.getBytes());
final String encodedSignature = new String(Base64.encodeBase64(
signingResult.getSignature(), false), "UTF-8");
return encodedSignature;
}
protected String clientId() {
return identityService.getServiceAccountName();
}
}
答案 1 :(得分:1)
1 /将google api google-api-services-storage添加到您的依赖项
2 /然后您需要从服务帐户ID和主键创建ServiceAccountAuthCredentials对象:
serviceAccountAuthCredentials = AuthCredentials.createFor(resources.getString("authentication.p12.serviceAccountId"), pk);
3 /最后,您从ServiceAccountAuthCredentials对象以及存储桶和文件名生成签名URL(无需生成要签名的字符串):
public String getSignedURL(String bucket, String fileName) throws IOException, GeneralSecurityException {
BlobId blobId = BlobId.of(bucket, fileName);
Blob blob = cloudStorageService.get(blobId);
URL signedURL = blob.signUrl(durationSignedURLAvailable, TimeUnit.MINUTES, com.google.cloud.storage.Storage.SignUrlOption.signWith(serviceAccountAuthCredentials));
return signedURL.toString();
}
这对我来说很好。
答案 2 :(得分:0)
使用storage.cloud.google.com
请求基于Cookie的经过身份验证的下载。
改变:
String baseURL = "https://storage.cloud.google.com/myproj/foo.txt"
到
String baseURL = "https://storage.googleapis.com/myproj/foo.txt"
应该更好。
我不确定为什么在使用通过App Engine创建的网址时才会看到这一点。也许您在测试App Engine应用程序时没有登录Google?或者在本地开发服务器中运行它?
有关可能的请求URI的详细信息,请参阅文档的reference URIs部分。