如何使用 Java 中的 API KEY 身份验证正确签署对 Coinbase V2 API 的请求

如何解决如何使用 Java 中的 API KEY 身份验证正确签署对 Coinbase V2 API 的请求

我正在为 coinbase v2 API 开发一个 java 包装器,因为我找不到任何按我想要的方式工作的库:以“流畅”的方式,使用 vavr 数据类型和 apiKey 身份验证。

首先,我开发了 coinbase API 的整个公共数据包装器,它运行良好。它允许我调用 Coinbase 货币、汇率、价格...

现在我想调用https://api.coinbase.com/v2/user 这样的安全端点。 Si 我遵循了 https://developers.coinbase.com/docs/wallet/api-key-authentication

中定义的规范

到目前为止没有什么疯狂的,我只是遵循指导方针,即使不是基于java而是基于python,ruby和nodejs,它看起来并不难......但是当我执行对端点的调用时,例如:https://api.coinbase.com/v2/user 我收到这个回复

"errors":[
  {
    "id": "authentication_error","message": "invalid signature"
  }
]

所以我决定进行一些研究,因为我认为我的签名过程有误。所以我找到了很多非官方的 java & C# 包装器并观察了它们都是如何做的,它看起来很像我在我的代码中所做的......但没关系,我采用了我找到的不同代码并对其进行了改编到我的情况,以便它们适合我的代码。然后,在实现了 6 种不同的方式来签署我的请求后,它仍然不起作用。

我有六种不同的方法来实现 coinbase API 的 api 密钥身份验证:

    public String[] getAuthenticationHeaders(
      final String apiKey,final String secret,final long timestamp,final String httpMethod,final String httpPath,final String httpBody) {

    if (StringUtils.isBlank(apiKey) || StringUtils.isBlank(secret)) {
      manageNotAllowed(
          new JCoinbaseException(
              "You must specify an Api key and a secret to access this resource."));
    }

    //SIGNATURE VERSION 1
    var message = timestamp + httpMethod + httpPath + ((httpBody == null) ? "" : httpBody);
    var signature1 = new HmacUtils(HMAC_SHA_256,secret.getBytes()).hmacHex(message);

    //SIGNATURE VERSION 2
    var signature2 = HmacUtils.hmacSha256Hex(secret,message);

    //SIGNATURE VERSION 3
    var mac =
        HmacUtils.getinitializedMac("HmacSHA256",secret.getBytes(StandardCharsets.UTF_8));
    mac.update(message.getBytes(StandardCharsets.UTF_8));
    var signature3 = String.format("%064x",new BigInteger(1,mac.doFinal()));

    //SIGNATURE VERSION 4
    String signature4 = "";
    try {
      String prehash = timestamp + httpMethod.toupperCase() + httpPath + httpBody;
      byte[] secretDecoded = Base64.getDecoder().decode(secret);
      SecretKeySpec keyspec = new SecretKeySpec(secretDecoded,Mac.getInstance("HmacSHA256").getAlgorithm());
      Mac sha256 = (Mac) Mac.getInstance("HmacSHA256");
      sha256.init(keyspec);
      signature4 = Base64.getEncoder().encodetoString(sha256.doFinal(prehash.getBytes()));
    } catch (InvalidKeyException | NoSuchAlgorithmException e) {
      e.printstacktrace();
      throw new RuntimeException(new Error("Cannot set up authentication headers."));
    }

    //SIGNATURE VERSION 5
    var hmacKey = secret.getBytes(StandardCharsets.UTF_8);
    var messageBytes = message.getBytes(StandardCharsets.UTF_8);
    var hmac = HmacUtils.getinitializedMac("HmacSHA256",hmacKey);
    var sig = hmac.doFinal(messageBytes);
    char[] c = new char[sig.length * 2];
    int b;
    for(int i = 0; i < sig.length; i++){
      b = sig[i] >> 4;
      c[i * 2] = (char)(87 + b + (((b - 10) >> 31) & -39));
      b = sig[i] & 0xF;
      c[i * 2 + 1] = (char)(87 + b + (((b - 10) >> 31) & -39));
    }
    var signature5 = String.valueOf(c);

    //SIGNATURE VERSION 6
    var preHash = timestamp + httpMethod.toupperCase() + httpPath + httpBody;
    byte[] secretDecoded = Base64.getDecoder().decode(secret);
    SecretKeySpec keySpec;
    Mac sha256 = null;
    try {
      keySpec = new SecretKeySpec(secretDecoded,Mac.getInstance("HmacSHA256").getAlgorithm());
      sha256 = Mac.getInstance("HmacSHA256");
      sha256.init(keySpec);
    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
      e.printstacktrace();
    }
    var signature6 = Base64.getEncoder().encodetoString(sha256.doFinal(preHash.getBytes()));


    return new String[] {
      "CB-ACCESS-SIGN",signature6,"CB-ACCESS-TIMESTAMP",String.valueOf(timestamp),"CB-ACCESS-KEY",apiKey,"Accept","application/json"
    };
  }

如你所见,我真的试过了,但还是不行,所以我请求你帮助我,拜托:)

为了更好地理解这里是使用此签名过程的我的包装器的不同简化代码片段:

//A RANDOM TESTING CLASS FOR DEVELOPMENT PURPOSE ONLY :)
@Test
void main() {
  JCoinbaseClient client = JCoinbaseClientFactory.build("myApiKey","myApiSecret");
  var currentUser = client.user().fetchCurrentUser();
}

