mirror of
https://github.com/iflytek/skillhub.git
synced 2026-08-28 11:25:00 +00:00
fix(auth): add OIDC sub claim validation and complete env example
- Add null/blank validation for OIDC sub claim in CustomOidcUserService - Throw OAuth2AuthenticationException when sub is missing or blank - Complete .env.release.example with all required OIDC environment variables - Add test cases for sub validation and providerLogin fallback scenarios - All 5 tests passing
This commit is contained in:
parent
bb9f8915e2
commit
4f44ea3976
3 changed files with 49 additions and 2 deletions
|
|
@ -63,8 +63,17 @@ OAUTH2_GITLAB_CLIENT_SECRET=
|
|||
OAUTH2_GITLAB_BASE_URI=https://gitlab.com
|
||||
OAUTH2_GITLAB_DISPLAY_NAME=GitLab
|
||||
|
||||
# Optional: OIDC providers are configured with Spring Security client registration variables.
|
||||
# See docs/09-deployment.md for the full OIDC environment variable set and Compose override notes.
|
||||
# Optional: OIDC login (e.g. Keycloak, Okta, Azure AD).
|
||||
# Replace "OIDC" in variable names with your registration id (uppercase).
|
||||
# The registration id becomes identity_binding.provider_code — keep it stable.
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_ID=
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_SECRET=
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_PROVIDER=oidc
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_AUTHORIZATION_GRANT_TYPE=authorization_code
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_REDIRECT_URI={baseUrl}/login/oauth2/code/{registrationId}
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_SCOPE=openid,profile,email
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_OIDC_CLIENT_NAME=OIDC
|
||||
SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=
|
||||
|
||||
# SMTP configuration for password reset verification emails.
|
||||
SPRING_MAIL_HOST=
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
|||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
|
|
@ -66,6 +67,10 @@ public class CustomOidcUserService implements OAuth2UserService<OidcUserRequest,
|
|||
static OAuthClaims toOAuthClaims(OidcUserRequest request, OidcUser oidcUser) {
|
||||
Map<String, Object> claims = new HashMap<>(oidcUser.getClaims());
|
||||
String subject = asString(claims.get("sub"));
|
||||
if (subject == null || subject.isBlank()) {
|
||||
throw new OAuth2AuthenticationException(
|
||||
new OAuth2Error("missing_sub", "OIDC sub claim is required", null));
|
||||
}
|
||||
String email = asString(claims.get("email"));
|
||||
boolean emailVerified = Boolean.TRUE.equals(claims.get("email_verified"));
|
||||
String providerLogin = firstPresent(
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
|||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
||||
|
|
@ -21,6 +22,7 @@ import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
|||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
|
@ -92,6 +94,37 @@ class CustomOidcUserServiceTest {
|
|||
assertThat(claims.providerLogin()).isEqualTo("Fallback Name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toOAuthClaims_throwsWhenSubIsMissing() {
|
||||
OidcUser user = mock(OidcUser.class);
|
||||
when(user.getClaims()).thenReturn(Map.of("email", "no-sub@example.com"));
|
||||
assertThatThrownBy(() -> CustomOidcUserService.toOAuthClaims(oidcRequest(), user))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.hasMessageContaining("sub");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toOAuthClaims_throwsWhenSubIsBlank() {
|
||||
OidcUser user = mock(OidcUser.class);
|
||||
when(user.getClaims()).thenReturn(Map.of(IdTokenClaimNames.SUB, " "));
|
||||
assertThatThrownBy(() -> CustomOidcUserService.toOAuthClaims(oidcRequest(), user))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.hasMessageContaining("sub");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toOAuthClaims_fallsBackToSubWhenAllOtherFieldsMissing() {
|
||||
OAuthClaims claims = CustomOidcUserService.toOAuthClaims(
|
||||
oidcRequest(),
|
||||
oidcUser(Map.of(IdTokenClaimNames.SUB, "only-sub"))
|
||||
);
|
||||
|
||||
assertThat(claims.subject()).isEqualTo("only-sub");
|
||||
assertThat(claims.providerLogin()).isEqualTo("only-sub");
|
||||
assertThat(claims.email()).isNull();
|
||||
assertThat(claims.emailVerified()).isFalse();
|
||||
}
|
||||
|
||||
private static OidcUserRequest oidcRequest() {
|
||||
Instant issuedAt = Instant.parse("2026-04-24T00:00:00Z");
|
||||
OidcIdToken idToken = new OidcIdToken(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue