java – Slack请求验证:无法使用signed secret计算匹配请求摘要

我正在Slack上实现交互式消息,它包含一些动作按钮.使用Slack App我能够处理Slack用户点击我的Java Springboot API上的按钮.

到目前为止,一切都很好.但是,我很难计算匹配请求签名(摘要)以验证它实际来自Slack.我在Slack verification documentation page阅读了所有相关文档.

页面描述了签名必须计算为HMAC SHA256哈希,使用签名密钥作为密钥,内容作为松弛版本,时间戳和请求正文的串联,例如:

v0:123456789:command=/weather&text=94070

页面上说明:

…Evaluate only the raw HTTP request body when computing signatures.

…所以我没有在哈希计算之前编码/反序列化请求(我已经从Slack下面收到了我收到的请求)

要计算哈希,我使用StackOverflow上的代码

private String computeMessageDigest(String content) {
    final String ALGORITHM = "HmacSHA256";
    final String UTF_8 = "UTF-8";

    try {
        Key signingKey = new SecretKeySpec(signingSecret.getBytes(UTF_8),ALGORITHM);
        Mac mac = Mac.getInstance(ALGORITHM);
        mac.init(signingKey);

        return Hex.encodeHexString(mac.doFinal(content.getBytes(UTF_8)));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

我也试过这个online hash generator来比较结果,它们是一样的.

从Slack收到的请求如下:

{
    "headers": {
        "x-forwarded-for": ["::ffff:52.72.111.29"],"x-forwarded-proto": ["https"],"x-pagekite-port": ["443"],"host": ["inqool.pagekite.me"],"user-agent": ["Slackbot 1.0 (+https://api.slack.com/robots)"],"accept-encoding": ["gzip,deflate"],"accept": ["application/json,*/*"],"x-slack-signature": ["v0=87fbffb089501ba823991cc20058df525767a8a2287b3809f9afff3e3b600dd8"],"x-slack-request-timestamp": ["1531221943"],"content-length": ["2731"],"Content-Type": ["application/x-www-form-urlencoded;charset=UTF-8"]
    },"body": "payload=%7B%22type%22%3A%22interactive_message%22%2C%22actions%22%3A%5B%7B%22name%22%3A%22reject_btn%22%2C%22type%22%3A%22button%22%2C%22value%22%3A%22false%22%7D%5D%2C%22callback_id%22%3A%22artwork%3D40d7a87f-466c-4fc9-b454-09ce020d4465%22%2C%22team%22%3A%7B%22id%22%3A%22T03NP6SA7%22%2C%22domain%22%3A%22artstaq%22%7D%2C%22channel%22%3A%7B%22id%22%3A%22G8F2WR4FJ%22%2C%22name%22%3A%22privategroup%22%7D%2C%22user%22%3A%7B%22id%22%3A%22U66T9QX60%22%2C%22name%22%3A%22majo%22%7D%2C%22action_ts%22%3A%221531221943.512498%22%2C%22message_ts%22%3A%221531221198.000225%22%2C%22attachment_id%22%3A%221%22%2C%22token%22%3A%22ZABrZDXgJCOOLNau5mXnfNQR%22%2C%22is_app_unfurl%22%3Afalse%2C%22original_message%22%3A%7B%22text%22%3A%22User+just+put+item+on+*EXCHANGE*.%22%2C%22bot_id%22%3A%22BBM1W4QEL%22%2C%22attachments%22%3A%5B%7B%22author_name%22%3A%22Slack+Test%3B+slack%40test.com%22%2C%22callback_id%22%3A%22artwork%3D40d7a87f-466c-4fc9-b454-09ce020d4465%22%2C%22fallback%22%3A%22Slack+Test%3B+%3Cmailto%3Aslack%40test.com%7cslack%40test.com%3E+just+put+item+Panenka+%5C%2F+Doll+by+artist+Jaroslav+Vale%5Cu010dka+into+ON+REQUEST+mode%22%2C%22text%22%3A%22%3Chttp%3A%5C%2F%5C%2Flocalhost%3A8080%5C%2Fartist%5C%2F609cd328-d533-4ab0-b982-ec2f104476f2%7CJaroslav+Vale%5Cu010dka%3E%22%2C%22title%22%3A%22Panenka+%5C%2F+Doll%22%2C%22footer%22%3A%22ARTSTAQ+Slack+Reporter%22%2C%22id%22%3A1%2C%22title_link%22%3A%22http%3A%5C%2F%5C%2Flocalhost%3A8080%5C%2Fartwork%5C%2F40d7a87f-466c-4fc9-b454-09ce020d4465%22%2C%22color%22%3A%22f0d0ad%22%2C%22fields%22%3A%5B%7B%22title%22%3A%22Trading+type%22%2C%22value%22%3A%22ON+REQUEST%22%2C%22short%22%3Atrue%7D%5D%2C%22actions%22%3A%5B%7B%22id%22%3A%221%22%2C%22name%22%3A%22approve_btn%22%2C%22text%22%3A%22APPROVE%22%2C%22type%22%3A%22button%22%2C%22value%22%3A%22true%22%2C%22style%22%3A%22primary%22%2C%22confirm%22%3A%7B%22text%22%3A%22Do+you+really+want+to+approve+this+artwork%3F%22%2C%22title%22%3A%22Approve+artwork%22%2C%22ok_text%22%3A%22Yes%22%2C%22dismiss_text%22%3A%22Cancel%22%7D%7D%2C%7B%22id%22%3A%222%22%2C%22name%22%3A%22reject_btn%22%2C%22text%22%3A%22REJECT%22%2C%22type%22%3A%22button%22%2C%22value%22%3A%22false%22%2C%22style%22%3A%22danger%22%2C%22confirm%22%3A%7B%22text%22%3A%22Do+you+really+want+to+reject+this+artwork%3F%22%2C%22title%22%3A%22Reject+artwork%22%2C%22ok_text%22%3A%22Yes%22%2C%22dismiss_text%22%3A%22Cancel%22%7D%7D%5D%7D%5D%2C%22type%22%3A%22message%22%2C%22subtype%22%3A%22bot_message%22%2C%22ts%22%3A%221531221198.000225%22%7D%2C%22response_url%22%3A%22https%3A%5C%2F%5C%2Fhooks.slack.com%5C%2Factions%5C%2FT03NP6SA7%5C%2F395760858899%5C%2FGlP9jsNQak7FqEciEHhscx4L%22%2C%22trigger_id%22%3A%22395632563524.3771230347.851ab60578de033398338a9faeb41a15%22%7D"
}

当我计算HMAC SHA256哈希时,我得到561034bb6860c07a6b4eaf245b6da3ea869c7806c7f7be20b1a830b6d25c54c8,但我应该得到87fbffb089501ba823991cc20058df525767a8a2287b3809f9afff3e3b600dd8,如请求标头中所示.

我还尝试从URL解码的主体计算哈希值,但仍然无法获得匹配的签名.

难道我做错了什么?谢谢你的答案/提示.

编辑:这是我的REST控制器和请求验证器的完整源代码

package com.artstaq.resource;

import com.artstaq.integration.slack.slackrequestVerifier;
import org.springframework.http.httpentity;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.inject.Inject;

@RestController
@RequestMapping("/content_admin")
public class ContentAdminResource {

    private slackrequestVerifier slackVerifier;


    @RequestMapping(value = "/slack/artwork/resolve",method = RequestMethod.POST,consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public void resolve(httpentityslackrequest(request);
    }


    @Inject
    public void setSlackVerifier(slackrequestVerifier slackVerifier) {
        this.slackVerifier = slackVerifier;
    }
}
package com.artstaq.integration.slack;

import com.artstaq.exception.SignatureVerificationException;
import com.artstaq.exception.TimestampTooOldException;
import org.apache.commons.codec.binary.Hex;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.httpentity;
import org.springframework.stereotype.Component;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.time.Instant;
import java.time.temporal.ChronoUnit;

/**
 * Class providing request verification received from Slack
 */
@Component
public class slackrequestVerifier {

    @Value("${integration.slack.version:v0}")
    private String version;

    @Value("${integration.slack.signingSecret}")
    private String signingSecret;

    /**
     * Verifies the integrity of received Slack request.
     */
    public void verifyslackrequest(httpentityirst(SlackHeaders.TIMESTAMP);
        Instant timeInstant = Instant.ofEpochSecond(Long.valueOf(timestamp));
        if (timeInstant.plus(5,ChronoUnit.MINUTES).compareto(Instant.Now()) < 0) {
            throw new TimestampTooOldException(timeInstant);
        }

        String expectedDigest = request.getHeaders().getFirst(SlackHeaders.SIGNATURE);

        String basestring = String.join(":",version,timestamp,request.getBody());
        String computedDigest = version + "=" + computeMessageDigest(basestring);

        if (!computedDigest.equals(expectedDigest)) {
            throw new SignatureVerificationException(expectedDigest,computedDigest);
        }
    }

    /**
     * Compute HMAC SHA256 digest for given content using defined slack signing secret
     */
    private String computeMessageDigest(String content) {
        final String ALGORITHM = "HmacSHA256";
        final String UTF_8 = "UTF-8";

        try {
            Key signingKey = new SecretKeySpec(signingSecret.getBytes(UTF_8),ALGORITHM);
            Mac mac = Mac.getInstance(ALGORITHM);
            mac.init(signingKey);

            return Hex.encodeHexString(mac.doFinal(content.getBytes(UTF_8)));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    private static class SlackHeaders {
        private static final String TIMESTAMP = "X-Slack-Request-Timestamp";
        private static final String SIGNATURE = "X-Slack-Signature";
    }
}
最佳答案
以下为我们工作:

public enum SigningVerification {
    VERIFIED,DENIED
}

public SigningVerification verify(ImmutableSigningSecretRequest request) {
    String basestring = String.join(":","v0",request.timestamp(),request.body());
    SecretKeySpec secret_key = new SecretKeySpec(signingSecret.getBytes(),"HmacSHA256");
    Mac sha256_HMAC = Try.of(() -> Mac.getInstance("HmacSHA256")).getorElseThrow((supplierRuntimeException) RuntimeException::new);
    Try.run(() -> sha256_HMAC.init(secret_key));
    String hash = "v0=" + Hex.encodeHexString(sha256_HMAC.doFinal(basestring.getBytes()));
    return hash.equals(request.verificationSignature()) ? VERIFIED : DENIED;
}

控制器:

@PostMapping("/command")
public RichMessage postCommand(@RequestHeader(value = "X-Slack-Request-Timestamp") String timestamp,@RequestHeader(value = "X-Slack-Signature") String signature,@RequestParam(value = "text",required = false) String message,@RequestBody String body) {
    SigningSecretVerification.SigningVerification verification = verifier.verify(ImmutableSigningSecretRequest
            .builder()
            .timestamp(timestamp)
            .verificationSignature(signature)
            .body(body)
            .build()
    );
    return new RichMessage(message);

}

我们基本上只是遵循Slack doc中的步骤,它工作正常.

相关文章

最近看了一下学习资料,感觉进制转换其实还是挺有意思的,尤...
/*HashSet 基本操作 * --set:元素是无序的,存入和取出顺序不...
/*list 基本操作 * * List a=new List(); * 增 * a.add(inde...
/* * 内部类 * */ 1 class OutClass{ 2 //定义外部类的成员变...
集合的操作Iterator、Collection、Set和HashSet关系Iterator...
接口中常量的修饰关键字:public,static,final(常量)函数...