Implement OIDC, GitHub, Google attribute extractor

This commit is contained in:
2025-06-11 01:18:53 +05:00
parent aa7ccc45ea
commit 76bc5d4853
20 changed files with 430 additions and 54 deletions

View File

@ -4,6 +4,8 @@ import com.backend.user.service.anyame.entity.User;
import com.backend.user.service.anyame.model.AuthorizedUser; import com.backend.user.service.anyame.model.AuthorizedUser;
import com.backend.user.service.anyame.service.UserAuthorityService; import com.backend.user.service.anyame.service.UserAuthorityService;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Collections; import java.util.Collections;
@ -38,6 +40,28 @@ public class AuthorizedUserMapper {
.username(userEntity.getName()) .username(userEntity.getName())
.active(userEntity.isActive()) .active(userEntity.isActive())
.authorities(authorities) .authorities(authorities)
.attributes(attributes)
.build(); .build();
} }
public AuthorizedUser mapOidcUser(User userEntity, Map<String, Object> attributes, OidcIdToken idToken, OidcUserInfo userInfo) {
Set<GrantedAuthority> authorities = authorityService.getUserAuthorities(userEntity);
AuthorizedUser.Builder builder = new AuthorizedUser.Builder(userEntity.getEmail(), "", Collections.emptyList())
.id(userEntity.getId())
.username(userEntity.getName())
.active(userEntity.isActive())
.authorities(authorities)
.attributes(attributes);
if (idToken != null) {
builder.oidcIdToken(idToken);
}
if (userInfo != null) {
builder.oidcUserInfo(userInfo);
}
return builder.build();
}
} }

View File

@ -0,0 +1,19 @@
package com.backend.user.service.anyame.config;
import com.backend.user.service.anyame.exception.UnsupportedOAuth2ProviderException;
import com.backend.user.service.anyame.service.extractor.OAuth2AttributeExtractorFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ServiceLocatorFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OAuth2AttributeExtractorConfig {
@Bean("oauth2AttributeExtractorFactory")
public FactoryBean<?> serviceLocatorFactoryBean() {
ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
factoryBean.setServiceLocatorInterface(OAuth2AttributeExtractorFactory.class);
factoryBean.setServiceLocatorExceptionClass(UnsupportedOAuth2ProviderException.class);
return factoryBean;
}
}

View File

@ -0,0 +1,19 @@
package com.backend.user.service.anyame.config;
import com.backend.user.service.anyame.exception.UnsupportedOidcProviderException;
import com.backend.user.service.anyame.service.extractor.OidcAttributeExtractorFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ServiceLocatorFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OidcAttributeExtractorConfig {
@Bean("oidcAttributeExtractorFactory")
public FactoryBean<?> serviceLocatorFactoryBean() {
ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
factoryBean.setServiceLocatorInterface(OidcAttributeExtractorFactory.class);
factoryBean.setServiceLocatorExceptionClass(UnsupportedOidcProviderException.class);
return factoryBean;
}
}

View File

@ -1,25 +0,0 @@
package com.backend.user.service.anyame.configurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
public class SocialConfigurer extends AbstractHttpConfigurer<SocialConfigurer, HttpSecurity> {
private AuthenticationFailureHandler failureHandler;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
@Override
public void init(HttpSecurity http) throws Exception {
http.oauth2Login(c -> {
if (this.successHandler != null) {
c.successHandler(this.successHandler);
}
if (this.failureHandler != null) {
c.failureHandler(this.failureHandler);
}
});
}
}

View File

@ -1,11 +1,15 @@
package com.backend.user.service.anyame.controller; package com.backend.user.service.anyame.controller;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.Collection;
import java.util.stream.Collectors;
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
public class HelloController { public class HelloController {
@ -17,7 +21,8 @@ public class HelloController {
@GetMapping("/principalhello") @GetMapping("/principalhello")
public String principalhello() { public String principalhello() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication.getName(); Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
return String.format("%s, %s", authentication.getName(), authorities.stream().map(GrantedAuthority::toString).collect(Collectors.joining(",")));
} }
} }

View File

@ -0,0 +1,11 @@
package com.backend.user.service.anyame.exception;
public class UnsupportedOAuth2ProviderException extends Exception {
public UnsupportedOAuth2ProviderException(String message, Throwable cause) {
super(message, cause);
}
public UnsupportedOAuth2ProviderException(String message) {
super(message);
}
}

View File

