Spring MVC Session Attribute Access - java

Is there any way under spring 3.0 to access the HttpSession without including it in the method signature? What I really want to do is be able to pass in values from an HttpSession that CAN BE null.
Something like this:
#RequestMapping("/myHomePage")
public ModelAndView show(UserSecurityContext ctx) {}
instead of this:
#RequestMapping("/myHomePage")
public ModelAndView show(HttpSession session) {
UserSecurityContext ctx = (UserSecurityContext) session.getAttribute("userSecurityCtx");
}

The #SessionAttribute annotation mentioned by #uthark is not suitable for this task - I thought it was too, but a bit of reading shows otherwise:
Session attributes as indicated using
this annotation correspond to a
specific handler's model attributes,
getting transparently stored in a
conversational session. Those
attributes will be removed once the
handler indicates completion of its
conversational session. Therefore, use
this facility for such conversational
attributes which are supposed to be
stored in the session temporarily
during the course of a specific
handler's conversation.
For permanent session attributes, e.g.
a user authentication object, use the
traditional session.setAttribute
method instead. Alternatively,
consider using the attribute
management capabilities of the generic
WebRequest interface.
In other words, #SessionAttribute is for storing conversation MVC-model objects in the session (as opposed to storing them as request attributes). It's not intended for using with arbitrary session attributes. As you discovered, it only works if the session attribute is always there.
I'm not aware of any other alternative, I think you're stuck with HttpSession.getAttribute()

You can use a RequestContextHolder:
class SecurityContextHolder {
public static UserSecurityContext currentSecurityContext() {
return (UserSecurityContext)
RequestContextHolder.currentRequestAttributes()
.getAttribute("userSecurityCtx", RequestAttributes.SCOPE_SESSION));
}
}
...
#RequestMapping("/myHomePage")
public ModelAndView show() {
UserSecurityContext ctx = SecurityContextHolder.currentSecurityContext();
}
For cross-cutting concerns such as security this approach is better because you doesn't need to modify your controller signatures.

Yes, you can.
#SessionAttributes("userSecurityContext")
public class UserSecurityContext {
}
#RequestMapping("/myHomePage")
public String show(#ModelAttribute("userSecurityContext") UserSecurityContext u) {
// code goes here.
}
See for details:
http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/bind/annotation/SessionAttributes.html
http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/bind/annotation/ModelAttribute.html

Related

Async and EhCache is not working for the second user logging in

