How to use form validation and session in Spring MVC - model-view-controller

This is my code:
#Controller
#RequestMapping("loginform.htm")
public class LoginController {
#RequestMapping(method = RequestMethod.GET)
public String showForm(Map<String, LoginForm> model) {
LoginForm loginForm = new LoginForm();
model.put("loginForm", loginForm);
return "loginform";
}
#RequestMapping(method = RequestMethod.POST)
public String processForm(#Valid LoginForm loginForm, BindingResult result,
Map<String, LoginForm> model) {
String userName = "UserName";
String password = "password";
if (result.hasErrors()) {
return "loginform";
}
loginForm = (LoginForm) model.get("loginForm");
if (!loginForm.getUserName().equals(userName)
|| !loginForm.getPassword().equals(password)) {
return "loginform";
}
model.put("loginForm", loginForm);
return "success";
}
}
I use this to validate form when user input username and password. But the question is when validate success, I want to add user information to session in this page. Please tell me how I can do that, I tried to add function
public ModelAndView handleRequest(HttpServletRequest request,HttpServletResponse response)
but it show nothing. Do you have any idea? Thanks!

In general: instead of implementing security stuff by your self you should use Spring Security.
To access sessions in Spring you have tree different ways:
Work with the Http Session direcly (add the parameter HttpSession session to your controller method)
#SessionAttributes - to access an specific field of you session
Attaches beans to the session (session scoped beans)

Related

ModelAndView thread-saftey Spring Boot web app

Is using the ModelAndView in this manner "thread-safe"? The UserToken bean passed on the constructor is session-scoped and proxied, so each user should be accessing their own token, right? Or is using the same ModelAndView for all requests overwriting the UserToken each time for every user, thus possibly causing user A to see user B's token?
#Controller
public class ViewController {
private final UserToken userToken;
private final ModelAndView mav;
#Value("${redirect.url}")
String redirectUrl;
#Autowired
public ViewController(UserToken userToken) {
this.userToken = userToken;
this.mav = new ModelAndView();
}
#RequestMapping("/")
public ModelAndView defaultView() {
return getModelAndView("home");
}
#RequestMapping("/entryPoint")
public ModelAndView accessDenied(#RequestParam(required=false) String token) {
userToken.deserialize(token);
mav.addObject("userToken", userToken);
return getModelAndView("redirect:/");
}
/**
* Handle redirect if the userToken is invalid
* #param viewName The view to map
* #return the ModelAndView
*/
private ModelAndView getModelAndView(String viewName) {
if (userToken.isValid()) {
mav.setViewName(viewName);
} else {
mav.setViewName("redirect:" + redirectUrl);
}
return mav;
}
}
Not even sure how to test for thread-safety in this scenario, so any insight would be appreciated (techniques, tools, etc.).

Redirect does not working properly using spring mvc

Redirection does not work properly. I could not understand the problem because I very new to spring.
Here is my controller when I submit my form then ("schoolform") submitForm controller called and it redirect to another controller to ('form') form controller but it goes to ("login") login controller. I don't know why ?
I want to redirect schoolform to form controller.
#RequestMapping(value = "/schoolform", method = RequestMethod.POST)
public String submitForm(#ModelAttribute("school")School school,Model model,HttpServletRequest request,HttpServletResponse resp) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
schoolService.update(school);
System.out.println("Form submitted finaly, No further changes can be made.");
return "redirect:/form.html";
}
#RequestMapping(value = "/form", method = RequestMethod.GET)
public String form(Model model,HttpServletRequest request) {
HttpSession session = request.getSession(true);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName(); // get logged in username
System.out.println(name+"--------form page-----");
}
#RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView login(
#RequestParam(value = "error", required = false) String error,
#RequestParam(value = "logout", required = false) String logout) {
logger.info("------------------LoginController ---------------");
System.out.println("LoginController ");
ModelAndView model = new ModelAndView();
if (error != null) {
model.addObject("error", "Invalid username and password!");
}
if (logout != null) {
model.addObject("msg", "You've been logged out successfully.");
}
model.setViewName("login");
return model;
}
I think it is not working because the method in which you are trying to redirect to a url, accepts POST requests. You cannot redirect from POST methods UNLESS you have a handler method that accepts GET method and whose #RequestMapping accepts the value where you are trying to redirect.
So basically, the method submitForm which accepts POST requests only, is trying to redirect to /form.html. Now, there is no method in your controller that accepts /form.html, So now you gotto have a method in your controller class whose mapping value is /form.html and it accepts GET requests:
#RequestMapping(value = "/form.html", method = RequestMethod.GET)
public String methodName(arg1 ..){ ... }

