How to synchronize two session scoped beans in JSF? - session

I have this case where I have two session-scoped beans. One is used for Login-functionality. So this bean remembers what the current user is, which it sets after a user has successfully logged in.
There is another session scoped bean that allows the user to configure some stuff, which is also supposed to be be kept along the session. The user can go back to the configuration-site whenever he wants and see his old (from the same session) data. Important to know is that the user does not need to be logged in to use this site. Imagine like a cart on a webshop, with many shops you can put stuff in the cart before even logging in and it will remain throughout your session.
Here is where it gets tricky: On this configuration-site, the user can access some special functionality, such as permanently saving his configuration-stuff, but only if he is logged in. If not, he simply won't have the option. Again, very similar to a webshop, if you actually want to order the cart of your session you usually have to log in at that point.
The problem is that if the user first goes onto the configuration-site, then this session bean will be created first. The session bean retrieves the user by a binding-annotation (CurrentUser) which is #Provided by the Login-Bean via it's getter for the current user.
However, at creation time of the configuration-site bean, there is no current user.
Now, if the user then decides to go and login, the configuration-site bean will still think that there is no currentUser, since that field was initialized when the bean was initialized and there is no logic that will update it.
How can I handle this situation? Do I have to start manually putting and retrieving stuff from the Session-Objects? So far everything was handled automatically by JSF / Application Server simply because of the #SessionScoped annotations.
Edit: Here goes some code to explain the situation further:
The Login-Bean:
#SessionScoped
#Named
public class LoginUserManager implements Serializable {
private UserBean currentUser;
// Logic that does the login and set the currentUser if successfull
// ...
// "Produces" currentUsers for other beans, that want to inject it simply
// via the #CurrentUser annotation, see below
#Produces
#CurrentUser
public UserBean getCurrentUser() {
return currentUser;
}
}
Then there is the configuration-manager
#SessionScoped
#Named
public class ConfigurationManager implements Serializable {
// Session based configuration data here
// And, the current user (if any)
#CurrentUser
private UserBean currentUser;
}
The CurrentUser annotation should be a simple "binding annotation" if I understood correctly. It's taken from a snippet I saw on the internet, to be honest. I found it elegant, thought it's smooth to read and functionally identical to injecting the LoginUserManager directly and then calling it's getCurrentUser() getter.
#Retention(RUNTIME)
#Target({TYPE, METHOD, FIELD})
#BindingType
public #interface CurrentUser {
}

Related

Update online user-data from outside the session (without reauthentication)

