Class CloudAuthManager
管理UltiCloud身份验证令牌。 支持魔法链接登录(无需密码)和令牌持久化。
-
Field Summary
FieldsModifier and TypeFieldDescriptionprivate static final AtomicLong凭证的生命周期代际。private static TokenEntityprivate static final com.google.gson.Gsonprivate static final intprivate static final StringBasic auth header for OAuth2 client credentials (client:112233)private static final longprivate static ScheduledExecutorServiceprivate static ScheduledFuture<?> private static ScheduledExecutorServiceprivate static ScheduledFuture<?> private static final longHow often to check if the access token needs refreshing (1 hour)private static final longRefresh the token when it has less than this many seconds remaining (2 hours) -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic voidClear the saved token (logout).static booleancommitTokenIfCurrent(TokenEntity token, long generation) 仅当代际未变时才提交凭证。private static voidcompleteMagicLinkLogin(com.google.gson.JsonObject responseBody, Consumer<TokenEntity> onComplete, long generation) 处理一次拿到completed的登录:落凭证、再激活云功能。static long取当前的凭证代际。异步凭证操作在出发时调它记下自己那一代。static TokenEntityGet the current token (in-memory).static booleanCheck if we have a valid (non-expired) token.static void让一切在途的凭证操作作废。static TokenEntityTry to load a saved token from data.json on startup.private static voidpollLoginStatusOnce(String requestId, Consumer<TokenEntity> onComplete, long generation) 查一次登录状态。任何异常都只记 FINE——轮询要继续,直到超时或拿到终态。static TokenEntityrefreshToken(String refreshTokenValue) Refresh the access token using the refresh token.static StringrequestMagicLink(Consumer<String> errorCallback) Request a magic link for server authentication.static voidsaveToken(TokenEntity token) Save the current token to data.json for persistence across restarts.static voidstartPolling(String requestId, Consumer<TokenEntity> onComplete) Start polling for magic-link auth completion.static voidstartPolling(String requestId, Consumer<TokenEntity> onComplete, long generation) 带显式代际的轮询入口。static voidStart a background scheduler that proactively refreshes the access token before it expires (checks every hour, refreshes when <2 hours remaining).static voidStop polling for magic-link completion.static voidStop the background token refresh scheduler.private static voidwriteDataFile(Map<String, Object> data)
-
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_MSHow often to check if the access token needs refreshing (1 hour)- See Also:
-
TOKEN_REFRESH_THRESHOLD_SECONDS
private static final long TOKEN_REFRESH_THRESHOLD_SECONDSRefresh the token when it has less than this many seconds remaining (2 hours)- See Also:
-
OAUTH2_BASIC_AUTH
Basic auth header for OAuth2 client credentials (client:112233)- See Also:
-
currentToken
-
pollExecutor
-
pollTask
-
refreshExecutor
-
refreshTask
-
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
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
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
Save the current token to data.json for persistence across restarts.- Throws:
IOException
-
clearToken
Clear the saved token (logout).- Throws:
IOException
-
currentCredentialGeneration
public static long currentCredentialGeneration()取当前的凭证代际。异步凭证操作在出发时调它记下自己那一代。- Returns:
- 当前代际
-
invalidateCredentialOperations
public static void invalidateCredentialOperations()让一切在途的凭证操作作废。拆线路径(
disableCloud()//ulticloud logout)必须调它。只停调度器 是不够的——见credentialGeneration上的说明。 -
commitTokenIfCurrent
仅当代际未变时才提交凭证。与
invalidateCredentialOperations()和clearToken()同步在类锁上, 因此「比对代际」与「写入」之间不存在窗口:拆线要么整个发生在提交之前(这次提交被 拒),要么整个发生在提交之后(拆线把刚写的清掉)。两种都是干净的。- Parameters:
token- 待提交的凭证generation- 调用方出发时记下的代际- Returns:
- 已提交返回 true;代际已变、结果被丢弃则返回 false
- Throws:
IOException- 写入失败
-
getCurrentToken
Get the current token (in-memory). -
hasValidToken
public static boolean hasValidToken()Check if we have a valid (non-expired) token. -
requestMagicLink
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
Start polling for magic-link auth completion.- Parameters:
requestId- the magic link request IDonComplete- 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 请求 IDonComplete- 登录完成回调,可为 nullgeneration- 发起这次登录时的凭证代际
-
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
- Throws:
IOException
-
writeDataFile
- Throws:
IOException
-