使用Java解析Facebook的signed_request返回格式不正确的JSON

7

我正在尝试在Java Servlet的doPost中解析Facebook signed_request。我使用commons-codec-1.3的Base64解码了签名请求。

以下是我在servlet的doPost中使用的代码:

String signedRequest = (String) req.getParameter("signed_request");
String payload = signedRequest.split("[.]", 2)[1];
payload = payload.replace("-", "+").replace("_", "/").trim();
String jsonString = new String(Base64.decodeBase64(payload.getBytes()));

当我System.out打印jsonString时,它是格式错误的。有时会缺少JSON的结尾},有时候字符串末尾会缺少"}
如何从Facebook得到正确的JSON响应?
5个回答

7

Facebook正在使用Base64进行URL编码,您可能正在尝试使用标准的Base64算法解码文本。 除其他事项外,URL变体不需要使用“=”填充。

  1. 您可以在代码中添加所需的字符(填充等)
  2. 您可以使用commons-codec 1.5(new Base64(true)),其中他们添加了对此编码的支持。

在此处查看有关Apache Commons Codec的更多详细信息:http://commons.apache.org/proper/commons-codec/ - N.Martignole

4
Facebook正发送给您“未填充”的Base64值(即URL“标准”),这对于不希望它的Java解码器是有问题的。 当您要解码的Base64编码数据的长度不是4的倍数时,您可以知道您遇到了这个问题。
我使用了这个函数来修复这些值:
public static String padBase64(String b64) {
    String padding = "";
    // If you are a java developer, *this* is the critical bit.. FB expects
    // the base64 decode to do this padding for you (as the PHP one
    // apparently
    // does...
    switch (b64.length() % 4) {
    case 0:
        break;
    case 1:
        padding = "===";
        break;
    case 2:
        padding = "==";
        break;
    default:
        padding = "=";
    }
    return b64 + padding;

}

1

你好,这是2021年。

其他答案已经过时了,因为使用Java 8及更高版本,你可以使用新的Base64.getUrlDecoder()(而不是getDecoder)来解码base64url方案。

base64url方案是主要base64方案的URL和文件名安全方言,使用"-"代替"+","_"代替"/"(因为加号和斜杠在URL中有特殊含义)。此外,它不使用"="字符作为字符串末尾的填充(0到4个字符)。

以下是将Facebook signed_request参数解析为Java Map对象的方法:

public static Map<String, String> parseSignedRequest(HttpServletRequest httpReq, String facebookSecret) throws ServletException {
    String signedRequest = httpReq.getParameter("signed_request");
    String splitArray[]  = signedRequest.split("\\.", 2);
    String sigBase64     = splitArray[0];
    String payloadBase64 = splitArray[1];
    String payload       = new String(Base64.getUrlDecoder().decode(payloadBase64));

    try {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(facebookSecret.getBytes(), "HmacSHA256");
        sha256_HMAC.init(secretKey);

        String sigExpected = Base64.getUrlEncoder().withoutPadding().encodeToString(sha256_HMAC.doFinal(payloadBase64.getBytes()));
        if (!sigBase64.equals(sigExpected)) {
            LOG.warn("sigBase64 = {}", sigBase64);
            LOG.warn("sigExpected = {}", sigExpected);
            throw new ServletException("Invalid sig = " + sigBase64);
        }
    } catch (IllegalStateException | InvalidKeyException | NoSuchAlgorithmException ex) {
        throw new ServletException("parseSignedRequest", ex);
    }

    // use Jetty JSON parsing or some other library
    return (Map<String, String>) JSON.parse(payload);
}

我使用了Jetty的JSON解析器:
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-util</artifactId>
    <version>9.4.43.v20210629</version>
</dependency>

但是在Java中有更多可用于解析JSON的库。


1

我在Java中从未做过这件事,因此我没有完整的答案,但是您有时会从字符串末尾丢失一个或两个字符,这表明可能存在Base64填充问题。您可以输出payload的值,并查看当它以'='结尾时,jsonString是否缺少'}',并且当payload以'=='结尾时,jsonString是否缺少'"}'。如果情况似乎是这样的,那么在解释表示空位的等号符时,payload末尾出现了问题。

编辑:经过进一步思考,我认为这是因为Facebook使用Base64 URL编码(不添加=作为填充字符),而您的解码函数期望使用带有尾随=字符的常规Base64。


当我打印signed_request时,payload末尾没有显示任何=或==。这是否意味着我得到了错误的signed request?因为我在Facebook应用程序设置期间提供的URL有可能出错吗? - amadamala
我认为你的Java函数期望输入使用=字符进行填充,但Facebook没有使用它们。尝试直接传递有效载荷而不是payload.getBytes(),以防decodeBase64在字符串中表现不同。然后尝试跳过replace调用,以防decodeBase64根据- _或+/字符的存在自动检测编码。如果所有这些都失败了,您将不得不找到另一种理解Base64 URL变体的方法,或者通过欺骗输入或输出来解决不匹配问题(这应该是可行的,因为它只会影响最后1-2个字符)。 - Floyd Wilburn
请查看以下网址:http://qugstart.com/blog/ruby-and-rails/facebook-base64-url-decode-for-signed_request/ - maximbr

1

我使用非常类似于这个的代码升级到了common-codec-1.5,并没有遇到这个问题。您是否通过在线解码器确认了有效载荷确实存在格式错误?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接