fix(auth): restore oauth redirect and gitlab email checks

This commit is contained in:
wowo-zZ 2026-04-16 16:42:03 +08:00
parent d1459aea83
commit caa89d54ff
3 changed files with 119 additions and 12 deletions

View file

@ -56,7 +56,7 @@ spring:
scope:
- read:user
- user:email
redirect-uri: ${SKILLHUB_PUBLIC_BASE_URL:http://localhost:8080}/login/oauth2/code/github
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
client-name: GitHub
authorization-grant-type: authorization_code
gitlab:
@ -66,7 +66,7 @@ spring:
- read_user
- email
authorization-grant-type: authorization_code
redirect-uri: ${SKILLHUB_PUBLIC_BASE_URL:http://localhost:8080}/login/oauth2/code/gitlab
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
client-name: ${OAUTH2_GITLAB_DISPLAY_NAME:GitLab}
provider:
github:

View file

@ -1,5 +1,6 @@
package com.iflytek.skillhub.auth.oauth;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
@ -26,8 +27,8 @@ public class GitLabClaimsExtractor implements OAuthClaimsExtractor {
private final RestClient restClient;
public GitLabClaimsExtractor() {
this.restClient = RestClient.builder()
public GitLabClaimsExtractor(RestClient.Builder restClientBuilder) {
this.restClient = restClientBuilder
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
}
@ -45,9 +46,7 @@ public class GitLabClaimsExtractor implements OAuthClaimsExtractor {
// GitLab returns email directly in user info
String email = (String) attrs.get("email");
// GitLab provides email_verified field in the user info response
Boolean emailVerifiedObj = (Boolean) attrs.get("email_verified");
boolean emailVerified = emailVerifiedObj != null && emailVerifiedObj;
boolean emailVerified = isConfirmed(attrs.get("confirmed_at"));
log.debug("Initial email from GitLab: {}, verified: {}", email, emailVerified);
@ -129,10 +128,15 @@ public class GitLabClaimsExtractor implements OAuthClaimsExtractor {
}
/**
* Represents a GitLab email object from the /user/emails API.
*
* @param email the email address
* @param confirmed whether the email has been confirmed
* GitLab marks a confirmed email by populating confirmed_at.
*/
private record GitLabEmail(String email, boolean confirmed) {}
private record GitLabEmail(String email, @JsonProperty("confirmed_at") String confirmedAt) {
boolean confirmed() {
return confirmedAt != null && !confirmedAt.isBlank();
}
}
private boolean isConfirmed(Object confirmedAt) {
return confirmedAt instanceof String value && !value.isBlank();
}
}

View file

@ -0,0 +1,103 @@
package com.iflytek.skillhub.auth.oauth;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import java.time.Instant;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestClient;
class GitLabClaimsExtractorTest {
@Test
void extract_marksProfileEmailVerifiedWhenConfirmedAtPresent() {
RestClient.Builder restClientBuilder = RestClient.builder();
GitLabClaimsExtractor extractor = new GitLabClaimsExtractor(restClientBuilder);
OAuthClaims claims = extractor.extract(
userRequest(),
new DefaultOAuth2User(
java.util.List.of(),
Map.of(
"id", 42,
"username", "alice",
"email", "alice@gitlab.example",
"confirmed_at", "2026-04-16T08:00:00Z"
),
"username"
)
);
assertThat(claims.email()).isEqualTo("alice@gitlab.example");
assertThat(claims.emailVerified()).isTrue();
assertThat(claims.providerLogin()).isEqualTo("alice");
}
@Test
void extract_loadsConfirmedEmailFromEmailListWhenProfileEmailIsUnconfirmed() {
RestClient.Builder restClientBuilder = RestClient.builder();
MockRestServiceServer server = MockRestServiceServer.bindTo(restClientBuilder).build();
server.expect(requestTo("https://gitlab.example.com/api/v4/user/emails"))
.andExpect(header(HttpHeaders.AUTHORIZATION, "Bearer token-123"))
.andRespond(withSuccess(
"""
[
{"email":"alice@gitlab.example","confirmed_at":"2026-04-16T08:00:00Z"},
{"email":"alice+pending@gitlab.example","confirmed_at":null}
]
""",
MediaType.APPLICATION_JSON
));
GitLabClaimsExtractor extractor = new GitLabClaimsExtractor(restClientBuilder);
OAuthClaims claims = extractor.extract(
userRequest(),
new DefaultOAuth2User(
java.util.List.of(),
Map.of(
"id", 42,
"username", "alice",
"email", "alice+pending@gitlab.example"
),
"username"
)
);
assertThat(claims.email()).isEqualTo("alice@gitlab.example");
assertThat(claims.emailVerified()).isTrue();
server.verify();
}
private OAuth2UserRequest userRequest() {
ClientRegistration registration = ClientRegistration.withRegistrationId("gitlab")
.clientId("client-id")
.clientSecret("client-secret")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("read_user", "email")
.authorizationUri("https://gitlab.example.com/oauth/authorize")
.tokenUri("https://gitlab.example.com/oauth/token")
.userInfoUri("https://gitlab.example.com/api/v4/user")
.userNameAttributeName("username")
.clientName("GitLab")
.build();
OAuth2AccessToken accessToken = new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER,
"token-123",
Instant.now(),
Instant.now().plusSeconds(3600)
);
return new OAuth2UserRequest(registration, accessToken);
}
}