Spring Boot Auditing is Called Infinitely - java

I have an auditor class:
#Component
#EnableJpaAuditing
public class AuditorAwareImpl implements AuditorAware<String> {
private final SecurityService securityService;
public AuditorAwareImpl(SecurityService securityService) {
this.securityService = securityService;
}
#Override
#NonNull
public Optional<String> getCurrentAuditor() {
if (securityService.getAuthenticatedUser().isEmpty()) {
return Optional.empty();
}
return Optional.of(securityService.getAuthenticatedUser().get().getMobilePhoneNumber());
}
}
and has a security service:
#Service
public class SecurityService {
private final UserRepository userRepository;
public SecurityService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Optional<User> getAuthenticatedUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || authentication instanceof AnonymousAuthenticationToken) {
return Optional.empty();
}
UserDetails authUser = (UserDetails) authentication.getPrincipal();
return userRepository.findByMobilePhoneNumber(authUser.getUsername());
}
}
When I try to update an entity, these two classes are called infinitely which results with a stack over flow.
I use Java 11 and Spring Boot 2.3.4.RELEASE.
How can I fix that recurrent recursion to avoid it?

One approach is having #Lazy annotation on your service at the point where it is #Autowired in. This is because if you don't have #Lazy on your component, then will be injected eagerly into the bean. So try the following and it should just work:
#Component
#EnableJpaAuditing
public class AuditorAwareImpl implements AuditorAware<String> {
#Lazy
#Autowired
private final SecurityService securityService;
.....
}

Related

Problem with password encoder in the test