@ -0,0 +1,11 @@
package com.backend.user.service.anyame.exception;
public class UnsupportedOidcProviderException extends Exception {
public UnsupportedOidcProviderException(String message, Throwable cause) {
super(message, cause);
}
public UnsupportedOidcProviderException(String message) {
super(message);
}
}

View File

@ -1,20 +1,22 @@
package com.backend.user.service.anyame.model; package com.backend.user.service.anyame.model;
import com.backend.user.service.anyame.entity.Role;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Set;
public class AuthorizedUser extends User implements OAuth2User { public class AuthorizedUser extends User implements OAuth2User, OidcUser {
private Long id; private Long id;
private String email; private String email;
private boolean active; private boolean active;
private Set<Role> roles;
private Map<String, Object> attributes; private Map<String, Object> attributes;
private OidcUserInfo userInfo;
private OidcIdToken idToken;
public Long getId() { public Long getId() {
return id; return id;
@ -41,6 +43,9 @@ public class AuthorizedUser extends User implements OAuth2User {
this.id = builder.id; this.id = builder.id;
this.email = builder.email; this.email = builder.email;
this.active = builder.active; this.active = builder.active;
this.attributes = builder.attributes;
this.userInfo = builder.oidcUserInfo;
this.idToken = builder.oidcIdToken;
} }
@Override @Override
@ -53,6 +58,21 @@ public class AuthorizedUser extends User implements OAuth2User {
return getUsername(); return getUsername();
} }
@Override
public Map<String, Object> getClaims() {
return attributes;
}
@Override
public OidcUserInfo getUserInfo() {
return userInfo;
}
@Override
public OidcIdToken getIdToken() {
return idToken;
}
public static class Builder { public static class Builder {
private String email; private String email;
private String password; private String password;
@ -64,6 +84,9 @@ public class AuthorizedUser extends User implements OAuth2User {
private boolean accountNonExpired = true; private boolean accountNonExpired = true;
private boolean credentialsNonExpired = true; private boolean credentialsNonExpired = true;
private boolean accountNonLocked = true; private boolean accountNonLocked = true;
private Map<String, Object> attributes;
private OidcUserInfo oidcUserInfo;
private OidcIdToken oidcIdToken;
public Builder(String email, String password, Collection<? extends GrantedAuthority> authorities) { public Builder(String email, String password, Collection<? extends GrantedAuthority> authorities) {
this.email = email; this.email = email;
@ -121,6 +144,23 @@ public class AuthorizedUser extends User implements OAuth2User {
return this; return this;
} }
public Builder attributes(Map<String, Object> attributes) {
this.attributes = attributes;
return this;
}
public Builder oidcUserInfo(OidcUserInfo oidcUserInfo) {
this.oidcUserInfo = oidcUserInfo;
return this;
}
public Builder oidcIdToken(OidcIdToken oidcIdToken) {
this.oidcIdToken = oidcIdToken;
return this;
}
public AuthorizedUser build() { public AuthorizedUser build() {
return new AuthorizedUser(this); return new AuthorizedUser(this);
} }

View File

@ -10,9 +10,7 @@ import java.util.Optional;
@Repository @Repository
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email); Optional<User> findByName(String name);
Optional<User> findByEmailOrName(String email, String name);
@Query("SELECT u FROM User u " + @Query("SELECT u FROM User u " +
"LEFT JOIN FETCH u.roles " + "LEFT JOIN FETCH u.roles " +

View File

@ -5,6 +5,8 @@ import com.backend.user.service.anyame.entity.Role;
import com.backend.user.service.anyame.entity.User; import com.backend.user.service.anyame.entity.User;
import com.backend.user.service.anyame.repository.RoleRepository; import com.backend.user.service.anyame.repository.RoleRepository;
import com.backend.user.service.anyame.repository.UserRepository; import com.backend.user.service.anyame.repository.UserRepository;
import com.backend.user.service.anyame.service.extractor.OAuth2AttributeExtractor;
import com.backend.user.service.anyame.service.extractor.OAuth2AttributeExtractorFactory;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
@ -14,6 +16,8 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Map;
@Service @Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> { public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private static final Logger log = LoggerFactory.getLogger(CustomOAuth2UserService.class); private static final Logger log = LoggerFactory.getLogger(CustomOAuth2UserService.class);
@ -21,11 +25,16 @@ public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequ
private final RoleRepository roleRepository; private final RoleRepository roleRepository;
private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
private final AuthorizedUserMapper authorizedUserMapper; private final AuthorizedUserMapper authorizedUserMapper;
private final OAuth2AttributeExtractorFactory attributeExtractorFactory;
public CustomOAuth2UserService(UserRepository userRepository, RoleRepository roleRepository, AuthorizedUserMapper authorizedUserMapper) { public CustomOAuth2UserService(UserRepository userRepository,
RoleRepository roleRepository,
AuthorizedUserMapper authorizedUserMapper,
OAuth2AttributeExtractorFactory attributeExtractorFactory) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.roleRepository = roleRepository; this.roleRepository = roleRepository;
this.authorizedUserMapper = authorizedUserMapper; this.authorizedUserMapper = authorizedUserMapper;
this.attributeExtractorFactory = attributeExtractorFactory;
} }
@Override @Override
@ -36,28 +45,41 @@ public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequ
} }
private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oauth2User) { private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oauth2User) {
if (!oauth2User.getAttributes().containsKey("id")) {
throw new OAuth2AuthenticationException("oauth2User id is not provided");
}
if (!oauth2User.getAttributes().containsKey("name")) {
throw new OAuth2AuthenticationException("oauth2User name is not provided");
}
log.info("NAME IS: {}, {}, {}", oauth2User.getName(), oauth2User.getAttribute("login"), oauth2User.getAttribute("name"));
log.info("NAME IS: {}, {}, {}", oauth2User.getName(), oauth2User.getAttribute("login"), oauth2User.getAttribute("name"));
String registrationId = userRequest.getClientRegistration().getRegistrationId(); String registrationId = userRequest.getClientRegistration().getRegistrationId();
String email = oauth2User.getAttribute("email");
String name = oauth2User.getAttribute("name");
String providerId = oauth2User.getAttribute("id").toString();
User user = userRepository.findByEmail(email) OAuth2AttributeExtractor extractor = attributeExtractorFactory.create(registrationId);
if (extractor == null) {
throw new OAuth2AuthenticationException("Unsupported OAuth2 provider: " + registrationId);
}
Map<String, Object> attributes = oauth2User.getAttributes();
String email = extractor.extractEmail(attributes);
String name = extractor.extractName(attributes);
String providerId = extractor.extractId(attributes);
String avatarUrl = extractor.extractAvatarUrl(attributes);
if (name == null || name.trim().isEmpty()) {
throw new OAuth2AuthenticationException("Name is required but not provided by " + registrationId);
}
if (providerId == null || providerId.trim().isEmpty()) {
throw new OAuth2AuthenticationException("User ID is required but not provided by " + registrationId);
}
log.info("OAuth2 User - Provider: {}, Email: {}, Name: {}, ID: {}",
registrationId, email, name, providerId);
User user = userRepository.findByName(name)
.orElseGet(() -> createNewUser(email, name, registrationId, providerId)); .orElseGet(() -> createNewUser(email, name, registrationId, providerId));
// TODO: Should be toggleable nor documented behaviour // TODO: Should be toggleable nor documented behaviour
// First user becomes admin // First user becomes admin
if (userRepository.count() == 1) { if (user.getId() == 1 && !user.isActive()) {
Role adminRole = roleRepository.findByName("ADMIN") Role adminRole = roleRepository.findByName("ADMIN")
.orElseThrow(() -> new RuntimeException("Admin role not found")); .orElseThrow(() -> new RuntimeException("Admin role not found"));
user.getRoles().add(adminRole); user.getRoles().add(adminRole);
user.setActive(true);
userRepository.save(user); userRepository.save(user);
} }
@ -66,7 +88,6 @@ public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequ
private User createNewUser(String email, String name, String provider, String providerId) { private User createNewUser(String email, String name, String provider, String providerId) {
User user = new User(name, email, provider, providerId); User user = new User(name, email, provider, providerId);
user.setActive(true);
Role userRole = roleRepository.findByName("USER") Role userRole = roleRepository.findByName("USER")
.orElseThrow(() -> new RuntimeException("User role not found")); .orElseThrow(() -> new RuntimeException("User role not found"));

View File

@ -0,0 +1,100 @@
package com.backend.user.service.anyame.service;
import com.backend.user.service.anyame.component.AuthorizedUserMapper;
import com.backend.user.service.anyame.entity.Role;
import com.backend.user.service.anyame.entity.User;
import com.backend.user.service.anyame.repository.RoleRepository;
import com.backend.user.service.anyame.repository.UserRepository;
import com.backend.user.service.anyame.service.extractor.OidcAttributeExtractor;
import com.backend.user.service.anyame.service.extractor.OidcAttributeExtractorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
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.user.OidcUser;
import org.springframework.stereotype.Service;
@Service
public class CustomOIDCUserService extends OidcUserService {
private static final Logger log = LoggerFactory.getLogger(CustomOIDCUserService.class);
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final OidcUserService delegate = new OidcUserService();
private final AuthorizedUserMapper authorizedUserMapper;
private final OidcAttributeExtractorFactory attributeExtractorFactory;
public CustomOIDCUserService(UserRepository userRepository,
RoleRepository roleRepository,
AuthorizedUserMapper authorizedUserMapper,
OidcAttributeExtractorFactory attributeExtractorFactory) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.authorizedUserMapper = authorizedUserMapper;
this.attributeExtractorFactory = attributeExtractorFactory;
}
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser oidcUser = delegate.loadUser(userRequest);
return processOIDCUser(userRequest, oidcUser);
}
private OidcUser processOIDCUser(OidcUserRequest userRequest, OidcUser oidcUser) {
String registrationId = userRequest.getClientRegistration().getRegistrationId();
OidcAttributeExtractor extractor = attributeExtractorFactory.create(registrationId);
if (extractor == null) {
throw new OAuth2AuthenticationException("Unsupported OIDC provider: " + registrationId);
}
OidcIdToken idToken = oidcUser.getIdToken();
OidcUserInfo userInfo = oidcUser.getUserInfo();
String email = extractor.extractEmail(idToken, userInfo);
String name = extractor.extractName(idToken, userInfo);
String subject = extractor.extractSubject(idToken, userInfo);
String preferredUsername = extractor.extractPreferredUsername(idToken, userInfo);
String picture = extractor.extractPicture(idToken, userInfo);
String givenName = extractor.extractGivenName(idToken, userInfo);
String familyName = extractor.extractFamilyName(idToken, userInfo);
if (email == null || email.trim().isEmpty()) {
throw new OAuth2AuthenticationException("Email is required but not provided by " + registrationId);
}
if (subject == null || subject.trim().isEmpty()) {
throw new OAuth2AuthenticationException("Subject is required but not provided by " + registrationId);
}
log.info("OIDC User - Provider: {}, Email: {}, Name: {}, Subject: {}",
registrationId, email, name, subject);
User user = userRepository.findByName(name)
.orElseGet(() -> createNewUser(email, name, registrationId, subject, picture, preferredUsername));
// TODO: Should be toggleable nor documented behaviour
// First user becomes admin
if (user.getId() == 1 && !user.isActive()) {
Role adminRole = roleRepository.findByName("ADMIN")
.orElseThrow(() -> new RuntimeException("Admin role not found"));
user.getRoles().add(adminRole);
user.setActive(true);
userRepository.save(user);
}
return authorizedUserMapper.mapOidcUser(user, oidcUser.getAttributes(), idToken, userInfo);
}
private User createNewUser(String email, String name, String provider, String subject, String picture, String preferredUsername) {
User user = new User(name, email, provider, subject);
Role userRole = roleRepository.findByName("USER")
.orElseThrow(() -> new RuntimeException("User role not found"));
user.getRoles().add(userRole);
return userRepository.save(user);
}
}

View File

@ -22,7 +22,7 @@ public class CustomUserDetailsService implements UserDetailsService {
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> userOptional = userRepository.findByEmailOrName(username, username); Optional<User> userOptional = userRepository.findByName(username);
if (userOptional.isEmpty()) { if (userOptional.isEmpty()) {
throw new UsernameNotFoundException("user not found " + username); throw new UsernameNotFoundException("user not found " + username);
} }

View File

@ -2,7 +2,6 @@ package com.backend.user.service.anyame.service;
import com.backend.user.service.anyame.entity.User; import com.backend.user.service.anyame.entity.User;
import com.backend.user.service.anyame.repository.PermissionRepository; import com.backend.user.service.anyame.repository.PermissionRepository;
import com.backend.user.service.anyame.repository.UserRepository;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -15,11 +14,9 @@ import java.util.Set;
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class UserAuthorityService { public class UserAuthorityService {
private final UserRepository userRepository;
private final PermissionRepository permissionRepository; private final PermissionRepository permissionRepository;
public UserAuthorityService(UserRepository userRepository, PermissionRepository permissionRepository) { public UserAuthorityService(PermissionRepository permissionRepository) {
this.userRepository = userRepository;
this.permissionRepository = permissionRepository; this.permissionRepository = permissionRepository;
} }

View File

@ -0,0 +1,33 @@
package com.backend.user.service.anyame.service.extractor;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component("github")
public class GitHubOAuth2AttributeExtractor implements OAuth2AttributeExtractor {
@Override
public String extractEmail(Map<String, Object> attributes) {
return (String) attributes.get("email");
}
@Override
public String extractName(Map<String, Object> attributes) {
String name = (String) attributes.get("name");
if (name == null || name.trim().isEmpty()) {
name = (String) attributes.get("login");
}
return name;
}
@Override
public String extractId(Map<String, Object> attributes) {
return attributes.get("id").toString();
}
@Override
public String extractAvatarUrl(Map<String, Object> attributes) {
return (String) attributes.get("avatar_url");
}
}

View File

@ -0,0 +1,54 @@
package com.backend.user.service.anyame.service.extractor;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.stereotype.Component;
@Component("google")
public class GoogleOidcAttributeExtractor implements OidcAttributeExtractor {
@Override
public String extractEmail(OidcIdToken idToken, OidcUserInfo userInfo) {
Object email = getClaim("email", idToken, userInfo);
return email != null ? email.toString() : null;
}
@Override
public String extractName(OidcIdToken idToken, OidcUserInfo userInfo) {
Object name = getClaim("name", idToken, userInfo);
return name != null ? name.toString() : null;
}
@Override
public String extractSubject(OidcIdToken idToken, OidcUserInfo userInfo) {
// Subject is always in ID token
return idToken.getSubject();
}
@Override
public String extractPreferredUsername(OidcIdToken idToken, OidcUserInfo userInfo) {
Object username = getClaim("preferred_username", idToken, userInfo);
if (username == null) {
username = getClaim("email", idToken, userInfo); // fallback to email
}
return username != null ? username.toString() : null;
}
@Override
public String extractPicture(OidcIdToken idToken, OidcUserInfo userInfo) {
Object picture = getClaim("picture", idToken, userInfo);
return picture != null ? picture.toString() : null;
}
@Override
public String extractGivenName(OidcIdToken idToken, OidcUserInfo userInfo) {
Object givenName = getClaim("given_name", idToken, userInfo);
return givenName != null ? givenName.toString() : null;
}
@Override
public String extractFamilyName(OidcIdToken idToken, OidcUserInfo userInfo) {
Object familyName = getClaim("family_name", idToken, userInfo);
return familyName != null ? familyName.toString() : null;
}
}

View File

@ -0,0 +1,13 @@
package com.backend.user.service.anyame.service.extractor;
import java.util.Map;
public interface OAuth2AttributeExtractor {
String extractEmail(Map<String, Object> attributes);
String extractName(Map<String, Object> attributes);
String extractId(Map<String, Object> attributes);
String extractAvatarUrl(Map<String, Object> attributes);
}

View File

@ -0,0 +1,5 @@
package com.backend.user.service.anyame.service.extractor;
public interface OAuth2AttributeExtractorFactory {
OAuth2AttributeExtractor create(String provider);
}

View File

@ -0,0 +1,46 @@
package com.backend.user.service.anyame.service.extractor;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
public interface OidcAttributeExtractor {
String extractEmail(OidcIdToken idToken, OidcUserInfo userInfo);
String extractName(OidcIdToken idToken, OidcUserInfo userInfo);
String extractSubject(OidcIdToken idToken, OidcUserInfo userInfo);
String extractPreferredUsername(OidcIdToken idToken, OidcUserInfo userInfo);
String extractPicture(OidcIdToken idToken, OidcUserInfo userInfo);
String extractGivenName(OidcIdToken idToken, OidcUserInfo userInfo);
String extractFamilyName(OidcIdToken idToken, OidcUserInfo userInfo);
default String constructName(String givenName, String familyName, String preferredUsername, String email) {
if (givenName != null && familyName != null) {
return givenName + " " + familyName;
}
if (givenName != null) {
return givenName;
}
if (familyName != null) {
return familyName;
}
if (preferredUsername != null) {
return preferredUsername;
}
return email.split("@")[0];
}
default Object getClaim(String claimName, OidcIdToken idToken, OidcUserInfo userInfo) {
if (userInfo != null && userInfo.getClaims().containsKey(claimName)) {
return userInfo.getClaim(claimName);
}
if (idToken != null && idToken.getClaims().containsKey(claimName)) {
return idToken.getClaim(claimName);
}
return null;
}
}

View File

@ -0,0 +1,5 @@
package com.backend.user.service.anyame.service.extractor;
public interface OidcAttributeExtractorFactory {
OidcAttributeExtractor create(String provider);
}

View File

@ -29,7 +29,7 @@ spring:
default-schema: main default-schema: main
jpa: jpa:
hibernate: hibernate:
ddl-auto: create ddl-auto: none
logging: logging:
level: level: