4

我成功地创建了一个独立的 Java 应用程序,该应用程序为 Google Cloud Storage 中的资产创建了过期签名 URL。但是,我没有成功弄清楚如何通过 AppEngine 为这些相同的资产创建过期签名 URL。

如何创建可返回给客户端应用程序的 Google Cloud Storage Assets 的过期签名 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;
}

结果是一个过期的 URL,但要求我使用我的谷歌电子邮件/密码进行身份验证,因此签名无法正常工作。

我终于能够使用 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>

我用来生成 URL 的代码是:

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);
    }       
4

3 回答 3

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();
    }
}
于 2013-07-23T00:53:20.633 回答
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();
}

这对我来说很好。

于 2016-09-05T14:11:15.980 回答
0

Using storage.cloud.google.com is requesting cookie based authenticated downloads. Changing:

String baseURL = "https://storage.cloud.google.com/myproj/foo.txt"

to

String baseURL = "https://storage.googleapis.com/myproj/foo.txt"

should work better.

I'm not sure why you're only seeing this when using the URL created via App Engine. Maybe you're not logged in to Google when testing the App Engine application? Or running it in the local dev server?

See the reference URIs section of the docs for details on possible request URIs.

于 2013-07-08T01:02:57.317 回答