【问题标题】:Created OAuth bearer token generated with jwt token is invalid for REST calls使用 jwt 令牌生成的创建的 OAuth 不记名令牌对 REST 调用无效
【发布时间】:2021-07-23 23:15:18
【问题描述】:

我是 DocuSign 和 REST-API 的新手。我创建了一个开发者帐户并为我的应用程序添加了一个新的“应用程序和密钥”条目(身份验证 = 隐式授予和 RSA 密钥对)。密钥存储在两个单独的文件中。

我使用以下 URL 激活了密钥:

http://account-d.docusign.com/oauth/auth?response_type=code&scope=signature%20impersonation&client_id=_the_integration_key&redirect_uri=http://localhost

我正在尝试编写一个没有 spring 安全框架(或任何其他框架)的 JAVA 应用程序。

为了读取我使用的关键文件并修改(一点点)DocuSign 示例中的函数。

private static RSAPublicKey readPublicKeyFromFile(String filepath, String algorithm) throws IOException {
    File pemFile = new File(filepath);
    if (!pemFile.isFile() || !pemFile.exists()) {
        throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath()));
    }
    PemReader reader = new PemReader(new FileReader(pemFile));
    try {
        PemObject pemObject = reader.readPemObject();
        byte[] bytes = pemObject.getContent();
        RSAPublicKey publicKey = null;
        try {
            KeyFactory kf = KeyFactory.getInstance(algorithm);
            EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
            publicKey = (RSAPublicKey) kf.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("Could not reconstruct the public key, the given algorithm could not be found.");
        } catch (InvalidKeySpecException e) {
            System.out.println("Could not reconstruct the public key");
        }

        return publicKey;
    } finally {
        reader.close();
    }
}

private static RSAPrivateKey readPrivateKeyFromFile(String filepath, String algorithm) throws IOException {
    File pemFile = new File(filepath);
    if (!pemFile.isFile() || !pemFile.exists()) {
        throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath()));
    }
    PemReader reader = new PemReader(new FileReader(pemFile));
    try {
        PemObject pemObject = reader.readPemObject();
        byte[] bytes = pemObject.getContent();
        RSAPrivateKey privateKey = null;
        try {
            Security.addProvider(new BouncyCastleProvider());
            KeyFactory kf = KeyFactory.getInstance(algorithm, "BC");
            EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
            privateKey = (RSAPrivateKey) kf.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("Could not reconstruct the private key, the given algorithm could not be found.");
        } catch (InvalidKeySpecException e) {
            System.out.println("Could not reconstruct the private key");
        } catch (NoSuchProviderException e) {
            System.out.println("Could not reconstruct the private key, invalid provider.");
        }

        return privateKey;
    } finally {
        reader.close();
    }
}

private static RSAPrivateKey readPrivateKeyFromByteArray(byte[] privateKeyBytes, String algorithm) throws IOException {
    PemReader reader = new PemReader(new StringReader(new String(privateKeyBytes)));
    try {
        PemObject pemObject = reader.readPemObject();
        byte[] bytes = pemObject.getContent();
        RSAPrivateKey privateKey = null;
        try {
            Security.addProvider(new BouncyCastleProvider());
            KeyFactory kf = KeyFactory.getInstance(algorithm, "BC");
            EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
            privateKey = (RSAPrivateKey) kf.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("Could not reconstruct the private key, the given algorithm could not be found.");
        } catch (InvalidKeySpecException e) {
            System.out.println("Could not reconstruct the private key");
        } catch (NoSuchProviderException e) {
            System.out.println("Could not reconstruct the private key, invalid provider.");
        }

        return privateKey;
    } finally {
        reader.close();
    }
}

为了获取 JWT 令牌,我使用了以下函数:

public static String generateJWTAssertion(String publicKeyFilename, String privateKeyFilename, String oAuthBasePath, String clientId, String userId, long expiresIn) throws JWTCreationException, IOException {
    String token = null;
    if (expiresIn <= 0L) {
        throw new IllegalArgumentException("expiresIn should be a non-negative value");
    }
    if (publicKeyFilename == null || "".equals(publicKeyFilename) || privateKeyFilename == null || "".equals(privateKeyFilename) || oAuthBasePath == null || "".equals(oAuthBasePath) || clientId == null || "".equals(clientId)) {
        throw new IllegalArgumentException("One of the arguments is null or empty");
    }

    try {
        RSAPublicKey publicKey = readPublicKeyFromFile(publicKeyFilename, "RSA");
        RSAPrivateKey privateKey = readPrivateKeyFromFile(privateKeyFilename, "RSA");
        Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
        long now = System.currentTimeMillis();
        token = JWT.create()
                .withIssuer(clientId)  // integration key
                .withSubject(userId)   // null
                .withAudience(oAuthBasePath) // account-d.docusign.com
                .withNotBefore(new Date(now))
                .withExpiresAt(new Date(now + expiresIn * 1000))
                .withClaim("scope", "signature impersonation")
                .sign(algorithm);
    } catch (JWTCreationException e){
        throw e;
    } catch (IOException e) {
        throw e;
    }

    return token;
}

