Initial implementation of CustomUserDetailsService

This commit is contained in:
2025-06-10 02:06:15 +05:00
parent 2b43e8f7a6
commit aa7ccc45ea
17 changed files with 866 additions and 118 deletions

221
pom.xml
View File

@ -1,113 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.backend.user.service</groupId>
<artifactId>anyame-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>anyame-backend</name>
<description>User service for anyame</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.backend.user.service</groupId>
<artifactId>anyame-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>anyame-backend</name>
<description>User service for anyame</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>21</java.version>
<jjwt.version>0.12.6</jjwt.version>
<spring-dotenv.version>4.0.0</spring-dotenv.version>
<passay.version>1.6.6</passay.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
</dependency>
<jjwt.version>0.12.6</jjwt.version>
<spring-dotenv.version>4.0.0</spring-dotenv.version>
<passay.version>1.6.6</passay.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>me.paulschwarz</groupId>
<artifactId>spring-dotenv</artifactId>
<version>${spring-dotenv.version}</version>
</dependency>
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>${passay.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>me.paulschwarz</groupId>
<artifactId>spring-dotenv</artifactId>
<version>${spring-dotenv.version}</version>
</dependency>
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>${passay.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,101 @@
package com.backend.user.service.anyame;
import com.backend.user.service.anyame.entity.Permission;
import com.backend.user.service.anyame.entity.Role;
import com.backend.user.service.anyame.repository.PermissionRepository;
import com.backend.user.service.anyame.repository.RoleRepository;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
public class RoleDataInitializer implements ApplicationRunner {
private final RoleRepository roleRepository;
private final PermissionRepository permissionRepository;
public RoleDataInitializer(RoleRepository roleRepository, PermissionRepository permissionRepository) {
this.roleRepository = roleRepository;
this.permissionRepository = permissionRepository;
}
@Override
public void run(ApplicationArguments args) {
if (permissionRepository.count() == 0) {
initializePermissions();
initializeRoles();
}
}
private void initializePermissions() {
savePermission("CREATE_ANIME");
savePermission("EDIT_ANIME");
savePermission("DELETE_ANIME");
savePermission("APPROVE_ANIME");
savePermission("VIEW_USERS");
savePermission("EDIT_USER");
savePermission("BAN_USER");
savePermission("DELETE_USER");
savePermission("MODERATE_COMMENTS");
savePermission("MODERATE_REVIEWS");
savePermission("HANDLE_REPORTS");
savePermission("BLACKLIST_CONTENT");
savePermission("MANAGE_ROLES");
savePermission("VIEW_ANALYTICS");
savePermission("SYSTEM_CONFIG");
savePermission("BACKUP_DATA");
savePermission("VIEW_PREMIUM_CONTENT");
savePermission("DOWNLOAD_CONTENT");
}
private void initializeRoles() {
Role userRole = saveRole("USER");
Role moderatorRole = saveRole("MODERATOR");
Role adminRole = saveRole("ADMIN");
moderatorRole.getParentRoles().add(userRole);
moderatorRole.getPermissions().addAll(Arrays.asList(
findPermission("MODERATE_COMMENTS"),
findPermission("MODERATE_REVIEWS"),
findPermission("HANDLE_REPORTS"),
findPermission("EDIT_ANIME"),
findPermission("APPROVE_ANIME"),
findPermission("VIEW_USERS")
));
adminRole.getParentRoles().add(moderatorRole);
adminRole.getPermissions().addAll(Arrays.asList(
findPermission("DELETE_ANIME"),
findPermission("EDIT_USER"),
findPermission("BAN_USER"),
findPermission("DELETE_USER"),
findPermission("MANAGE_ROLES"),
findPermission("VIEW_ANALYTICS"),
findPermission("SYSTEM_CONFIG"),
findPermission("BACKUP_DATA"),
findPermission("BLACKLIST_CONTENT")
));
roleRepository.saveAll(Arrays.asList(moderatorRole, adminRole));
}
private Permission savePermission(String name) {
Permission permission = new Permission(name);
return permissionRepository.save(permission);
}
private Role saveRole(String name) {
Role role = new Role(name);
return roleRepository.save(role);
}
private Permission findPermission(String name) {
return permissionRepository.findByName(name)
.orElseThrow(() -> new RuntimeException("Permission not found: " + name));
}
}

View File

@ -0,0 +1,43 @@
package com.backend.user.service.anyame.component;
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.stereotype.Component;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@Component
public class AuthorizedUserMapper {
private final UserAuthorityService authorityService;
public AuthorizedUserMapper(UserAuthorityService authorityService) {
this.authorityService = authorityService;
}
public AuthorizedUser mapFormLogin(User userEntity) {
Set<GrantedAuthority> authorities = authorityService.getUserAuthorities(userEntity);
return new AuthorizedUser.Builder(userEntity.getEmail(), userEntity.getPassword(), Collections.emptyList())
.id(userEntity.getId())
.username(userEntity.getName())
.active(userEntity.isActive())
.authorities(authorities)
.accountNonExpired(true)
.credentialsNonExpired(true)
.accountNonLocked(true)
.build();
}
public AuthorizedUser mapOAuth2User(User userEntity, Map<String, Object> attributes) {
Set<GrantedAuthority> authorities = authorityService.getUserAuthorities(userEntity);
return new AuthorizedUser.Builder(userEntity.getEmail(), "", Collections.emptyList())
.id(userEntity.getId())
.username(userEntity.getName())
.active(userEntity.isActive())
.authorities(authorities)
.build();
}
}

View File

@ -1,22 +1,35 @@
package com.backend.user.service.anyame.config;
import com.backend.user.service.anyame.service.CustomOAuth2UserService;
import com.backend.user.service.anyame.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfigurationSource;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final CustomUserDetailsService userDetailsService;
private final CustomOAuth2UserService oauth2UserService;
public SecurityConfig(CustomUserDetailsService userDetailsService,
CustomOAuth2UserService oauth2UserService) {
this.userDetailsService = userDetailsService;
this.oauth2UserService = oauth2UserService;
}
@Bean
@Order(2)
public SecurityFilterChain filterChain(HttpSecurity http, @Qualifier("corsConfigurationSource") CorsConfigurationSource configurationSource) throws Exception {
@ -26,17 +39,17 @@ public class SecurityConfig {
c.anyRequest().authenticated()
)
.formLogin(withDefaults())
.oauth2Login(c ->
c.userInfoEndpoint(userInfo -> userInfo
.userService(oauth2UserService)
)
)
.userDetailsService(userDetailsService)
.build();
}
@Bean
public UserDetailsService users() {
UserDetails user = User.builder()
.username("admin")
.password("{noop}password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,25 @@
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,5 +1,7 @@
package com.backend.user.service.anyame.controller;
import org.springframework.security.core.Authentication;
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;
@ -11,4 +13,11 @@ public class HelloController {
public String hello() {
return "Hello World";
}
@GetMapping("/principalhello")
public String principalhello() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication.getName();
}
}

View File

@ -0,0 +1,28 @@
package com.backend.user.service.anyame.entity;
import jakarta.persistence.*;
@Entity
@Table(name = "permissions")
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name;
protected Permission() {
}
public Permission(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,64 @@
package com.backend.user.service.anyame.entity;
import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "roles")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "role_hierarchy",
joinColumns = @JoinColumn(name = "child_role_id"),
inverseJoinColumns = @JoinColumn(name = "parent_role_id")
)
private Set<Role> parentRoles = new HashSet<>();
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "role_permissions",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id")
)
private Set<Permission> permissions = new HashSet<>();
protected Role() {
}
public Role(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Set<Role> getParentRoles() {
return parentRoles;
}
public Set<Permission> getPermissions() {
return permissions;
}
public Set<Permission> getAllPermissions() {
Set<Permission> allPermissions = new HashSet<>(permissions);
for (Role parentRole : parentRoles) {
allPermissions.addAll(parentRole.getAllPermissions());
}
return allPermissions;
}
}

View File

@ -0,0 +1,88 @@
package com.backend.user.service.anyame.entity;
import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String name;
@Column(unique = true)
private String email;
private String password;
private boolean active;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
private String provider;
private String providerId;
protected User() {
}
public User(String name, String email, String provider, String providerId) {
this.name = name;
this.email = email;
this.provider = provider;
this.providerId = providerId;
}
public Set<Role> getRoles() {
return roles;
}
public Set<Permission> getAllPermissions() {
Set<Permission> allPermissions = new HashSet<>();
for (Role role : roles) {
allPermissions.addAll(role.getAllPermissions());
}
return allPermissions;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public String getProvider() {
return provider;
}
public String getProviderId() {
return providerId;
}
}

View File

@ -0,0 +1,136 @@
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.user.OAuth2User;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
public class AuthorizedUser extends User implements OAuth2User {
private Long id;
private String email;
private boolean active;
private Set<Role> roles;
private Map<String, Object> attributes;
public Long getId() {
return id;
}
public String getEmail() {
return email;
}
public boolean isActive() {
return active;
}
private AuthorizedUser(Builder builder) {
super(
builder.username,
builder.password,
builder.enabled,
builder.accountNonExpired,
builder.credentialsNonExpired,
builder.accountNonLocked,
builder.authorities
);
this.id = builder.id;
this.email = builder.email;
this.active = builder.active;
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getName() {
return getUsername();
}
public static class Builder {
private String email;
private String password;
private Collection<? extends GrantedAuthority> authorities;
private Long id;
private String username;
private boolean active;
private boolean enabled = true;
private boolean accountNonExpired = true;
private boolean credentialsNonExpired = true;
private boolean accountNonLocked = true;
public Builder(String email, String password, Collection<? extends GrantedAuthority> authorities) {
this.email = email;
this.password = password;
this.authorities = authorities;
}
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder username(String username) {
this.username = username;
return this;
}
public Builder password(String password) {
this.password = password;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder active(boolean active) {
this.active = active;
return this;
}
public Builder enabled(boolean enabled) {
this.enabled = enabled;
return this;
}
public Builder accountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
return this;
}
public Builder credentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
return this;
}
public Builder accountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
return this;
}
public Builder authorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
return this;
}
public AuthorizedUser build() {
return new AuthorizedUser(this);
}
}
public AuthorizedUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public AuthorizedUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
}

