Implement OIDC, GitHub, Google attribute extractor
This commit is contained in:
@ -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.service.UserAuthorityService;
|
||||
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 java.util.Collections;
|
||||
@ -38,6 +40,28 @@ public class AuthorizedUserMapper {
|
||||
.username(userEntity.getName())
|
||||
.active(userEntity.isActive())
|
||||
.authorities(authorities)
|
||||
.attributes(attributes)
|
||||
.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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
package com.backend.user.service.anyame.controller;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class HelloController {
|
||||
@ -17,7 +21,8 @@ public class HelloController {
|
||||
@GetMapping("/principalhello")
|
||||
public String principalhello() {
|
||||
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(",")));
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,20 +1,22 @@
|
||||
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.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 java.util.Collection;
|
||||
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 String email;
|
||||
private boolean active;
|
||||
private Set<Role> roles;
|
||||
private Map<String, Object> attributes;
|
||||
private OidcUserInfo userInfo;
|
||||
private OidcIdToken idToken;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
@ -41,6 +43,9 @@ public class AuthorizedUser extends User implements OAuth2User {
|
||||
this.id = builder.id;
|
||||
this.email = builder.email;
|
||||
this.active = builder.active;
|
||||
this.attributes = builder.attributes;
|
||||
this.userInfo = builder.oidcUserInfo;
|
||||
this.idToken = builder.oidcIdToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -53,6 +58,21 @@ public class AuthorizedUser extends User implements OAuth2User {
|
||||
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 {
|
||||
private String email;
|
||||
private String password;
|
||||
@ -64,6 +84,9 @@ public class AuthorizedUser extends User implements OAuth2User {
|
||||
private boolean accountNonExpired = true;
|
||||
private boolean credentialsNonExpired = 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) {
|
||||
this.email = email;
|
||||
@ -121,6 +144,23 @@ public class AuthorizedUser extends User implements OAuth2User {
|
||||
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() {
|
||||
return new AuthorizedUser(this);
|
||||
}
|
||||
|
@ -10,9 +10,7 @@ import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
Optional<User> findByEmail(String email);
|
||||
|
||||
Optional<User> findByEmailOrName(String email, String name);
|
||||
Optional<User> findByName(String name);
|
||||
|
||||
@Query("SELECT u FROM User u " +
|
||||
"LEFT JOIN FETCH u.roles " +
|
||||
|
@ -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.repository.RoleRepository;
|
||||
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.LoggerFactory;
|
||||
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.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
|
||||
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 DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
|
||||
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.roleRepository = roleRepository;
|
||||
this.authorizedUserMapper = authorizedUserMapper;
|
||||
this.attributeExtractorFactory = attributeExtractorFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -36,28 +45,41 @@ public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequ
|
||||
}
|
||||
|
||||
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 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));
|
||||
|
||||
// TODO: Should be toggleable nor documented behaviour
|
||||
// First user becomes admin
|
||||
if (userRepository.count() == 1) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -66,7 +88,6 @@ public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequ
|
||||
|
||||
private User createNewUser(String email, String name, String provider, String providerId) {
|
||||
User user = new User(name, email, provider, providerId);
|
||||
user.setActive(true);
|
||||
|
||||
Role userRole = roleRepository.findByName("USER")
|
||||
.orElseThrow(() -> new RuntimeException("User role not found"));
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ public class CustomUserDetailsService implements UserDetailsService {
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
Optional<User> userOptional = userRepository.findByEmailOrName(username, username);
|
||||
Optional<User> userOptional = userRepository.findByName(username);
|
||||
if (userOptional.isEmpty()) {
|
||||
throw new UsernameNotFoundException("user not found " + username);
|
||||
}
|
||||
|
@ -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.repository.PermissionRepository;
|
||||
import com.backend.user.service.anyame.repository.UserRepository;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -15,11 +14,9 @@ import java.util.Set;
|
||||
@Transactional(readOnly = true)
|
||||
public class UserAuthorityService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final PermissionRepository permissionRepository;
|
||||
|
||||
public UserAuthorityService(UserRepository userRepository, PermissionRepository permissionRepository) {
|
||||
this.userRepository = userRepository;
|
||||
public UserAuthorityService(PermissionRepository permissionRepository) {
|
||||
this.permissionRepository = permissionRepository;
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.backend.user.service.anyame.service.extractor;
|
||||
|
||||
public interface OAuth2AttributeExtractorFactory {
|
||||
OAuth2AttributeExtractor create(String provider);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.backend.user.service.anyame.service.extractor;
|
||||
|
||||
public interface OidcAttributeExtractorFactory {
|
||||
OidcAttributeExtractor create(String provider);
|
||||
}
|
@ -29,7 +29,7 @@ spring:
|
||||
default-schema: main
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: create
|
||||
ddl-auto: none
|
||||
|
||||
logging:
|
||||
level:
|
||||
|
Reference in New Issue
Block a user