I am very new to the Micronaut framework and decided to try it on a project, that I am working on.
I have a problem where I am trying to register a user. I have followed this guide https://guides.micronaut.io/latest/micronaut-database-authentication-provider-gradle-groovy.html but extracted the logic from the AuthenticationProvider to an Authentication Service so I ended up with this:
@Singleton
public class UserAuthProvider implements AuthenticationProvider {
private final AuthService authService;
public UserAuthProvider(AuthService authService) {
this.authService = authService;
}
@Override
public Publisher<AuthenticationResponse> authenticate(HttpRequest<?> httpRequest, AuthenticationRequest<?, ?> authenticationRequest) {
return Flux.create(emitter -> {
var username = authenticationRequest.getIdentity().toString();
var password = authenticationRequest.getSecret().toString();
var user = this.authService.getValidatedUser(username, password);
if (user != null) {
emitter.next(AuthenticationResponse.success(username, List.of(user.roleId().toString())));
emitter.complete();
} else {
emitter.error(AuthenticationResponse.exception());
}
}, FluxSink.OverflowStrategy.ERROR);
}
}
Here is the code in the AuthService:
@Singleton
public class AuthService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final RoleRepository roleRepository;
public AuthService(UserRepository userRepository, PasswordEncoder passwordEncoder, RoleRepository roleRepository) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.roleRepository = roleRepository;
}
public void register(RegistrationRequest registrationRequest) {
var user = this.userRepository.findByEmailEmail(registrationRequest.email());
if (user.isPresent()) {
throw new EntityAlreadyExistsException(String.format(EMAIL_ALREADY_EXISTS,registrationRequest.email()));
}
var role = this.roleRepository.findByName(RoleEnum.ROLE_PARENT)
.orElseThrow(() -> new EntityNotFoundException(ENTITY_NOT_FOUND));
var newUser = new ApplicationUser(
new Email(registrationRequest.email()),
this.passwordEncoder.encode(registrationRequest.password()),
registrationRequest.firstName(),
registrationRequest.middleName(),
registrationRequest.lastName(),
new MobilePhone(registrationRequest.phoneNumber()),
role
);
var patient = new Parent();
newUser.setParent(patient);
this.userRepository.save(newUser);
}
public UserDto getValidatedUser(String username, String password) {
if (this.validateCredentials(username, password)) {
return this.findByEmail(username);
}
return null;
}
private boolean validateCredentials(String username, String password) {
var user = this.userRepository.findByEmailEmail(username)
.orElseThrow(() -> new EntityNotFoundException(ENTITY_NOT_FOUND));
return this.passwordEncoder.matches(password, user.getPassword());
}
public UserDto findByEmail(String email) {
return this.userRepository.findByEmailEmail(email)
.map(u -> new UserDto(
u.getId(),
u.getFirstName(),
u.getLastName(),
u.getEmail().getEmail(),
u.getRole().getId()))
.orElseThrow(() -> new EntityNotFoundException(ENTITY_NOT_FOUND));
}
}
Every time I try to create a POST request to register a user I get this error:
11:51:21.631 [default-nioEventLoopGroup-1-2] WARN i.n.c.AbstractChannelHandlerContext - An exception 'io.micronaut.context.exceptions.BeanInstantiationException: Error instantiating bean of type [server.application.services.auth.AuthService]
Message: org/apache/commons/logging/LogFactory Path Taken: new SecurityFilter(Collection securityRules,Collection authenticationFetchers,SecurityConfiguration securityConfiguration) --> new SecurityFilter(Collection securityRules,[Collection authenticationFetchers],SecurityConfiguration securityConfiguration) --> new BasicAuthAuthenticationFetcher([Authenticator authenticator]) --> new Authenticator([Collection authenticationProviders],SecurityConfiguration securityConfiguration) --> new UserAuthProvider([AuthService authService]) --> new AuthService(UserRepository userRepository,[PasswordEncoder passwordEncoder],RoleRepository roleRepository)' [enable DEBUG level for full stacktrace] was thrown by a user handler's exceptionCaught() method while handling the following exception: io.micronaut.context.exceptions.BeanInstantiationException: Error instantiating bean of type [server.application.services.auth.AuthService]
Message: org/apache/commons/logging/LogFactory Path Taken: new SecurityFilter(Collection securityRules,Collection authenticationFetchers,SecurityConfiguration securityConfiguration) --> new SecurityFilter(Collection securityRules,[Collection authenticationFetchers],SecurityConfiguration securityConfiguration) --> new BasicAuthAuthenticationFetcher([Authenticator authenticator]) --> new Authenticator([Collection authenticationProviders],SecurityConfiguration securityConfiguration) --> new UserAuthProvider([AuthService authService]) --> new AuthService(UserRepository userRepository,[PasswordEncoder passwordEncoder],RoleRepository roleRepository)
Not to paste the whore stacktrace, but here is the end of it, that makes me think that there is some kind of error instanciating the PasswordEncoder:
Caused by: java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.<init>(BCryptPasswordEncoder.java:40)
at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.<init>(BCryptPasswordEncoder.java:79)
at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.<init>(BCryptPasswordEncoder.java:56)
at org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.<init>(BCryptPasswordEncoder.java:49)
at server.infrastructure.config.encoding.AuthPasswordEncoderService.<init>(AuthPasswordEncoderService.java:9)
at server.infrastructure.config.encoding.$AuthPasswordEncoderService$Definition.build(Unknown Source)
at io.micronaut.context.DefaultBeanContext.resolveByBeanFactory(DefaultBeanContext.java:2331)
... 162 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
This is how the AuthPasswordEncoderService looks like:
package server.infrastructure.config.encoding;
import jakarta.inject.Singleton;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Singleton
public class AuthPasswordEncoderService implements PasswordEncoder {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Override
public String encode(CharSequence rawPassword) {
return this.passwordEncoder.encode(rawPassword);
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return this.passwordEncoder.matches(rawPassword, encodedPassword);
}
}
As stacktrace says
java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
You are missing class LogFactory that is required for BCryptPasswordEncoder class. Spring security uses this old dependency for logging purposes, and Spring projects are using spring-jcl to fix it. You need to add spring-jcl to you build.gradle dependencies section,
implementation 'org.springframework:spring-jcl:5.3.25'
Spring JCL (Java Class Library) is a utility library that provides common functionality to Spring-related projects. It includes classes for logging, resource loading, and other common tasks that are used across multiple Spring modules.