View File

@ -0,0 +1,21 @@
package com.backend.user.service.anyame.repository;
import com.backend.user.service.anyame.entity.Permission;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import java.util.Set;
@Repository
public interface PermissionRepository extends JpaRepository<Permission, Long> {
Optional<Permission> findByName(String name);
@Query("SELECT DISTINCT p FROM User u " +
"JOIN u.roles r " +
"JOIN r.permissions p " +
"WHERE u.name = :name")
Set<Permission> findByUserName(@Param("name") String name);
}

View File

@ -0,0 +1,22 @@
package com.backend.user.service.anyame.repository;
import com.backend.user.service.anyame.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Optional<Role> findByName(String name);
@Query("SELECT r FROM Role r " +
"LEFT JOIN FETCH r.permissions " +
"LEFT JOIN FETCH r.parentRoles " +
"WHERE r.id IN :roleIds")
List<Role> findRolesWithPermissionsAndParents(@Param("roleIds") Set<Long> roleIds);
}

View File

@ -0,0 +1,21 @@
package com.backend.user.service.anyame.repository;
import com.backend.user.service.anyame.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByEmailOrName(String email, String name);
@Query("SELECT u FROM User u " +
"LEFT JOIN FETCH u.roles " +
"WHERE u.name = :name")
Optional<User> findByNameWithRoles(@Param("name") String name);
}