//JCoinbaseClient class
public UserService user() {
  var allowed = authService.allow(this);

  if (allowed.isLeft()) {
    manageNotAllowed(allowed.getLeft());
  }
   return userService;
}

//UserService class
public User fetchCurrentUser() {
  return service
      .fetchCurrentUser(client,authentication)
      .onSuccess(user -> log.info("Successfully fetch current user."))
      .onFailure(
          throwable ->
              manageOnFailure(
                  new JCoinbaseException(throwable),"An error occurred while fetching current user",throwable))
      .get();
}

//CoinbaseUserService class
public Try<User> fetchCurrentUser(final JCoinbaseClient client,final AuthenticationService authentication) {

  var requestHeaders =
      authentication.getAuthenticationHeaders(
          client.getProperties(),"GET",client.getProperties().getUserPath(),"");

  var request =
      HttpRequest.newBuilder()
          .GET()
          .uri(
              URI.create(
                  client.getProperties().getApiUrl() + client.getProperties().getUserPath()))
          .headers(requestHeaders)
          .build();

  return Try.of(() -> client.getClient().send(request,BodyHandlers.ofString()))
      .mapTry(
          stringHttpResponse ->
              client
                  .getJsonSerDes()
                  .readValue(stringHttpResponse.body(),UserDto.class)
                  .toUser());
}

//AuthenticationService class
public String[] getAuthenticationHeaders(
    final JCoinbaseProperties properties,final String httpBody) {
  return getAuthenticationHeaders(
      properties.getApiKey().getorNull(),properties.getSecret().getorNull(),getCurrentTime(),httpMethod,httpPath,httpBody);
}

public String[] getAuthenticationHeaders(
      final String apiKey,final String httpBody) {
   // YOU CAN FIND THIS CODE IN THE FirsT CODE SNIPPET
}

最后,我的控制台中的结果(尚未开发 http 错误处理。这是假设的)

com.github.badpop.jcoinbase.exception.JCoinbaseException: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "errors" (class com.github.badpop.jcoinbase.client.service.user.dto.UserDto),not marked as ignorable (15 kNown properties: "bitcoin_unit","avatar_url","created_at","name","resource","profile_bio","username","profile_url","id","email","time_zone","profile_location","resource_path","native_currency","country"])
 at [Source: (String)"{"errors":[{"id":"authentication_error","message":"invalid signature"}]}"; line: 1,column: 73] (through reference chain: com.github.badpop.jcoinbase.client.service.user.dto.UserDto["errors"])

    at com.github.badpop.jcoinbase.client.service.user.UserService.lambda$fetchCurrentUser$1(UserService.java:26)
    at io.vavr.control.Try.onFailure(Try.java:659)
    at com.github.badpop.jcoinbase.client.service.user.UserService.fetchCurrentUser(UserService.java:24)
    at com.github.badpop.jcoinbase.client.service.user.UserServiceTest.main(UserServiceTest.java:59)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.EngineExecutionorchestrator.execute(EngineExecutionorchestrator.java:108)
    at org.junit.platform.launcher.core.EngineExecutionorchestrator.execute(EngineExecutionorchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionorchestrator.lambda$execute$0(EngineExecutionorchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionorchestrator.withInterceptedStreams(EngineExecutionorchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionorchestrator.execute(EngineExecutionorchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
    at com.intellij.junit5.junit5IdeaTestRunner.startRunnerWithArgs(junit5IdeaTestRunner.java:71)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "errors" (class com.github.badpop.jcoinbase.client.service.user.dto.UserDto),column: 73] (through reference chain: com.github.badpop.jcoinbase.client.service.user.dto.UserDto["errors"])
    at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
    at com.fasterxml.jackson.databind.DeserializationContext.handleUnkNownProperty(DeserializationContext.java:987)
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnkNownProperty(StdDeserializer.java:1974)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnkNownProperty(BeanDeserializerBase.java:1686)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnkNownProperties(BeanDeserializerBase.java:1635)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:541)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializefromObjectUsingNonDefault(BeanDeserializerBase.java:1390)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializefromObject(BeanDeserializer.java:362)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:195)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3548)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3516)
    at com.github.badpop.jcoinbase.client.service.user.CoinbaseUserService.lambda$fetchCurrentUser$faa3259$1(CoinbaseUserService.java:37)
    at io.vavr.control.Try.mapTry(Try.java:634)
    at com.github.badpop.jcoinbase.client.service.user.CoinbaseUserService.fetchCurrentUser(CoinbaseUserService.java:33)
    at com.github.badpop.jcoinbase.client.service.user.UserService.fetchCurrentUser(UserService.java:22)
    ... 66 more

如果你有什么想法,我会采纳的!

注意:如果您愿意,可以在 github 上访问包装器代码feat/fetch-user-resources git 分支上的 https://github.com/Bad-Pop/JCoinbase

解决方法

您编码的 requestPath 只是 /user 而不是 /v2/user

根据链接的规范:

requestPath 是 URL 的完整路径和查询参数,例如:/v2/exchange-rates?currency=USD.

根据你的来源

//CoinbaseUserService.java:
public Try<User> fetchCurrentUser(final JCoinbaseClient client,final AuthenticationService authentication) {
    var requestHeaders = authentication.getAuthenticationHeaders(
        client.getProperties(),"GET",client.getProperties().getUserPath(),"");
    // ...

//JCoinbaseProperties.java:
private void extractProperties(final String apiKey,final String secret) { /*...*/
    this.userPath = properties.getProperty(    "coinbase.api.path.resource.user"    ); // ...

//jcoinbase.properties:
    coinbase.api.path.resource.user=/user

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?