AWS Cognito 使用适用于桌面应用程序的 Java 开发工具包登录

我搜索了很多,但似乎不可能从头到尾找到这个主题的解决方案。作为前提,我已经在iOS和Android的两个本机应用程序中实现了Cognito注册,登录和凭据刷新,因此我已经对身份验证流程有了(至少)基本的了解。

这些移动应用程序使用最简单的认知设置:用户池、具有 IAM 角色的身份池,用于经过身份验证的用户,并且不可能进行未经身份验证的使用。我没有使用(至少目前)Facebook,Google或Amazon登录,也没有使用其他身份验证方法。

现在我需要用Java制作这些应用程序的桌面版本,在我看来,这是一个完全不同的野兽。我想做的是:

  1. 在我的Java桌面应用程序中打开登录窗口;
  2. 在其字段中插入用户名和密码,然后按登录按钮;
  3. 获取一些凭证并开始使用连接到其他 AWS 服务的应用程序,特别是我需要使用 S3、Lambda 和 DynamoDB。

实现这一目标的方法在纸面上相当简单:

  1. 从 Cognito 用户池获取令牌;
  2. 将此令牌提供给Cognito身份池以换取一些凭据;
  3. 使用此凭证访问其他 AWS 服务。

在阅读了大量文档,下载了许多不同的项目示例和许多绝望之后,我最终找到了在移动应用程序中实现这一点的方法。例如,在 Android 中,身份验证流程的工作方式如下:

  1. 实例化 CognitoUserPool,使用 UserPoolID、AppClientID、PoolRegion 和(可选)ClientSecret;
  2. 使用 IdentityPoolID 和 PoolRegion 实例化凭证提供程序;
  3. 在应用程序UI中,插入用户名和密码,然后按“登录”按钮;
  4. 使用先前实例化的用户池中的用户名检索 CognitoUser;
  5. 获取该 CognitoUser 的 CognitoUserSession,使用具有各种回调的身份验证处理程序在需要时传递密码;
  6. 将 CognitoUserSession 添加到之前实例化的凭据提供程序,其形式为 TokenKey + 从会话中提取的 JWT 令牌。
  7. 此时,每当我需要访问 S3、Lambda 或 DynamoDB 时,我只需将此凭证提供程序作为其客户端构造函数的参数传递即可。

在我看来,使用Java SDK实现相同的功能似乎要困难得多。

我设法相当容易地实现用户注册。但是,对于用户登录,我根本不知道从哪里开始。

每个示例都以不同的方式执行此操作。最重要的是,每个示例都使用特定的用例,例如开发人员身份验证的登录或自定义 URL 来连接到某些拥有的后端。为什么很难找到像我这样的基本用例的示例?我开始认为我的基本用例根本不是基本的,而是非典型的。为什么使用针对AWS的默认用户/凭证服务的用户名和密码登录是非典型的,但是,我真的不知道。

到目前为止,我所做的最好的事情是从这个示例项目中复制相关类(我也从中获得了注册部分,效果很好),并在控制台中打印IdToken,AccessToken和RefreshToken。它们打印正确且不为空。我无法真正理解的是如何获取凭证并将其添加到凭证提供程序,以便实例化客户端以访问其他 AWS 服务。我在项目中看到的唯一方法是调用方法

Credentials getCredentials(String accessCode)

我想它应该接受使用InitAuth方法检索的访问代码(这将启动OAuth2.0身份验证流程,如果我错了,请纠正我)。问题是我找不到检索该代码的方法。我找不到访问代码的在线示例来查看其外观。我试图放置其中一个令牌,Web请求响应

{"error":"invalid_grant"}

这表明它不是一个有效的代码,但至少Web请求是有效的。

为了更清楚,我能做的是:

String username; //retrieved from UI
String password; //retrieved from UI

//I copied AuthenticationHelper as is from the project
AuthenticationHelper helper = new AuthenticationHelper(POOL_ID, CLIENT_APP_ID, CLIENT_SECRET);

//I then retrieve the tokens with SRP authentication
AuthenticationResultType result = helper.performSRPAuthentication(username, password);

//Now I can successfully print the tokens, for example:
System.out.println(result.getAccessToken());

如何从此处检索凭据?我应该将身份池 ID 放在哪里?在Android中,我只需将JWT令牌添加到中,然后像这样使用它HashMap

credentialsProvider.setLogins(loginsMap).

此外,该项目包含具有数百行代码的类,BigInteger变量,许多行随机字符的硬编码字符串(我想是某种键或令牌)以及其他类似的黑魔法(特别是在SauthorationHelper类中)。我不喜欢这个解决方案的另一件事是,它通过手动编写的Web请求检索凭据(临时创建另一个单独的类来发出请求)。在Java SDK中真的没有一些方便的方法可以将所有这些东西包装在一堆优雅的代码行中吗?为什么称它为SDK比?iOS和Android SDK以更简单的方式自行处理所有这些。这是因为他们希望桌面应用程序的开发人员更加有能力/更专业,而不是普通人有一天从床上起床,决定制作一个iOS / Android应用程序[暗示他自己]?这可以解释他们努力使移动SDK相比之下对开发人员如此友好。

总的来说,我发现很难相信我必须这样做,阅读谁知道在文档页面上知道什么谁知道在哪里,登录用户,这让我觉得我真的错过了一些东西。我从字面上阅读了我能够找到的每个堆栈交换问题和文档。事实是,对于我需要的东西,几乎总是有一个AWS文档页面,但是实际上找到它有时并不那么简单,至少对于Cognito文档而言。

