Initial implementation of CustomUserDetailsService
This commit is contained in:
13
pom.xml
13
pom.xml
@ -50,11 +50,24 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.security</groupId>
|
||||||
<artifactId>spring-security-oauth2-authorization-server</artifactId>
|
<artifactId>spring-security-oauth2-authorization-server</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.flywaydb</groupId>
|
||||||
|
<artifactId>flyway-database-postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt-api</artifactId>
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
@ -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;
|
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.beans.factory.annotation.Qualifier;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.annotation.Order;
|
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.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
||||||
import static org.springframework.security.config.Customizer.withDefaults;
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity(prePostEnabled = true)
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final CustomUserDetailsService userDetailsService;
|
||||||
|
private final CustomOAuth2UserService oauth2UserService;
|
||||||
|
|
||||||
|
public SecurityConfig(CustomUserDetailsService userDetailsService,
|
||||||
|
CustomOAuth2UserService oauth2UserService) {
|
||||||
|
this.userDetailsService = userDetailsService;
|
||||||
|
this.oauth2UserService = oauth2UserService;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Order(2)
|
@Order(2)
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http, @Qualifier("corsConfigurationSource") CorsConfigurationSource configurationSource) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http, @Qualifier("corsConfigurationSource") CorsConfigurationSource configurationSource) throws Exception {
|
||||||
@ -26,17 +39,17 @@ public class SecurityConfig {
|
|||||||
c.anyRequest().authenticated()
|
c.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.formLogin(withDefaults())
|
.formLogin(withDefaults())
|
||||||
|
.oauth2Login(c ->
|
||||||
|
c.userInfoEndpoint(userInfo -> userInfo
|
||||||
|
.userService(oauth2UserService)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.userDetailsService(userDetailsService)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public UserDetailsService users() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
UserDetails user = User.builder()
|
return new BCryptPasswordEncoder();
|
||||||
.username("admin")
|
|
||||||
.password("{noop}password")
|
|
||||||
.roles("USER")
|
|
||||||
.build();
|
|
||||||
return new InMemoryUserDetailsManager(user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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;
|
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.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
@ -11,4 +13,11 @@ public class HelloController {
|
|||||||
public String hello() {
|
public String hello() {
|
||||||
return "Hello World";
|
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:
|
authorizationserver:
|
||||||
issuer-url: http://localhost:8080
|
issuer-url: http://localhost:8080
|
||||||
introspection-endpoint: /oauth2/token-info
|
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:
|
datasource:
|
||||||
url: ${DATABASE_URL}
|
url: ${DATABASE_URL}
|
||||||
username: ${DATABASE_USERNAME}
|
username: ${DATABASE_USERNAME}
|
||||||
password: ${DATABASE_PASSWORD}
|
password: ${DATABASE_PASSWORD}
|
||||||
|
flyway:
|
||||||
|
enabled: true
|
||||||
|
locations: classpath:db/migration/structure, classpath:db/migration/data
|
||||||
|
validate-on-migrate: true
|
||||||
|
default-schema: main
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: create
|
ddl-auto: create
|
||||||
|
Reference in New Issue
Block a user