-
May 19th, 2009, 10:53 AM
#1
POST called in controller instead of GET!
I've just noticed a bug in a fairly simple system I built using Spring security/Spring MVC. Here's a bit of background about what the system does:
There are 2 pages, the logon page, and the main page. The main page has a form which the user fills in, and once submitted, the user is shown a "result" on the same page. Once the user has submitted the form 5 times or more, they are shown an error on the main page, and they won't be shown the "result" any more. They would have to log out then log in again.
I have a session timeout set up in web.xml (1 minute for testing):
<session-config>,
<session-timeout>1</session-timeout>,
</session-config>
So far the system is ALMOST working fine. If I log on, wait for over a minute for their session to timeout, then submit the form, spring security sends the user back to the entry point (the logon page). This is as expected. However, when I log on again, the POST method of my controller is being called, not the GET method. It's as if Spring remembers that the GET has already been called. This causes a NullPointerException to be thrown in the POST method, at the following line:
int transformationAttemptNo=((Integer)session.getAttri bute("transformationAttemptNo")).intValue() + 1;
This is because the session attribute "transformationAttemptNo" no longer exists because of the timeout.
Is this a common problem: Spring thinking that it should invoke the POST method on a controller instead of GET, when the session has timed out?
The controller for the main page uses annotations:
@Controller
@RequestMapping("/secure/transform.htm")
public class TransformController {
.
.
.
@RequestMapping(method = RequestMethod.GET)
public String setupForm(HttpServletRequest request,ModelMap model) {
//get the session object
HttpSession session = request.getSession();
if ((session.getAttribute("transformationAttemptNo") == null)) {
//set up the number of transformation attempts as a session attribute
session.setAttribute("transformationAttemptNo", new Integer(0));
}
.
.
.
//create command object and store it into a model attribute
model.addAttribute("transformForm",new TransformForm());
return "transform";
}
@RequestMapping(method = RequestMethod.POST)
public String submitTransformForm(HttpServletRequest request, @ModelAttribute("transformForm")TransformForm form, Errors errors) {
//get the session object
HttpSession session = request.getSession();
.
.
.
//add 1 to the number of times they have attempted a transformation
int transformationAttemptNo=((Integer)session.getAttri bute("transformationAttemptNo")).intValue() + 1;
session.setAttribute("transformationAttemptNo",tra nsformationAttemptNo);
//if this is their 6th attempt, cancel the transformation process, and send back an error
if(transformationAttemptNo>5){
return "transform";
}
.
.
.
//put the result in request scope
request.setAttribute("result", result);
return "transform";
}
}
-
May 21st, 2009, 05:26 AM
#2
I added the following line to both the GET and POST method of the controller:
System.out.println(request.getMethod());
Obviously, in the GET method of the controller, "GET" is printed, and in the POST method, "POST" is printed.
In my speds-security-context.xml, I have a custom AuthenticationProcessingFilter:
<bean id="myAuthenticationProcessingFilter"
class="com.johnlewis.pde.speds.pres.logon.LogonAut henticationProcessingFilter">
<property name="defaultTargetUrl" value="/secure/transform.htm" />
<property name="alwaysUseDefaultTargetUrl" value="true"/>
<property name="authenticationFailureUrl" value="/login.htm?authentication_error=true" />
<property name="authenticationManager" ref="authenticationManagerAlias" />
<property name="auditService" ref="auditService" />
<property name="messageSource" ref="messageSource" />
<property name="userAuthenticationService"
ref="userAuthenticationService" />
<security:custom-filter
position="AUTHENTICATION_PROCESSING_FILTER" />
</bean>
If you logon after manually logging off, defaultTargetUrl seems to use GET. But, after a session timeout, if you logon again, defaultTargetUrl uses POST.
If anyone finds an answer to this I'll pay your mortgage for the next year.
-
May 21st, 2009, 06:28 AM
#3
try to use a redirect
Hello,
you could try to use a redirect for your defaultTargetUrl:
"redirect:/secure/transform.htm"
-
May 21st, 2009, 07:04 AM
#4
Thanks for the reply. Unfortunately, it doesn't look like you can do this, as I get the following error when the beans load up:
Property 'defaultTargetUrl' threw exception; nested exception is java.lang.IllegalArgumentException: defaultTarget must start with '/' or with 'http(s)'
-
May 21st, 2009, 07:34 AM
#5
i noticed you use a custom AuthenticationProcessingFilter
Probably internally a forward is used.
You could extend that class with your own implementation, and add a flag wether a forward or a redirect should be used
-
May 21st, 2009, 08:12 AM
#6
The problem looks to be that the URL you are posting to is the same as the one that is your defaultTargetUrl. Spring Security caches the original request before redirecting to the login page. Unfortunately the new incoming request matches the cached one and a request wrapper reproducing the original request is substituted in the chain, with the original HTTP method.
We hope to provide more flexible support for "saved request" handling strategies in version 3.0, but for the moment the best way to get round this would be to map a different URL path to your defaultTargetUrl.
-
May 21st, 2009, 09:55 AM
#7
Thanks for your reply Luke.
I made the following change to speds-security-context.xml:
<property name="defaultTargetUrl" value="/secure/enter_transform.htm" />
and the following change to TransformController:
@RequestMapping(value={"/secure/transform.htm","/secure/enter_transform.htm"})
I'm still getting the same problem, only now when I post the form it just POSTs to /secure/enter_transform.htm instead.
The requirement of the system is to have a logon page, and then go straight to the page with the form on it (so no intermediate page between the two).
Is this going to cause a problem?
Thanks
-
May 21st, 2009, 11:42 AM
#8
I tried the following, which made no difference:
1)
<property name="defaultTargetUrl" value="/secure/enter_transform.htm" />
@Controller
public class ForwardingController {
@RequestMapping("/secure/enter_transform.htm")
public String forwardingMethod(){
return "enter_transform";
}
}
And I created "enter_transform.jsp":
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<a href="<c:url value="/secure/transform.htm"/>">Click here</a>
And the following set in TransformController:
@Controller
@RequestMapping("/secure/transform.htm")
public class TransformController {.....}
2) I also tried the following which, yet again, made no difference:
<property name="defaultTargetUrl" value="/secure/enter_transform.htm" />
@Controller
public class ForwardingController {
@RequestMapping("/secure/enter_transform.htm")
public String forwardingMethod(){
return "redirect:transform.htm";
}
}
I don't really have a clue what to do now to be honest.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules