From b308d71824aeff9510415400d416dd381aad5605 Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 4 Apr 2026 21:12:50 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=9A=E6=AC=A1?= =?UTF-8?q?=E6=89=93=E5=BC=80=E5=BE=AE=E8=BD=AF=E7=99=BB=E5=BD=95=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E5=90=8E=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/game/OAuthServer.java | 5 ++ .../java/org/jackhuang/hmcl/auth/OAuth.java | 67 ++++++++++--------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java index 726494e38f..db8086058a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -163,6 +163,11 @@ public Response serve(IHTTPSession session) { return newFixedLengthResponse(Response.Status.OK, "text/html; charset=UTF-8", html); } + @Override + public void close() { + stop(); + } + public static class Factory implements OAuth.Callback { public final EventManager onGrantDeviceCode = new EventManager<>(); public final EventManager onOpenBrowserAuthorizationCode = new EventManager<>(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java index 51eec94366..d47860d617 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java @@ -80,37 +80,37 @@ public Result authenticate(GrantFlow grantFlow, Options options) throws Authenti } private Result authenticateAuthorizationCode(Options options) throws IOException, InterruptedException, JsonParseException, ExecutionException, AuthenticationException { - Session session = options.callback.startServer(); - - String codeVerifier = session.getCodeVerifier(); - String state = session.getState(); - String codeChallenge = generateCodeChallenge(codeVerifier); - - options.callback.openBrowser(GrantFlow.AUTHORIZATION_CODE, NetworkUtils.withQuery(authorizationURL, - mapOf(pair("client_id", options.callback.getClientId()), - pair("response_type", "code"), - pair("redirect_uri", session.getRedirectURI()), - pair("scope", options.scope), - pair("prompt", "select_account"), - pair("code_challenge", codeChallenge), - pair("state", state), - pair("code_challenge_method", "S256") - ))); - String code = session.waitFor(); - - // Authorization Code -> Token - AuthorizationResponse response = HttpRequest.POST(accessTokenURL) - .form(pair("client_id", options.callback.getClientId()), - pair("code", code), - pair("grant_type", "authorization_code"), - pair("code_verifier", codeVerifier), - pair("redirect_uri", session.getRedirectURI()), - pair("scope", options.scope)) - .ignoreHttpCode() - .retry(5) - .getJson(AuthorizationResponse.class); - handleErrorResponse(response); - return new Result(response.accessToken, response.refreshToken); + try (Session session = options.callback.startServer()) { + String codeVerifier = session.getCodeVerifier(); + String state = session.getState(); + String codeChallenge = generateCodeChallenge(codeVerifier); + + options.callback.openBrowser(GrantFlow.AUTHORIZATION_CODE, NetworkUtils.withQuery(authorizationURL, + mapOf(pair("client_id", options.callback.getClientId()), + pair("response_type", "code"), + pair("redirect_uri", session.getRedirectURI()), + pair("scope", options.scope), + pair("prompt", "select_account"), + pair("code_challenge", codeChallenge), + pair("state", state), + pair("code_challenge_method", "S256") + ))); + String code = session.waitFor(); + + // Authorization Code -> Token + AuthorizationResponse response = HttpRequest.POST(accessTokenURL) + .form(pair("client_id", options.callback.getClientId()), + pair("code", code), + pair("grant_type", "authorization_code"), + pair("code_verifier", codeVerifier), + pair("redirect_uri", session.getRedirectURI()), + pair("scope", options.scope)) + .ignoreHttpCode() + .retry(5) + .getJson(AuthorizationResponse.class); + handleErrorResponse(response); + return new Result(response.accessToken, response.refreshToken); + } } private Result authenticateDevice(Options options) throws IOException, InterruptedException, JsonParseException, AuthenticationException { @@ -234,7 +234,7 @@ public Options setUserAgent(String userAgent) { } } - public interface Session { + public interface Session extends AutoCloseable { String getState(); String getCodeVerifier(); @@ -253,6 +253,9 @@ public interface Session { default String getIdToken() { return null; } + + @Override + void close(); } public interface Callback { From 60d9506b49ad7251479d1a344d19eba75f846dbb Mon Sep 17 00:00:00 2001 From: Glavo Date: Sat, 4 Apr 2026 22:00:31 +0800 Subject: [PATCH 2/2] fix: handle incomplete OAuth future when server closes --- HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java index db8086058a..daa1fc90fc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java @@ -165,6 +165,8 @@ public Response serve(IHTTPSession session) { @Override public void close() { + if (!future.isDone()) + future.completeExceptionally(new AuthenticationException("OAuth server is closing")); stop(); }