When trying to test a method, I'm getting a NullPointerException in this line of code:
when(webConfig.passwordEncoder().encode(any())).thenReturn(userUpdated.getPassword());
java.lang.NullPointerException: Cannot invoke "org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.encode(java.lang.CharSequence)" because the return value of "com.example.paymybuddy.security.WebConfig.passwordEncoder()" is null
I have provided a mock of WebConfig class but it looks like still missing something. Thank you for your help in advance.
My code:
#ExtendWith(MockitoExtension.class)
public class Test {
#Mock
SecurityService securityService;
#Mock
IUserRepository userRepository;
#Mock
WebConfig webConfig;
#InjectMocks
UserService userService;
#Test
public void updateProfileTest() {
UserProfileDTO userDTO = new UserProfileDTO();
userDTO.setEmail("john#simons");
userDTO.setFirstName("John");
userDTO.setLastName("Simons");
userDTO.setPassword("pass");
userDTO.setConfirmPassword("pass");
User userForUpdate = new User();
userForUpdate.setFirstName("Joo");
userForUpdate.setLastName("Sim");
userForUpdate.setBalance(242.20);
userForUpdate.setEmail("john#simons");
userForUpdate.setPassword("pass");
userForUpdate.setRole("ROLE_USER");
User userUpdated = new User();
userUpdated.setFirstName("John");
userUpdated.setLastName("Simons");
userUpdated.setBalance(242.20);
userUpdated.setEmail("john#simons");
userUpdated.setPassword("pass");
userUpdated.setRole("ROLE_USER");
when(userRepository.findByEmail(any())).thenReturn(userForUpdate);
when(webConfig.passwordEncoder().encode(any())).thenReturn(userUpdated.getPassword());
when(userRepository.save(any())).thenReturn(userUpdated);
User test = userService.updateProfile(userDTO);
assertEquals("john#simons",test.getEmail());
}
}
#Service
public class UserService {
#Autowired
private SecurityService securityService;
#Autowired
private IUserRepository userRepository;
#Autowired
private WebConfig webConfig;
public User updateProfile (UserProfileDTO userProfileDTO) {
User user = userRepository.findByEmail(securityService.getLoggedUser());
user.setFirstName(userProfileDTO.getFirstName());
user.setLastName(userProfileDTO.getLastName());
user.setEmail(securityService.getLoggedUser());
if(!userProfileDTO.getConfirmPassword().equals(userProfileDTO.getPassword())){
throw new PasswordException("Password confirmation not match");
}
user.setPassword(webConfig.passwordEncoder().encode(userProfileDTO.getPassword()));
return userRepository.save(user);
}
}
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
#Bean
RequestRejectedHandler requestRejectedHandler() {
return new HttpStatusRequestRejectedHandler();
}
}
when(webConfig.passwordEncoder().encode(any())).thenReturn(userUpdated.getPassword());
This line is the problem (or more broadly your design but more on that later). webConfig is a mock, which means it will intercept all method calls and excert the registered behavior or the default if none. webConfig.passwordEncoder() is the first call on yuor mock and you haven't told the mock what to do, hence it will do the default which is to return null. You go then on by mocking the encode call on the return value, which will fail.
You should either tell Mockito to do something else by default (#Mock(answer = Answers.RETURNS_DEEP_STUBS instead of plain #Mock) or explicitly mock the call to webConfig.passwordEncoder() and return a mocked PasswordEncoder and then mock behavior on that for encode.
However the fact that you inject the WebConfig and not a PasswordEncoder in your UserService is the actual problem (or the problem in your design). You should inject a PasswordEncoder.
#Service
public class UserService {
private final SecurityService securityService;
private final IUserRepository userRepository;
private final PasswordEncoder encoder;
public UserService(SecurityService securityService, IUserRepository userRepository, PasswordEncoder encoder) {
this.securityService=securityService;
this.userRepository=userRepository;
this.encoder=encoder;
}
public User updateProfile (UserProfileDTO userProfileDTO) {
User user = userRepository.findByEmail(securityService.getLoggedUser());
user.setFirstName(userProfileDTO.getFirstName());
user.setLastName(userProfileDTO.getLastName());
user.setEmail(securityService.getLoggedUser());
if(!userProfileDTO.getConfirmPassword().equals(userProfileDTO.getPassword())){
throw new PasswordException("Password confirmation not match");
}
user.setPassword(encoder.encode(userProfileDTO.getPassword()));
return userRepository.save(user);
}
}
Now you can modify your test as well.
#ExtendWith(MockitoExtension.class)
public class Test {
#Mock
SecurityService securityService;
#Mock
IUserRepository userRepository;
#Mock
PasswordEncoder encoder;
#InjectMocks
UserService userService;
#Test
public void updateProfileTest() {
UserProfileDTO userDTO = new UserProfileDTO();
userDTO.setEmail("john#simons");
userDTO.setFirstName("John");
userDTO.setLastName("Simons");
userDTO.setPassword("pass");
userDTO.setConfirmPassword("pass");
User userForUpdate = new User();
userForUpdate.setFirstName("Joo");
userForUpdate.setLastName("Sim");
userForUpdate.setBalance(242.20);
userForUpdate.setEmail("john#simons");
userForUpdate.setPassword("pass");
userForUpdate.setRole("ROLE_USER");
User userUpdated = new User();
userUpdated.setFirstName("John");
userUpdated.setLastName("Simons");
userUpdated.setBalance(242.20);
userUpdated.setEmail("john#simons");
userUpdated.setPassword("pass");
userUpdated.setRole("ROLE_USER");
when(userRepository.findByEmail(any())).thenReturn(userForUpdate);
when(encoder.encode(any())).thenReturn(userUpdated.getPassword());
when(userRepository.save(any())).thenReturn(userUpdated);
User test = userService.updateProfile(userDTO);
assertEquals("john#simons",test.getEmail());
}
}
Now your service just depends on the classes it needs instead of knowing something about the configuration.

while writing Junit test cases for #post method, i am getting null pointer exception at userService.addUser() mehod. could anyone help here

public class UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private ConverterService converterService;
public User addUser(UserDto userdto) {
User convertedUser = converterService.convertToEntity(userdto);
convertedUser.setUserId(userdto.getUserId());
convertedUser.setUserName(userdto.getUserName());
User savedUser = userRepository.save(convertedUser);
return savedUser;
}
}
//while debugging userRepository.save(convertedUser) method, it always returning null.
Below is my UserServiceTest.java class
#RunWith(SpringRunner.class)
#SpringBootTest
public class UserServiceTest {
#Autowired
private UserService userService;
#Mock
private ConverterService converterService;
#MockBean
private UserRepository userRepository;
#Test
public void addUserTest() {
UserDto userDto = new UserDto();
userDto.setUserId("123");
userDto.setUserName("AB");
User user = new User("123","ABC");
Mockito.when(converterService.convertToEntity(new UserDto())).thenReturn(user);
Mockito.when(userRepository.save(user)).thenReturn(user);
User user1 = userService.addUser(userDto);
Assert.assertEquals(user,userService.addUser(userDto));
}
}
userService.addUser(userDto) this method is always return by null from service class. because of below condition is failing : Assert.assertEquals(user,userService.addUser(userDto));
you shouldn't pass null value to the assertEquals method. But you can do like this;
Assert.assertEquals(Objects.equals(user,userService.addUser(userDto)),true);

