I am new to programming, trying to make a simple application with the implementation of users, roles and privileges according to the manual. I use Sprint 3.3.3 and Java 17.
The manual suggested creating a Loader class that will populate the database when building the application. So it would be possible, without resorting to a pure SQL, to create the first admin, create roles and privileges for roles.
SetupDataLoader
class implements ApplicationListener<ContextRefreshedEvent>
interface. The class is marked with a @Component annotation. I also tried the onApplicationEvent method to mark with an annotation @EventListener.
But when the APP starts, the code from the class is not executed. Every time after launch, I check the contents of db and see that no new rows have been added to the User table. I also tried to output a message from that block of code to the console - it was not output.
MY APP
ENTITIES
@Entity
@Table(name = "users")
@Data
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "login")
private String login;
@Column(name = "password")
private String password;
@ManyToMany
@JoinTable(
name = "users_roles",
joinColumns = @JoinColumn(
name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(
name = "role_id", referencedColumnName = "id"))
private Collection<RoleEntity> roles;
public UserEntity() {}
}
@Entity
@Table(name = "roles")
@Data
public class RoleEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "name")
private String name;
@ManyToMany
@JoinTable(
name = "roles_privileges",
joinColumns = @JoinColumn(
name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(
name = "privilege_id", referencedColumnName = "id"))
private Collection<PrivilegeEntity> privileges;
public RoleEntity() {}
public RoleEntity(final String name) {
this.name = name;
}
}
@Entity
@Table(name = "priveleges")
@Data
public class PrivilegeEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name = "name")
private String name;
@ManyToMany(mappedBy = "privileges")
private Collection<RoleEntity> roles;
public PrivilegeEntity() {}
public PrivilegeEntity(final String name) {
this.name = name;
}
}
REPOS
public interface UserRepo extends CrudRepository<UserEntity, Long> {
Optional<UserEntity> findByLogin (String login);
}
public interface RoleRepo extends CrudRepository<RoleEntity, Long> {
Optional<RoleEntity> findByName(String name);
}
public interface PrivilegeRepo extends CrudRepository<PrivilegeEntity, Long> {
Optional<PrivilegeEntity> findByName(String name);
}
SEQURITY settings:
@EnableWebSecurity
@Configuration
public class SequrityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.securityContext((securityContext) -> securityContext.requireExplicitSave(true))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(HttpMethod.GET, "/roleHierarchy")
.hasRole("STAFF"));
return http.build();
}
@Bean
public SecurityExpressionHandler<FilterInvocation> customWebSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy());
return expressionHandler;
}
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_ADMIN > ROLE_MODER \n ROLE_MODER > ROLE_STAFF";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
}
and finally
@Component
public class SetupDataLoader implements ApplicationListener<ContextRefreshedEvent> {
private boolean alreadySetup = false;
@Autowired
UserRepo userRepo;
@Autowired
RoleRepo roleRepo;
@Autowired
PrivilegeRepo privilegeRepo;
@Autowired
PasswordEncoder passwordEncoder;
// API
@Override
@Transactional
public void onApplicationEvent(final @NonNull ContextRefreshedEvent event) {
if (alreadySetup) {
return;
}
// == create init privileges
final PrivilegeEntity readPrivilege = createPrivilege("READ_PRIVILEGE");
final PrivilegeEntity userWritePrivilege = createPrivilege("USER_WRITE_PRIVILEGE");
final PrivilegeEntity switchStatePrivilege = createPrivilege("CHANGE_STATE_PRIVILEGE");
final PrivilegeEntity switchWritePrivilege = createPrivilege("SWITCH_WRITE_PRIVILEGE");
final PrivilegeEntity commentWritePrivilege = createPrivilege("COMMENT_WRITE_PRIVILEGE");
// == create init roles;
final List<PrivilegeEntity> adminPrivileges = new ArrayList<>(Arrays
.asList(readPrivilege,
userWritePrivilege,
switchStatePrivilege,
switchWritePrivilege,
commentWritePrivilege));
final List<PrivilegeEntity> moderPrivileges = new ArrayList<>(Arrays
.asList(readPrivilege,
switchWritePrivilege,
commentWritePrivilege));
final List<PrivilegeEntity> staffPrivileges = new ArrayList<>(Arrays.asList(readPrivilege));
final RoleEntity adminRole = createRole("ROLE_ADMIN", adminPrivileges);
createRole("ROLE_MODER", moderPrivileges);
createRole("ROLE_STAFF", staffPrivileges);
// == create init users FIRST ADMIN in db
createUser("ADMIN", "ADMIN", new ArrayList<>(Arrays.asList(adminRole)));
alreadySetup = true;
}
@Transactional
public PrivilegeEntity createPrivilege(final String name) {
Optional<PrivilegeEntity> optPrivilege = privilegeRepo.findByName(name);
PrivilegeEntity privilege;
if (optPrivilege.isPresent())
privilege = optPrivilege.get();
else {
privilege = new PrivilegeEntity(name);
privilege = privilegeRepo.save(privilege);
}
return privilege;
}
@Transactional
public RoleEntity createRole(final String name, final Collection<PrivilegeEntity> privileges) {
Optional<RoleEntity> optRole = roleRepo.findByName(name);
RoleEntity role;
if (optRole.isPresent())
role = optRole.get();
else {
role = new RoleEntity(name);
}
role.setPrivileges(privileges);
role = roleRepo.save(role);
return role;
}
@Transactional
public UserEntity createUser(final String login, final String password, final Collection<RoleEntity> roles) {
Optional<UserEntity> optUser = userRepo.findByLogin(login);
UserEntity user;
if (optUser.isPresent())
user = optUser.get();
else {
user = new UserEntity();
user.setLogin(login);
user.setPassword(passwordEncoder.encode(password));
}
user.setRoles(roles);
user = userRepo.save(user);
return user;
}
}
I try various solutions from the Internet, but after each attempt I check my db and everything is the same:
prototype=# select * from users;
id | login | password
----+-------+----------
(0 rows)
My structure is like this:
🗁 src
└─── 🗁 main
├─── 🗁 java/ru/dikun/prototype
├─── 🗁 config
│ ├───SequrityConfig.java
│ └───SetupDataLoader.java
├─── 🗁 controllers
├─── 🗁 domain
│ ├───PrivilegeEntity.java
│ ├───RoleEntity.java
│ └───UserEntity.java
├─── 🗁 repos
│ ├───PrivilegeRepo.java
│ ├───RoleRepo.java
│ └───UserRepo.java
├─── 🗁 services
│ └───UserService.java
└─── PrototypeApplication.java
└─── 🗁 resources
└─── application.properties
Debug:
DEBUG 26790 --- [prototype] [ restartedMain] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to CORRECT
DEBUG 26790 --- [prototype] [ restartedMain] o.s.boot.devtools.restart.Restarter : Creating new Restarter for thread Thread[main,5,main]
DEBUG 26790 --- [prototype] [ restartedMain] o.s.boot.devtools.restart.Restarter : Immediately restarting application
DEBUG 26790 --- [prototype] [ restartedMain] o.s.boot.devtools.restart.Restarter : Starting application ru.dikun.prototype.PrototypeApplication with URLs [file:/home/ogurchik/workspace/prototype/target/classes/]
DEBUG 26790 --- [prototype] [ restartedMain] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
DEBUG 26790 --- [prototype] [ionShutdownHook] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed from ACCEPTING_TRAFFIC to REFUSING_TRAFFIC
DEBUG 26790 --- [prototype] [ionShutdownHook] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@1cbe84e7, started on ...
Please tell me why the code from the SetapDataLoader.java
is not executed?
In fact, @SpringBootApplication(scanBasePackages={"ru.dikun.something", "ru.dikun.application"})
limited the scope of the scan when building the application. But without this annotation parameter, the application did not work in principle, which means it was necessary to solve the error.
And the error was very common, which was answered on the Internet a long time ago, it was just necessary to solve it correctly, and not add parameters to the annotations.
***************************
APPLICATION FAILED TO START
***************************
Description:
Field passwordEncoder in ru.dikun.prototype.config.SetupDataLoader required a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' in your configuration.
In order for the encoder to work, I had to actually declare a bin of the appropriate type, which was what the exception said. Added to my SequrityConfig:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
After that, everything worked. And The App started, and my SetupDataLoader class filled the database, as I wanted.