View File

@ -0,0 +1,77 @@
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private static final Logger log = LoggerFactory.getLogger(CustomOAuth2UserService.class);
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
private final AuthorizedUserMapper authorizedUserMapper;
public CustomOAuth2UserService(UserRepository userRepository, RoleRepository roleRepository, AuthorizedUserMapper authorizedUserMapper) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.authorizedUserMapper = authorizedUserMapper;
}
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oauth2User = delegate.loadUser(userRequest);
return processOAuth2User(userRequest, 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 email = oauth2User.getAttribute("email");
String name = oauth2User.getAttribute("name");
String providerId = oauth2User.getAttribute("id").toString();
User user = userRepository.findByEmail(email)
.orElseGet(() -> createNewUser(email, name, registrationId, providerId));
// TODO: Should be toggleable nor documented behaviour
// First user becomes admin
if (userRepository.count() == 1) {
Role adminRole = roleRepository.findByName("ADMIN")
.orElseThrow(() -> new RuntimeException("Admin role not found"));
user.getRoles().add(adminRole);
userRepository.save(user);
}
return authorizedUserMapper.mapOAuth2User(user, oauth2User.getAttributes());
}
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"));
user.getRoles().add(userRole);
return userRepository.save(user);
}
}

View File

@ -0,0 +1,31 @@
package com.backend.user.service.anyame.service;
import com.backend.user.service.anyame.component.AuthorizedUserMapper;
import com.backend.user.service.anyame.entity.User;
import com.backend.user.service.anyame.repository.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
private final AuthorizedUserMapper authorizedUserMapper;
public CustomUserDetailsService(UserRepository userRepository, AuthorizedUserMapper authorizedUserMapper) {
this.userRepository = userRepository;
this.authorizedUserMapper = authorizedUserMapper;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> userOptional = userRepository.findByEmailOrName(username, username);
if (userOptional.isEmpty()) {
throw new UsernameNotFoundException("user not found " + username);
}
return authorizedUserMapper.mapFormLogin(userOptional.get());
}
}

View File

@ -0,0 +1,39 @@
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;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashSet;
import java.util.Set;
@Service
@Transactional(readOnly = true)
public class UserAuthorityService {
private final UserRepository userRepository;
private final PermissionRepository permissionRepository;
public UserAuthorityService(UserRepository userRepository, PermissionRepository permissionRepository) {
this.userRepository = userRepository;
this.permissionRepository = permissionRepository;
}
public Set<GrantedAuthority> getUserAuthorities(User user) {
Set<GrantedAuthority> authorities = new HashSet<>();
user.getRoles().forEach(role ->
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()))
);
permissionRepository.findByUserName(user.getName()).forEach(permission ->
authorities.add(new SimpleGrantedAuthority(permission.getName()))
);
return authorities;
}
}

View File

@ -6,10 +6,27 @@ spring:
authorizationserver:
issuer-url: http://localhost:8080
introspection-endpoint: /oauth2/token-info
client:
registration:
github:
clientId: ${GITHUB_CLIENT_ID}
clientSecret: ${GITHUB_CLIENT_SECRET}
scope:
- user:email
- read:user
google:
clientId: ${GOOGLE_CLIENT_ID}
clientSecret: ${GOOGLE_CLIENT_SECRET}
datasource:
url: ${DATABASE_URL}
username: ${DATABASE_USERNAME}
password: ${DATABASE_PASSWORD}
flyway:
enabled: true
locations: classpath:db/migration/structure, classpath:db/migration/data
validate-on-migrate: true
default-schema: main
jpa:
hibernate:
ddl-auto: create