Spring Injection of JPA Repository bean is not working - NullPointerException

I'm trying to inject JPA repositories managed and implemented by Spring to a service class. But in the runtime program throws NPE on line 39.
Here is my service class:
#Service
public class RegistrationFormProcessor {
private ContractorRepository contractorRepository;
private AddressRepository addressRepository;
private UserRepository userRepository;
private PasswordEncoder passwordEncoder;
#Autowired
public RegistrationFormProcessor(ContractorRepository contractorRepository, AddressRepository addressRepository, UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.contractorRepository = contractorRepository;
this.addressRepository = addressRepository;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
public User prepareUser(RegistrationForm registrationForm) {
Address address = new Address(
registrationForm.getCountry(),
registrationForm.getCity(),
registrationForm.getStreet(),
registrationForm.getZipCode(),
registrationForm.getProperty()
);
Address savedAddress = addressRepository.save(address); // line 39
Contractor userCompany = new Contractor(
registrationForm.getCompanyName(),
registrationForm.getShortName(),
savedAddress,
registrationForm.getNip(),
registrationForm.getRegon(),
registrationForm.getPhone(),
registrationForm.getEmail(),
registrationForm.getWebsite()
);
Contractor savedUserCompany = contractorRepository.save(userCompany); // line 51
User user = new User(
registrationForm.getFirstName(),
registrationForm.getLastName(),
registrationForm.getLastName(),
passwordEncoder.encode(registrationForm.getPassword()),
"ROLE_USER",
true,
savedUserCompany
);
User savedUser = userRepository.save(user);
savedUserCompany.setOwner(savedUser);
contractorRepository.save(savedUserCompany);
return savedUser;
}
AddressRepository and other fields are autowired with interfaces that are managed by Spring which is implementing all methods automatically.
import org.springframework.data.repository.CrudRepository;
public interface AddressRepository extends CrudRepository <Address, Long> {}
In my case an instance of RegistrationFormProcessor is an instance managed by Spring (at least I hope it is). RegistrationFormProcessor is annotated #Service and is injected in one of controller class. There it is used and program is throwing mentioned exception. Below a controller:
#Controller
#RequestMapping("/register")
public class RegistrationController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public RegistrationController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#GetMapping
public String registerForm(Model model) {
model.addAttribute("registrationForm", new RegistrationForm());
return "register/registration";
}
#PostMapping
public String processRegistrationForm(RegistrationForm form, #Autowired RegistrationFormProcessor registrationFormProcessor) {
registrationFormProcessor.prepareUser(form);
return "redirect:/login";
}
}
If line 39 would be removed program would thow NPE on line 51 (and so on if userRepository.save(user) is called). In these lines should be used repository with methods like save() implemented by Spring-data automatically.
I have similar injections (also in constructors) in controller classes and there everything is working fine.
What I'm doing wrong? Why in service class Spring does not inject these dependencies and fields are null? I tried to set a breakpoint and debug - confirmed that fields are null . As I said above in other classes which are annotated as #Controller same fields also injected in constructor aren't nulls and all is working fine.
Tried to annotate interface AddresRepository as #Repository but it is not working. On the other hand it is not necessary for sure for Spring - Spring is implementing methods correctly as checked in controllers.
Thank you in advance for any suggestions.
You should try add final keyword for your repositories for injecting, like that:
private final ContractorRepository contractorRepository;
private final AddressRepository addressRepository;
...
Problem solved by moving RegistrationFormProcessor injection in controller from injection in controller method as a parameter to declaration as field and injection in controller field.
Before
#Controller
#RequestMapping("/register")
public class RegistrationController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public RegistrationController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#PostMapping
public String processRegistrationForm(RegistrationForm form, #Autowired RegistrationFormProcessor registrationFormProcessor) {
registrationFormProcessor.prepareUser(form); // throws NPE - not working
return "redirect:/login";
}
}
After:
#Controller
#RequestMapping("/register")
public class RegistrationController {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final RegistrationFormProcessor registrationFormProcessor;
public RegistrationController(UserRepository userRepository, PasswordEncoder passwordEncoder, RegistrationFormProcessor registrationFormProcessor) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.registrationFormProcessor = registrationFormProcessor;
}
#PostMapping
public String processRegistrationForm(RegistrationForm form) {
registrationFormProcessor.prepareUser(form); //working fine
return "redirect:/login";
}
}
But can't explain why...
Can you remove #Autowired annotation and add final keyword to repository definitions like below:
private final ContractorRepository contractorRepository;
private final AddressRepository addressRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public RegistrationFormProcessor(ContractorRepository contractorRepository, AddressRepository addressRepository, UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.contractorRepository = contractorRepository;
this.addressRepository = addressRepository;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}

