spring #transactional does not work with multitenancy - spring

I am developing multi-tenancy application using spring 4/hibernate 4 (no jpa). My user data for all tenants is on master database. Rest all tables are on individual tenant database. Using Spring security to authenticate the user. If user gets authenticated then I set tenatIdentifier.
For User Management each tenant admin needs to get connected to master database irrespective of their own tenantIdentifier. I am doing some thing like below.
My service method
#Transactional
public void saveUser() {
dao.saveUser();
}
My Dao method
public void saveUser() {
Session session = sessionFactory.withOptions().tenantIdentifier("master").openSession();
//Transaction transaction = session.beginTransaction();
//some dummy code to show case my problem
User user1 = new User();
user1.setUserName("manisha");
user1.setPassword("password");
user1.setFirstName("manisha first");
user1.setLastName("manisha last");
user1.setEmail("manisha#email.com");
user1.setState("Active");
user1.setTenantName("manisha");
session.persist(user1);
//transaction.commit();
session.close();
}
The problem is my user does not get persist unless I uncomment "transaction" code. So it seems in my case spring #Transactional does not work.
On the other hand for all other tables which resides on tenant db and if I get session using sessionFactory.getCurrentSession(), then data persists well inside tenant database. If any error occurs, it gets rolled back properly. So spring #Transactional annotation seems to be working as expected.
What am I doing wrong here?. Any help is most welcome

Related

Spring Data: How to maintain cache consistency in transactional methods?

Let's assume we have a standard Spring Boot application with JPA. We have repositories, services and REST controllers. On the service layer, we have this:
#Service
public class UserService {
#Autowired
private UserRepository userRepo;
#Transactional(readOnly = true)
public User getUserById(userId: String) {
return this.userRepo.findById(userId).orElse(null);
}
#Transactional(readOnly = false)
public User saveUser(User user){
this.userRepo.save(user);
}
}
We want to cache the result of getUserById, either via #Cacheable or via an explicit cache. We have the following two options:
If a call to saveUser(...) occurs, we call repo.save(user) and then we immediately put the saved user into our cache (write-through).
If a call to saveUser(...) occurs, we invalidate the corresponding entry in the cache. Once the user is requested by ID again, we read it from the database and put it into the cache.
Both methods have issues:
If we write-through, we risk that the database transaction fails at some point after the repo.save(user) call and is rolled back. We may have written a version of the user into our cache that never hit the database. Our cache is out-of-sync with the database.
If we only invalidate the cache, there is a time period between the invalidation and the transaction commit where a concurrent transaction may call getUserById(...). This transaction will still read the old version of the user and write it into the cache. The result is that we have outdated data in our cache.
Is the built-in spring cache susceptible to these issues as well? How do you avoid such problems in your application?
Ok so I got confused here. There is the Spring Boot side of caching, which is supposed to cache method results, and then there is the 2nd level cache of the JPA provider. These are different things with different purposes. My bad for mixing them up.

Why is the JPA repository called from spring schedular not able to get the authentication from Security Context

I have a springboot application where with authentication available in SecurityContext post login. Any call from Rest Controller to persist any entity, getCurrentAuditor() method is called which returns the current principle which is used for auto updating the created date column.
I created an schedular using spring "awaitility" dependency. However, this schedular calls an update on a entity. When update is called and spring authentication is checked, it comes as null, even though i have logged in from front end. From front end i am able to persist other entities and gets the authentication object as well.
As per my understanding, this might be happening because the schedular starts as soon as Springboot kicks in and making save request independently. If that understanding is correct, how should i resolve this?
If the Scheduler can use a "system" user for update the entity, you can do something like the following and in the scheduler code perform the authentication:
public void authenticate() {
Authentication auth = authenticationManager.authenticate(getBatch());
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
}
public UsernamePasswordAuthenticationToken getBatch() {
return UsernamePasswordAuthenticationTokenBuilder.anUsernamePasswordAuthenticationToken()
.withCredentials(batchProperties.getPassword()).withUserCode(batchProperties.getUser()).withUserDto(
userDtoFactory.getBatch()).build();
}

