I'm relatively new to Java/Spring/MVC. I hope someone can shine some light on this, because this problem is about to ruin yet another weekend without getting solved.
At the moment I'm working on a project, implementing a couple of RFC's. Each RFC has it's own 'challenges', ranging from changing hibernate queries and stored procedures to minor textual changes and adding extra fields to forms.
The problem I am now facing has (I think) to do with Spring security.
The present situation:
To login to the application, user is presented by a login page (/user/login.jsp). After entering a username and password, user is sent to welcome page (/welcome/hello.jsp).
If a user forgets their password, they use a link that will sent an email providing a link to a page (/welcome/newPassword?code=xxx) where a new password can be entered. Entering the Submit-button will do some validation, save the password in the database and displays a 'password changed' message on the page. To enter the application, user will now have to login with the new password.
What the customer wants: After submitting the new password, the user should be automatically logged in with the new password and redirected to /welcome/hello.jsp
I searched the web, but couldn't find the scenario I was looking for (or maybe didn't recognize it!).
What I thought would do the trick:
I have this method in WelcomeController which presently handles the new password:
@RequestMapping(method = RequestMethod.POST)
public void newPassword(Model model, HttpSession httpSession, ModelAttribute(PASSWORD) Passwords password, BindingResult result) {
logger.debug("Posting password for new password");
ScreenMessage message = null;
//
try {
// errors from initBinder - back to page
if (result.hasErrors()) {
return;
}
// Validate input: on error go back to form
passwordValidator.validate(password, result);
if (result.hasErrors()) {
return;
}
// Save password
passwordService.createNewOwnPassword(result, password);
if (result.hasErrors()) {
return;
}
// No errors
message = new ScreenMessage();
message.setText(CHANGED);
message.setArg(THIS_PASSWORD, 0);
model.addAttribute(MESSAGE, message);
httpSession.setAttribute(ACTION_READ, Boolean.TRUE);
// Errors are fatal.
} catch (Exception e) {
logger.error("ERROR-CHANGE-password for user: " + password.getUser().getLoginName(), e);
result.reject( ERROR_FATAL, new Object[] { password}, ERROR_FATAL);
} finally {
model.addAttribute(PASSWORD, password);
}
}
I thought I could simply change this method to have it return a ModelAndView and use redirect:
ModelAndView mav = new ModelAndView("redirect:/welcome/hello", (ModelMap) model);
return mav;
But: It keeps sending me to /user/login, exactly the page I don't want to go to...
Extra information that could be handy:
As far as I can tell the application is using Spring 2.5.
(I hope relevant) Part of my security-applicationContext.xml:
<!-- always-use-default-target="true" -->
<security:form-login login-page="/dyn/user/login"
login-processing-url="/dyn/user/j_spring_security_check"
default-target-url="/dyn/welcome/hello"
always-use-default-target="true"
authentication-failure-handler-ref="exceptionTranslationRouting" />
<security:access-denied-handler error-page="/dyn/welcome/hello" />
<security:logout logout-success-url="/dyn/user/logout" invalidate-session="true" />
Is my idea of redirecting to /welcome/hello too simple or am I just missing something small?
Firstly, you'd better move your logic from web controller (I don't like password validation) to component dedicated to password change. This will allow you to logically separate parts of your application. Filter maybe? (more later) Or special service?
Secondly, take a look at the source code of Spring Security. From my experience, I can tell you that any customization in Spring Security is only possible if you understand its architecture (especially filter chain). I would recommend taking look at class AbstractAuthenticationProcessingFilter.
It's responsible for authenticating users when they provide username and password in your application. Take a look at the method AbstractAuthenticationProcessingFilter.successfulAuthentication.
This line of that method sets security context which makes the user logged in:
SecurityContextHolder.getContext().setAuthentication(authResult);
Reference authResult is of type Authentication. It is an interface implemented by for instance UsernamePasswordAuthenticationToken. Take a look at UsernamePasswordAuthenticationFilter.attemptAuthentication method for example of how to construct such an object.
Going back to filter solution:
I personally would think about implementing my own filter that would extend AbstractAuthenticationProcessingFilter. It would be responsible for password change. That way you don't have to think about things like session change on successful authentication. You just need to inject it in you XML.
To sum up,
classes that are recommended to look at:
AbstractAuthenticationProcessingFilterUsernamePasswordAuthenticationTokenUsernamePasswordAuthenticationFilterYou should read Spring Security Documentation, especially parts about architecture, filter chain and its role in authenticating users.
http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#overall-architecture
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With