There are several scenarios, where I want to update the user/principal data such that the changes are reflected while the user stays logged in (I do not want to force re-authentication)
From "within" the session this is not a Problem:
#PostMapping("/updateInfo")
fun updateMyData(
#AuthenticationPrincipal user: AppUser,
#Valid #RequestBody newInfo: UpdateDataRequest
): ResponseEntity<TestUserInfo> {
val testInfo = TestUserInfo(user, newInfo)
user.info = testInfo
val updatedUser = users.save(user)
return ResponseEntity.ok(updatedUser.info!!)
}
When I allow the user to for example change the their own data, I can easily access and change the #AuthenticationPrincipal - in successive requests i can observe that the data is updated.
This is different when I need to change the user data from 'outside' the session.
use cases
There are 2 use cases for this:
a). an administrator changes user-data
b). the user confirms his email address
Now a). clearly happens from within another http-session where the principal is a user with some admin privileges.
For b). you might ask, why this doesn't happen within a session: I want a simple one-time confirmation link, i.e. a get request. I cannot assume, that the user is logged in via a session on the device the confirmation link is opened. It wouldn't feel right to me, to do a separate preauthentication provider or something to get the user authenticated - then there will an unnecessary session opened on a browser that is never used again.
So in both cases, when I fetch the user via a JPArepository, update data, and save it back, the change is up to date in the databse - but the logged-in users don't know of that change, because their user data is stored in the http session and doesn't know that it needs to be updated.
Note that I am not using redis/spring-session anything - this is just a plain http session, so from my understanding I can not use FindByIndexNameSessionRepository.
What I have tried
In spring-security issue #3849 it was suggested by rwinch to override SecurityContextRepository - however, there is no further information on how to do that exactly - I tried to understand the interface but couldn't get much further there.
I tried to get through the responses tothe followinf SO post:
How to reload authorities on user update with Spring Security (ignoring answers using redis.)
the most upvoted answer by leo doesn't help, as mentioned in the comments there
Aure77 suggests using SessionRegistry, which I tried to use also following bealdung - but to no avail: I cannot the right session, getallprincipals() is always empty when there is an active session for a logged in user. In case I had the right session I'm still not even sure how to move on from there, as Aure just suggests using expireNow() which forces reauthentication - I want to avoid that.
alexkasko suggests something similar - from his I am thinking that maybe spring boot uses a thread-local securityContextRepository by default, and thats why i have no principals. He the suggests something that i haven'T yet understood - also the answers are quite old (2012) and I don'T feel very secure about trying to understand and apply that
TwiN suggests using a HandlerInterceptor. Hasler Choo suggests a modified version with a hashset that seems to be more close to what i need. As described below - it has its problems though.
HandlerInterceptor based approach
This is the only solution so far that I could successfully implement - but it doesn't seem very flexible. My implementation so far will only cover user-role changes.
Configuration:
#Configuration
class WebMvcConfig : WebMvcConfigurer {
#Autowired
private lateinit var updateUserDataInterceptor : UpdateUserDataInterceptor
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(updateUserDataInterceptor)
}
}
The HandlerInterceptor:
#Component
class UpdateUserDataInterceptor(
#Autowired
private val users: AppUserRepository
) : HandlerInterceptor {
private val usersToUpdate = ConcurrentHashMap.newKeySet<Long>()
fun markUpdate(user: AppUser) = usersToUpdate.add(user.id)
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
val auth = SecurityContextHolder.getContext().authentication
(auth.principal as? AppUser)?.apply {
synchronized(usersToUpdate) {
if (id in usersToUpdate) {
role = users.findById(id).get().role
usersToUpdate.remove(id)
}
}
}
return true
}
}
Instead of just updating the role, what I would rather like, is just replace the entire principle - but the principal is final in the Authentication object.
So whenever a would wnat something else than the role updated, this has to specifically be mentioned here.
Remaining questions:
Are there other solutions than the HandlerInterceptor?
Is there a HandlerInterceptor based solution, that allows me to fully update the principal object
I am not considering single instance applications
1. Three factors in play
How quickly you want the changes reflected ( current session and current request vs current session and next request vs next session)
Do you have to keep the response time minimally affected by using distributed memory or cache?
Do you want to cut the cost (cannot use distributed memory) at the expense of response time?
Now you can you choose one option from first factor. But with second and third factors, you optimise one factor at the expensive of other one. Or you try to find a balance like your attempt to keep a list of affected users in memory and then hit the database for those affected.
( Unfortunately your optimisation to keep list of affected users in UpdateUserDataInterceptor as it is not stored in distributed memory won't work unless it is a single instance application)
2. Now based on my understanding of your question, I am making the following answers to the three factors in play.
current session next request
reduced cost (no distributed memory)
performance hit with database calls
( I will later update my thoughts on other possible paths and possible implementations for those paths)
3. Implementation options for the selected path - next-request-with-db-calls-and-no-distributed-memory
Any component that is part of request filter chain with the ability to call the database can achieve this by updating the SecurityContext. If you do this in the SecurityContextRepository, you are doing it at the earliest opportunity and you may even have the opportunity to restore the SecurityContext with updated principle instead of updating the already created SecurityContext. But any other filter or Interceptor can achieve this too by updating the SecurityContext.
4. Detailed look into each Implementation
SecurityContextRepository Option :
Looking at the HttpSessionSecurityContextRepository, it seems straight forward to extend it.
public class HttpSessionSecurityContextRepository
implements SecurityContextRepository {
.....
public SecurityContext loadContext(HttpRequestResponseHolder reqRespHolder) {
HttpServletRequest request = reqRespHolder.getRequest();
HttpServletResponse response = reqRespHolder.getResponse();
HttpSession httpSession = request.getSession(false);
SecurityContext context = readSecurityContextFromSession(httpSession);
........
//retrieve the user details from db
//and update the principal.
.......
return context;
}
}
SecurityContextHolderStrategy Option
Looking at the ThreadLocalSecurityContextHolderStrategy, it also looks straightforward
final class ThreadLocalSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
....
public void setContext(SecurityContext context) {
// you can intercept this call here, manipulate the SecurityContext and set it
Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}
.....
}
Another filter or HandlerInterceptor //TODO WILL UPDATE
Note:
You mentioned principal is final in authentication object and you want to replace it. You can achieve this by creating a mutable wrapper of UserDetails, extending your current UserDetailsService and returning that wrapper. Then you can update the principal,
YourWrapper principalWrapper =(YourWrapper) securityContext
.getAuthentication().getPrincipal();
principalWrapper.setPrincipal(updated);

