实现远程 oauth 服务器并与客户端交换令牌而不在服务器上保存令牌

implement remote oauth server and exchange token with client without save token on server

我正在开发一个小型 Java 应用程序来访问我的 yt 直播流的聊天。除了 twitch,yt 没有 irc 服务器来访问聊天所以我不得不使用 yt API。基本上我打算只自己用,但我可能会提供给一些朋友,甚至可能会public。

我已经成功获得了 API 的访问权限,但前提是我的系统上存储了客户端密码和令牌。当我想打开它时 public 我必须设置一个身份验证服务器,然后将令牌传输到客户端。主要问题是实际创建令牌所需的方法 com.google.api.client.auth.oauth2.AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String)。这会将令牌存储在代码运行的任何地方 - 因此在服务器上的计划环境中,而不是在客户端上。

我还发现了这一点:OAuth-server, storing user tokens - 因此,如果我正确理解这一点,每当服务使用 google oauth 时,它会将访问令牌存储在它自己的存储中,然后有人与客户端交互。 所以,我有这两个问题:

  1. 如果我想在 public 上打开一个项目供其他人使用,如何实现这样的授权服务?
  2. 如何将令牌发送给客户端?还是所有通信都必须通过我的服务器完成? 2a) 如果对上一个问题的回答是肯定的,我如何确保访问安全以便每个客户端只能访问它自己的令牌,当它仅由一个简单的字符串标识时?

目前,这是获取令牌的代码:

public class Main
{
    public final static void main(final String... args)
    {
        (new Main()).start();
    }
    private Main() {}
    private void start()
    {
        try
        {
            File DATA_STORE_DIR=new File(System.getProperty("user.home"), "yta");
            JsonFactory JSON_FACTORY=JacksonFactory.getDefaultInstance();
            List<String> SCOPES=Arrays.asList(YouTubeScopes.YOUTUBE_READONLY, YouTubeScopes.YOUTUBE, YouTubeScopes.YOUTUBE_FORCE_SSL, YouTubeScopes.YOUTUBE_UPLOAD);
            DataStoreFactory DATA_STORE_FACTORY=new FileDataStoreFactory(DATA_STORE_DIR);
            NetHttpTransport transport=GoogleNetHttpTransport.newTrustedTransport();
            GoogleClientSecrets clientSecrets=GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(new FileInputStream(new File(DATA_STORE_DIR, "client_secret.json"))));
            GoogleAuthorizationCodeFlow googleAuthorizationCodeFlow=new GoogleAuthorizationCodeFlow.Builder(transport, JSON_FACTORY, clientSecrets, SCOPES).setDataStoreFactory(DATA_STORE_FACTORY).build();
            String authURL=googleAuthorizationCodeFlow.newAuthorizationUrl().setRedirectUri("urn:ietf:wg:oauth:2.0:oob").build();
            Desktop.getDesktop().browse(new URI(authURL));
            String authToken=System.console().readLine();
            GoogleTokenResponse googleTokenResponse=googleAuthorizationCodeFlow.newTokenRequest(authToken).setRedirectUri("urn:ietf:wg:oauth:2.0:oob").execute();
            Credential credential=googleAuthorizationCodeFlow.createAndStoreCredential(googleTokenResponse, "userId");
        }
        catch(IOException|GeneralSecurityException|URISyntaxException ex)
        {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
        
    }
}

最终出现在文件“StoredCredential”中的文件是在 DATA_STORE_DIR 目录中创建的,该目录包含由“userId”标识的身份验证令牌。要将令牌传输到客户端,我只需调用 getRefreshToken 和 getAccessToken,将它们发送到客户端,将它们存储在客户端上,从这两个字符串构建一个新的 Credential 实例并通过刷新它来“激活”它。为了摆脱服务器端的令牌,我可以在数据存储上调用 delete - 为了提高安全性,我可以为这个流程使用 UUID,这样一些攻击者就很难拦截令牌 - 但风险是还在那里。所以我正在寻找一种方法来创建令牌而不将其存储在服务器上(即使是短时间)以防止可能的攻击媒介。 你们有人知道怎么做吗?

