A much better way to automatically log user in after account verification in spring boot? - spring-boot

I'm working on a practice project with spring boot and thymeleaf.
I have a registration form. After the user has registered their account, a verification code will be sent to their email. The next page is where they will enter the code.
I'm using RedirectAttributes to store the verification code id so I will be able to lookup/confirm the code in the verification code page. I am also using RedirectAttributes to pass the user email and raw password to the verification code page so they can be automatically logged in after confirming their account.
#PostMapping("/register")
public String processRegisterForm(#ModelAttribute NewAccountRequest newAccountRequest, RedirectAttributes redirectAttributes) {
Long verifyCodeId = accountService.createAccount(newAccountRequest);
redirectAttributes.addFlashAttribute("verifyCodeId", verifyCodeId);
redirectAttributes.addFlashAttribute("userEmail", newAccountRequest.getEmail());
redirectAttributes.addFlashAttribute("rawPassword", newAccountRequest.getPassword());
return "redirect:/register/verify-account";
}
Now, here is where it gets messy. In the GetMapping for the page to enter verification code, I also had to add the exact same attribute I added in the above PostMapping. Like this:
#GetMapping("/register/verify-account")
public String loadVerificationPage(#ModelAttribute("verifyCodeId") String verifyCodeId, #ModelAttribute("userEmail") String userEmail, #ModelAttribute("rawPassword") String rawPassword, Model model) {
model.addAttribute("verifyCodeId", verifyCodeId);
model.addAttribute("userEmail", userEmail);
model.addAttribute("rawPassword", rawPassword);
return "verify-account";
}
And in thymeleaf, I did this:
<input type="hidden" name="verificationCodeId" th:value="${verifyCodeId}">
<input type="hidden" name="userEmail", th:value="${userEmail}">
<input type="hidden" name="rawPassword" th:value="${rawPassword}">
And in the PostMapping for the page to enter verification code:
#PostMapping("/register/verify-account")
public String confirmVerifyCode(#RequestParam String enteredVerificationCode, #RequestParam String verificationCodeId, #RequestParam String userEmail, #RequestParam String rawPassword,
HttpServletRequest request) {
accountService.confirmVerificationCode(enteredVerificationCode, verificationCodeId);
try {
request.login(userEmail, rawPassword);
}
catch (ServletException e) {
System.out.println("Login error: " + e);
}
return "redirect:/?loginSuccess";
}
I can't get the flash attribute directly in the PostMapping of register/verify-account so I needed to use input fields in thymeleaf
I feel like there's a much easier and simpler way to do this because this just seem really messy. Any tips? Please also note that I haven't added any validations so ignore that. And could their be any security risks in using flash attributes to hold verification codes and passwords? Or to storing verification code in "hidden" input fields?
The registration flow:
User loads the register page
When the form is submitted, processRegisterForm() is called;
The user's account is created and the verification code id is returned.
To confirm the verification code in the next page, the id is stored in a flash attribute. The user's email and raw password is also stored. User is redirected to the page to enter verification code
For the GetMapping of the page to enter verification code, model attributes are added to store the code id, user email, and password. In thymeleaf, hidden input fields are used to hold those 3 values mentioned.
The PostMapping of the page to enter verification code gets those 3 fields and uses it to verify/auto login the user.
I'm hopping there is a much better way to achieve this. Sorry for the long post. Any help will be appreciated, thanks

I ended up cleaning the code up by using a DTO instead of those request params. Don't know why I didn't do that earlier.
In the /register/verify-account mapping, before logging in the user, I did this:
if (passwordEncoder.matches(userPassword, dbPassword)) {
try {
request.login(account.getEmail(), userPassword);
}
catch (ServletException e) {
System.out.println("Login after account verification failed" + e);
}
}
else {
System.out.println("fishy fishy");
}
I'm not sure if this really adds any security but it's good enough for me for now. Special thanks to these people for helping me: , , and
EDIT: I ended up using HttpSession

Related

Changing back the url to original if exception thrown and return back to form

