Broken HttpSession integrity in distributed WAS7 environment - session

The project is running on WAS7 with load balancing environment, using JSF2.1 and primefaces4. After authentication filter the userinfo is to put it into session. when viewing the page backend bean is getting the user info from session using facescontext like below, bean scope is viewscoped
FacesContext context = FacesContext.getCurrentInstance();
HttpServletRequest request = (HttpServletRequest) context
.getExternalContext().getRequest();
HttpSession session = request.getSession(false);
UserInfo userInfo= null;
if(session != null) {
userInfo= (UserProfile) session
.getAttribute("UserInfo");
// do some logic
}
Its working fine as expected. when more number user (concurrently) accessing the page or doing same operation in the page at the same time, some times session gives wrong object i.e. other users info object not mine. Based on the user info the all business logic is getting executed, so the page shows wrong info.
Any idea why is this happening, and a solution to prevent this if any ?

Related

Spring Session integration into Spring Security - how to populate Authentication

When integrating spring session with spring security I'm not sure how the SecurityContextImpl#Authentication is supposed to be populated when a session is identified by spring session.
Context:
The spring-boot application does not actually handle login,logout or creating the session itself. The session is created in an external, non-spring microservice and shared via MongoDB. Sharing and mapping the session information works and was utilized without issues before spring security.
What works:
Spring session properly resolves the session-id
Spring session retrieves the session (using the session-id) from the session-repository(mongo) and the attributes are populated
The request has a populated session object including all the attributes
What does not work:
Using http.authorizeRequests().antMatchers("admin/**").authenticated() and then requestion and endpoint (with an session cookie) does by no means populate SecurityContext#Authenticated
Possible options
a) I understand, I could implement a custom CustomUserNameFromSessionFilter to prepopulate the
Authenticated in SecureContext (yet authenticated=false) and place it early in SecurityFilter chain. In addition I implement a custom AuthenticationProvider CustomFromSessionAuthenticationProvider, which then picks up Authenticated and basically sets authenticated=true if the session is valid (which is always true at this point)
b) Use RememberMeAuthenticationFilter but I'm not sure how this documentation suits that purpose
c) Somehow utilize AbstractPreAuthenticatedProcessingFilter but it rather seems to be used for external auth-requests
Every option seems not right, and the need for such an implementation seems too common that there is not an existing/better solution. What is the correct approach?
Code snippets
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable();
// Logout is yet handled by PHP Only, we yet cannot delete/write sessions here
http.logout().disable();
http.formLogin().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.authorizeRequests()
.antMatchers("/admin").authenticated();
}
Thank you #Steve Riesenberg for providing just enough of the hint for me to find the right solution!
To understand my solution and understand when one needs to go this extra route, if first explain the default integration:
Classic spring-session integration in spring-security
When you use spring-security including the authentication via your spring-boot app, the integration of spring-session and spring-security will become natural without anything else required.
When you authorize (login) your user initially the first time via an Authentication via spring-security, it will:
Store the Authentication object in the SecurityContext of that request. - Then SecurityContext will then be stored in the HttpSession (if one exists), with spring-session where ever you configured spring-session to (redis/mongo).
The SecurityContext is stored using a session attribute of the key SPRING_SECURITY_CONTEXT right in the common session data (serialized).
When you then take the session-id given to you after this authentication and make an additional request, the following happens
spring session loads the HttpSession from your storage (including the SecurityContext in the session attribute with the key SPRING_SECURITY_CONTEXT
spring security will call HttpSessionSecurityContextRepository very early in the SecurityFilter chain and check HttpSession for the existence of the session attribute SPRING_SECURITY_CONTEXT and if a SecurityContext is found. If yes, it uses this SecurityContext and loads it as the current request SecurityContext. Since this context includes the already authenticated Authentication object from the prior authentication, the AuthenticationManager/Provider will skip authentication since it is all done and your request is treated as authenticated.
This is the vanilla way and it has one requirement - the authentication process (login) needs to write the SecurityContext into the HttpSession object during the login process.
My case - external login process
In my case, an external, a non spring-boot microservice is handling the entire login process. Still, it stores the session in the (external) session storage, which in my case is MongoDB.
Spring-session is properly configured and can read this session using the session cookie/session id and load the session created externally.
The big 'but' is, this external login will not store any SecurityContext in the session data, since it cannot do that.
At this point - if you have an external login service creating the session, and it is a spring-boot service, be sure you write the SecurityContext properly. So all other microservices then can load this session properly (authenticated) just using the session id and the default spring-session/security integration.
Since this is not an option for me, and if it is none for you, the following solution seems to be the 'by design' way in spring-boot/security IMHO:
You implement your own CustomHttpSessionSecurityContextRepository and register that in the security configuration via
public class ApplicationSecurity extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.securityContext()
.securityContextRepository(new FromExternalSessionSecurityContextRepository());
}
}
This ensure we replace the stock HttpSessionSecurityContextRepository with our own implementation.
Now our custom implementation
public class CustomExternalSessionSecurityContextRepository implements SecurityContextRepository
{
#Override
public SecurityContext loadContext(final HttpRequestResponseHolder requestResponseHolder)
{
HttpServletRequest request = requestResponseHolder.getRequest();
HttpSession httpSession = request.getSession(false);
// No session yet, thus we cannot load an authentication-context from the session. Create a new, blanc
// Authentication context and let others AuthenticationProviders deal with it.
if (httpSession == null) {
return generateNewSecurityContext();
}
Optional<Long> userId = Optional.ofNullable(httpSession.getAttribute(Attribute.SUBJECT_ID.attributeName))
SecurityContext sc = generateNewSecurityContext();
if (userId.isEmpty()) {
// Return a emtpy context if the session has neither no subjectId
return sc;
}
// This is an session of an authenticated user. Create the security context with the principal we know from
// the session and mark the user authenticated
// OurAuthentication uses userId.get() as principal and implements Authentication
var authentication = new OurAuthentication(userId.get());
authentication.setAuthenticated(true);
sc.setAuthentication(authentication);
httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc);
return sc;
}
#Override
public void saveContext(
final SecurityContext context, final HttpServletRequest request, final HttpServletResponse response
)
{
// do implement storage back into HttpSession if you want spring-boot to be
// able to write it.
}
#Override
public boolean containsContext(final HttpServletRequest request)
{
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
return session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) != null;
}
private SecurityContext generateNewSecurityContext()
{
return SecurityContextHolder.createEmptyContext();
}
}
So now changed the behavior of how spring-security loads a SecurityContext from the session. Instead of expecting SecurityContext to be present already, we check if the session is proper and the create the SecurityContext from the given data and store return it. This will make the entire following chain use and respect this SecurityContext

