feat: add dedicated Logto OIDC config with custom JWS algorithm support#99
feat: add dedicated Logto OIDC config with custom JWS algorithm support#99SXFreell wants to merge 1 commit into
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
There was a problem hiding this comment.
Pull request overview
This PR adds a dedicated Logto OIDC provider/configuration path and introduces an explicit jwsAlgorithm setting to support non-RS256 ID Token signatures (e.g., ES384), while keeping the existing custom SSO OIDC behavior intact.
Changes:
- Add Logto-specific AuthProvider + client registration + setting form (single
endpoint), and derive issuer/OIDC endpoints from it. - Add
jwsAlgorithmtoOauth2ClientRegistrationSpec, propagate it intoClientRegistrationmetadata, and update ID Token decoder algorithm resolution to honor it. - Add regression tests for Logto endpoint normalization, JWS algorithm resolution precedence/fallback, and extension resource contents.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/run/halo/oauth/OauthClientRegistrationRepository.java | Adds Logto config parsing + endpoint derivation and injects jwsAlgorithm into provider metadata. |
| src/main/java/run/halo/oauth/Oauth2ClientRegistration.java | Replaces configurationMetadata with explicit jwsAlgorithm field in the spec model. |
| src/main/java/run/halo/oauth/HaloOAuth2AuthenticationWebFilter.java | Centralizes JWS algorithm resolution with explicit override support. |
| src/main/resources/extensions/auth-provider.yaml | Adds logto AuthProvider definition and fixes indentation for SSO configMapRef. |
| src/main/resources/extensions/client-registrations.yaml | Adds logto client registration (including jwsAlgorithm: ES384). |
| src/main/resources/extensions/setting.yaml | Adds dedicated Logto settings form with endpoint field. |
| src/main/resources/static/logto.svg | Adds Logto logo asset for provider UI. |
| src/test/java/run/halo/oauth/OauthClientRegistrationRepositoryTest.java | Adds regression tests for SSO behavior and Logto endpoint derivation + metadata. |
| src/test/java/run/halo/oauth/HaloOAuth2AuthenticationWebFilterTest.java | Adds tests for JWS algorithm resolution precedence and fallback behavior. |
| src/test/java/run/halo/oauth/Oauth2ExtensionResourcesTest.java | Validates shipped extension YAML contains Logto resources and no private endpoint examples. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var configuredJwsAlgorithm = configurationMetadata.get(JWS_ALGORITHM_METADATA_KEY); | ||
| if (configuredJwsAlgorithm instanceof String jwsAlgorithm | ||
| && StringUtils.hasText(jwsAlgorithm)) { | ||
| return SignatureAlgorithm.from(jwsAlgorithm); |
There was a problem hiding this comment.
resolveJwsAlgorithm calls SignatureAlgorithm.from(jwsAlgorithm) for the explicitly configured value without guarding against invalid/unknown algorithms. If a user misconfigures jwsAlgorithm, this will throw IllegalArgumentException and can break the authentication flow; consider catching it (similar to the metadata path) and either fallback to provider metadata/default or surface a clearer configuration error.
| return SignatureAlgorithm.from(jwsAlgorithm); | |
| try { | |
| return SignatureAlgorithm.from(jwsAlgorithm); | |
| } catch (IllegalArgumentException ex) { | |
| log.warn( | |
| "Invalid JWS algorithm '{}' configured for client '{}' in metadata key '{}'; " | |
| + "falling back to provider metadata/default.", | |
| jwsAlgorithm, | |
| clientRegistration.getRegistrationId(), | |
| JWS_ALGORITHM_METADATA_KEY, | |
| ex | |
| ); | |
| } |
| @Component | ||
| public class HaloOAuth2AuthenticationWebFilter implements AuthenticationSecurityWebFilter { | ||
|
|
||
| static final String JWS_ALGORITHM_METADATA_KEY = "jwsAlgorithm"; |
There was a problem hiding this comment.
The metadata key "jwsAlgorithm" is defined separately here and in OauthClientRegistrationRepository. Duplicating the literal/constant in multiple places risks future drift (one side changes and the other doesn’t), which would silently disable the override; consider centralizing the key in a shared constant (or referencing one class’s constant) to keep writer/reader aligned.
| static final String JWS_ALGORITHM_METADATA_KEY = "jwsAlgorithm"; | |
| static final String JWS_ALGORITHM_METADATA_KEY = | |
| OauthClientRegistrationRepository.JWS_ALGORITHM_METADATA_KEY; |
| if (StringUtils.isBlank(value) || "{}".equals(value.trim())) { | ||
| String legacyValue = data.getOrDefault(ADVANCED_OIDC_SETTING_GROUP, "{}"); | ||
| if (!"{}".equals(legacyValue.trim())) { | ||
| SsoClientConf ssoClientConf = | ||
| JsonUtils.jsonToObject(legacyValue, SsoClientConf.class); | ||
| return SsoClientRegistration(ssoClientConf, authProvider); | ||
| } | ||
| } |
There was a problem hiding this comment.
LOGTO_SETTING_GROUP handling attempts to fall back to ADVANCED_OIDC_SETTING_GROUP data from the same provider ConfigMap. However, the shipped extension resources use different ConfigMaps for sso (oauth2-sso-config) and logto (oauth2-logto-config), so this fallback won’t migrate existing SSO-based Logto settings unless users manually copy the old group key into the new ConfigMap. If migration is intended, consider fetching from the SSO ConfigMap explicitly; otherwise removing this branch would avoid confusing, hard-to-reason-about behavior.
| if (StringUtils.isBlank(value) || "{}".equals(value.trim())) { | |
| String legacyValue = data.getOrDefault(ADVANCED_OIDC_SETTING_GROUP, "{}"); | |
| if (!"{}".equals(legacyValue.trim())) { | |
| SsoClientConf ssoClientConf = | |
| JsonUtils.jsonToObject(legacyValue, SsoClientConf.class); | |
| return SsoClientRegistration(ssoClientConf, authProvider); | |
| } | |
| } |
| private static SignatureAlgorithm resolveJwsAlgorithm(ClientRegistration registration) { | ||
| try { | ||
| Method method = HaloOAuth2AuthenticationWebFilter.class.getDeclaredMethod( | ||
| "resolveJwsAlgorithm", ClientRegistration.class | ||
| ); | ||
| method.setAccessible(true); | ||
| return (SignatureAlgorithm) method.invoke(null, registration); | ||
| } catch (NoSuchMethodException e) { | ||
| fail("Expected resolveJwsAlgorithm(ClientRegistration) helper to exist", e); | ||
| } catch (IllegalAccessException | InvocationTargetException e) { | ||
| fail("Failed to invoke resolveJwsAlgorithm(ClientRegistration)", e); | ||
| } |
There was a problem hiding this comment.
This test uses reflection to invoke HaloOAuth2AuthenticationWebFilter.resolveJwsAlgorithm, but the method is package-visible and the test is in the same run.halo.oauth package, so it can be called directly. Dropping reflection would make the test simpler and less brittle (no setAccessible(true) / NoSuchMethodException handling needed).
| assertThat(content).contains("name: logto"); | ||
| assertThat(content).contains("clientName: \"Logto\""); | ||
| assertThat(content).contains("redirectUri: \"{baseUrl}/login/oauth2/code/logto\""); | ||
| assertThat(content).contains("jwsAlgorithm: \"ES384\""); | ||
| assertThat(content).doesNotContain("configurationMetadata:"); |
There was a problem hiding this comment.
clientRegistrationsShouldContainLogtoRegistrationAndExplicitJwsAlgorithm asserts the entire client-registrations.yaml does not contain configurationMetadata:. That’s a broad, file-level invariant and could cause unrelated future changes (e.g., another provider legitimately adding metadata) to break this test; consider scoping the assertion to the logto registration block instead.
| assertThat(content).contains("name: logto"); | |
| assertThat(content).contains("clientName: \"Logto\""); | |
| assertThat(content).contains("redirectUri: \"{baseUrl}/login/oauth2/code/logto\""); | |
| assertThat(content).contains("jwsAlgorithm: \"ES384\""); | |
| assertThat(content).doesNotContain("configurationMetadata:"); | |
| var logtoNameMarker = "name: logto"; | |
| int logtoIndex = content.indexOf(logtoNameMarker); | |
| assertThat(logtoIndex).as("logto client registration should exist").isGreaterThanOrEqualTo(0); | |
| int nextRegistrationIndex = content.indexOf("\n- name:", logtoIndex + logtoNameMarker.length()); | |
| String logtoRegistrationBlock = nextRegistrationIndex >= 0 | |
| ? content.substring(logtoIndex, nextRegistrationIndex) | |
| : content.substring(logtoIndex); | |
| assertThat(logtoRegistrationBlock).contains("name: logto"); | |
| assertThat(logtoRegistrationBlock).contains("clientName: \"Logto\""); | |
| assertThat(logtoRegistrationBlock).contains("redirectUri: \"{baseUrl}/login/oauth2/code/logto\""); | |
| assertThat(logtoRegistrationBlock).contains("jwsAlgorithm: \"ES384\""); | |
| assertThat(logtoRegistrationBlock).doesNotContain("configurationMetadata:"); |
概述
新增 Logto 专用 OIDC 配置,并支持自定义 ID Token 签名算法。
变更内容
jwsAlgorithm配置endpoint字段完成配置endpoint自动推导 Logto 的issuer、authorization、token、userinfo和jwks地址变更原因
Logto 返回的 ID Token 可能使用
ES384等非RS256签名算法,而插件此前默认按RS256处理,导致登录绑定失败。同时,Logto 的
issuer配置如果填写不准确,容易触发 ID Token 的iss校验失败。本次改动通过专用配置和 endpoint 自动推导,减少了这类配置错误。验证情况
./gradlew testissuer与 OIDC 相关端点能够正确推导Fixes #97