using spring webflux and EntityListener - spring

I am converting a Springboot app from using Spring MVC to Spring Webflux. The app has EntityListener on a JPAs, inside the EntityListener it calls SecurityContextHolder.getContext().getAuthentication() to get information about the current user and sets some fields on the JPA object based on the user info.
#MappedSuperClass
#EntityListner(value = {MyEntityListener.class})
public class MyBaseJPA {
#Column(name = "CREATED_BY")
private String createdBy;
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
}
public class MyEntityListener{
#PrePersist
public void perPersist(MyBaseJPA jpa) {
User u = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
jpa.setCreatedBy(u.getUserId());
...
}
}
in Spring Webflux, the SecurityContextHolder can no longer be used, ReactiveSecurityContextHolder is used for getting the context, to get the actual User object, it has to call block
User u = (User)ReactiveSecurityContextHolder.getContext().map(ctx->(User)ctx.getAuthentication().getPrincipal()).block();
when I do this i get at InvalidDataAccessApiUsageExcepiton: block()/blockFirst()/blockLast() are blocking, which is not suported in thread reactor-http-nio-5;....
In a spring webflux app, how can I implement an entity listener that can set some fields on the JPA where the data comes from ReactiveSecurityContextHolder?

Related

#PostLoad and #PrePersist in Spring Data JDBC project

Does Spring Data JDBC have anything similar to #PostLoad and #PrePersist from Spring Data JPA?
With Spring Data JDBC you currently can't annotate the entity directly. But there are life cycle listeners and callbacks that you can use for the same purpose.
One of the examples given:
#Component
class UserCallbacks implements BeforeConvertCallback<User>,
BeforeSaveCallback<User> {
#Override
public Person onBeforeConvert(User user) {
return // ...
}
#Override
public Person onBeforeSave(User user) {
return // ...
}
}

#CreatedBy becomes null when updating

I have this entity:
#Entity
#EntityListeners( AuditingEntityListener.class )
public class Employee {
#Id
#GeneratedValue
int id;
private String name;
...
#LastModifiedBy
private String modifiedBy;
#CreatedBy
private String createdBy;
}
And i have this config class:
#Configuration
#EnableJpaAuditing
public class DataConfig {
#Bean
public AuditorAware<String> auditorAware() {
return () ->
SecurityContextHolder.getContext().getAuthentication().getName();
}
}
The problem is:
When updating entity, the created_by becomes null.
Any help please.
I'd suggest to you to ensure if your spring boot app is scanning the DataConfig class.
In addition, well in case of having a REST Service (I don't know because that info is not added to the question) but bear in mind a REST Service is Stateless, and you need fetch the Authorization from the request to add it to the spring security context BEFORE executing the request.
But if your spring boot app is just a Spring MVC one with basic Authorization, be sure you have an open session once the data is updated/created

Mock security context for JPA integration test

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);

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.

Having trouble injecting my Spring security user into my controller

I'm using Spring 3.1.0.RELEASE with Spring Security 3.1. I want to inject my Spring user (i.e. the user who is currently logged in) into a controller. I want to do this as opposed to using
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
because it allows me to test the controller more easily with JUnit. However, I'm having a problem with my current setup. My question is, what is the correct way to inject my user (per request) into my controller? In my application context file, I have ...
<bean id="userDetails" class="com.myco.eventmaven.security.SecurityHolder" factory-method="getUserDetails" scope="request">
<aop:scoped-proxy />
</bean>
where I define my factory class as ...
public class SecurityHolder {
#Autowired
private static UserService userService;
public static MyUserDetails getUserDetails() {
final Authentication a = SecurityContextHolder.getContext().getAuthentication();
if (a == null) {
return null;
} else {
final MyUserDetails reg = (MyUserDetails) a.getPrincipal();
final int userId = reg.getId();
final MyUserDetails foundUser = userService.findUserById(userId);
return foundUser;
} // if
} // getUserDetails
}
but the factory class repeatedly dies because "userService" fails to get autowired (the value is always null). I'm looking for a better way to do all this that can easily also integrate into my JUnit test. Any ideas?
Edit: Here's the JUnit test I'm looking to work with ...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "file:src/test/resources/testApplicationContext.xml" })
public class UserEventFeedsControllerTest extends AbstractTransactionalJUnit4SpringContextTests {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
...
#Autowired
private RequestMappingHandlerAdapter handlerAdapter;
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#Before
public void setUp() {
...
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
...
#Test
public void testSubmitUserEventFeedsForm() throws Exception {
request.setRequestURI("/eventfeeds.jsp");
request.setMethod("POST");
final List<EventFeed> allEventFeeds = getAllEventFeeds();
request.setParameter("userEventFeeds", allEventFeeds.get(0).getId().toString());
final Object handler = handlerMapping.getHandler(request).getHandler();
final ModelAndView mav = handlerAdapter.handle(request, response, handler);
assertViewName(mav, "user/eventfeeds");
}
You cannot autowire static fields. There are some workarounds, but I don't want to show them to you...
There are plenty of ways to access current user in an easier and more elegant matter:
Inject Principal to your controller (see When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?):
public ModelAndView showResults(final HttpServletRequest request, Principal principal) {
final String currentUser = principal.getName();
UserDetails ud = ((Authentication)principal).getPrincipal()
Develop your custom facade over SecurityContext
Replace built-in contextHolderStrategy in SecurityContextHolder for the purpose of testing
See also
How to get active user's UserDetails
Spring 3 MVC Controller integration test - inject Principal into method

Resources