Session is null on first request - spring-session

I'm using spring-session and I really like it. However I think I'm missing something. In my application the flow goes like this:
1) User requests HomepageController and that controller tries to put an attribute in the request:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
final String sessionIds = sessionStrategy.getRequestedSessionId(request);
if (sessionIds != null) {
final ExpiringSession session = sessionRepository.getSession(sessionIds);
if (session != null) {
session.setAttribute("attr", "value");
sessionRepository.save(session);
model.addAttribute("session", session);
}
}
As you can see it will try to get the sessionID from the request-cookie, and if there's a session with that ID in the repository than use it (add attribute). This is perfect, but only after the second request. Why? Because if I restart the server than the cookie is left with the old value, and then the first request will not find the session in the repository. After the response is committed though the cookie will be updated, so the second request will be correct.
And here's the question: what is wrong with my logic and how should one develop the application in order to support the first request too?
BTW, here's a sample application that demonstrates the problem:
https://github.com/paranoiabla/spring-session-issue

If you are wanting to obtain the session, you should not use requested session id. The requested session id is just that...what the browser requests. Some problems with using requested session (some of which you already outlined):
If you clear all your cookies and make a request, then no session is requested by the browser.
As you pointed out if the data store is restarted and is not persistent, then the requested session id is invalid
If the session expires, then the requested session will be invalid
Instead, you should use the session id:
final String sessionIds = request.getSession().getId();
This will use the requested session id if it is valid, otherwise it will create a new session and ensure the session is written to the response (i.e. included in the response as a cookie).

I would say your approach is wrong, your controller does to much and you should be just using the HttpSession for which Spring Session provides support. You shouldn't also be putting the session in the model imho as you should be just accessing the HttpSession. Your application shouldn't know about Spring Session.
Your controller should look like this
#Controller
public class HomepageController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home(HttpSession session) {
session.setAttribute("attr", "value");
return "homepage";
}
}
if you don't want to force session creation inject the HttpServletRequest and do getSession(false) instead of injecting the HttpSession.
Everything else (storing the session after request handling etc.) will be handled transparently by Spring Session.

Related

What could cause the original 'OAuth2' state parameter to be null in org.springframework.social.connect.web.ConnectSupport?