writing junit test for spring security login with database user

Hello I am trying to write test for spring user login.
Basically my program uses registration where user provides username, password and email to register to database. Then with registered username and password you can log in to application.
public AuthenticationResponse login(LoginRequest loginRequest) {
try{
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),
loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
return AuthenticationResponse.builder()
.token(jwtProvider.generateToken(authentication))
.refreshToken(refreshTokenService.generateRefreshToken().getRefreshToken())
.expiresAt(Instant.now().plusMillis(jwtProvider.getJwtExpirationInMillis()))
.build();
} catch (BadCredentialsException e){
throw new AuthException("Incorrect username or password");
}
}
But I cant figure out how to write spring boot junit5 test for login. First time I am trying to write these spring tests. Where do I need start?
You can write unit test by mocking every dependency and check if correct methods were called with verify()
You can create test with spring context up, and just mocking db layer, then you'll be able to test more then one class
You can create test with spring context but instead of mocking db layer you can stub your db layer (implement dao which will use cache). This way you can insert user into cache and during testing login you will return that user from cache
Same as above but using h2 database
Same as above but using test containers which will start docker image of your actual database. Then you will be able to test whole flow with db constraints

How to maintain session for a user until user is logged-in in spring mvc

I'm creating a project in spring mvc, hibernate. I'm stuck at maintaining sessions after a user is logged in. I want to maintain the session until the user is logged in.
Thanks.
Using the controller to log in your user you could create and store your session there...
public ModelAndView yourMethod(HttpSession session){
session.setAttribute("user", 1);
return new ModelAndView("home");
}
If you want to retrieve it you can do it like this
Long userId = (Long) session.getAttribute("user");
Spring will maintain the session for you,

Spring Security user account registration, creation and management

I've been looking into using Spring Security for the authentication/authorization of my web application (this will be JDBC based).
However, a core component seems to be left out from my perspective. How do I register/create new users? Is there an out of the box API for that?
Do i need to write user registration and management from scratch?
Things i need to do include:
- Registering a new user
- Resetting passwords
- Emailing a user to activate their account
- Emailing a user to reset their account.
Thank you in advance.
I use Spring Security on my project. The framework does not have an API for user creation or registration as you asked. For Spring Security to be generic and usable across multiple frameworks, it can only take you so far before you have to write custom code. You can't really get a more specific answer about a framework or tool to use because at this point you will just use the frameworks you are already using anyway.
If you've set it up to use users and roles in your database, from your data access layer you would create a record in the user table or update a password (preferably stored as a hash) in that record. And as Aravind said, Spring does provide email support.
If you really want to see one way to do it: I'm using Spring MVC, JSP, and Hibernate. I use Spring's form tags in a JSP to bind a new user form to a Person object, and my controller method passes that Person object to my Dao to persist it.
The controller method signature looks like this...
#RequestMapping(value = "/newUser", method = RequestMethod.POST)
public ModelAndView createNewUser(final #Valid #ModelAttribute Person user,
final BindingResult result,
final SessionStatus status,
final #RequestParam(value = "unencodedPassword", required = true) String password) {
...
user.getRoles().add(new Role(user, Role.APPLICATION_ROLE.ROLE_USER));
userDao.createNewUser(user);
...
}
and my PersonDao would use Hibernate to persist the user like so
#Transactional
public void createNewUser(Person user)
{
Session session = sessionFactory.getCurrentSession();
session.save(user);
session.flush();
}
Have a look at my answer here.
"I have implemented a JAVA project for this use case. It is open
source, based on Spring-Security. A release version is on
Maven-Central, so you do not need to compile it, but instead you can
fetch it as maven-dependency to your project!"
<dependency>
<groupId>com.ohadr</groupId>
<artifactId>authentication-flows</artifactId>
<version>1.5.0-RELEASE</version>
</dependency>
As far as I know, Spring Security does not have built in support for new user creation and registration. You will have to manage this yourself. However it does have emailing support. Check here for more on this.

Resources