I am trying to implement #async with #cacheable as below ..
#RequestMapping(value="ehcacheExample")
public ModelAndView loadehcacheExamplePage(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) {
cacheHelper.getDataUsingAsyncAndStoreInEhcache(userInfo, session);
//diffetent execution
...
return modelAndView;
}
And
#Cacheable(value="cacheKey")
public Future<String> getDataUsingAsyncAndStoreInEhcache(UserLoginSessionInfo userInfo,
HttpSession session) {
return ehcacheExampleDelegate.getDataUsingAsyncAndStoreInEhcache(userInfo,session);
}
#Async
public Future<String> getDataUsingAsyncAndStoreInEhcache(UserLoginSessionInfo userInfo,
HttpSession session) {
//Async execution
return new AsyncResult<String>(jsonInString);
}
Now as per my understanding this 'getDataUsingAsyncAndStoreInEhcache' method produces the result independent of the caller method,saves it in ehcache implementation. So, the next time I call an ajax method for getting the data from future, I am doing below implementation -
#RequestMapping(value="getCachedData")
public #ResponseBody String getCachedData(HttpSession session) {
UserLoginSessionInfo userInfo = HttpRequestSessionUtils.getLoggedinUserSessionInfo(session);
Future<String> cachedData = cacheHelper.getDataUsingAsyncAndStoreInEhcache(userInfo, session);
…
return dataFromFuture;
}
Now my expectation is, if any other user logs in after the first user, for him cacheHelper.getDataUsingAsyncAndStoreInEhcache(userInfo, session) should not execute the whole method. Rather it should retrieve the data from Ehcache memory.
But it is not happening. I can assure that caching is working because in the same session if the user calls the getCachedData ajax call multiple times, it is returning the data from cache only. But the issue is happening with the cacheHelper.getDataUsingAsyncAndStoreInEhcache(userInfo, session) implementation (mentioned in the #RequestMapping(value="ehcacheExample") method).
Can you please help me understand why this method is executing in whole, everytime the user calls loadehcacheExamplePage method rather than retrieving it from cache when a second user logging in?
I see different possible issues here. First, there is a bug between caching and async in Spring. However, I don't think it is relevant to you since your CacheHelper and CacheHelperDelegate are not the same object.
Now, it's the Future that will get cached. Not the value returned by it. It's weird. But it should still work.
Finally, Spring uses by default the parameters as the cache key. In your case UserLoginSessionInfo userInfo, HttpSession session. These are highly user specific. So it's normal that for two different users, the method will be called. If that's not what you want, you probably do not need to pass these parameters. You can also tell Spring what to use as a key with #Cacheable(key=...).

How to create a session scoped bean?

I would like to know how to crete a user session object that can be used across all user requests. The user can select profiles after login, so I need to update that session object with this profile data.
How is it done? How do I initialize a session scoped bean? How can I change objects in that SessionScoped bean later (it is necessary to remember some user actions in his session).
I would be very happy if you could help me in that matter :)
#SessionScoped #Named
public class UserSession implements Serializable {
private ExtendedUserPrincipal extendedUserPrincipal;
#PostConstruct
private void instantiateSession() {
extendedUserPrincipal = new ExtendedUserPrincipal();
}
public void setUserPrincipal(UserPrincipal userPrincipal) {
extendedUserPrincipal.setUserPrincipal(userPrincipal);
}
public void setUser(User user) {
extendedUserPrincipal.setUser(user);
}
public void setUserSecurityData(UserSecurityData userSecurityData) {
extendedUserPrincipal.setUserSecurityData(userSecurityData);
}
#Produces #AuthenticatedUser
private ExtendedUserPrincipal getPrincipal() {
return extendedUserPrincipal;
}
}
I invalidate the session by calling logout() and invalidate() on the session which I get from HttpServletRequest.
I'm injecting the user principal like this. The object should be the same for every user session
#Inject
#AuthenticatedUser
private ExtendedUserPrincipal extendedUserPrincipal;
I'm not sure what all the annotations mean, but you should just be able to put objects in the session manually with your HttpServletRequest object.
request.getSession().setAttribute("userbean", yourBean);
Then if you need to update it just get and set it like any other map
request.getSession().getAttribute("userbean");
The userbean will remain in the session until it is invalidated.
There are many different java libraries with different # annotations, but an annotation is usually just a shortcut for a more basic, manual operation.
I'm not familiar with the specific library/annotations you are using, but from the looks of it, #SessionScoped will just inject the bean into the user's session attributes 'automagically' for you.
A user's session is just a map that is active while a particular user is logged in. You could put any kind of java object in the session attributes map, it doesnt need to be a special kind of object or anything. A 'session scoped bean' is basically fancy words for 'a java object which has been added to the user's session attributes map.'
When the user's session ends, the request.getSession() object is destroyed, along with the objects in the attributes map (so long as they are not referenced anywhere else) thats why they are 'session scoped'.
Also, you might want to try and put #SessionScoped #Named on seperate lines, I'm not sure it will parse them on one line like that.

Spring MVC3 #SessionAttributes and #ModelAttribute will automatically get value from request