我在https://jwt.io/ 上检查了生成的令牌,内容看起来不错。

要获取不记名令牌,我使用以下代码:

 public Boolean getBearer(long expiresIn) throws IOException {
    String jwtToken = JwtUtils.generateJWTAssertion(
            RESOURCES_DIR + "public.key",
            RESOURCES_DIR + "private.key",
            oAuthBasePath,
            integrationKey,
            null,
            expiresIn
    );

    OkHttpClient client = new OkHttpClient().newBuilder()
            .build();
    MediaType mediaType = MediaType.parse("text/plain");
    MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
            .addFormDataPart("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
            .addFormDataPart("assertion", jwtToken)
            .build();
    Request request = new Request.Builder()
            .url("https://" + oAuthBasePath + "/oauth/token") // https://account-d.docusign.com/oauth/token
            .method("POST", body)
            .build();
    Response response = client.newCall(request).execute();
    int responseCode = response.code();
    String responseText = response.body().string();
    Gson gson = new Gson();
    OAuthResponse oAuthResponse = gson.fromJson(responseText, OAuthResponse.class);

    if (responseCode >= 200 && responseCode <= 299) {
        bearerToken = oAuthResponse.getAccess_token();
        return true;
    }

    System.out.println("Errorcode: " + oAuthResponse.getError());
    System.out.println("Error: " + oAuthResponse.getError_description());
    return false;
}

我获得了不记名令牌并希望将其用于以下 REST 调用。

例如:

public void getUsers () throws IOException {
    OkHttpClient client = new OkHttpClient().newBuilder()
            .build();
    Request request = new Request.Builder()
            .url(getRestBaseUrl() +"/users") // https://demo.docusign.net/restapi/v2.1/accounts/_API_account_id/users
            .method("GET", null)
            .addHeader("Accept", "application/json")
            .addHeader("Authorization", "Bearer " + bearerToken)
            .build();
    Response response = client.newCall(request).execute();
    String responseText = response.body().string();
    System.out.println(responseText);
}

但我的开发者帐户的用户不是 JSON 结构,而是得到以下响应:

{"errorCode":"AUTHORIZATION_INVALID_TOKEN","message":"提供的访问令牌已过期、已撤销或格式错误。系统应用程序的身份验证失败。"}

当我使用 API 资源管理器和不记名令牌时,我可以将其用于身份验证(显示为有效),但“用户”的 REST 调用得到相同的错误响应。

所以我使用 API Explorer 进行登录,REST 调用正常工作。

我使用 API 资源管理器中的不记名令牌并将其(作为固定输入的字符串值)用作不记名令牌。并且 JAVA REST 调用有效。

所以,生成/请求JWT令牌或不记名令牌一定有错误。

知道有什么问题吗?

问候,

雷纳

【问题讨论】:

    标签: rest authentication oauth-2.0 jwt docusignapi


    【解决方案1】:

    我找到了原因。

    API 用户名丢失。

        String jwtToken = JwtUtils.generateJWTAssertion(
            RESOURCES_DIR + "public.key",
            RESOURCES_DIR + "private.key",
            oAuthBasePath,
            integrationKey,
            "_here_the_API_username",
            expiresIn
    );
    

    添加用户名后,我可以使用 API。

    【讨论】:

    • 我强烈建议您使用 JWT SDK 为您处理此问题。这将在稍后帮助您从 Java 进行 API 调用。如果你愿意接受这个解决方案——我可以给你代码。
    • 如果你有一些代码给我,那就太好了。
    • github.com/docusign/code-examples-java/blob/…获取token的代码是:oAuthToken = apiClient.requestJWTUserToken(resource.getClientId(), resource.getImpersonatedUserGuid(), scopes, privateKeyBytes, TOKEN_EXPIRATION_IN_SECONDS);
    • 既然你说你已经弄清楚了这个问题 - 我没有把这个作为答案发布。
    猜你喜欢
    • 2023-01-27
    • 1970-01-01
    • 2020-05-08
    • 2017-05-28
    • 1970-01-01
    • 2014-10-08
    • 2019-07-14
    • 2020-05-24
    • 2019-08-11
    相关资源
    最近更新 更多