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;
}
}
Related
Currently in most of my controller classes i check whether the session exists for each mapping.
For example:
#Controller
#RequestMapping("/admin")
public class AdminController{
#GetMapping
public ModelAndView admin(HttpSession session){
if(exists(session)){
...
}
}
#GetMapping("/addUser")
public ModelAndView user(HttpSession session){
if(exists(session)){
...
}
}
#GetMapping("/addBook")
public ModelAndView book(HttpSession session){
if(exists(session)){
...
}
}
Is there a way i can check session once the AdminController is called, instead of checking session for each mapping?
If you don't want to involve Spring Security, you can add an interceptor that will intercept all requests.
public class SessionCheck extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return exists(request.getSession());
}
}
First of all, in case you can, switch to Spring Boot. Configuration will be easier then.
Yes, in your WebSecurityConfigurerAdapterImpl you can specify, that the user must have a certain role to access certain areas. In your case it would be
http
.antMatches("/admin/**").hasRole(<UserRole.Admin>);
You can also use PreAuthorize:
#Controller
#RequestMapping("/admin")
public class AdminController{
#PreAuthorize("hasRole('ROLE_ADMIN')")
#GetMapping
public ModelAndView admin(HttpSession session){
}
#PreAuthorize("hasRole('ROLE_ADMIN')")
#GetMapping("/addUser")
public ModelAndView user(HttpSession session){
}
#PreAuthorize("hasRole('ROLE_ADMIN')")
#GetMapping("/addBook")
public ModelAndView book(HttpSession session){
}
}
You have to enable using this annotation. It's described in the Documentation.
So summarizing, you need Spring Security, then either configure access in WebSecurityConfigurerAdapter or enable annotation based access control and then specify the #PreAuthorize.
Edit:
OK, as the OP has asked for clarification, I decided to add some more general information about using Spring Security.
In Spring Security we base on UserDetails and UserDetailsService which returns UserDetails on login. Although, I recommend you to refactor your code and just have one class User which has a Collection of authorities you can do the following.
Create new class which would be UserDetailsImpl which would implement UserDetails. Also create some custom roles like ROLE_USER, ROLE_ADMIN. They should be an enum. Then in your UserDetailsImpl have fields that are important during the session - logged user UUID, name, email and some somehow required by the interface - isAccountLocked, isPasswordExpired etc, Also you'll need a collection of authorities which you'll return by a certain method.
You'll be able to use hasRole in your code then. What is cool, you'll also be able to get the logged user details in your application from the context.
I'm using Spring MVC Framework and I'd like all the .jsp pages of the View to have access to the User's attributes(name, sex, age...). So far, I use the addAttribute method of the Model(UI) in every Controller to pass the current User's attributes to the View. Is there a way to do this only once and avoid having the same code in every Controller?
You can use Spring's #ControllerAdvice annotation on a new Controller class like this:
#ControllerAdvice
public class GlobalControllerAdvice {
#ModelAttribute("user")
public List<Exercice> populateUser() {
User user = /* Get your user from service or security context or elsewhere */;
return user;
}
}
The populateUser method will be executed on every request and since it has a #ModelAttribute annotation, the result of the method (the User object) will be put into the model for every request through the user name, it declared on the #ModelAttribute annotation.
Theefore the user will be available in your jsp using ${user} since that was the name given to the #ModelAttribute (example: #ModelAttribute("fooBar") -> ${fooBar} )
You can pass some arguments to the #ControllerAdvice annotation to specify which controllers are advised by this Global controller. For example:
#ControllerAdvice(assignableTypes={FooController.class,BarController.class})
or
#ControllerAdvice(basePackages={"foo.bar.web.admin","foo.bar.web.management"}))
If it is about User's attributes, you can bind the model bean to session as an attribute which can be accessed on every view. This needs to be done only once.
Another option could be is to implement a HandlerInterceptor, and expose the model to every request.
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.
Am #SessionAttributes for maintaining SpringMVC.
Say, #SessionAttribute("user")
Currently am passing the object as ModelAttribute in all the controller, which needs to use the SessionObject "user" like
Class controller{
public ModelAndView method1(#ModelAttribute("user")){ }
public ModelAndView method2(#ModelAttribute("user")){ }
public ModelAndView method3(#ModelAttribute("user")){ }
public ModelAndView method4(#ModelAttribute("user")){ }
}
Is this the only way??
or
Is there any other way? such that I can have a base controller, which can return the session object by just extending the the base controller.
What I've been using in some of my projects is this:
String user = (String) hsr.getSession().getAttribute("user");
If you're looking for some sort of authentication I suggest you start using spring security or other authentication mechanisms that can filter out pages according to roles or authentication status.
Not sure what your exact requirement is, but what about creating a filter/interceptor that reads the value from session and stores it in a ThreadLocal that can be accessed by controllers later
The controllers that need to access #SessionAttributes you need to add the annotation as shown below.
#Controller
#SessionAttributes({"user"})
public class Controller {
.............
}
HTH
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