How to set attributes with easymock

I need to mock a request with a session, the session has an attribute (project)
HttpServletRequest req = createNiceMock(HttpServletRequest.class);
HttpSession session = createNiceMock(HttpSession.class);
session.setAttribute("currentProject", project); // project is not null here
expect(req.getSession()).andReturn(session);
replay(req);
But the test produces a NullPointerException in the following code
HttpSession session = request.getSession();
Project p = (Project) session.getAttribute("currentProject");
Project p is null after that line. Why??
You are not recording anything for getAttribute. session is a mock. So you can't set anything to it. You need to record calls.
A working version of this code would be
HttpServletRequest req = createNiceMock(HttpServletRequest.class);
HttpSession session = createNiceMock(HttpSession.class);
expect(session.getAttribute("currentProject").andReturn(project);
expect(req.getSession()).andReturn(session);
replay(req, session);
Note that I recommend using spring-test instead of EasyMock if you are in a Spring context. It makes testing much simpler. See my answer to another question.

Grails: User logs out while ajax request is running

There is a Grails (v.2.3.2) web app with Spring Security Core plugin (v.2.0-RC2).
I stumbled upon an issue with users who log out while there is an ajax request running in the background.
The scenario is as follows:
User requests a web page
When the page is ready I fire an ajax request
User logs out while the ajax request is still being processed on the server side
The server side, naturally, heavily depends on the current user, and the app crushes on the third step because the current user suddenly disappears as the springSecurityService indicates that the user is not logged in.
This is the code I used to fetch the current user in the UserService.
public User getLoggedInUser() {
if (!springSecurityService.isLoggedIn()) {
return null
}
User user = User.get(springSecurityService.getPrincipal().id)
user
}
Which, returns the current user alright up until the moment the user logs out, causing the issue.
I came up with the idea to make the UserService stateful and store the current user in a separate field.
static scope = 'request' // create a new instance for every request
private Long currentUserId = null
public User getLoggedInUser() {
if (!currentUserId) {
if (!springSecurityService.isLoggedIn()) {
return null
}
// Store the ID of the current user in the instance variable.
currentUserId = springSecurityService.getPrincipal().id
}
// Fetch and return the user.
return User.get(currentUserId)
}
In addition, I created a new Spring bean which defines a proxy object for my UserService.
userServiceProxy(ScopedProxyFactoryBean) {
targetBeanName = 'userService'
proxyTargetClass = true
}
Now, this works very well for the most scenarios, but fails when there is no web request present. In particular, in BootStrap.groovy, where I use other services of my application.
This is the error message I get:
Error initializing the application: Error creating bean with name 'scopedTarget.userServiceProxy': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
Any suggestions on how to fix this?
After some investigation and lots of swear words the solution was finally found.
This is the code I use in BootStrap.groovy to mimic an ongoing web request.
class BootStrap {
def init = { ServletContext servletContext ->
// Mock request and response.
HttpServletRequest request = new MockHttpServletRequest(servletContext)
HttpServletResponse response = new MockHttpServletResponse()
// Now store them in the current thread.
GrailsWebRequest grailsRequest = new GrailsWebRequest(request, response, servletContext)
WebUtils.storeGrailsWebRequest(grailsRequest)
/**
* Perform whatever you need to do that requires an active web request.
*/
}
}

How to successfully invalidate a session when logging out in Spring MVC

I have tried several times to invalidate the session. I have used the following code to remove the items from the session and invalidating the session itself.
public String logout(HttpServletRequest request) {
HttpSession session = request.getSession();
if(session != null) {
session.removeAttribute("user");
}
session.invalidate();
return "redirect:/";
}
Somehow, when another user logs in, the previous user's details are loaded onto the page. I didn't use Spring Security not wanting to complicate things. I'm not sure how to handle this problem.
my suggestion is to set the userDetails object in sessionObject using #sessionAttribute() in contrller. While doing logOut operation, set the userDetails object to null. In Jsp check if userDetails is null or not using jstl. This is really simple and will fit for your requirement.

Access a Managed Bean created by JSF in a servlet through a session

I'm trying to communicate JSF variables to a servlet, the way I found to do this was storing these variables in the session and then accessing them in the servlet. It worked fine in my first tests, but now, for apparent no reason, it's just not working.
Now, the servlet cannot find a session, it returns null. As if was not strange enough, it returns null just when i'm using Firefox and Chrome, if I use IE it returns the session correctly. I already restarted all browsers to restart sessions, but it still doesn't work.
My problem is: I need to communicate an attribute in my composite component to the servlet, here is what I'm doing:
...
<composite:implementation>
<f:metadata>
<f:event type="preRenderView" listener="#{Plupload.inicialize(cc.attrs.value, cc.attrs.servletPath,cc.attrs.runtimePreferences,cc.attrs.maxFileSize,cc.attrs.resizingWidth, cc.attrs.resizingHeight,cc.attrs.resizingQuality, cc.attrs.allowedTypes)}" />
</f:metadata>
...
</composite:implementation>
Plupload is a SessionScoped bean, with its method "initialize" I pass all the attributes I need to the session.
In the servlet I do the following:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession(false);
if (session == null) System.out.println("null session");
plupload = (Plupload) session.getAttribute("Plupload");
...
}
The result when doPost is executed is "null session" printed in the screen and a NullPointerException throwed by line:
plupload = (Plupload) session.getAttribute("Plupload");
Of course, unless I'm executing it in IE.
Thanks in advance for any answer.

Resources