HttpSession in Springboot - java

I have two controllers. I'm trying to put the logged user into the session in one method, and then get it in a other method. But the sessions are different, how to fix it?
#RestController
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(value = "/user/signIn", method = RequestMethod.POST)
public ResponseEntity<DataUser> signIn(#RequestBody #Valid SignInUser signInUser,
HttpSession session) {
User user = userService.getUser(signInUser.getEmail(), signInUser.getPassword());
session.setAttribute("user", user);
DataUser dataUser = new DataUser((User) session.getAttribute("user"));
return ResponseEntity.ok(dataUser);
}
}
#RestController
public class MessageController {
#Autowired
private MessageService messageService;
#RequestMapping(value = "/data/message", method = RequestMethod.POST)
public Message save(#RequestBody NewMessage newMessage,
HttpSession session) {
System.out.println(session.getAttribute("user"));
Message message = new Message(newMessage);
LocalDateTime dateTime = LocalDateTime.now();
message.setDateTime(dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
message.setNumberRating(0);
return messageService.save(message);
}
}
session.getAttribute("user") is null

The common behavior of sessions for WebApps is that your client is identified, commonly through a cookie called JSESSIONID, but for REST calls you do not have such possibility you probably don't even call from a browser, so you can not say that one request is coming from the same "machine/user" as this other request.
In order to do that you'll need to:
Properly configure and enable spring session
Have a way identify your requests, unique IDs of some sort.
And every new request have to inform you the same identificator, so you can ask for spring something like "give me the session for this user".
Here is a more detailed tutorial for Spring Session.

Related

Spring Boot 2 - Identify if method is triggered through controller or async service

I have a spring boot microservice application. I have a requirement such that,
If method is called through controller (i.e. some user hit API), response should be the username of logged in user.
If method is called from any non-controller like scheduled task, Async method etc. response should be default user i.e. System.
So, I have a UserDetailService method where I want to write this logic.
#Service
public class UserDetailsService
{
#Autowired
WebClient.Builder webClientBuilder;
#Autowired
HttpServletRequest request;
#Value("${common.serverurl}")
private String reqUrl;
public UserReturnData getCurrentUser()
{
if (request == null)
{
UserReturnData userDetails = new UserReturnData();
userDetails.setId((long) 404);
userDetails.setUsername("system");
return userDetails;
} else
{
UserReturnData userDetails = webClientBuilder.build().get().uri(reqUrl + "user/me")
.header("Authorization", request.getHeader("Authorization")).retrieve()
.bodyToMono(UserReturnData.class).block();
return userDetails;
}
}
}
To check if method is triggered from controller, I auto-wired HttpServletRequest and compared it to null.
However, when this is triggered from Async service, its throwing exception "Are you referring to request attributes outside of an actual web request"
Is there any other way to identify where the method is getting triggered from. Logic is simple, if controller is involved i.e. if some API call is involved, return logged in user else return default user.
SecurityContextHolder can be used to check if the current thread is associated with a logged in user. Following method demonstrates the use.
public String getCurrentUser() {
String userName="SYSTEM";
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication!=null && !(authentication instanceof AnonymousAuthenticationToken)) {
userName = authentication.getName();
}
return userName;
}
Contributed by #Sridhar Patnaik. Taking reference from R.G.'s comment, handled it in below way
Removed autowired HttpServletRequest
Created request variable
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
Added null check to request. So, if controller is involved, request will be non-null and logged in user will be returned. Else, system user will be returned
#Service
public class UserDetailsService
{
#Autowired
WebClient.Builder webClientBuilder;
/*
* #Autowired HttpServletRequest request;
*/
#Value("${common.serverurl}")
private String reqUrl;
public UserReturnData getCurrentUser() throws Exception
{
try
{
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
if (request == null)
{
UserReturnData userDetails = new UserReturnData();
userDetails.setId((long) 404);
userDetails.setUsername("system");
return userDetails;
}
else
{
UserReturnData userDetails = webClientBuilder.build().get().uri(reqUrl + "user/me")
.header("Authorization", request.getHeader("Authorization")).retrieve()
.bodyToMono(UserReturnData.class).block();
return userDetails;
}
} catch (Exception e)
{
throw new Exception("Something Went Wrong !");
}}
The proposed approach is sensible, injecting a request-scoped HttpServletRequest bean into the singleton service bean will work. See the answer to Access a request scoped bean in servce for more details.
As other answers have suggested, you can also use the security context to determine if the current thread is associated with a logged in user. However, that's not sufficient to identify whether the method was called in the context of an HTTP request, as opposed to some other non-HTTP request context (e.g. scheduled task). HTTP requests can be made by anonymous or unauthenticated users, if your Spring Security configuration permits it.

springmvc validate httpsession containing userobject

I need to validate HttpSession (for Spring MVC Application) in a better way for my current Project.
Here is the Scenario:
1) Once user is successfully validated, userObject object is added to httpSession class
HttpSession session = req.getSession(true);
session.setAttribute(AppConstants.LOGGEDIN_PARAM, userDetail);
2) Then for each request, userObject is retrieved from HttpSession Class to validate user Session
#RequestMapping(value = "/apply", method = RequestMethod.GET)
public String getTourApplyPage(HttpServletRequest req, ModelMap map) {
UserDetailTO userDetail = (UserDetailTO) req.getSession().getAttribute(AppConstants.LOGGEDIN_PARAM);
Long employeeId = userDetail.getUserType() == 1 ? userDetail.getEmployeeId():userDetail.getUserId();
if (employeeId == 0) {
req.setAttribute(AppConstants.MSG_PARAM, "Invalid employee Id.");
return userDetail.getUserType() == 1 ? AppConstants.PIS_MESSAGE : AppConstants.ADMIN_PIS_MESSAGE;
}
...
}
There can be better approaches to set userDetail object inside HttpSession but I had a restriction to not change this implementation (Point 1).
Can it possible to change getting a better implementation for getting a userDetail object from HttpSession (Point 2)?
Is it possible to write a better implementation for getting a userDetail object from httpSession?
Working at such a high level of abstraction, as controllers are at, you don't necessarily need to inject neither an HttpServletRequest nor an HttpSession.
You can make your controller session-scoped and inject a session-scoped bean there. The bean can hold a userDetails and a message for failed validations.
#RestController
#Scope("session")
public class Controller {
#Autowired
private SessionDetails details;
#PostMapping(path = "/validate")
public void validate() {
details.setUserDetails(...);
}
#GetMapping(path = "/apply")
public String apply() {
final UserDetailTO userDetails = details.getUserDetails();
...
}
}
#Component
#Scope("session")
class SessionDetails {
private String message;
private UserDetailTO userDetails;
// getters & setters
}