I have a thymeleaf signup form, which if we submit then a controller at "/signup_do" is called which validates and saves the user to database:
<form action="/signup_do" method="post">
...
</form>
The controller at "/signup_do" passes the request to the accountRegistration service method, which does the validation:
#PostMapping("/signup_do")
public String register(Account account, HttpSession session) {
session.setAttribute("accountToRegister", account);
accountManagement.accountRegistration(account);
return "Success";
}
The account registration method can throw an exception SignupFormException, which is handled by the #ExceptionHandler defined in that controller class:
#ExceptionHandler(value=SignupFormException.class)
public String handle(HttpSession session, Model response) {
Account returnDataToForm = (Account) session.getAttribute("accountToRegister");
response.addAttribute("name", returnDataToForm.getFirstName());
session.invalidate();
return "signup";
}
Now the problem is that when exception occurs, the inputs entered in the form is passed back to the signup form, and the entered data remains intact, but the url still remains as /signup_do.
I have tried using return "redirect:/signup" instead, which does change the url, but it ends up making a get request to the /signup url like
/signup?name=John...
but my /signup controller is not designed to handle a get request, it just knows to display the form, so the information is lost.
#GetMapping("/signup")
public String signupPage() {return "signup";}
I also tried using forward:/signup, but that just ended up throwing 405 error.
I figured out a clean workaround a few hours after asking this question.
What I did is change the name of the controller that handles the signup process to ("/signup") as well. Since the controller that displays the page is a #GetMapping("/signup") and the one that handles the signup process is a #PostMapping("/signup") there is no clash.
Now even if the controller changes, the url remains the same, since both of them are signup...
#GetMapping("/signup")
public String signupPage() {return "signup";}
#PostMapping("/signup")
public String register(Account account, HttpSession session) {
session.setAttribute("accountToRegister", account);
accountManagement.accountRegistration(account);
return "success";
}
And this works just like I wanted!!
Redirecting will make a get request to the controller looking for the view to display, which in your situation means losing your data for the reasons you give. I can think of two workarounds:
Don't do the redirect and change the URL manually with javascript everytime you enter this view. If you dislike having a "wrong" URL in a view, editing it manually looks the most reasonable and direct approach. You can see how to do this here, including it in a script that executes everytime the page loads/the submit button is pressed.
Do the redirect and avoid losing your info by storing it in the session for a while longer, accessing it in thymeleaf in this way, instead of getting it from a model attribute. This would mean you would have to be careful to remove this session attributes later. It's also not very "clean" that your get request for the form view includes the user info, so I wouldn't go with this solution if avoidable.

how to display bindingresult errors on JSP page

