From 4e4936e8517a6095c542fe992120d6c9a585bb9b Mon Sep 17 00:00:00 2001 From: David Polach Date: Tue, 10 Sep 2024 15:42:01 +0200 Subject: [PATCH] Fixed broken identity federation --- .../ApplicationUsersInMemoryRepository.java | 2 +- .../LoginPageSecurityConfiguration.java | 6 +- .../RegisterMemberFromSocialLoginHandler.java | 1 + ...cialLoginAuthenticationSuccessHandler.java | 1 + .../CustomOidcUserService.java | 72 +++++++++++++++++++ ...UserToKlabisSocialLoginOidcUserMapper.java | 16 +++-- ...alLoginOidcUserToKlabisOidcUserMapper.java | 42 +---------- 7 files changed, 93 insertions(+), 47 deletions(-) create mode 100644 backend/src/main/java/club/klabis/config/authserver/socialloginsupport/CustomOidcUserService.java diff --git a/backend/src/main/java/club/klabis/adapters/inmemorystorage/ApplicationUsersInMemoryRepository.java b/backend/src/main/java/club/klabis/adapters/inmemorystorage/ApplicationUsersInMemoryRepository.java index a2cb9e6..78b865d 100644 --- a/backend/src/main/java/club/klabis/adapters/inmemorystorage/ApplicationUsersInMemoryRepository.java +++ b/backend/src/main/java/club/klabis/adapters/inmemorystorage/ApplicationUsersInMemoryRepository.java @@ -19,7 +19,7 @@ public Optional findByUserName(String username) { @Override public Optional findByGoogleSubject(String googleSub) { - return this.findAll().stream().filter(it -> googleSub.equals(it.getGoogleSubject())).findAny(); + return this.findAll().stream().filter(it -> it.getGoogleSubject().filter(googleSub::equals).isPresent()).findAny(); } @Override diff --git a/backend/src/main/java/club/klabis/config/authserver/LoginPageSecurityConfiguration.java b/backend/src/main/java/club/klabis/config/authserver/LoginPageSecurityConfiguration.java index ec5579e..9ec12d0 100644 --- a/backend/src/main/java/club/klabis/config/authserver/LoginPageSecurityConfiguration.java +++ b/backend/src/main/java/club/klabis/config/authserver/LoginPageSecurityConfiguration.java @@ -19,7 +19,7 @@ @Configuration(proxyBeanMethods = false) public class LoginPageSecurityConfiguration { - public static RequestMatcher UI_REQUESTS_MATCHER = new OrRequestMatcher(AntPathRequestMatcher.antMatcher("/login"), AntPathRequestMatcher.antMatcher("/oauth2/**"), AntPathRequestMatcher.antMatcher("/logout"), AntPathRequestMatcher.antMatcher("/login/**")); + public static RequestMatcher LOGIN_REQUESTS_MATCHER = new OrRequestMatcher(AntPathRequestMatcher.antMatcher("/login"), AntPathRequestMatcher.antMatcher("/oauth2/**"), AntPathRequestMatcher.antMatcher("/logout"), AntPathRequestMatcher.antMatcher("/login/**")); @Bean @Order(AuthorizationServerConfiguration.AUTH_SERVER_LOGIN_PAGE) @@ -27,8 +27,8 @@ public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, AuthenticationSuccessHandler authenticationSuccessHandler ) throws Exception { return http - .securityMatcher(UI_REQUESTS_MATCHER) - .formLogin(withDefaults()) + .securityMatcher(LOGIN_REQUESTS_MATCHER) + .formLogin(form -> form.successHandler(authenticationSuccessHandler)) .oauth2Login(oauth -> oauth.successHandler(authenticationSuccessHandler)) .build(); } diff --git a/backend/src/main/java/club/klabis/config/authserver/sociallogin/RegisterMemberFromSocialLoginHandler.java b/backend/src/main/java/club/klabis/config/authserver/sociallogin/RegisterMemberFromSocialLoginHandler.java index c2e5700..b09c20f 100644 --- a/backend/src/main/java/club/klabis/config/authserver/sociallogin/RegisterMemberFromSocialLoginHandler.java +++ b/backend/src/main/java/club/klabis/config/authserver/sociallogin/RegisterMemberFromSocialLoginHandler.java @@ -8,6 +8,7 @@ import java.util.function.Consumer; @Component +// TODO: complete link of social login to Klabis App user (currently it's not used) public class RegisterMemberFromSocialLoginHandler implements Consumer { private static final Logger LOG = LoggerFactory.getLogger(RegisterMemberFromSocialLoginHandler.class); diff --git a/backend/src/main/java/club/klabis/config/authserver/sociallogin/SocialLoginAuthenticationSuccessHandler.java b/backend/src/main/java/club/klabis/config/authserver/sociallogin/SocialLoginAuthenticationSuccessHandler.java index 0516262..c3a7c02 100644 --- a/backend/src/main/java/club/klabis/config/authserver/sociallogin/SocialLoginAuthenticationSuccessHandler.java +++ b/backend/src/main/java/club/klabis/config/authserver/sociallogin/SocialLoginAuthenticationSuccessHandler.java @@ -16,6 +16,7 @@ /** * Social login in authorization server */ +// TODO: complete link of social login to Klabis App user (currently it's not used) public class SocialLoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final AuthenticationSuccessHandler delegate = new SavedRequestAwareAuthenticationSuccessHandler(); diff --git a/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/CustomOidcUserService.java b/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/CustomOidcUserService.java new file mode 100644 index 0000000..68233b0 --- /dev/null +++ b/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/CustomOidcUserService.java @@ -0,0 +1,72 @@ +package club.klabis.config.authserver.socialloginsupport; + +import club.klabis.config.authserver.KlabisOidcUser; +import club.klabis.domain.appusers.ApplicationUser; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.OidcUserInfo; +import org.springframework.security.oauth2.core.oidc.StandardClaimNames; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +class CustomOidcUserService extends OidcUserService { + private final Map mappers; + + public CustomOidcUserService(List mappers) { + this.mappers = mappers.stream().collect(Collectors.toMap(SocialLoginOidcUserToKlabisOidcUserMapper::getOAuthClientId, Function.identity())); + } + + private Optional getMapperForRegistrationId(ClientRegistration registration) { + return mappers.values().stream().filter(it -> registration.getRegistrationId().equals(it.getOAuthClientId())).findAny(); + } + + @Override + public KlabisOidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { + OidcUser oidcUser = super.loadUser(userRequest); + + SocialLoginOidcUserToKlabisOidcUserMapper mapper = getMapperForRegistrationId(userRequest.getClientRegistration()) + .orElseThrow(() -> new RuntimeException("No OIDC mapper defined for registrationId %s".formatted(userRequest.getClientRegistration().getRegistrationId()))); + + return mapper.findApplicationUserForToken(userRequest.getIdToken()) + .map(applicationUser -> createAuthentication(oidcUser.getIdToken(), oidcUser.getUserInfo(), applicationUser, List.of())) + .orElseThrow(() -> new OAuth2AuthenticationException("User with subject %s (%s) not found!".formatted(oidcUser.getSubject(), mapper.getOAuthClientId()))); + } + + KlabisOidcUser createAuthentication(OidcIdToken idToken, OidcUserInfo userInfo, ApplicationUser user, List roles) { + Set authorities = roles.stream() + .map(roleName -> new SimpleGrantedAuthority(roleName)) + .collect(Collectors.toSet()); + + Map klabisClaims = new HashMap<>(); + //claims.putAll(idToken.getClaims()); + klabisClaims.put(StandardClaimNames.SUB, user.getUsername()); + klabisClaims.put("memberId", user.getMemberId().orElse(null)); +// claims.put(StandardClaimNames.GIVEN_NAME, user.getFirstName()); +// claims.put(StandardClaimNames.MIDDLE_NAME, user.getMiddleName()); +// claims.put(StandardClaimNames.FAMILY_NAME, user.getLastName()); +// claims.put(StandardClaimNames.LOCALE, user.getLocale()); +// claims.put(StandardClaimNames.PICTURE, user.getAvatarUrl()); + + OidcIdToken customIdToken = new OidcIdToken( + idToken.getTokenValue(), idToken.getIssuedAt(), idToken.getExpiresAt(), klabisClaims + ); + + KlabisOidcUser oidcUser = new KlabisOidcUser(authorities, customIdToken, userInfo); +// oidcUser.setId(user.getId()); +// oidcUser.setUsername(user.getUsername()); +// oidcUser.setCreatedAt(user.getCreatedAt()); +// oidcUser.setActive(user.isActive()); + return oidcUser; + }; + +} \ No newline at end of file diff --git a/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/GoogleOidcUserToKlabisSocialLoginOidcUserMapper.java b/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/GoogleOidcUserToKlabisSocialLoginOidcUserMapper.java index cb2fd7a..e1a4371 100644 --- a/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/GoogleOidcUserToKlabisSocialLoginOidcUserMapper.java +++ b/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/GoogleOidcUserToKlabisSocialLoginOidcUserMapper.java @@ -2,20 +2,28 @@ import club.klabis.domain.appusers.ApplicationUser; import club.klabis.domain.appusers.ApplicationUsersRepository; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.stereotype.Component; import java.util.Optional; -import java.util.function.Function; @Component class GoogleOidcUserToKlabisSocialLoginOidcUserMapper implements SocialLoginOidcUserToKlabisOidcUserMapper { + + private final ApplicationUsersRepository userService; + + GoogleOidcUserToKlabisSocialLoginOidcUserMapper(ApplicationUsersRepository userService) { + this.userService = userService; + } + @Override - public String getRegistration() { + public String getOAuthClientId() { return "google"; } @Override - public Function> findMemberFunction(ApplicationUsersRepository memberService) { - return memberService::findByGoogleSubject; + public Optional findApplicationUserForToken(OidcIdToken token) { + return userService.findByGoogleSubject(token.getSubject()); } + } diff --git a/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/SocialLoginOidcUserToKlabisOidcUserMapper.java b/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/SocialLoginOidcUserToKlabisOidcUserMapper.java index 8a15647..b3e75d1 100644 --- a/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/SocialLoginOidcUserToKlabisOidcUserMapper.java +++ b/backend/src/main/java/club/klabis/config/authserver/socialloginsupport/SocialLoginOidcUserToKlabisOidcUserMapper.java @@ -1,52 +1,16 @@ package club.klabis.config.authserver.socialloginsupport; -import club.klabis.config.authserver.KlabisOidcUser; import club.klabis.domain.appusers.ApplicationUser; -import club.klabis.domain.appusers.ApplicationUsersRepository; -import club.klabis.domain.members.Member; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.core.oidc.StandardClaimNames; -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; +import java.util.Optional; public interface SocialLoginOidcUserToKlabisOidcUserMapper { /** * Returns registration ID for which is mapper supposed to be used */ - String getRegistration(); + String getOAuthClientId(); - Function> findMemberFunction(ApplicationUsersRepository memberService); - - default KlabisOidcUser map(OidcIdToken idToken, OidcUserInfo userInfo, ApplicationUser user, List roles) { - Set authorities = roles.stream() - .map(roleName -> new SimpleGrantedAuthority(roleName)) - .collect(Collectors.toSet()); - - Map klabisClaims = new HashMap<>(); - //claims.putAll(idToken.getClaims()); - klabisClaims.put(StandardClaimNames.SUB, user.getUsername()); - klabisClaims.put("memberId", user.getMemberId().orElse(null)); -// claims.put(StandardClaimNames.GIVEN_NAME, user.getFirstName()); -// claims.put(StandardClaimNames.MIDDLE_NAME, user.getMiddleName()); -// claims.put(StandardClaimNames.FAMILY_NAME, user.getLastName()); -// claims.put(StandardClaimNames.LOCALE, user.getLocale()); -// claims.put(StandardClaimNames.PICTURE, user.getAvatarUrl()); - - OidcIdToken customIdToken = new OidcIdToken( - idToken.getTokenValue(), idToken.getIssuedAt(), idToken.getExpiresAt(), klabisClaims - ); - - KlabisOidcUser oidcUser = new KlabisOidcUser(authorities, customIdToken, userInfo); -// oidcUser.setId(user.getId()); -// oidcUser.setUsername(user.getUsername()); -// oidcUser.setCreatedAt(user.getCreatedAt()); -// oidcUser.setActive(user.isActive()); - return oidcUser; - }; + Optional findApplicationUserForToken(OidcIdToken token); }