Spring-Boot with CustomAuthenticationProvider and CustomPasswordEncoder

Its not clear for me how to glue my CustomPasswordEncoder to the authentication process of spring boot. I define in a configuration that spring boot should use my CustomAuthenticationProvider with my UserDetailsService and my CustomPasswordEncoder
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
builder.authenticationProvider(customAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new CustomPasswordEncoder();
return encoder;
}
}
My CustomPasswordEncoder will encode to a md5 value (I know its unsecure, but its a legacy database)
#Component
public class CustomPasswordEncoder implements PasswordEncoder{
#Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equals(encodedPassword);
}
}
In the CustomAuthtenticationProvider the authentication check will be done. The delivered password will be encoded by using the passwordEncoder.encode() The user will be fetched from the database, then I am using the passwordEncoder again do a match. If the match is successfull then the authentication object will be generated.
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserServiceImpl userService;
#Autowired
private CustomPasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("authentication = [" + authentication + "]");
String name = authentication.getName();
Object credentials = authentication.getCredentials();
String password = credentials.toString();
//why is this necessary isnt it called automatically?
String passwordEncoded = passwordEncoder.encode((CharSequence) credentials);
Optional<UserEntity> userOptional = userService.findByUsername(name);
if (userOptional.isPresent() && passwordEncoder.matches(passwordEncoded, userOptional.get().getPassword())) {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority(userOptional.get().getRoles().toString()));
Authentication auth = new
UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
return auth;
}
else{
throw new BadCredentialsException("Authentication failed for " + name);
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Is this the correct approach? I thought the CustomPasswordEncoder will be used "automatically" or ist that only the case if you use one of the provided authenticationProviders like jdbcAuthenticationProvider. Maybe someone can explain the order of events of the authentication process. I did some research in the net but still I cannot understand this in detail.
First as you can see from the matches method it validates the raw password (thus as entered by the user) with the encoded password. So the code for encoding belongs in the matches method instead of what you have now.
public class CustomPasswordEncoder implements PasswordEncoder{
#Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String rawEncoded = encode(rawPassword);
return Objects.equals(rawEncoded, encodedPassword);
}
}
Now you can remove the encoding line/step whatever from your code.
However you don't really need a custom AuthenticationProvider as that is generally only needed if you add another authentication mechanism like LDAP or OAuth.
What you need is an adapter for your UserService to a UserDetailsService and use that. I assume that the UserDetailsServiceImpl does exactly that. If not you can use something like the code below.
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserService delegate;
public UserDetailsServiceAdapter(UserService delegate) {
this.delegate=delegate;
}
public UserDetails loadUserByUsername(String username) {
reutrn userService.findByUsername(name)
.map(this::toUserDetails).orElseThrow(() -> new UsernameNotFoundException("Unknown user " + username);
}
private UserDetails toUserDetails(User user) {
Set<GrantedAuthority> authorities = new HashSet<>();
user.getRoles().forEach(r -> authorities.add(new SimpleGrantedAuthority(r));
return new UserDetails(user.getUsername(), user.getPassword(), authorities);
}
}
Now you can use your PasswordEncoder and this adapter in the configuration and you don't need your custom AuthenticationProvider.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new CustomPasswordEncoder();
return encoder;
}
}