Conditional validation in spring mvc

Now I have following controller method signature:
#ResponseBody
#RequestMapping(value = "/member/createCompany/addParams", method = RequestMethod.POST)
public ResponseEntity setCompanyParams(
#RequestParam("companyName") String companyName,
#RequestParam("email") String email,
HttpSession session, Principal principal) throws Exception {...}
I need to add validation for input parameters.
Now I am going to create object like this:
class MyDto{
#NotEmpty
String companyName;
#Email // should be checked only if principal == null
String email;
}
and I am going to write something like this:
#ResponseBody
#RequestMapping(value = "/member/createCompany/addParams", method = RequestMethod.POST)
public ResponseEntity setCompanyParams( MyDto myDto, Principal principal) {
if(principal == null){
validateOnlyCompanyName();
}else{
validateAllFields();
}
//add data to model
//return view with validation errors if exists.
}
can you help to achieve my expectations?
That's not the way Spring MVC validations work. The validator will validate all the fields and will put its results in a BindingResult object.
But then, it's up to you to do a special processing when principal is null and in that case look as the validation of field companyName :
#ResponseBody
#RequestMapping(value = "/member/createCompany/addParams", method = RequestMethod.POST)
public ResponseEntity setCompanyParams(#ModelAttribute MyDto myDto, BindingResult result,
Principal principal) {
if(principal == null){
if (result.hasFieldErrors("companyName")) {
// ... process errors on companyName Fields
}
}else{
if (result.hasErrors()) { // test any error
// ... process any field error
}
}
//add data to model
//return view with validation errors if exists.
}

Getting CommandBean or form bean object with out #ModelAttribute in spring mvc

