how to display bindingresult errors on JSP page - spring

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");

Related

A much better way to automatically log user in after account verification in 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

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.

Neither BindingResult nor plain target object for bean in spring 3

Hi I am facing an issue and looked all over internet but still not able to find out the root cause. I am posting my code snippet please help me out as I am new to spring 3. I am using modelAttribute in form and what I want that in controller all the values from request should be backed in the object so that I can perform validation and other business logic.
I know there is mistake only in my controller.
1) index.jsp
<form:form action="login" method="POST" modelAttribute="login">
<table>
<tr><td>User Id:</td><td><form:input path="userId"/></td></tr>
<tr><td>Password:</td><td><form:password path="userPassword"/></td></tr>
<tr><td></td><td><input type="submit" value="Login"/></td></tr>
</table>
</form:form>
2) Controller
#RequestMapping(value="/login/", method=RequestMethod.POST)
public String login(#ModelAttribute("login") #Valid Login login,BindingResult result)
{
System.out.println("We have entered into controller class");
if(result.hasErrors())
{
System.out.println("Errors:"+result.getFieldError("userReject"));
return "redirect:/login";
}
else
{
return "home";}
}
}
3) JBoss Log
04:35:29,067 ERROR [org.springframework.web.servlet.tags.form.InputTag] (http--0.0.0.0-8090-1) Neither BindingResult nor plain target object for bean name 'login' available as request attribute: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'login' available as request attribute
at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:141) [spring-webmvc-3.0.5.Release.jar:3.0.5.RELEASE]
The problem is not in the method you posted, which handles the login form submission. It's in the method used to display the form. The form needs to populate its fields from a bean named "login", and you didn't place any bean named "login" in the model, i.e. in a request attribute.
Side note: a login form should never use GET. It should use POST. You really don't want the password to appear in the browser address bar. And you don't want it to appear in the browser history, the server and proxy logs, etc.

In Spring 3.2, does RedirectAttributes really pass the attributes themselves? Losing elements

