迁移微软授权代码流 client_secret 到 PKCE #5575
Conversation
|
更新后,Microsoft Auth Server 是如何验证 client 是 HMCL?我看请求参数内只传递 client_id,不再传递 client_secret 了。 |
是。 本地应用不能安全保存 这里目的不是用来证明 client 就是 HMCL,而是用来证明当前换 token 的 client 是最初发起请求的 client ,目的是解决 |
|
需要在entra上配置什么呢?client_secret不需要的话,已经不需要再换客户端证书了吧? |
|
所以是给移动桌面应用程序分类,将原来Web的RedirectURI重新添加一遍? |
There was a problem hiding this comment.
Pull request overview
将微软 OAuth 授权码流程从依赖 client_secret 迁移到 PKCE(S256),以符合公共客户端最佳实践并避免刷新时必须携带机密导致的失败(#5566)。
Changes:
- 在授权码登录流程中加入 PKCE:生成
code_challenge(S256)并在换取 token 时提交code_verifier - 刷新 token 请求中移除
client_secret相关逻辑,并同步删除构建时注入的 secret 配置 - 在本地回调服务器
OAuthServer中生成并保存code_verifier
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OAuth.java | 授权码流程增加 PKCE 参数;刷新流程移除 client_secret;新增 generateCodeChallenge 与 Session#getCodeVerifier |
| HMCL/src/main/java/org/jackhuang/hmcl/game/OAuthServer.java | 生成并提供 PKCE code_verifier(通过 OAuth.Session) |
| HMCL/build.gradle.kts | 删除微软授权 secret 的环境变量/属性注入 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 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")))); | ||
| 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("code_challenge_method", "S256") | ||
| ))); |
There was a problem hiding this comment.
Authorization code flow adds PKCE, but the request still omits the OAuth state parameter. Without generating and validating state, the localhost callback can be vulnerable to CSRF/login-swapping (accepting a code not initiated by this client). Consider generating a random state per auth attempt, include it in the authorize URL, and verify it in the callback handler before accepting the code.
| @@ -52,6 +55,20 @@ private OAuthServer(int port) { | |||
| super(port); | |||
|
|
|||
| this.port = port; | |||
| this.codeVerifier = generateCodeVerifier(); | |||
| } | |||
|
|
|||
| // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 | |||
| private String generateCodeVerifier() { | |||
| SecureRandom sr = new SecureRandom(); | |||
| byte[] code = new byte[64]; | |||
| sr.nextBytes(code); | |||
| return Base64.getUrlEncoder().withoutPadding().encodeToString(code); | |||
| } | |||
|
|
|||
| @Override | |||
| public String getCodeVerifier() { | |||
| return codeVerifier; | |||
| } | |||
There was a problem hiding this comment.
PKCE verifier is generated and stored, but there is still no per-request state nonce stored on the session and validated in serve() before completing the future. Adding a state field here (and exposing it via OAuth.Session, e.g. getState()) would allow the authorize URL to include state and the callback to reject mismatches.
|
|
||
| // https://datatracker.ietf.org/doc/html/rfc7636#section-4.1 | ||
| private String generateCodeVerifier() { | ||
| SecureRandom sr = new SecureRandom(); |
There was a problem hiding this comment.
为什么这里两个随机数用两种风格的写法?另外 generateState 和 generateCodeVerifier 是不是共享一个 SecureRandom 实例更好?
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try { | ||
| byte[] bytes = codeVerifier.getBytes(StandardCharsets.US_ASCII); | ||
| MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); | ||
| messageDigest.update(bytes, 0, bytes.length); | ||
| byte[] digest = messageDigest.digest(); | ||
| return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); | ||
| } catch (Exception e) { | ||
| LOG.warning("Failed to generate code challenge", e); | ||
| throw new RuntimeException(e); | ||
| } |
There was a problem hiding this comment.
generateCodeChallenge catches a broad Exception, logs, and then throws a generic RuntimeException. Since this is deterministic code (SHA-256 should always be available), consider narrowing the catch to NoSuchAlgorithmException (or treating it as an AssertionError/IllegalStateException) so unexpected runtime errors aren’t accidentally swallowed and rethrown with less context.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!this.state.equals(query.get("state"))) | ||
| future.completeExceptionally(new AuthenticationException( | ||
| query.containsKey("state") ? "invalid state" : "missing state")); |
There was a problem hiding this comment.
The code continues executing after state validation fails. When the state doesn't match, the code should return early instead of continuing to process the request. Currently, after calling future.completeExceptionally, the code continues to lines 130-136 which may call future.complete() or future.completeExceptionally() again, causing unpredictable behavior. Add a return statement after the state validation block to prevent further execution when validation fails.
| if (!this.state.equals(query.get("state"))) | |
| future.completeExceptionally(new AuthenticationException( | |
| query.containsKey("state") ? "invalid state" : "missing state")); | |
| if (!this.state.equals(query.get("state"))) { | |
| future.completeExceptionally(new AuthenticationException( | |
| query.containsKey("state") ? "invalid state" : "missing state")); | |
| return newFixedLengthResponse(Response.Status.BAD_REQUEST, MIME_PLAINTEXT, | |
| query.containsKey("state") ? "invalid state" : "missing state"); | |
| } |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


resolves #5566
https://learn.microsoft.com/zh-cn/entra/identity-platform/v2-oauth2-auth-code-flow
需要黄鱼在 entra 进行一些配置