Storing session-scope beans in #controller

I am trying to store my session-scoped user beans in a singleton-scoped controller throughout their lifecycle in session. So whenever a user is being connected, I want to store it in an array with the rest of the users those who keep their sessions.
I know about injecting a session-scoped bean into a #Controller through proxy beans so that i have defined my session-scoped user beans as follow,
#Bean
#Scope(value="session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public IUser user ()
{
IUser user = new MyUser();
return user;
}
I have used #Autowire annotation to inject that bean into my controller class as below,
#Autowired
private IUser sessionUser;
So whenever a user is getting connected, I am storing that user in a ConcurrentHashMap which is defined and added as below,
private ConcurrentHashMap<Integer,IUser> userMap = new ConcurrentHashMap<>(50,0.9f,2);
public void addUser(IUser user)
{
if(user == null) return;
IUser retUser = userMap.putIfAbsent(user.getDbid(),user);
//...
}
So everything is working when the first user gets connected, I store its reference to map. Let's assume first user reference is
us.com.soemthing.orm.model.MyUser#135debf
Then let us assume the second user gets connected whose reference is,
us.com.soemthing.orm.model.MyUser#28zbdh
From the references, I can see that my session-scoped beans work fine as their reference is different. However, problems start when execution goes into addUser method. Even before adding the second user to the map, I check my userMap and see that user object it is storing replaced with the second one which is MyUser#28zbdh. So at the end, after adding the second user, My user map looks like this,
Map --> "1"- us.com.soemthing.orm.model.MyUser#28zbdh
Map --> "2"- us.com.soemthing.orm.model.MyUser#28zbdh
So that references are being updated always with the last one. I know that they are the proxy object to real objects but how I can store them?
Thanks
[EDIT] I wanted to provide additional information.
I am calling addUser from another singleton bean as, userInDBMemory.addUser(sessionUser); userInDBMemory is another singleton bean where i add my session user to a ConcurrentHashMap actually. I want to store my current online users on a map as I would like to search and query them without going to the database. So i would like to keep online users (who has a session in context) in memory for easier and faster access. To handle session expires, every online user sends a heartbeat to server to show he is online, I have a scheduled thread on server running in every X minutes and if it finds any user who didn't get heartbeat from the user for a while then it removes it from the map as it means user went offline. To summary my case I have a main controller where i get requests then the chain is like this: #Controller->singleton application bean->Singleton inMemoryDB bean (where I define my map and add user) My SessionUser session-scoped bean is #Autowired in #Controller and i pass it to other singleton beans as a parameter.Thanks for the response.
I have solved my problem by not storing session-scoped beans directly but their object copies.
//IMyUser sessionUser; --let say sessionUser is session-scoped bean in a singleton bean
so instead of ;
userInDBMemory.addUser(sessionUser);
I have copied the user first and added that object instead.
IMyUser copyUser = new MyUser();
BeanUtils.copyProperties(sessionUser, copyUser);
userInDBMemory.addUser(copyUser);

Spring: new() operator and autowired together