I’m trying to develop a Spring MVC application, now I encounter a question. When login successful I add the User entity to session and call http://localhost:8080/user to get the session user. Everything is OK here. But if I call the URL like this http://localhost:8080/user?username=testuser then the session user's username will change to testuser. What should I do that just get current user from session?
The code likes below
Entity:
#Entity
public class User {
private Long id;
private String username;
// ...Getter and Setter...
}
Controller:
#Controller
#RequestMapping("user")
#SessionAttributes("current_user")
public class UserController {
#RequestMapping(method=RequestMethod.GET)
#ResponseBody
public User testSession(#ModelAttribute("current_user") User user) {
return user;
}
}
Response of http://localhost:8080/user
[{"id":1,"username":"aaa111"}]
Response of http://localhost:8080/user?username=testuser; it should be same as above, but is
[{"id":1,"username":"testuser"}]
The #SessionAttributes annotation isn't intended for this. Its intend is to store objects in the session during http requests. Imagine a lengthy database call to retrieve an object you don't want to retrieve this object each time but probably reuse an existing one. The object is to be intended to be used as a #ModelAttribute, this annotation indicates that you want to use this object for binding (i.e. you have a form to change attributes of the object). When you are finished with the editing of the object you should make this clear by calling setComplete() on the SessionStatus object. See also here.
You want to store an object in the session and retrieve it when you need it. For this use the HttpSession in the normal way of calling setAttribute and getAttribute. To obtain the current HttpSession you can simply add a method argument of the type HttpSession and it will be injected for you. (See here for a list of supported method arguments).
public void myRequestHandlingMethod(HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
}
Or as you are already using Spring you could use the WebUtils for convenience. You can use the getSessionAttribute or getRequiredSessionAttribute methods to obtain the value from the session.
public void myRequestHandlingMethod(HttpServletRequest request) {
User currentUser = (User) WebUtils.getSessionAttribute("currentUser", request)
}
Another solution would be to extend Spring MVC. Spring MVC uses a HandlerMethodArgumentResolver to handle all the different types of method arguments. This mechanism is pluggable. You could create an annotation #CurrentUser and create a CurrentUserHandlerMethodArgumentResolver that will retrieve the user from the session and injects it in that place. You could then simply add your current user to your method signature.
public void myRequestHandlingMethod(#CurrentUser User user) { ... }
Configure the custom argument resolver
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="com.yourcomponany.app.web.CurrentUserHandlerMethodArgumentResolver />
</mvc:argument-resolvers>
</mvc:annotation-driven>
It also looks like you are rolling your own security framework, which I would advice against. Instead I would suggest using Spring Security instead. Advantage of this is that this provides integration with the Servlet API allowing for retrieval of the current Principal by either doing it yourself (request.getUserPrincipal()) or simply adding a method argument of the type java.security.Principal. It also comes with a custom HandlerMethodArgumentResolver which allows you to obtain the current Spring Security Authentication object.
try to get session value in controller from servlet request like below
#Controller
#RequestMapping("user")
#SessionAttributes("current_user")
public class UserController{
#RequestMapping(method=RequestMethod.GET)
#ResponseBody
public User testSession(HttpServletRequest request){
//false means do not create new session
HttpSession session = request.getSession(false);
return session != null?session.getAttribute("current_user"):null;
}
}

Spring MVC validator: validate using current session object

I want to validate my MyAccount form using a custom Spring Validator.
Basic validation rules are no problem.
I now have this requirement, which seems pretty obvious and common:
If the user (currently logged in) changes his username, I'll have to check if it's already in use. If it's not been changed, I'm fine (it would definitely be in use when checked, that's why I don't want to check it in that case). The problem is, that the validator is a Spring managed Singleton and I don't seem to have access to the current session (where I store my login context - i.e. not Spring Security). But I need the currently logged in user, in order to compare its e-mail with the one entered in the form.
This is my scenario, but the question is actually about how to validate using an object from the user's session, in general.
The ways I was thinking about solving this:
Do only basic validation in the Validator and do the rest, that I need the session for, in the Controller. Doesn't seem to be a nice solution.
#RequestMapping(value="/myaccount", method=RequestMethod.GET)
public String myAccount(#Valid MyAccountForm form, BindingResult result, HttpSession session)
{
boolean hasUsernameChanged = // check using session
if (hasUsernameChanged && CustomerService.customerAlreadyExists(form.getUsername()))
result.rejectValue("username", "my.error.code");
if (result.hasErrors()) {
// handle errors
} else {
// proceed
}
}
Adding a second validate method to the Validator like so
public void validateWithCurrentCustomer(Customer current) {
...
}
and call it explicitly from the controller, with the appropriate object. Not much better, but at least the validation logic is in one class, although separated in two methods, one of which is not standard.
Not having the Validator a Spring managed (singleton) bean, but create it everytime in the initBinder method. Instead of:
#Autowired
public MyAccountController(MyAccountFormValidator validator)
{
this.validator = validator;
}
#InitBinder
protected void initBinder (WebDataBinder binder)
{
binder.setValidator(validator);
}
do
#InitBinder
protected void initBinder (WebDataBinder binder, HttpSession session)
{
Customer current = ...// get from session
binder.setValidator(new MyAccountFormValidator(current));
}
But here, the problem is, that since the validator is not Spring managed, it's hard to get a service (like CustomerService) injected into the validator, for checking if an e-mail is available or not.
Am I missing something, any other ways to achieve what I want? This seems to be a common problem, but I couldn't find any pattern on Google or SO.
Thanks for your hints.
You may try to access any Spring bean from your validator. Maybe this answer can help.
Where should I place validation code that requires access to database?
Basically, you can make a SpringBeanUtil singleton, which gives you access to any bean you want.

Spring lifecycle issue: the annotation #SessionAttributes makes session attributes live longer than the session itself

here is the code that can reproduce this issue:
#Controller
public class FirstController {
#RequestMapping(value = "firstpage", method = GET)
public String myHander(HttpSession httpSession) {
if (httpSession.getAttribute("someClass") == null) {
httpSession.setAttribute("someClass", new SomeClass());
}
return "firstpage";
}
}
the first controller puts something in the session if it's not already there.
#Controller
#SessionAttributes(types = SomeClass.class)
public class SecondController {
#RequestMapping(value = "secondpage", method = GET)
public String myHandler(SomeClass someClass, HttpSession httpSession) {
//asking spring for the SomeClass parameter, that's why we put it in the annotation.
System.out.print(someClass.hashCode());
httpSession.invalidate();
return "secondpage";
}
}
the second controller kills the session.
and in both jsp files, i have the following code that prints the hashcode of the session object and the hashcode of the session attribute:
session hash:
<%= session.hashCode() %>
<br/>
someclass hash:
<%= session.getAttribute("someClass").hashCode() %>
now if i run the application and visit "firstpage", i'll get this:
session hash: 1838367636
someclass hash: 1075505853
and then i visit the "secondpage", and will get this:
session hash: 842656294
someclass hash: 1075505853
we can see that the session itself is changed, because the second controller kills the session. but the session attribute(of the type SomeClass) remains the same.
then if i try to revisit the "secondpage", the session object changes every time, but the session attribute remains the same.
why does the session attribute(which is supposed to be attached to the session) have a longer lifecycle than the session itself?
PS:the complete code is here:https://github.com/cuipengfei/One-hundred-thousand-why/tree/master/20130701SessionAttributesLifeCycle/SpringMVC
you can run it with mvn jetty:run to reproduce the issue.
The documentation for this annotation isn't particularly clear, but my understanding is that it's used to share values between methods in the same controller. As I read the following:
This will typically list the names of model attributes or types of model attributes which should be transparently stored in the session or some conversational storage, serving as form-backing beans between subsequent requests
I interpret it to mean that each controller method invocation will be wrapped with a call to (1) load the session attributes before entry, and (2) store them at exit. Which would have the behavior that you're seeing.
This is reinforced (imo) by the following in the annotation's javadoc:
For permanent session attributes, e.g. a user authentication object, use the traditional session.setAttribute method instead
If they're telling you to use the standard functions to modify the session, it indicates to me that the annotation isn't simply a way to access items in the session.
And for a final data point: if this annotation were the way to manage session data, why have session-scoped beans?

Categories