Initial implementation of CustomUserDetailsService
This commit is contained in:
221
pom.xml
221
pom.xml
@ -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>
|
@ -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));
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Reference in New Issue
Block a user