Full validation test in Spring Boot, injection failing

Hello everyone I wanted to tested the full validation of a Request in my Spring Boot application I mean no testing one validator at a time but all of them on the target object)
First I have my object :
public class UserCreationRequest {
#JsonProperty("profileId")
#NotNull
#ValidProfile
private Integer profileId;
}
Then my Validator (#ValidProfile):
#Component
public class ProfileValidator implements ConstraintValidator<ValidProfile, Integer> {
#Autowired
private IProfileService profileService;
#Autowired
private IUserRestService userRestService;
#Override
public void initialize(ValidProfile constraintAnnotation) {
}
#Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
RestUser restUser = userRestService.getRestUser();
ProfileEntity profileEntity = profileService.getProfile(value, restUser.getAccountId());
return profileEntity != null;
}
}
Now I write my unit test :
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {ValidationTestConfiguration.class})
public class UserCreationRequestValidationTest {
private static LocalValidatorFactoryBean localValidatorFactory;
#Autowired
private IUserService userService;
#Autowired
private IProfileService profileService;
#Autowired
private IUserRestService restService;
#BeforeClass
public static void createValidator() {
localValidatorFactory = new LocalValidatorFactoryBean();
localValidatorFactory.setProviderClass(HibernateValidator.class);
localValidatorFactory.afterPropertiesSet();
}
#AfterClass
public static void close() {
localValidatorFactory.close();
}
#Test
public void validateUserCreationRequestStringfields() {
UserCreationRequest userCreationRequest = new UserCreationRequest();
/* Here fill test object*/
when(userService.getUser(any(Integer.class), any(Integer.class))).thenReturn(new UserEntity());
when(profileService.getProfile(any(Integer.class), any(Integer.class))).thenReturn(new ProfileEntity());
when(restService.getRestUser()).thenReturn(new RestUser());
Set<ConstraintViolation<UserCreationRequest>> violations
= localValidatorFactory.validate(userCreationRequest);
assertEquals(violations.size(), 8);
}
}
and my TestConfiguration is like that :
#Configuration
public class ValidationTestConfiguration {
#Bean
#Primary
public IProfileService profileService() {
return Mockito.mock(IProfileService.class);
}
#Bean
#Primary
public IUserRestService userRestService() { return Mockito.mock(IUserRestService.class); }
}
On execution I can see that in the test itself the injection works :
restService is mapped to "Mock for IUserRestService"
But in my validator it is not injected, userRestService is null.
Same thing for ProfileService
I tried several things seen here, nothing works (code is running, only test conf is failing)
This is because you do not produce the Validator bean so it can be injected.
As you manually instantiate the LocalValidatorFactoryBean, it cannot access to the spring DI defined for this test.
You should produce instead a bean for the Validator, or even reference an existing spring configuration to do so.

Categories

Resources