NOTE: Ultimately my goal is simply to change the resulting URL from "/public/academy/register?param=blah" to a customized SEO-ified URL, as shown in the code. If I'm on the wrong path by trying to change from returning a "success view" JSP in the POST mapping to instead using post-redirect-get (which is good practice anyway), I'm open to suggestions.
Below are two methods: the POST request mapping to retrieve a registration form and process it, and the mapping method for the success page. I'm adding a flash attribute to redirect, which holds the form POSTed to the first method.
The form has a property hierarchy of Form -> Schedule -> Course -> Content -> Vendors, where each is its own class object except that Vendors is a SortedSet<Vendor>. When I load the success page, I get a Hibernate exception stating that the Vendors could not be lazily initialized. Why is it so far down the chain that it stops loading, or more basically, why is it losing this property value in the first place? When I set a breakpoint before the return, the RedirectAttributes object has the Vendors populated in the form I passed to it. What gives?
#RequestMapping(value = "/public/academy/register", method = RequestMethod.POST)
public String processSubmit(Site site, Section section, User user,
#ModelAttribute #Valid AcademyRegistrationForm form,
BindingResult result, Model model, RedirectAttributes redirectAttributes) {
validator.validate(form, result);
if (site.isUseStates()
&& StringUtils.isBlank(form.getBooker().getState())) {
result.rejectValue("booker.state",
"gui.page.academy.attendee.state");
}
if (result.hasErrors()) {
LOG.debug("Form has errors: {}", result.getAllErrors());
return "common/academy-registration";
}
// Form is valid when no errors are present. Complete the registration.
AcademyRegistration registration = form.toAcademyRegistration();
academyService.performRegistration(registration, site);
redirectAttributes.addFlashAttribute(form);
String redirectUrl = "redirect:/public/academy/register/"
+ registration.getSchedule().getCourse().getContent().getSeoNavTitle()
+ "-completed";
return redirectUrl;
}
#RequestMapping(value="/public/academy/register/**-completed", method=RequestMethod.GET)
public String displayRegistrationSuccess(#ModelAttribute("academyRegistrationForm") final AcademyRegistrationForm form)
{
SortedSet<Vendor> dummy = form.getSchedule().getCourse().getContent().getVendors();
return "common/academy-registration-success";
}
Here's the exception:
Oct 2, 2013 2:11:31 PM org.apache.catalina.core.ApplicationDispatcher invoke
SEVERE: Servlet.service() for servlet jsp threw exception
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.horn.cms.domain.Content.vendors, could not initialize proxy - no Session
Here's what I assume happens (until you update with the details):
AcademyRegistration registration = form.toAcademyRegistration();
academyService.performRegistration(registration, site);
does some Hibernate queries and retrieves some persisten entities lazily, ie. they haven't been initialized. The loading that did happen probably occurred in some Hibernate Session (do you have a #Transactional somewhere?). The Session is closed and dis-associated from the lazily loaded object.
You then add the form object, which has some nested reference to the lazily loaded entity (it'll be a hibernate proxy), to the RedirectAttributes. This in itself is not a problem because all you're doing is passing a reference.
The request handling completes by sending a 302 response. Your client will then make the new request that is handled by displayRegistrationSuccess() and hits this line
SortedSet<Vendor> dummy = form.getSchedule().getCourse().getContent().getVendors();
Here, the form object is the same as was added in the previous request. One of the objects in this reference chain is your Hibernate proxy that was lazily initialized. Because the object is no longer associated with a Session, Hibernate complains and you get the exception you get.
It's not a good idea to pass around (across request boundaries) objects that depend on persistent state. Instead, you should pass around an ID that you use to retrieve the entity. The alternative is to fully initialize your object inside your academyService method.

Need help with ReturnUrl in MVC 3 LoginPage

I have this url in my login view: http://localhost:5550/login?ReturnUrl=/forum/456&theme=1
I get the right url value when I am in the login page.
As you can see I have 2 query string parameters: ReturnUrl and theme
So far good.
Now the login page posts a form to a controller action.
All I need is to read the values of these 2 query string params.
I just can't make it work.
This is my login page view:
#using (Html.BeginForm("try", "login"))
{
//set text boxes and button so user can try login
}
This is my controller where I need those 2 values:
[HttpPost]
public ActionResult Try(LoginModel model, string ReturnUrl, string theme)
{
//read all query string param values...but how?
//I am not getting anything in string ReturnUrl, string theme. They are null
}
Other part of the question:
I can debug other controller. But not this controller.
Is this because I am posting a form using BeginForm?
Doesn't make sense. But the breakpoint never hits even though I get error on browser.
Normally you shouldn't be reading from ReturnUrl. The flow works like this:
Request to controller action which requires authentication using the [Authorize] attribute
ASP.NET MVC adds the ReturnUrl and redirects to /Account/LogOn, automatically encoding and appending the ReturnUrl parameter
The Account/LogOn POST controller action, after authenticating the login information, redirects to the URL in the ReturnUrl.
I wrote about that in gory detail in the following posts:
Looking at how the ASP.NET MVC Authorize interacts with ASP.NET Forms Authorization
Preventing Open Redirection Attacks in ASP.NET MVC
The second post indicates one good reason why need to be careful with ReturnUrl - users can tamper with them.
In the example you showed above, I'd expect that, after authorization, the user would be redirected to the Forum / Index action, which would then be reading the values. If you are creating the ReturnUrl, you should be URL Encoding the second &.
Not sure on the exact reason the values don't get bound, but the easy workaround is to add those properties to your login model and add hidden fields on the form.
Try changing the decoration:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Try(LoginModel model, string ReturnUrl, string theme)
{
//read all query string param values...but how?
//I am not getting anything in string ReturnUrl, string theme. They are null
}

Resources