//更新 因此,我修改了它以了解如何在客户端上创建令牌——这仍然需要我在授权代码流中使用的客户端 ID 和客户端密码(否则您会收到一个异常,告诉您设置这些)。这最终在这些行中:

credential=(new Credential.Builder(BearerToken.authorizationHeaderAccessMethod()))
    .setTransport(transport)
    .setJsonFactory(JSON_FACTORY)
    .setTokenServerEncodedUrl("https://oauth2.googleapis.com/token")
    .setClientAuthentication(new ClientParametersAuthentication("client-id", "client-secret"))
    .build()
.setAccessToken("access-token")
.setRefreshToken("refresh-token");

好吧,据我所知,在不暴露必须保密的 API 秘密的情况下,甚至不可能将 Credential 对象传输到远程客户端。这意味着任何呼叫都必须通过服务器进行,​​服务器必须以某种方式在几个 sessions/connections 上唯一地标识客户端,并且每个回复都必须转发回客户端。

所以 - 这让我想到了一个问题:我想做的事情 * 是可能的吗? *= 在 java 中编写一个程序来访问我的实时流的聊天 - 它在本地存储其令牌但请求外部服务器来获取它

我也尝试使用 API 密钥 - 但似乎我想做的事情没有足够的权限 - 即使是我给定用户名的简单频道 ID 查找也失败了......所以我必须使用 OAuth

//更新2 好吧 - 这似乎无关紧要 - 我在 5 分钟内达到了每天 10k 的普通免费用户帐户的最大限制 - 我想我必须切换到 twitch 然后......

所以,我找到了一个解决方案:https://github.com/cryptearth/YouTubeLiveChat/commit/b1ce15400688b6907600b006463ce538132bd807 归结为我直到现在才明白的两件事:

  1. Credential.Builder 只需将 "null" 作为客户端密码即可正常工作。
  2. AuthorizationCodeFlow 不需要 DataStoreFactory。

因此,当不为 AuthorizationCodeFlow 设置 DataStoreFactory 时,会创建 Credential,但不会存储在任何地方(在源代码中有一个简单的 if(null) 来检查是否设置了 DataStoreFactory)。这也是客户端 ID 并不重要,因为不存在另一个线程可以访问新创建的凭据的风险。 由于 AccessTokens 的有效期有限(大约一个小时),我没有测试客户端运行时间超过此时间时会发生什么,但正如文档所暗示的那样,存在内部检查,所以我猜它会尝试自行刷新 - 这会失败。或者,如果刷新没有发生,下一次调用将失败,只是返回 401 - 来自 Google 的未经授权的回复。因此,通过禁用自动刷新机制,我想我必须以某种方式自己检查它并让刷新在正确的时间在服务器上发生。要刷新,我只是将刷新令牌发送到服务器,然后用新的访问令牌及其新的生命周期进行响应。 因此,服务器不必存储任何内容,客户端只需存储刷新令牌 - 完成!重新实现自动刷新的东西可以通过在有效性 运行 之后滥用由 401 回复引起的异常来做脏 - 可以工作但被认为是错误的代码风格 - 必须弄清楚如何编写它有点不-不好。

关于在 5 分钟内达到 10k 限制:我将超时设置为 10 秒 - 在测试中给了我大约 2 小时 30 分钟 - 仍然不够我自己的日常流 - 如果我要分享它,我不得不延长超时甚至进一步。因此,如果 10 秒得到 2 小时 30 分钟,我需要 100 秒的轮询超时才能得到大约 24 小时 - 一次使用(!) - 即 1 分钟 40 秒 - 乘以用户数量。我想我必须制作一个合适的项目页面,并且不得不以某种方式增加我的最大配额......但这是另一天的故事。

问题已解决 - 项目开发暂停。