Class CloudAuthManager

java.lang.Object
com.ultikits.ultitools.utils.CloudAuthManager

public class CloudAuthManager extends Object
Manages UltiCloud authentication tokens. Supports magic-link login (no password needed) and token persistence.
管理UltiCloud身份验证令牌。 支持魔法链接登录(无需密码)和令牌持久化。
  • Field Details

    • GSON

      private static final com.google.gson.Gson GSON
    • POLL_INTERVAL_MS

      private static final long POLL_INTERVAL_MS
      See Also:
    • MAX_POLL_ATTEMPTS

      private static final int MAX_POLL_ATTEMPTS
      See Also:
    • TOKEN_REFRESH_CHECK_INTERVAL_MS

      private static final long TOKEN_REFRESH_CHECK_INTERVAL_MS
      How often to check if the access token needs refreshing (1 hour)
      See Also:
    • TOKEN_REFRESH_THRESHOLD_SECONDS

      private static final long TOKEN_REFRESH_THRESHOLD_SECONDS
      Refresh the token when it has less than this many seconds remaining (2 hours)
      See Also:
    • OAUTH2_BASIC_AUTH

      private static final String OAUTH2_BASIC_AUTH
      Basic auth header for OAuth2 client credentials (client:112233)
      See Also:
    • currentToken

      private static TokenEntity currentToken
    • pollExecutor

      private static ScheduledExecutorService pollExecutor
    • pollTask

      private static ScheduledFuture<?> pollTask
    • refreshExecutor

      private static ScheduledExecutorService refreshExecutor
    • refreshTask

      private static ScheduledFuture<?> refreshTask
    • credentialGeneration

      private static final AtomicLong credentialGeneration
      凭证的生命周期代际。

      存在的理由只有一句:取消不等于失效。stopTokenRefreshScheduler()stopPolling() 用的是 cancel(false)shutdown(),两者都只 承诺不再调度新的执行,对一个已经进入 HTTP 请求的任务毫无约束——而 refreshToken(String) 在返回之前saveToken(TokenEntity) 写盘。 于是这样的时序完全成立:

         1. 刷新任务发出 HTTP 请求(网络往返,秒级)
         2. 管理员 /ulticloud logout → 停调度器 → clearToken() 清掉 data.json
         3. HTTP 返回 → saveToken() 把新凭证写回 data.json
         4. 重启服务器 → 读到有效凭证 → 自动登录
       
      logout 于是成了一条没有效果的命令,而它恰恰是安全语义的。

      规则:一切在途的异步凭证操作出发时记下当时的代际,提交结果之前用 commitTokenIfCurrent(TokenEntity, long) 比对;拆线路径调 invalidateCredentialOperations() 推进代际,迟到的结果一律作废。

  • Constructor Details

    • CloudAuthManager

      public CloudAuthManager()
  • Method Details

    • loadSavedToken

      public static TokenEntity loadSavedToken()
      Try to load a saved token from data.json on startup. If the access token is expired but a refresh token exists, attempts automatic refresh. Returns the token if valid, null otherwise.
    • refreshToken

      public static TokenEntity refreshToken(String refreshTokenValue)
      Refresh the access token using the refresh token. Calls POST /oauth/token with grant_type=refresh_token.
      Parameters:
      refreshTokenValue - the refresh token string
      Returns:
      a new TokenEntity with fresh access and refresh tokens, or null on failure
    • saveToken

      public static void saveToken(TokenEntity token) throws IOException
      Save the current token to data.json for persistence across restarts.
      Throws:
      IOException
    • clearToken

      public static void clearToken() throws IOException
      Clear the saved token (logout).
      Throws:
      IOException
    • currentCredentialGeneration

      public static long currentCredentialGeneration()
      取当前的凭证代际。异步凭证操作在出发时调它记下自己那一代。
      Returns:
      当前代际
    • invalidateCredentialOperations

      public static void invalidateCredentialOperations()
      让一切在途的凭证操作作废。

      拆线路径(disableCloud() / /ulticloud logout)必须调它。只停调度器 是不够的——见 credentialGeneration 上的说明。

    • commitTokenIfCurrent

      public static boolean commitTokenIfCurrent(TokenEntity token, long generation) throws IOException
      仅当代际未变时才提交凭证。

      invalidateCredentialOperations()clearToken() 同步在类锁上, 因此「比对代际」与「写入」之间不存在窗口:拆线要么整个发生在提交之前(这次提交被 拒),要么整个发生在提交之后(拆线把刚写的清掉)。两种都是干净的。

      Parameters:
      token - 待提交的凭证
      generation - 调用方出发时记下的代际
      Returns:
      已提交返回 true;代际已变、结果被丢弃则返回 false
      Throws:
      IOException - 写入失败
    • getCurrentToken

      public static TokenEntity getCurrentToken()
      Get the current token (in-memory).
    • hasValidToken

      public static boolean hasValidToken()
      Check if we have a valid (non-expired) token.
    • requestMagicLink

      public static String requestMagicLink(Consumer<String> errorCallback)
      Request a magic link for server authentication. Returns the URL the admin should open in their browser, or null on failure.
      Parameters:
      errorCallback - called with error message if the request fails
      Returns:
      the magic link URL, or null on failure
    • startPolling

      public static void startPolling(String requestId, Consumer<TokenEntity> onComplete)
      Start polling for magic-link auth completion.
      Parameters:
      requestId - the magic link request ID
      onComplete - called when auth succeeds (with the token), or null if no callback needed
    • startPolling

      public static void startPolling(String requestId, Consumer<TokenEntity> onComplete, long generation)
      带显式代际的轮询入口。

      代际由发起整次登录的那一刻决定,不能在这里就地读:调用方在到达这里之前 通常已经做过一次阻塞的 HTTP 请求,那段时间里发生的 logout 必须对这次登录可见。

      Parameters:
      requestId - magic-link 请求 ID
      onComplete - 登录完成回调,可为 null
      generation - 发起这次登录时的凭证代际
    • pollLoginStatusOnce

      private static void pollLoginStatusOnce(String requestId, Consumer<TokenEntity> onComplete, long generation)
      查一次登录状态。任何异常都只记 FINE——轮询要继续,直到超时或拿到终态。
    • completeMagicLinkLogin

      private static void completeMagicLinkLogin(com.google.gson.JsonObject responseBody, Consumer<TokenEntity> onComplete, long generation) throws IOException
      处理一次拿到 completed 的登录:落凭证、再激活云功能。

      两步都要对 logout 设防,而且是两道不同的闸: commitTokenIfCurrent(TokenEntity, long) 保证凭证不会在 logout 之后被写回; activateCloudIfCurrent 保证连接不会在 logout 之后被重新建起来。只有前者是 不够的——真正把服务器连回去的是后面那一串。

      Throws:
      IOException - 凭证落盘失败
    • startTokenRefreshScheduler

      public static void startTokenRefreshScheduler()
      Start a background scheduler that proactively refreshes the access token before it expires (checks every hour, refreshes when <2 hours remaining).
    • stopTokenRefreshScheduler

      public static void stopTokenRefreshScheduler()
      Stop the background token refresh scheduler.
    • stopPolling

      public static void stopPolling()
      Stop polling for magic-link completion.
    • readDataFile

      private static Map<String,Object> readDataFile() throws IOException
      Throws:
      IOException
    • writeDataFile

      private static void writeDataFile(Map<String,Object> data) throws IOException
      Throws:
      IOException