Store User object in session with Spring Security

Based on my understanding, there are a number of different ways to retrieve the authenticated username in Spring Security.
I'm currently grabbing the username by included the Principal as a controller method argument:
#RequestMapping(value = "/dashboard", method = RequestMethod.GET)
public ModelAndView displayHomePage(ModelAndView modelAndView, Principal principal) {
modelAndView.addObject("email", principal.getName());
// Render template located at src/main/resources/templates/dashboard.html
modelAndView.setViewName("dashboard");
return modelAndView;
}
Does Spring Security offer an easy way for me to store the User object into the session so it can be easily retrieved by any controller method?
I want to avoid performing a DB lookup each time:
// Lookup user in database by e-mail
User user = userService.findUserByEmail(principal.getName());
I'm using Spring Security 4.2.
Spring Security provides you with a static method for quickly and easy access:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
Or
User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String name = user.getUsername();
Maybe you would like do this in a base abstract class
public abstract class BaseController {
protected User getCurrentUser() {
return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
...
public YourController extends BaseController {
...
}
Update
If you want to store the current authenticated user in session, then you need store only first time in a object as suggested by #gkatzioura.
#Component
#Scope("session")
public class MySessionInfo {
private User user;
protected User getCurrentUser() {
if (user == null) {
user = userService.findUserByEmail(SecurityContextHolder.getContext().getAuthentication().getPrincipal().getName());
}
return user;
}
}
You can inject this bean in yours controllers like
#Autowired
private MySessionInfo mySessionInfo;
You must take care about cases when user is not logged, but this is another problem.
You can always use the methods that spring security provides to get basic information such as name, authorities and everything provided by the Authentication.class.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
authentication.getAuthorities();
authentication.getName();
But if you want more information, using a session bean to store the information is also a good idea.
#Component
#Scope("session")
public class UserInfo { .. }

Autowiring HttpSession give different object than HttpServletRequest

I use Spring Security, and I found strange behavior of framework while login. Spring Security WebAuthenticationDetails has parameter sessionId which is getting from HTTP request, and it all should be good, but in fact REST request gives me another session id. If I will autowire HttpSession and then get session id from it, I will get Spring-like id. So it seems that I have two ids for one user. Is it correct? Or I missed something?
EDITED:
For example this class will gave some session id
public class AuthenticationEventListener implements ApplicationListener<AbstractAuthenticationEvent> {
#Autowired
HttpSession httpSession;
#Override
public void onApplicationEvent(AbstractAuthenticationEvent event) {
if (event instanceof AuthenticationSuccessEvent) {
LoggedUser loggedUser = (LoggedUser) event.getAuthentication().getPrincipal();
loggedUser.initSessionParams(event.getAuthentication());
String sessionId = httpSession.getId();
}
}
}
and this method will give another one:
#RequestMapping(value = "/chart")
public Map getTestStatusesChart(HttpServletRequest request) {
String sessionId= request.getSession(false).getId();
return null;
}
So the answer is next: with condition of security Spring change session id by default. To prevent such behavior you need to disable session-fixation-protection in Spring Security config. more info by link

Spring 3.0 set and get session attribute

I want to read a domain object (UserVO) from session scope.
I am setting the UserVO in a controller called WelcomeController
#Controller
#RequestMapping("/welcome.htm")
public class WelcomeController {
#RequestMapping(method = RequestMethod.POST)
public String processSubmit(BindingResult result, SessionStatus status,HttpSession session){
User user = loginService.loginUser(loginCredentials);
session.setAttribute("user", user);
return "loginSuccess";
}
}
I am able to use the object in jsp pages <h1>${user.userDetails.firstName}</h1>
But I am not able to read the value from another Controller,
I am trying to read the session attribute as follows:
#Controller
public class InspectionTypeController {
#RequestMapping(value="/addInspectionType.htm", method = RequestMethod.POST )
public String addInspectionType(InspectionType inspectionType, HttpSession session)
{
User user = (User) session.getAttribute("user");
System.out.println("User: "+ user.getUserDetails().getFirstName);
}
}
The code you've shown should work - the HttpSession is shared between the controllers, and you're using the same attribute name. Thus something else is going wrong that you're not showing us.
However, regardless of whether or not it works, Spring provides a more elegant approach to keeping your model objects in the session, using the #SessionAttribute annotation (see docs).
For example (I haven't tested this, but it gives you the idea):
#Controller
#RequestMapping("/welcome.htm")
#SessionAttributes({"user"})
public class WelcomeController {
#RequestMapping(method = RequestMethod.POST)
public String processSubmit(ModelMap modelMap){
User user = loginService.loginUser(loginCredentials);
modelMap.addtAttribute(user);
return "loginSuccess";
}
}
and then
#Controller
#SessionAttributes({"user"})
public class InspectionTypeController {
#RequestMapping(value="/addInspectionType.htm", method = RequestMethod.POST )
public void addInspectionType(InspectionType inspectionType, #ModelAttribute User user) {
System.out.println("User: "+ user.getUserDetails().getFirstName);
}
}
However, if your original code isn't working, then this won't work either, since something else is wrong with your session.
#SessionAttributes works only in context of particular handler, so attribute set in WelcomeController will be visible only in this controller.
Use a parent class to inherit all the controllers and use SessionAttributes over there. Just that this class should be in the package scan of mvc.
May be you have not set your UserVO as Serializable.

Categories