I have a requirement of getting commandbean or form bean object into the controller without using #ModelAttribute either from ModelMap or HttpServletRequest or anything else.
My code is:
JSP:
<form:form commandName="user" method="POST"
action="${pageContext.request.contextPath}/user/createUser">
Name:<form:input path="name" />
Password:<form:input path="password" />
<input type="submit"/>
</form:form>
Controller:
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(method = RequestMethod.GET)
public String setupForm(ModelMap model) {
modelMap.addAttribute("user", new User());
return "userRegistration";
}
#RequestMapping(value = "/createUser", method = RequestMethod.POST)
public String createUser(ModelMap model,HttpServletRequest request) {
User user=(User)model.get("user");// Retruns null
//Tried using request object but user object is not available in it.
return "message";
}
}
I tried different ways but nothing worked out.
You can implement a HandlerMethodArgumentResolver to create the bean manually, then (for example) use a WebMvcConfigurerAdapter to declare it. The argument resolvers supportsParameter method should check the expected type of the parameter. After that you can add a parameter in your Controller that is of the desired type.
You can do it by hand like you would do if you did not know the magic of Spring : just the the HttpServletRequest and gets its parameters to feed your User. It could look like :
#RequestMapping(value = "/createUser", method = RequestMethod.POST)
public String createUser(ModelMap model,HttpServletRequest request) {
User user= new User();
String name = request.getParameter("name");
String password = request.getParameter("password");
if (name != null) {
user.setName(name);
}
if (password!= null) {
user.setPassword(password);
}
model.addAttribute("user", user);
//Tried using request object but user object is not available in it.
return "message";
}
Or in a more terse way : user.setName(request.getParameter("name");

SpringMVC controller: how to stay on page if form validation error occurs

I have next working code in my SpringMVC controller:
#RequestMapping(value = "/register", method = RequestMethod.GET)
public void registerForm(Model model) {
model.addAttribute("registerInfo", new UserRegistrationForm());
}
#RequestMapping(value = "/reg", method = RequestMethod.POST)
public String create(
#Valid #ModelAttribute("registerInfo") UserRegistrationForm userRegistrationForm,
BindingResult result) {
if (result.hasErrors()) {
return "register";
}
userService.addUser(userRegistrationForm);
return "redirect:/";
}
In short create method try to validate UserRegistrationForm. If form has errors, it leaves user on the same page with filled form fields where error message will be shown.
Now I need to apply the same behaviour to another page, but here I have a problem:
#RequestMapping(value = "/buy/{buyId}", method = RequestMethod.GET)
public String buyGet(HttpServletRequest request, Model model, #PathVariable long buyId) {
model.addAttribute("buyForm", new BuyForm());
return "/buy";
}
#RequestMapping(value = "/buy/{buyId}", method = RequestMethod.POST)
public String buyPost(#PathVariable long buyId,
#Valid #ModelAttribute("buyForm") BuyForm buyForm,
BindingResult result) {
if (result.hasErrors()) {
return "/buy/" + buyId;
}
buyForm.setId(buyId);
buyService.buy(buyForm);
return "redirect:/show/" + buyId;
}
I faced with issue of dynamic url. Now if form has errors I should specify the same page template to stay on current page, but also I should pass buyId as a path variable. Where are a conflict in this two requirements. If I leave this code as is, I get an error (I'm using Thymeleaf as a template processor):
Error resolving template "/buy/3", template might not exist or might not be accessible by any of the configured Template Resolvers
I can write something like return "redirect:/buy/" + buyId, but in this case I lose all data and errors of form object.
What should I do to implement in buyPost method the same behaviour as in create method?
I tried the solution metioned in this post at this weekend, but it doesn't work for BindingResult.
The code below works but not perfect.
#ModelAttribute("command")
public PlaceOrderCommand command() {
return new PlaceOrderCommand();
}
#RequestMapping(value = "/placeOrder", method = RequestMethod.GET)
public String placeOrder(
#ModelAttribute("command") PlaceOrderCommand command,
ModelMap modelMap) {
modelMap.put(BindingResult.MODEL_KEY_PREFIX + "command",
modelMap.get("errors"));
return "placeOrder";
}
#RequestMapping(value = "/placeOrder", method = RequestMethod.POST)
public String placeOrder(
#Valid #ModelAttribute("command") PlaceOrderCommand command,
final BindingResult bindingResult, Model model,
final RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("errors", bindingResult);
//it doesn't work when passing this
//redirectAttributes.addFlashAttribute(BindingResult.MODEL_KEY_PREFIX + "command", bindingResult);
redirectAttributes.addFlashAttribute("command", command);
return "redirect:/booking/placeOrder";
}
......
}
*I'm using Hibernate Validator APIs to validate my beans. To preserve form data along with displaying error messages, you need to do these 3 things:
Annotate your bean (eg. #NotEmpty, #Pattern, #Length, #Email etc.)
Inside controller:
#Controller
public class RegistrationController {
#Autowired
private RegistrationService registrationService;
#RequestMapping(value="register.htm", method=RequestMethod.GET, params="new")
public String showRegistrationForm(Model model) {
if (!model.containsAttribute("employee")) {
model.addAttribute("employee", new Employee());
}
return "form/registration";
}
#RequestMapping(value="register.htm", method=RequestMethod.POST)
public String register(#Valid #ModelAttribute("employee") Employee employee, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.employee", bindingResult);
redirectAttributes.addFlashAttribute("employee", employee);
return "redirect:register.htm?new";
}
registrationService.save(employee);
return "workspace";
}
// ....
}
Update your view/jsp to hold error messages:
This article can surely be helpful.
You can change your POST implementation to this:
#RequestMapping(value = "/buy/{buyId}", method = RequestMethod.POST)
public String buyPost(#PathVariable long buyId,
#Valid #ModelAttribute("buyForm") BuyForm buyForm,
BindingResult result) {
buyForm.setId(buyId); // important to do this also in the error case, otherwise,
// if the validation fails multiple times it will not work.
if (result.hasErrors()) {
byForm.setId(buyId);
return "/buy/{buyId}";
}
buyService.buy(buyForm);
return "redirect:/show/{buyId}";
}
Optionally, you can also annotate the method with #PostMapping("/buy/{buyId}") if you use Spring 4.3 or higher.

Resources