If I use Spring, which of these two methods is more correct.
Can I use the new() operator even if I use dipendency injection?.Can I mix both?
I would like to have some clarification on these concepts.
Thanks
First method:
#RequestMapping(method=RequestMethod.GET)
public String create(Model model){
model.addAttribute(new User());
return "index";
}
Second Method:
#Autowired
User user;
#RequestMapping(method=RequestMethod.GET)
public String create(Model model){
model.addAttribute(user);
return "index";
}
By using dependency injection does not mean that the use of new operator is automatically prohibited throughout your code. It's just different approaches applied to different requirements.
A web application in spring is composed of a number of collaborating beans that are instantiated by the framework and (unless overriding the default scope) are singletons. This means that they must not preserve any state since they are shared across all requests (threads). In other words if you autowire the User object (or any other model attribute), it is created on application context initialization and the same instance is given to any user request. This also means that if a request modifies the object, other requests will see the modification as well. Needless to say this is erroneous behavior in multithreaded applications because your User object (or other model attribute) belongs to the request, so it must have the very narrow scope of a method invocation, or session at most.
You can also have spring create beans with different scopes for you, but for a simple scenario of a model attribute initialization, the new operator is sufficient. See the following documentation if interested in bean scopes : Bean scopes
So in your use case, the second method is totally wrong.
But you can also delegate the creation of your model attributes to spring if they are used as command objects (i.e. if you want to bind request parameters to them). Just add it in the method signature (with or without the modelattribute annotation).
So you may also write the above code as
#RequestMapping(method=RequestMethod.GET)
public String create(#ModelAttribute User user){
return "index";
}
see also : Supported method argument types
If you want your beans to be "managed" by Spring (for e.g. to use with Dependency Injection or PropertySources or any other Spring-related functionality), then you do NOT create new objects on your own. You declare them (via XML or JavaConfig) and let Spring create and manage them.
If the beans don't need to be "managed" by Spring, you can create a new instance using new operator.
In your case, is this particular object - User - used anywhere else in code? Is it being injected into any other Spring bean? Or is any other Spring bean being injected in User? How about any other Spring-based functionality?
If the answer to all these questions is "No", then you can use the first method (create a new object and return it). As soon as the create() method execution is complete, the User object created there would go out of scope and will be marked for GC. The User object created in this method will eventually be GC-ed.
Things can be injected in two ways in a Spring MVC applications. And yes, you can you can mix injection and creation if doing right.
Components like the controller in your example are singletons managed by the application context. If you inject anything to them it is global, not per request or session! So a user is not the right thing to inject, a user directory can be. Be aware of this as you are writing a multithreaded application!
Request related things can be injected to the method like the used locale, the request, the user principal may be injected as parameters, see a full list at Spring MVC Documentation.
But if you create a model attribute you may use new() to create it from scratch. I will not be filled by spring but to be used by your view to display data created by the controller. When created in the request mapped method that is ok.

Spring caching - auto update cached setter

I am really new to spring caching.
I saw that spring caching annotations are based mostly on annotating methods.
My question is if i have a dao class that has the following method:
public User getUserById(long id);
And lets say i cache this method.
and have another dao method (with no annotation) like:
public void updateUser(User u);
Now imagine this scenario:
1) someone invokes the getUserById(user1Id); //(cache of size 1 now has user1)
2) someone else invokes the updateUser(User1) ; // lets say a simple name change
3) someone else invokes the getUserById(user1Id);
My question :
Assuming no other actions were taken, Will the 3rd invocation receives a deprecated data? (with the old name)?
If so , how to solve this simple use case?
Yes, the third invocation will return a stale data.
To overcome this, you should trigger a cache eviction after the update operation, by annotating your update method with a #CacheEvict annotation:
#CacheEvict(value = "users", key = "#user.id")
void updateUser(User user) {
...
}
Where value = "users" is the same cache name you had used for getUserById() method, and User class has an id property of type Long (which is used as the users cache key)
You need to remove the stale items from cache. The Spring framework helps with several caching related annotations (you could annotate the update-method with #CacheEvict for example). Spring has a good documentation on caching by the way.

Session map is null when printed

I am working on JSF1.1 with JSP as presentation technology.
I have a managed bean with an ArrayList and I display the list in as rows.
Everything works fine. I have session replication with two server nodes and when I replicate the session, and put one of the cluster down, app is now on second cluster but the session attributes are lost.
I tried to print sessionMap using ExternalContext to see session attributes but that is null too.
What could be a possible reason?
The attributes are likely not Serializable. That's a requirement to get them to persist on disk and/or to transfer as bytes over network.
To fix this, just ensure that all session attributes (including session scoped managed beans) implement Serializable like this:
public class SomeSessionClass implements Serializable {
// ...
}
Don't forget to make any members Serializable as well whenever applicable. E.g.
public class SomeSessionClass implements Serializable {
private SomeNestedClass foo; // Has to implement Serializable as well!
// ...
}

Resources