Mock security context for JPA integration test - spring-boot

I'm trying to test a spring JPA repository interface to ensure my mappings are correct. My entity extends a base entity which is annotated with..
#EntityListeners(BaseEntityEventListener.class)
#MappedSuperclass
public abstract class BaseEntity {...
The event listener populates some audit properties..
public class BaseEntityEventListener {
#PrePersist
public void onPreInsert(BaseEntity baseEntity){
MyUserPrincipal principal = (MyUserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = principal.getUsername();
baseEntity.setCreationUser(username);
Timestamp ts = new Timestamp(System.currentTimeMillis());
baseEntity.setCreationDate(ts);
}...
This is ok but when I want to test the repository I get a null pointer for the SecurityContextHolder.
#RunWith(SpringRunner.class)
#SpringBootTest
public class RepositoryTest {
#Autowired private MyRepository myRepo;
#Test
public void testSaveEntity() throws Exception {
Entity entity = new Entity(TEST_ID);
myRepo.save(entity);
}...
When event listener class is called from test the security context is not set. I have tried using #WithMockUser but this doesn't seem to work. Could I maybe wrap call to security context in a service and then somehow mock this call in my integration test. How do I set mock on entity listener if this is an option. When I use #CreatedBy and #CreatedDate the security context is not an issue but I need to manually use #PreInsert for a separate reason.

What is the error you got? Maybe because it cannot get the user.
You can try:
val userPrincipal =
new org.springframework.security.core.userdetails.User(username,
"",
true,
true,
true,
true,
authorities);
val auth = new TestingAuthenticationToken(userPrincipal, null, authorities);
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);

Related

Spring Boot with Validation - Inject repository inside custom validator gives Null

Although I found some similar problems (here and here for example) it did not completely answer my problem, as I used spring Boot 2.4.4, or simply don't work. My problem is that repository becomes null during second (JPA) validation. Here is the detail :
I use Spring Boot, and Bean Validation, via the spring-boot-starter-validation dependency.
I have a custom validator to check if an email exists in database. First I declare the #EmailExisting annotation :
#Documented
#Constraint(validatedBy = EmailValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface EmailExisting {
String message() default "Email already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then the custom validator, with the repository injected to check Database :
public class EmailValidator implements
ConstraintValidator<EmailExisting, String> {
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String email,
ConstraintValidatorContext cxt) {
List<User> users = userRepository.findByEmail(email);
if (!users.isEmpty()) {
return false;
}
return true;
}
}
Finally the SPRING MVC part, with the #Valid annotation to trigger validation :
#Autowired
UserRepository userRepository;
#PostMapping(value = "/users")
public ResponseEntity addUSer(#Valid #RequestBody User user) {
user.setEmail(user.getEmail());
user.setLastName(StringUtils.capitalize(user.getLastName()));
user.setFirstName(StringUtils.capitalize(user.getFirstName()));
userRepository.save(user);
return new ResponseEntity(user, HttpStatus.CREATED);
}
Problem:
When I test the endpoint, I have a NullPointerException.
At first, during MVC validation the repository is correctly injected. But after, when we call save() method to save the entity in the controller, the control triggers again, and the repository becomes null, hence the NullPointerException.
I found a workaround, to deactivate control on JPA side, with the following configuration :
spring.jpa.properties.javax.persistence.validation.mode=none
It works well, but I think it should be possible to have the repository injected in the 2 cases?
All code is in my repository
Thank you in advance!

Spring Boot - Hibernate custom constraint doesn't inject Service

I will try to ignore other details and make it short:
#Entity
public class User
#UniqueEmail
#Column(unique = true)
private String email;
}
#Component
public class UniqueEmailValidatior implements ConstraintValidator<UniqueEmail,String>, InitializingBean {
#Autowired private UserService userService;
#Override
public void initialize(UniqueEmail constraintAnnotation) {
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(userService == null) throw new IllegalStateException();
if(value == null) return false;
return !userService.isEmailExisted(value);
}
}
This will work when the validation is made in Spring (Spring MVC #Valid or inject the Validator using #Autowire), everything will be fine.
But as soon as I save the entity using Spring Data JPA:
User save = userRepository.save(newUser);
Hibernate will try to instantiate a new UniqueEmailValidatior without inject the UserService bean.
So how can I make Hibernate to use my UniqueEmailValidatior component without it instantiate a new one.
I could disable hibernate validation using spring.jpa.properties.javax.persistence.validation.mode=none but I hope there is another way
Update: Here is my UserService:
#Autowired private Validator validator;
#Transactional
public SimpleUserDTO newUser(UserRegisterDTO user) {
validator.validate(user);
System.out.println("This passes");
User newUser = new User(user.getUsername(),
passwordEncoder.encode(user.getPassword()),user.getEmail(),
"USER",
user.getAvatar());
User save = userRepository.save(newUser);
System.out.println("This won't pass");
return ....
}
I would expect that Spring Boot would wire the existing validator to the EntityManager apparently it doesn't.
You can use a HibernatePropertiesCustomizer and add properties to the existing EntityManagerFactoryBuilder and register the Validator.
NOTE: I'm assuming here that you are using Spring Boot 2.0
#Component
public class ValidatorAddingCustomizer implements HibernatePropertiesCustomizer {
private final ObjectProvider<javax.validation.Validator> provider;
public ValidatorAddingCustomizer(ObjectProvider<javax.validation.Validator> provider) {
this.provider=provider;
}
public void customize(Map<String, Object> hibernateProperties) {
Validator validator = provider.getIfUnique();
if (validator != null) {
hibernateProperties.put("javax.persistence.validation.factory", validator);
}
}
}
Something like this should wire the existing validator with hibernate and with that it will make use of auto wiring.
NOTE: You don't need to use #Component on the validator the autowiring is build into the validator factory before returning the instance of the Validator.
To have the Spring beans injected into your ConstraintValidator, you need a specific ConstraintValidatorFactory which should be passed at the initialization of the ValidatorFactory.
Something along the lines of:
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.constraintValidatorFactory( new MySpringAwareConstraintValidatorFactory( mySpringContext ) )
.build();
with MySpringAwareConstraintValidatorFactory being a ConstraintValidatorFactory that injects the beans inside your ConstraintValidator.
I suspect the ValidatorFactory used by Spring Data does not inject the validators when creating them, which is unfortunate.
I suppose you should be able to override it. Or better, you should open an issue against Spring Boot/Spring Data so that they properly inject the ConstraintValidators as it the second time in a row we have this question on SO.
The answer is quite big to post here. Please check for this article in S.O to help you with. This should help you get started.
Test Custom Validator with Autowired spring Service
The problem is hibernate will no way know spring definition. However you can make Entities to be aware of any type of javax.validation types. Hope this helps.

Mock SecurityContextHolder / Authentication always returning null

I'm aware this question gets asked a lot, but maybe I have some things that are particular to this. I'm trying to do some integration tests on a Spring Boot application that supports REST (not Spring MVC) and for some reason SecurityContextHolder.getContext().getAuthentication() always returns null, even when using #WithMockUser on the test. I'm not certain if this has to do with using profiles on the configuration classes, but so far we haven't had troubles with this.
Class
#Override
public ResponseEntity<EmployeeDTO> meGet() {
Principal principal = SecurityContextHolder.getContext().getAuthentication();
logger.debug("Endpoint called: me({})", principal);
EmployeeDTO result;
// Get user email from security context
String email = principal.getName(); // NPE here
// ...
}
Test
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"eureka.client.enabled:false"})
#WithMockUser
#ActiveProfiles(value = "test")
public class MeControllerTest extends IntegrationSpringBootTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private SecurityContext securityContext;
#MockBean
private Authentication authentication;
#MockBean
private EmployeeRepository employeeRepository;
#BeforeClass
public static void setUp() {
}
#Before
#Override
public void resetMocks() {
reset(employeeRepository);
}
#Test
public void meGet() throws Exception {
when(securityContext.getAuthentication()).thenReturn(authentication);
securityContext.setAuthentication(authentication);
when(authentication.getPrincipal()).thenReturn(mockEmployee());
SecurityContextHolder.setContext(securityContext);
when(employeeRepository.findByEmail(anyString())).thenReturn(mockEmployee());
ResponseEntity<EmployeeDTO> employeeDTOResponseEntity =
this.restTemplate.getForEntity("/me", EmployeeDTO.class);
// ...
}
If I return a mock Principal instead of mockEmployee() the test cannot even start because this happens:
org.springframework.beans.factory.BeanCreationException: Could not inject field: private org.springframework.security.core.Authentication com.gft.employee.controller.MeControllerTest.authentication; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'org.springframework.security.core.Authentication#0' is expected to be of type 'org.springframework.security.core.Authentication' but was actually of type '$java.security.Principal$$EnhancerByMockitoWithCGLIB$$657040e6'
Additional clarifications: This Spring Boot app also uses OAuth2 for authorization, but it must be turned off for these tests. That's why we use profiles. Omitting the #ActiveProfiles annotation gives us a 401 Unauthorized error against the endpoint request.
I could use PowerMock but I would like to avoid it if possible.
Easier Way of writing Junit for Authentication SecurityContextHolder would be to mock them. Following is the working implementation of it.
You can add the mock classes as per your need and then set context of SecurityContextHolder and then use when() to further mock and return proper mock value.
AccessToken mockAccessToken = mock(AccessToken.class);
Authentication authentication = mock(Authentication.class);
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
when(SecurityContextHolder.getContext().getAuthentication().getDetails()).thenReturn(mockSimpleUserObject);
I ended up using MockMvc despite the app not being Spring MVC-based. Additionally, I separated the SecurityContext calls into another service, but before doing that I could assert that the #WithMockUser annotation was working properly.
What's key for this to work is using these snippets at class level:
#WebMvcTest(MeController.class)
#Import({ControllerConfiguration.class, BeanConfiguration.class})
public class MeControllerTest {
// ...
}
Using #WebMvcTest facilitates not having to initialize a SecurityContext in the first place. You don't even have to call springSecurity(). You can just just the mockMvc.perform() operations as usual, and any calls to the SecurityContext will return whatever mocked user you specify, either with #WithMockUser or mocking the service that handles such a call.
This sample code is working for me. This code is using JUnit 5.
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc //need this in Spring Boot test
public class LoginControllerIntegrationTest {
// mockMvc is not #Autowired because I am customizing it #BeforeEach
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Mock
DefaultOidcUser principal;
#BeforeEach
public void beforeEach() {
Authentication authentication = mock(OAuth2AuthenticationToken.class);
// Mockito.whens() for your authorization object
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
when(authentication.getPrincipal()).thenReturn(principal);
SecurityContextHolder.setContext(securityContext);
// setting mockMvc with custom securityContext
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
#Test
public void given_any_OAuth2AuthenticationToken_when_login_then_redirect_to_logout() throws Exception {
final String loginName = "admin";
// given
// manipulate the principal as needed
when(principal.getAttribute("unique_name")).thenReturn(loginName);
// #formatter:off
// when
this.mockMvc.perform(get("/login"))
.andDo(print())
//then
.andExpect(status().isFound())
.andExpect(redirectedUrl("/logout"));
// #formatter:off
}
}

Spring Boot JPA #Transactional #Service does not update, but #Transactional in controller does

I have a very basic Spring Boot/JPA stack app, with a controller, service layer, and repository that does not persist updates as I understand it should.
A trivial Entity:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
protected Customer() {}
public Customer(String name) { this.name = name; }
// standard getters,setters //
}
A trivial Repository:
#Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {}
A simple Service layer:
// If the service is #Transactional and the controller is not, the update does NOT occur
#Transactional
#Service
public class CustomerService {
private static final Logger LOG = getLogger(CustomerService.class);
#Autowired
private CustomerRepository customerRepository;
boolean updateCustomerName(Long id, String name) {
Customer customer = customerRepository.findOne(id);
if (customer == null) { return false; }
// Modifies the entity
customer.setName(name);
// No explicit save()
return true;
}
}
And a REST controller that uses it all:
// If the controller is #Transactional and the service is not, the update occurs
#RestController
#RequestMapping("/mvc")
public class CustomerController {
#Autowired
private CustomerService customerService;
#RequestMapping(path = "{id}", method = RequestMethod.PUT)
public ResponseEntity updateCustomerName(#PathVariable Long id, #RequestParam("name") String name) {
customerService.updateCustomerName(id,name);
return ResponseEntity.noContent().build();
}
}
These are wired together with a simple one-liner SpringBootApplication
I have SQL debug logs enabled and see the selects, update, etc.
With the code above: When the service method is invoked by the controller, the modified entity is not persisted. SQL logs show the select of the entity but no update.
There is also no update if nothing is marked #Transactional
However, simply by moving the #Transactional annotation from the service class to the controller class, the SQL update does occur.
If I add an explicit customerRepository.save(customer) to the service method, the update also occurs. But my understanding is that the ORM should automatically save modified persistent entities.
I'm sure the issue has something to do with the EntityManager lifecycle in the web request, but I'm puzzled. Do I need to do additional configuration?
Complete example at https://github.com/monztech/SO-41515160
EDIT: This was solved, see below. Per the Spring spec #Transactional does not work in package-private methods and mistakenly did not make the update service method public.
The update will occur if the method is public and the service class has the #Transactional annotation.
I do have another question, however. Why is the #Transactional annotation necessary? (the update does not occur without it) Shouldn't the entity manager still persist the object because of the open session in view mechanism that Spring uses, independent of any transaction?
Make your updateCustomerName method public.

Add role to a method

I use Spring Boot with Spring Security.
To create an new user, the "creating" user should be an admin.
But if the "creating" user only has the standard role...
#PreAuthorize("hasRole('admin')")
#RequestMapping(value = "/users", method = RequestMethod.POST)
public Long createUser(#RequestBody #Valid final UserAppDto user) {
return userService.save(user);
}
...the result is that a non-admin user is able to create a new user.
In my UserApp class, for the role, I have:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private Set<Role> roles = new HashSet<>();
My main application
#EntityScan(basePackageClasses = {Application.class, Jsr310JpaConverters.class})
#SpringBootApplication
#EnableCaching
#EnableScheduling
public class Application implements SchedulingConfigurer{
#Bean
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
return new ApplicationSecurity();
}
}
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
}
Do not use pre-post annotations on controlers. They use AOP under the hood, so they do not work on controlers unless they implement interfaces or unless you use proxy-target-class=true, which may have other drawbacks (*).
This annotations should be used on services, because service beans are generaly bound in controlers as interfaces and so the AOP machinery will work as expected.
The spring-security way for limiting access to url is via http security, not via method security.
(*) in particular, never mix class proxying and JDK proxying in same application...

Resources