I am trying to use Spring Social on my application and I noticed while debugging that the original 'OAuth2' state parameter is always null on my app.
See Spring Social source code for org.springframework.social.connect.web.ConnectSupport below:
private void verifyStateParameter(NativeWebRequest request) {
String state = request.getParameter("state");
String originalState = extractCachedOAuth2State(request);//Always null...
if (state == null || !state.equals(originalState)) {
throw new IllegalStateException("The OAuth2 'state' parameter is missing or doesn't match.");
}
}
private String extractCachedOAuth2State(WebRequest request) {
String state = (String) sessionStrategy.getAttribute(request, OAUTH2_STATE_ATTRIBUTE);
sessionStrategy.removeAttribute(request, OAUTH2_STATE_ATTRIBUTE);
return state;
}
Can anyone please help?
edit: I do see the state parameter being passed back by facebook:
Request URL:https://www.facebook.com/v2.5/dialog/oauth?client_id=414113641982912&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fconnect%2Ffacebook&scope=public_profile&state=0b7a97b5-b8d1-4f97-9b60-e3242c9c7eb9
Request Method:GET
Status Code:302
Remote Address:179.60.192.36:443
edit 2: By the way, the exception I get is the following:
Exception while handling OAuth2 callback (The OAuth2 'state' parameter is missing or doesn't match.). Redirecting to facebook connection status page.
It turned out that the issue was caused by the fact that I was relying on headers - as opposed to cookies - to manage the session.
By commenting out the following spring session configuration bean:
#Bean
public HttpSessionStrategy sessionStrategy(){
return new HeaderHttpSessionStrategy();
}
The oauth2 state parameter issue was sorted.
P.S. Now I have got to find a way to get Spring Social to work with my current configuration of Spring Session...
Edit: I managed to keep the HeaderHttpSessionStrategy (on the spring session side) and get it to work by implementing my own SessionStrategy (on the spring social side) as follows:
public class CustomSessionStrategy implements SessionStrategy {
public void setAttribute(RequestAttributes request, String name, Object value) {
request.setAttribute(name, value, RequestAttributes.SCOPE_SESSION);
}
public Object getAttribute(RequestAttributes request, String name) {
ServletWebRequest servletWebRequest = (ServletWebRequest) request;
return servletWebRequest.getParameter(name);
}
public void removeAttribute(RequestAttributes request, String name) {
request.removeAttribute(name, RequestAttributes.SCOPE_SESSION);
}
}
Try this work around and see if that works for you:
To my surprise I opened application in a 'incognito' browser and everything worked. Just like that. I think before something got cached and was causing the issue.
I ran into this issue today, My application was working perfectly fine. I just took a break for few hours and when I ran it again it started complaining about 'The OAuth2 'state' parameter is missing or doesn't match.'
The state param is first put into the session then the request goes out to facebook and the request comes back with the same state param but when spring is looking for session object to get the state param, it is not finding the session. I think it is not finding the session because when the request comes back it thinks that it is a different client (or host), even though the old HttpSession object still exists. The container maintains a HttpSession per client.
What you're getting from Facebook is not a request attribute , it's a request parameter.
You should get it by something like:
request.getParameter("state")

Why does anonymous user get redirected to expiredsessionurl by Spring Security

I'm really trying to understand how Spring Security works, but I'm a bit lost at the moment. Here's the simple scenario:
User visits the website home page but doesn't log in
SecurityContextPersistenceFilter logs that no SecurityContext was available and a new one will be created
AnonymousAuthenticationFilter populates SecurityContextHolder with an anonymous token
A session is created with ID = C2A35ED5A41E29865FF53162B0024D52
User lets the page sit idle until the session times out
User clicks on the About page (or home page again)
SecurityContextPersistenceFilter again logs that no SecurityContext was available and a new one will be created
AnonymousAuthenticationFilter again populates SecurityContextHolder with an anonymous token
SessionManagementFilter logs that requested session ID C2A35ED5A41E29865FF53162B0024D52 is invalid
SessionManagementFilter logs that it is starting a new session and redirecting to /invalidsession
These pages are configured to .authorizeRequests().antMatchers("/","/home","/about").permitAll(). I have the invalid session option turned on to handle authenticated users: .sessionManagement().invalidSessionUrl("/errors/invalidSession"). If I comment out that option, then everything described above is exactly the same EXCEPT for step #10 - SessionManagementFilter sees that the requested session ID is invalid (#9) but does NOT start a new session and perform the redirect (#10).
WHY? What can I do to keep the invalid session option but correctly handle anonymous users, i.e., not be redirected? Or is that just not possible and I'll have to handle authenticated users separately? I'd be very grateful if anyone can help me understand what's happening here and point me in a direction to solve this. Let me know if you need to see my full http configuration.
EDIT
I ran a series of tests with anonymous and registered (authenticated) users. If .sessionManagement().invalidSessionUrl("/errors/invalidSession") is enabled then both types of users will eventually arrive at the error page. Authenticated users with RememberMe unchecked are the same as anon users. If RememberMe is checked, then the error page appears once RememberMe times out.
If I disable the invalid session option, no users ever get the error page (which makes sense). Both types of users can browse public pages as long as they want and authenticated users will be asked to log in after the session or RememberMe expires.
If you're interested the code involved here is in SessionManagementFilter
if (invalidSessionStrategy != null) {
invalidSessionStrategy
.onInvalidSessionDetected(request, response);
return;
}
If .sessionManagement().invalidSessionUrl is enabled the default method SimpleRedirectInvalidSessionStrategy is called, which executes this piece of code:
if (createNewSession) {
request.getSession();
}
redirectStrategy.sendRedirect(request, response, destinationUrl);
The createNewSession boolean can be set through setCreateNewSession(boolean createNewSession), which is described as:
Determines whether a new session should be created before redirecting (to avoid possible looping issues where the same session ID is sent with the redirected request). Alternatively, ensure that the configured URL does not pass through the SessionManagementFilter.
So, it looks to me like .sessionManagement().invalidSessionUrl works best for sites where all pages are authenticated. The options I'm looking at are a custom filter placed before the SessionManagementFilter that checks the page access and turns 'createNewSession' on/off as needed or turning off the invalid session option and handling it elsewhere for authenticated pages (?). I also stumbled across <%# page session=“false” %> in this SO question - Why set a JSP page session = “false” directive? - which I'm going to look into further. Being so new to Spring Security I don't have a good sense of the best practice for handling this situation correctly. Any help would be appreciated.
OK, so I've spent the last couple of weeks digging around in Spring Security trying to understand how it all fits together. I'm still learning, but for this particular situation I found two approaches that work.
The obvious one is to just bypass security for public pages like this:
#Override
public void configure(WebSecurity web) throws Exception
{
web
.ignoring()
.antMatchers("/", "/home", "/about", "/login**", "/thankyou", "/user/signup**", "/resources/**")
;
}
I still don't know enough about web security in general to know if this is an acceptable approach or not, but it allows anonymous users to browse the site w/o ever getting an invalid session error.
The harder solution (for a Java and Spring noob like me) is based upon these SO questions:
Spring security invalid session redirect
How to set a custom invalid session strategy in Spring Security
The default SimpleRedirectInvalidSessionStrategy class is final which meant I had to create basically a copy of that class (not sure how good an idea that is). You can't use a session attribute because the session has been destroyed by the time it gets to this strategy so I created a helper class for a session cookie called authUser (I can post the class if anyone wants to see it). The cookie is created or updated in the LoginSuccessHandler or RememberMeSuccessHandler and it indicates if the user is anonymous or authenticated:
authCookie.setCookie(request, response, "anonymousUser");
or
authCookie.setCookie(request, response, authentication.getName());
I'm currently using the actual login only for testing purposes - it will ultimately be just a simple yes/no indicator of some sort. CustomLogoutSuccessHandler resets it to anonymousUser
The invalid session method looks like this:
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String url = destinationUrl;
//reset context default value
redirectStrategy.setContextRelative(false);
if (authCookie.isCurrentCookieAnonymous()) {
//pass the URL originally requested by the anonymous user
url = request.getRequestURI();
//the URL needs to have the context removed
redirectStrategy.setContextRelative(true);
}
//always revert to anonymous user
authCookie.setCookie(request, response, "anonymousUser");
logger.debug("Starting new session (if required) and redirecting to '" + url + "'");
if (createNewSession)
request.getSession();
redirectStrategy.sendRedirect(request, response, url);
}
Again, I can post the full class if requested.
The SecurityConfig class includes the following:
#Bean
public SessionManagementBeanPostProcessor sessionManagementBeanPostProcessor() {
return new SessionManagementBeanPostProcessor();
}
protected static class SessionManagementBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof SessionManagementFilter) {
SessionManagementFilter filter = (SessionManagementFilter) bean;
filter.setInvalidSessionStrategy(new RedirectInvalidSession("/errors/invalidSession"));
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
My testing so far has been successful for both anonymous and authenticated users, but this approach has not been production tested.

Session without authentication with MemoryCacheClient in servicestack with MVC4

i am new to Servicestack. I am having MVC4 application and servicestack application deployed on diffrent servers .
I want to use the servicestack session without authentication with MemoryCacheClient.
i am not able to understand the explanation given in
https://github.com/ServiceStack/ServiceStack/wiki/Sessions
I want to check if session is there for each request and if seesion is null create new session with custom value as user id.
My configure method is as followes
public override void Configure(Container container)
{
// in global request filter check if session exists
this.GlobalRequestFilters.Add((req, res, requestDto) =>
{
//check if session exists
var sessionId = req.GetSessionId();
if (sessionId == null)
{
//if no populate session with user defined data ( user id from requestDto)
}
else
{
//how to get the values from session ?
}
}
Please help .
Thanks in advance
The SessionFeature already registers their own Global Request Filter to automatically create missing Temporary or Permanent Session Ids (i.e. ss-id or ss-pid).
It sounds like you want to register a Custom AuthEvent to respond to different events in the session lifecycle, i.e. IAuthEvents.OnCreated().

Is it possible to set the auth cookie in BeginRequest?

I am working with a client that can read, but not send cookies. This is a problem since the client is to post to authenticated methods. I figured an easy work around would be to send the cookie information in a header, intercepting it in BeginRequest and attaching a fabricated cookie to the request. Since begin request occurs before Authentication I figured it would work. It did not.
Here is my current method.
protected void Application_BeginRequest()
{
// I have added the auth cookie to the header
var a = Request.Headers["Authorization"];
if (a == null) return;
// get cookie value
var s = a.Replace(".AspNet.ApplicationCookie=", "");
// I am injecting the cookie into the request
var c = new HttpCookie(".AspNet.ApplicationCookie", s);
Request.Cookies.Add(c);
}
I have set breakpoints to observed the "real" cookie and it matches my "fabricated" cookie. Am I doing something wrong or am I trying for the impossible ? Moreover if anyone has any Idea how I could authorize the user using the cookie information I would be great-full.
This is a MVC5 application using the now standard Owins library.
The Owins framework is called on before BeginRequest. Moreover, the Owins request context is read only. So yes, adding to the cookie is impossible using Owins.
... But it is possible to create your own owins middleware that reads the header value and 'unprotects' the AuthenticationTicket. The solution is based off the Cookie Middleware.

Getting Session in Http Handler ashx

I am using Http Handler ashx file for showing the images.
I was using Session object to get image and return in the response
Now problem is i need to use custom Session object its nothing but the Wrapper on HttpSession State But when i am trying to get existing custom session object its creating new ...
its not showing session data , i checked the session Id which is also different
Please adive how can i get existing session in ashx file ?
Note: When i use ASP.NET Sesssion its working fine
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class GetImage : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{
When you want to get access to your Session State from an ASHX or HttpHandler you need to implement IReadOnlySessionState or IRequiresSessionState if you need read/write access.
The fact that it's an ashx should be irrelevant - assuming the request is being spawned off a request from an exsiting session; I'm assuming it should be - but it might pay to check exactly how the request is being formed. Always pays to go back to basics :)
Assuming that's ok, this is how I've been doing it:
string sessionId = string.Empty;
System.Web.SessionState.SessionIDManager sessionIDManager = new System.Web.SessionState.SessionIDManager();
bool supportSessionIDReissue;
sessionIDManager.InitializeRequest(httpContext, false, out supportSessionIDReissue); sessionId = sessionIDManager.GetSessionID(httpContext);
if (sessionId == null)
{
// Create / issue new session id:
sessionId = sessionIDManager.CreateSessionID(httpContext);
}
At the end of this the sessionId variable will (should) contain the existing Session ID, or a newly created one that you can reuse later..
you can just use a Actionresult rather than a handler for this
return new FileStreamResult(new FileStream(path, FileMode.Open), "image/jpeg");
or
return(new FileResult(Pathtoimage, "image/jpeg"));
that should make things easier as you wil be using a controll/action as your url
ie
<img src="/Images/showImage/1">
you can then have your actions deal with anything like pulling from db as bytes
streaming, check validation etc

Resources