我读到我可以在PC文件系统中放置一个具有所需凭据的文件,Java SDK将使用这些凭据来访问所有资源,但是根据我的理解,此方法保留给在服务器上作为后端(servlet)运行的Java应用程序,最终用户无法通过浏览器访问它们。我的应用程序是面向最终用户的桌面应用程序,因此我甚至不能考虑在用户PC上保留AWS凭证(如果我错了,请纠正我,我真的很想做一些简单的事情)。


真正让我害怕的是,使用用户池和身份池登录可能根本无法实现。我知道Cognito相关的东西被添加到Java SDK中,很久以后它就可以用于iOS,Android和JavaScript。但是,如果他们添加了它,我想它应该支持至少与移动对应物相似的身份验证流。

使问题更加恶化的是,我最初使应用程序的所有功能脱机工作。我以为我最终会在应用程序中集成 AWS。通过这种方式,应用程序更加模块化,并且与AWS相关的东西集中在一个包中,与应用程序逻辑和UI的其余部分分离。在我无知的海洋中,这对我来说似乎是一个很好的做法,但是现在,如果我无法设法解决这个问题,我已经把几个月的工作扔进了垃圾桶,只是意识到我必须构建一个Web应用程序,因为JavaScript得到了更多的支持。

即使在MobileHub上,在Cognito上创建ClientApp的唯一选择也是iOS,Android,JavaScript和React-something。当为其他语言/SDK提供文档或示例时,Java经常被省略,我在选项中看到的最常见的是.NET。为了让我的挫败感更大,每次我在搜索引擎上搜索某些内容时,“Java”一词包含在“JavaScript”一词中的事实混淆了可能有用的少数结果,因为所有与Java相关的内容在搜索引擎中的排名通常都高于Java(这可以部分解释为什么与.NET相关的东西似乎更容易找到, 至少在StackOverflow或其他问答网站上)。


总而言之,所有这些都在我脑海中产生了一些问题:

  1. 为什么似乎很少有人需要这种身份验证方法(带有用户名和密码)?在我看来,这是桌面应用程序的一个非常常见和合理的用例。我知道Web应用程序的增长是彻头彻尾的,但是考虑到Java是当今最常用的语言之一,怎么可能没有人需要从桌面应用程序进行简单的登录?这就引出了下一个问题:

  2. 在桌面应用程序中使用 Java SDK 时,是否存在固有的坏/错误/风险/愚蠢之处?它是仅用于作为后端的服务器还是用于 Web 应用?创建连接到 AWS 服务的桌面应用程序的解决方案应该是什么?执行 AWS 连接的桌面应用程序是错误的吗?Web 应用是否应该是唯一需要考虑的选项?为什么?我选择Java来实现一个可以在Widows,macOS和Linux上运行的应用程序。我还选择了Java,因为我认为它在用法上与Android SDK非常相似,因为它的代码应该独立于平台UI,这使得重用代码变得简单。我错了。

  3. 如果像这样使用 Java 开发工具包没有错,那么一些好人能否帮我找到一个示例,从在两个字段中放置用户名和密码,并实例化客户端以访问 Java 桌面应用程序中的其他 AWS 服务(例如 S3 客户端)?

告诉我你需要知道的一切,我会编辑这个问题。

请有人帮帮我,我正在失去理智。


答案 1

对于OP来说可能为时已晚,但这是我在获取JWT身份令牌后用于从Cognito获取凭据的过程。通过 SRP 身份验证获得 JWT 后,我使用联合池 ID 获取身份 ID,并传递 Cognito Idp Url 和 JWT 的登录映射。该 URL 与您的 aws 区域和 Cognito 用户池 ID 一起完成。

    //create a Cognito provider with anonymous creds
    AnonymousAWSCredentials awsCreds = new AnonymousAWSCredentials();
    AmazonCognitoIdentity provider = AmazonCognitoIdentityClientBuilder
            .standard()
            .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
            .withRegion(REGION)
            .build();

    //get the identity id using the login map
    String idpUrl = String.format("cognito-idp.%s.amazonaws.com/%s", REGION, cognitoUserPoolId);
    GetIdRequest idrequest = new GetIdRequest();
    idrequest.setIdentityPoolId(FED_POOL_ID);
    idrequest.addLoginsEntry(idpUrl, jwt);

    //use the provider to make the id request
    GetIdResult idResult = provider.getId(idrequest);
    return idResult.getIdentityId();

如果您使用的是其他登录提供程序,则需要更改该 URL,但这应获取标识 ID。接下来是一个类似的请求,通过传递身份 ID 和相同的登录映射来获取 IAM 凭证。

    //create a Cognito provider with anonymous creds
    AnonymousAWSCredentials awsCreds = new AnonymousAWSCredentials();
    AmazonCognitoIdentity provider = AmazonCognitoIdentityClientBuilder
            .standard()
            .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
            .withRegion(REGION)
            .build();

    //request authenticated credentials using the identity id and login map for authentication
    String idpUrl = String.format("cognito-idp.%s.amazonaws.com/%s", REGION, cognitoUserPoolId);
    GetCredentialsForIdentityRequest request = new GetCredentialsForIdentityRequest();
    request.setIdentityId(identityId);
    request.addLoginsEntry(idpUrl, jwt);

    //use Cognito provider to perform credentials request
    GetCredentialsForIdentityResult result = provider.getCredentialsForIdentity(request);
    return result.getCredentials();

我花了整整一个星期才弄清楚。在我看来,AWS Java文档非常糟糕。希望这能帮助某人。


答案 2

推荐