如何在Java中验证Instagram实时API x-hub-signature?

时间:2013-12-18 12:54:08

标签: java json playframework playframework-2.0 instagram

我正在使用Play框架为Instagram实时API开发消费者。但仍无法正确执行x-hub-signature验证。那么,我们如何使用Java和Play框架执行Instagram x-hub-signature验证呢?

这是我目前的代码:

  1. 在Play框架中,我使用此方法获取JSON有效内容:

    public static Result receiveInstaData(){
        JsonNode json = request().body().asJson();
    
        //obtain the x-hub-signature from the header
        //obtain the corresponding client secret
    
        VerificationResult verificationResult =  
            SubscriptionUtil.verifySubscriptionPostSignature(
                clientSecret, json.toString(), xHubSignature);
    
        if(verificationResult.isSuccess()){
        //do something
        }
     }
    
  2. 然后在SubscriptionUtil内,我使用以下代码执行验证:

    public static VerificationResult verifySubscriptionPostSignature(String clientSecret, String rawJsonData, String xHubSignature) {
        SecretKeySpec keySpec;
        keySpec = new SecretKeySpec(clientSecret.getBytes("UTF-8"), HMAC_SHA1);
    
        Mac mac;
        mac = Mac.getInstance(HMAC_SHA1);
        mac.init(keySpec);
    
        byte[] result;
        result = mac.doFinal(rawJsonData.getBytes("UTF-8"));
        String encodedResult = Hex.encodeHexString(result);
    
        return new VerificationResult(encodedResult.equals(xHubSignature), encodedResult);
       }
    
  3. 我创建了一个独立的Python脚本来复制instagram-python实现,它们都为同一个clientSecretjsonString生成相同的结果。也许我应该提供原始二进制数据而不是String。

    如果让我们说我们需要JSON请求的原始二进制数据,那么我需要创建自定义BodyParser来将JSON请求解析为原始二进制数据[5]

    参考文献:

    [1-4] http://pastebin.com/g4uuDwzn(SO不允许我发布超过2个链接,因此我将所有引用放在此处。链接包含Ruby,Python和PHP中的签名验证)

    [5] https://groups.google.com/forum/#!msg/play-framework/YMQb6yeDH5o/jU8FD--yVPYJ

    [6]我的独立python脚本:     #! / usr / bin / env python

    import sys
    import hmac
    import hashlib
    
    hc_client_secret = "myclientsecret"
    hc_raw_response = "[{\"subscription_id\":\"1\",\"object\":\"user\",\"object_id\":\"1234\",\"changed_aspect\":\"media\",\"time\":1297286541},{\"subscription_id\":\"2\",\"object\":\"tag\",\"object_id\":\"nofilter\",\"changed_aspect\":\"media\",\"time\":1297286541}]"
    
    client_secret = hc_client_secret
    raw_response = hc_raw_response
    
    if len(sys.argv) != 3:
        print 'Usage verify_signature <client_secret> <raw_response>.\nSince the inputs are invalid, use the hardcoded value instead!'
    else:
        client_secret = sys.argv[1]
        raw_response = sys.argv[2]  
    
    print "client_secret = " + client_secret
    print "raw_response = " + raw_response
    
    digest = hmac.new(client_secret.encode('utf-8'), msg=raw_response.encode('utf-8'), digestmod=hashlib.sha1).hexdigest()
    print digest
    

1 个答案:

答案 0 :(得分:1)

最后我设法找到了解决方案。对于Play框架中的Controller,我们需要使用BodyParser.Raw,以便我们可以将有效负载请求提取为原始数据,即字节数组。

以下是Play Framework中控制器的代码:

@BodyParser.Of(BodyParser.Raw.class)
public static Result receiveRawInstaData(){
    Map<String, String[]> headers = request().headers();
    RawBuffer jsonRaw = request().body().asRaw();

    if(jsonRaw == null){
        logger.warn("jsonRaw is null. Something is wrong with the payload");
        return badRequest("Expecting serializable raw data");
    }

    String[] xHubSignature = headers.get(InstaSubscriptionUtils.HTTP_HEADER_X_HUB_SIGNATURE);
    if(xHubSignature == null){
        logger.error("Invalid POST. It does not contain {} in its header", InstaSubscriptionUtils.HTTP_HEADER_X_HUB_SIGNATURE);
        return badRequest("You are not Instagram!\n");
    }

    String json;
    byte[] jsonRawBytes;

    jsonRawBytes = jsonRaw.asBytes();
    json = new String(jsonRawBytes, StandardCharsets.UTF_8);

    try {
        String clientSecret = InstaSubscriptionUtils.getClientSecret(1);
        VerificationResult verificationResult = SubscriptionUtil.verifySubscriptionPostRequestSignature
                (clientSecret,jsonRawBytes, xHubSignature[0]);
        if(verificationResult.isSuccess()){
            logger.debug("Signature matches!. Received signature: {}, calculated signature: {}", xHubSignature[0], verificationResult.getCalculatedSignature());
        }else{
            logger.error("Signature doesn't match. Received signature: {}, calculated signature: {}", xHubSignature[0], verificationResult.getCalculatedSignature());
            return badRequest("Signature does not match!\n");
        }
    } catch (InstagramException e) {
        logger.error("Instagram exception.", e);
        return internalServerError("Internal server error. We will attend to this problem ASAP!");
    }

    logger.debug("Received xHubSignature: {}", xHubSignature[0]);
    logger.info("Sucessfully received json data: {}", json);

    return ok("OK!");
}

verifySubscriptionPostRequestSignature

中方法SubscriptionUtil的代码
public static VerificationResult verifySubscriptionPostRequestSignature(String clientSecret, byte[] rawJsonData, String xHubSignature) throws InstagramException{
    SecretKeySpec keySpec;
    keySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8), HMAC_SHA1);
    Mac mac;

    try {
        mac = Mac.getInstance(HMAC_SHA1);
        mac.init(keySpec);
        byte[] result = mac.doFinal(rawJsonData);
        String encodedResult = Hex.encodeHexString(result);

        return new VerificationResult(encodedResult.equals(xHubSignature), encodedResult);
    } catch (NoSuchAlgorithmException e) {
        throw new InstagramException("Invalid algorithm name!", e);
    } catch (InvalidKeyException e){
        throw new InstagramException("Invalid key: " + clientSecret, e);
    }
}

我在jInstagram中实施了此解决方案,以下是源代码的链接:SubscriptionUtil