I have a requirement to display errors on JSP page.
User enters credentials in login screen and it goes to logincontroller once login success user will be redirected to AccountsummaryController it has some backend calls to fetch data and display those data on landing page that is AccountSummary page. There could be possibility that some of the data may not be fetched for various reasons. I want those errors to be passed to the JSP page to show on landing page after successful login. Below is the code snippet of controller method in accountsummary controller. Also I am planning to have some validations in modelattribute i.e. stmtForm. I hope form errors also goes as part of the result.
#RequestMapping(method = { RequestMethod.GET }, value = "stmtSummary")
public ModelAndView getStatementSummary(#ModelAttribute("stmtForm") StatementForm stmtForm, BindingResult result, ModelMap modelMap, HttpServletRequest request, HttpServletResponse response)
throws Exception {
result.addError(new ObjectError("error1","unable to get account number"));
result.addError(new ObjectError("error2","unable to get routing number"));
if(result.hasErrors()){
System.out.println("Total errors are >>>>"+result.getErrorCount()); // *this is getting printed as 2*
}
}
Here is the tag I am using in JSP page.
<form:errors path="*"/>
I am not able to display errors on the UI. I am not able to find the what is wrong here. Please help
Since you havent posted model class or form, it is difficult to understand whether you have error1 or error2 as attribute in your StatementForm Bean. You are trying to show custom business errors with the spring mvc bindingResult. There are two options to do that:
Use custom errorObject and add it in modelMap and check that in jsp.
For using it with BindingResult, use:
result.rejectValue("property", "unable to get account number");
//or
result.reject("unable to get account number");

Spring security, bypass login page after changing password

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:
AbstractAuthenticationProcessingFilter
UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationFilter
You 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

Authenticating HMAC in MVC3

Im building my own web service on the premise of 2-legged OAuth.
With each authenticated request there will be an included HMAC.
I know it could be done like this:
public ActionResult userInfoExample(string HMAC, string username)
{
MyMembership.checkHMAC(HMAC);
//get user
return View();
}
but that is fairly nasty, because HMAC needs to be included in the parameters for every action. Its weakly typed and crap.
I wanted to do something like this:
[AuthorizeHMAC]
public ActionResult userInfoExample(string username)
{
//get user
return View();
}
I found this, and it mentioned I should look at Custom Modal Binders, so then I found this and after reading it I am unsure how I could make that work.
My goal is to authenticate (/authorise) using a HMAC that (I assume) is placed in the URL parameters i.e.: http:// www.website.com/foo/bar?username=xxx&hmac=xxxxxxxxx
I would like to know if anyone has any references I can read or a direct solution.
I am also welcome to criticism on my fundamental understanding of API security, or how I am doing things, I am fairly new to this area of
Check out my code at
http://mvcsecurity.codeplex.com/
I do something similar to validate parameters on the page (it is not an HMAC though). Since you will be generating it on the View Im assuming (or passing it to the view) you can check it the same way a similar way I check it in my attribute.
From:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//The hidden form field that contains our hash - for ex. CustomerId is rendered as a hidden input id="_CustomerIdToken"
string encryptedPropertyName = string.Format("_{0}Token", _propertyName);
//grab the token
string hashToken = filterContext.HttpContext.Request.Form[encryptedPropertyName];
//The encrypted form data MUST be there. We do not allow empty strings otherwise this could give
//an attack vector in our filter as a means to bypass checks by simply passing in an empty validation token.
if (string.IsNullOrEmpty(hashToken))
{
throw new MissingFieldException(string.Format("The hidden form field named value {0} was missing. This is created by the Html.AntiModelInjection methods. Ensure the name used on your [ValidateAntiModelInjectionAttribute(\"!HERE!\")] matches the field name used in Html.AntiModelInjection method. If this attribute is used on a controller method that is meant for HttpGet, then the form value would not yet exist. This attribute is meant to be used on controller methods accessed via HttpPost.", encryptedPropertyName));
}
//Get the plain text value
string formValue = filterContext.HttpContext.Request.Form[_propertyName];
//Plain text must be available to compare.
if (string.IsNullOrEmpty(formValue))
{
throw new MissingFieldException(string.Format("The form value {0} was missing. If this attribute is used on a controller method that is meant for HttpGet, then the form value would not yet exist. This attribute is meant to be used on controller methods accessed via HttpPost.", _propertyName));
}
//We cannot encrypt the form value and compare to the previously encrypted form token.
//Each time you Encrypt() with the MachineKey class even using the same plain text, the end result is difference.
byte[] plainTextBytes = MachineKey.Decode(hashToken, MachineKeyProtection.Encryption);
string plainText = Encoding.Unicode.GetString(plainTextBytes);
//And compare
if (string.Compare(plainText, formValue , false, CultureInfo.InvariantCulture) != 0)
{
throw new HttpAntiModelInjectionException(string.Format("Failed security validation for {0}. It is possible the data was tampered with as the original value used to create the form field does not match the current property value for this field. Ensure if this is a web farm, the machine keys are the same.",_propertyName));
}
filterContext.HttpContext.Trace.Write("(Logging Filter)Action Executing: " +
filterContext.ActionDescriptor.ActionName);
base.OnActionExecuting(filterContext);
}

Require re-authentication for certain actions

For certain actions like changing email settings or administrator activities, I want users to re-authenticate before the action is completed. Is there a good pattern for doing this in ASP.NET MVC 3?
Descpription
You can create your ActionMethod with Username, Password and the field you want to change (Email) for example. Than validate this data in the [HttpPost] of your data. If the authorization has success, change it and if not add the error to the ModelState.
Use a ViewModel for that.
Sample
public class ChangeEmailViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public string EmailAddress { get; set; }
}
public ActionResult ChangeEmail()
{
return this.View(new ChangeEmailViewModel());
}
public Action ChangeEmail(ChangeEmailViewModel model)
{
// authorize
bool isAuthorized = // your logic.
if (isAuthorized)
{
// change email
} else
{
ModelState.AddModelError("Username", "Username is not valid");
}
return this.View(model);
}
If you want to dynamically intercept and re-authenticate someone who is already authenticated, you could probably also handle this with a special cookie. The actions that require re-auth could be decorated with a custom filter that overrides OnAuthorization to check for the cookie, then redirect to take username & password if it is not found. Pattern, no code:
User clicks link to uber-protected action.
Filter on action looks for cookie and does not find it, redirects to sign in.
User signs in, and you write a special cookie
(different from the forms auth cookie),
then redirect back to original action.
Filter on action looks for cookie and finds it authorizing user.
The lifetime of the cookie would at least have to go all the way to the http post of the uber-protected action. You will have to decide when to delete it. For example, after user re-auths for one uber-protected action, do you want them to re-auth for second uber-protected action in the same browser session?

Resources