how to set custom login page instead of spring security default login page - spring-boot

I integrate Angular with Spring Boot, I want to set my own login
page, but when I run my application it shows spring security default
login page. I change set every thing in my configuration file but it
still shows spring security default login page.
How do I set the custom login page?
SecurityConfig
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("nilmani").password("{noop}akj#159")
.roles("USER");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.component.html")
.loginProcessingUrl("/perform_login")
.defaultSuccessUrl("/dashboard.component.html", true);
//.failureUrl("/login.html?error=true")
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
login.component.html
<div class="app-body">
<main class="main d-flex align-items-center">
<div class="container">
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card-group">
<div class="card p-4">
<div class="card-body">
<form>
<h1>Login</h1>
<p class="text-muted">Sign In to your account</p>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="icon-user"></i></span>
</div>
<input type="text" class="form-control" placeholder="Username" [(ngModel)]="username" [ngModelOptions]="{standalone: true}" autocomplete="username" required>
</div>
<div class="input-group mb-4">
<div class="input-group-prepend">
<span class="input-group-text"><i class="icon-lock"></i></span>
</div>
<input type="password" class="form-control" placeholder="Password"[(ngModel)]="password" [ngModelOptions]="{standalone: true}" autocomplete="current-password" required>
</div>
<div class="row">
<div class="col-6">
<button type="button" (click)="dologin()" class="btn btn-primary px-4">Login</button>
</div>
<div class="col-6 text-right">
<button type="button" class="btn btn-link px-0">Forgot password?</button>
</div>
</div>
</form>
</div>
</div>
<div class="card text-white bg-primary py-5 d-md-down-none" style="width:44%">
<div class="card-body text-center">
<div>
<h2>Sign up</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<button type="button" class="btn btn-primary active mt-3">Register Now!</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>

Your SecurityConfig is fine. I did it the same way. You have to make sure that you have a controller which accepts this url and delivers the correct page. And normally you did not define a html page! A correct url might simply be /login.
So your SecurityConfig looks like:
.formLogin()
.loginPage("/login")
And your controller for example:
#Controller
#RequestMapping(value="/login")
#Scope(value="session")
public class LoginController extends AbstractWebController {
#GetMapping
public ModelAndView loginView() {
ModelAndView mav = this.getModelAndView("login");
return mav;
}
// further code goes here
}
This controller e.g. returns a jsp page which you can style according to your needs with Angular or whatever.

Related

How can I fix the CSRF related issues when it is enabled using Spring Boot with Spring Security?

I have a spring boot application. I am using Spring Security. I am facing two issues when CSRF is enabled. Please find the below issues, code and screenshots. How can I fix this?
Whenever the server is restarted, the login always fails the first time. It gives 404 error. But it is successful the second time.
I am using customAuthenticationFailureHandler in case if login is
failed, I am setting the error message in session and redirecting it
to login jsp to display it. It was working fine before CSRF was
enabled. Now, it looks like the value stored in session is destroyed
Security configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
//http.csrf().disable();
http
.authorizeRequests()
.antMatchers("/ui/static/assets/**").permitAll()
.antMatchers("/register", "/forgotPassword").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/LoginPage")
.loginProcessingUrl("/authenticate")
.permitAll()
.defaultSuccessUrl("/addDocument")
.failureHandler(customAuthenticationFailureHandler)
.and().exceptionHandling().accessDeniedPage("/Access_Denied")
.and().logout().permitAll().invalidateHttpSession(true);
}
CustomAuthenticationFailureHandler
#Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
String errMsg=exception.getMessage();;
request.getSession().setAttribute("loginErrorMessage", errMsg);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.sendRedirect(request.getContextPath()+"/login?error");
}
}
Login.jsp
<c:set var="params" value="${requestScope['javax.servlet.forward.query_string']}"/>
<div class="account-content">
<c:if test="${params eq 'error' && loginErrorMessage ne null}">
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<strong>${loginErrorMessage}</strong>
</div>
</c:if>
<form action="${pageContext.servletContext.contextPath}/authenticate" class="form-horizontal" method="post" id="formLogin" data-parsley-validate="">
<sec:csrfInput />
<div class="form-group m-b-25">
<div class="col-12">
<label for="emailaddress">Email address<span class="text-danger">*</span></label>
<input class="form-control input-lg" type="email" name="username" id="username" placeholder="Enter your email" data-parsley-required="true">
</div>
</div>
<div class="form-group m-b-25">
<div class="col-12">
Forgot your password?
<label for="password">Password<span class="text-danger">*</span></label>
<input class="form-control input-lg" type="password" id="pwd" name="password" placeholder="Enter your password" data-parsley-required="true">
</div>
</div>
<div class="form-group account-btn text-center m-t-10">
<div class="col-12">
<button class="btn w-lg btn-rounded btn-lg btn-primary waves-effect waves-light"
id="signInBtn" type="submit" value="Next" >Sign In
<i class="fas fa-spinner fa-spin" id="loadingBtn" style="display:none;"></i></button>
</div>
</div>
</form>
<div class="clearfix"></div>
</div>

How to display error message from custom validation on thymeleaf page

Similar question is posted here-> Spring + Thymeleaf custom validation display but I could not figure out the solution so posting this new question.
I have a simple registration form having username,email, password and confirmPassword fields
<form action="#" th:action="#{/register}" th:object="${user}"
method=post>
<!--error detection start -->
<div class="alert alert-danger" th:if="${#fields.hasErrors('*')}">
<p th:each="err : ${#fields.errors('*')}" th:text="${err}"></p>
</div>
<!--error detection ends -->
<div class="form-group input-group">
<div class="input-group-prepend">
<span class="input-group-text"> <i class="fa fa-user"></i>
</span>
</div>
<input name="username" th:field="*{username}" class="form-control"
placeholder="User Name" type="text">
</div>
<div class="form-group input-group"
th:if="${#fields.hasErrors('username')}" th:errors="*{username}">Name
Error</div>
<!-- form-group// -->
<div class="form-group input-group">
<div class="input-group-prepend">
<span class="input-group-text"> <i class="fa fa-envelope"></i>
</span>
</div>
<input name="email" th:field="*{email}" class="form-control"
placeholder="Email address" type="email">
</div>
<div class="form-group input-group"
th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Name
Error</div>
<div class="form-group input-group">
<div class="input-group-prepend">
<span class="input-group-text"> <i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" th:field="*{password}"
placeholder="Create password" type="password">
</div>
<!-- form-group// -->
<div class="form-group input-group">
<div class="input-group-prepend">
<span class="input-group-text"> <i class="fa fa-lock"></i>
</span>
</div>
<input class="form-control" th:field="*{confirmPassword}"
placeholder="Repeat password" type="password">
<p class="error-message"
th:each="error: ${#fields.errors('user.confirmPassword')}"
th:text="${error}">Validation error</p>
</div>
<!-- form-group// -->
<div class="form-group">
<button type="submit" class="btn btn-primary btn-block">
Create Account</button>
</div>
<!-- form-group// -->
<p class="text-center">
Have an account? Log In
</p>
</form>
I added a custom validation which gets trigger when password and confirm password field do not match.
1.FieldsValueMatchValidator
public class FieldsValueMatchValidator implements ConstraintValidator<FieldsValueMatch, Object> {
private String field;
private String fieldMatch;
public void initialize(FieldsValueMatch constraintAnnotation) {
this.field = constraintAnnotation.field();
this.fieldMatch = constraintAnnotation.fieldMatch();
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
Object fieldValue = new BeanWrapperImpl(value).getPropertyValue(field);
Object fieldMatchValue = new BeanWrapperImpl(value).getPropertyValue(fieldMatch);
if (fieldValue != null) {
return fieldValue.equals(fieldMatchValue);
} else {
return fieldMatchValue == null;
}
}
}
3.FieldsValueMatch
#Constraint(validatedBy = FieldsValueMatchValidator.class)
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface FieldsValueMatch {
String message() default "Fields values don't match!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String field();
String fieldMatch();
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#interface List {
FieldsValueMatch[] value();
}
}
4.User.java
#FieldsValueMatch(field = "password", fieldMatch = "confirmPassword", message = "Passwords do not match!")
#Entity
public class User implements UserDetails
{
#NotBlank
private String password;
#Transient
#NotBlank
private String confirmPassword;
//other getters and setters
}
5.Controller code
#PostMapping("/register")
public String saveNonJsonData(#Valid #ModelAttribute("user") User theUser, BindingResult errors) {
if (errors.hasErrors()) {
return "register";
}
else
{
//successlogic
}
Custom validator is working fine and i can see the error message on the page using following code on thymeleaf page
<!--error detection start -->
<div class="alert alert-danger" th:if="${#fields.hasErrors('*')}">
<p th:each="err : ${#fields.errors('*')}" th:text="${err}"></p>
</div>
<!--error detection ends -->
As mentioned here - Spring + Thymeleaf custom validation display the problem is custom validator returning an ObjectError for password field match validation and not a fieldError. Even though I tried solution provided I can't figure out how to get Thymeleaf to display my custom error.
UPDATE
Got one more answer here Displaying "Passwords don't match" custom annotation message and now i can see the error message using following code
<input class="form-control" th:field="*{confirmPassword}" placeholder="Repeat password" type="password">
<div class="form-group input-group" th:if="${#fields.hasErrors('global')}" th:errors="*{global}"></div>
My updated question if I have two more fields for example 'email' and 'confirmEmail' field then how this approach will work on thymeleaf page?

Springboot authentication issue with customer login

I have a spring boot application with spring security configured. I have redirected the login request to http://localhost:8000 where I'm running my front-end on a python server. Now when I try to post the login to my springboot application, it doesn't work. I looked into some posts online and changed the login path to /j_spring_security_check but it doesn't even seem to be trying to login as I don't see any logs in the console. Its taking me to login?error .Are there any other places where I can check the logs. Can I debug this somehow from some springboot classes.
Form Data
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<!-- Add page specific code/html START -->
<div class="container">
<h1 th:text="#{welcome.message}"></h1>
<form class="form-signin" name="loginForm" th:action="#{/login}" action="/login" method="POST">
<h2 class="form-signin-heading">Please sign in</h2>
<label for="username" class="sr-only">Email address</label>
<input type="text" name="username" id="username" class="form-control" placeholder="Username" required="required" autofocus="autofocus" />
<label for="password" class="sr-only">Password</label>
<input type="password" name="password" id="password" class="form-control" placeholder="Password" required="required" />
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div> <!-- /container -->
</body>
</html>
HTML code hosted on photon server
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Assessment App</title>
<link href="../css/bootstrap.min.css" rel="stylesheet">
<link href="../css/main.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<div class="panel panel-default main-header">
<div class="panel-body">
<div class ="pull-left">Assessments</div>
</div>
</div>
<div class="row">
<div class="login-container col-md-4 col-md-offset-4 col-sm-10 col-sm-offset-1 col-xs-12 col-xs-offset-0">
<div class="panel panel-login">
<div class="panel-heading">
<div class="panel-title">Sign In</div>
</div>
<div class="panel-body">
<form id="loginform" class="form-horizontal" role="form">
<div class="input-group assessment-input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<input id="login-username" type="text" class="form-control" name="username" value="" placeholder="Username">
</div>
<div class="input-group assessment-input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<input id="login-password" type="password" class="form-control" name="password" placeholder="Password">
</div>
<div class="form-group">
<div class="col-sm-12 controls">
<input class="btn btn-primary" type="submit" value="Login">
</div>
</div>
</form>
<div class="login-form-error-text hidden">Invalid credentials</div>
</div>
</div>
</div>
</div>
</div>
<script src="../javascript/jquery-3.3.1.min.js"></script>
<script src ="../javascript/bootstrap.min.js"></script>
<script src="../javascript/lodash.min.js"></script>
<script src="../javascript/login.js"></script>
</body>
</html>
Corresponding js
$(document).ready(function () {
$('#loginform').submit(function (event) {
event.preventDefault();
$.ajax({
url : 'http://localhost:8080/j_spring_security_check',
type : 'POST',
contentType : 'application/json',
data : JSON.stringify({ j_username : $('#login-username').val(), j_password : $('#login-password').val() }),
success : function () {
window.location.href = '../html/assessment.html';
},
error : function () {
event.preventDefault();
alert('failed');
}
});
});
$('.form-tab-header').on('click', function () {
$('.login-form-error-text').addClass('hidden');
$('.form-tab-header').removeClass('active');
$(this).addClass('active');
$('.form-horizontal').addClass('hidden');
$('.' + $(this).attr('id') + '-content').removeClass('hidden');
});
});
Security Config
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${ldap.urls}")
private String ldapUrls;
#Value("${ldap.base.dn}")
private String ldapBaseDn;
#Value("${ldap.user.dn.pattern}")
private String ldapUserDnPattern;
#Value("${ldap.enabled}")
private String ldapEnabled;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/assessments/**").fullyAuthenticated()
.antMatchers("/").permitAll()
.and()
.formLogin()
//.loginPage("http://htmlcode.s3-website.us-east-2.amazonaws.com")
.loginPage("http://localhost:8000")
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("j_username")
.passwordParameter("j_password")
//.loginPage("/login")
.failureUrl("/login?error")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll();
}
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/register");
// .antMatchers("/assessments/**");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
if(Boolean.parseBoolean(ldapEnabled)) {
auth.ldapAuthentication()
.userDetailsContextMapper(userDetailsContextMapper())
.userDnPatterns(ldapUserDnPattern)
.contextSource()
.url(ldapUrls+ldapBaseDn);
}
}
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new LdapUserDetailsMapper() {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
UserDetails details = super.mapUserFromContext(ctx, username, authorities);
return details;
}
};
}
#Bean
CorsFilter corsFilter() {
CorsFilter filter = new CorsFilter();
return filter;
}
}
I was finally able to fix this by removing JSON.stringfy in my post body of ajax request and setting the content type to application/x-www-form-urlencoded.

Forbidden Error when login in SpringBoot app

I have a basic SpringBoot app. using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
This is my config file:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(publicMatchers()).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/menu/config")
.failureUrl("/login?error").permitAll()
.and()
.logout().permitAll();
}
my Thymeleaf template:
<form id="loginForm" th:action="#{/login}" method="post">
<div class="row">
<div class="col-md-6 col-md-offset-3 text-center">
<div th:if="${param.error}" class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">x</span>
</button>
<p th:text="#{login.error.message}" />
</div>
<div th:if="${param.logout}" class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">x</span>
</button>
<p th:text="#{login.logout.success}" />
</div>
</div>
</div>
<div class="input_label"><i class="fa fa-user"></i><input type="text" id="usernameId" name="username" th:attr="placeholder=#{login.user.placeholder}" value="peris" /></div>
<div class="input_label"><i class="fa fa-key"></i><input type="password" name="password" value="peris"/></div>
<input type="submit" value="LOGIN" />
</form>
and my Login controller:
#Controller
public class LoginController {
public static final Logger LOG = LoggerFactory.getLogger(LoginController.class);
/** The login view name */
public static final String LOGIN_VIEW_NAME = "login/login";
#RequestMapping(value={ "/", "/login", "/elCor/login"}, method = {RequestMethod.GET})
public String login() {
LOG.info(serverContextPath + "/" + LOGIN_VIEW_NAME);
return serverContextPath + "/" + LOGIN_VIEW_NAME;
}
}
Evefything is OK using the browser, but when I use the mobile, I log in, I go back using the browser button, then I try to log in again but I have this error:
2018-06-28 08:56 [http-nio-5678-exec-2] ERROR c.t.w.c.AppErrorController - getErrorAttributes(request, true) --> {timestamp=Thu Jun 28 08:56:48 CEST 2018, status=403, error=Forbidden, message=Forbidden, path=/elCor/login}
I found the same problem in the computer browser but just once, and I can't not reproduce the problem.. I am trying to guess it
Try adding csrf tokens for login request.
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

400 the request sent by the client was syntactically incorrect

I have gone through so many examples of this nature and proposed solutions from this site, but none of the solutions provided thereon apply to my problem. I believe that this error message, 400, shows up when the information sent to the controller is mulformed. I spent the last two days cross referrencing to another project I worked on in the past, which works, but I cannot pick up the problem.
#RequestMapping(value = {"/", "/home"}, method = RequestMethod.GET)
public String homePage(ModelMap model) {
model.addAttribute("user", getPrincipal());
Catalog catalog = catalogService.getCatalogByCategory(Catalog.CatalogCategory.ALL);
model.addAttribute("catalog", catalog);
return "welcome";
}
This sends the data to a JSTL Spring form on my JSP as follows:
<form:form method="POST" modelAttribute="catalog">
<form:hidden path="id"/>
<form:hidden path="name"/>
<form:hidden path="category"/>
<form:hidden path="orderItems"/>
<div id="products" class="row list-group">
<c:forEach var="orderItem" items="${catalog.orderItems}">
<div class="item col-xs-4 col-lg-4">
<div class="thumbnail">
<img class="group list-group-image" src="http://placehold.it/400x250/000/fff" alt=""/>
<div class="caption">
<h4 class="group inner list-group-item-heading">
${orderItem.name}</h4>
<p class="group inner list-group-item-text">
${orderItem.description}
</p>
<div class="row">
<div class="col-xs-12 col-md-6">
<p class="lead">
R ${orderItem.price}</p>
</div>
<div class="col-xs-12 col-md-6">
<label for="${orderItem.id}" class="btn btn-primary">Add to Cart <input
type="checkbox" id="${orderItem.id}" name="orderItem.addedToCart"
class="badgebox"><span class="badge">&check;</span></label>
</div>
</div>
</div>
</div>
</div>
</c:forEach>
</div>
<div class="row">
<div class="form-group">
<div class="col-sm-12 pull-right">
</div>
<div class="col-sm-2 pull-right">
<input type="submit"
class="btn btn-default btn-block btn-primary"
value="Next" name="action" formmethod="POST"
formaction="confirmList"/>
</div>
</div>
</div>
</form:form>`
At this point I submit the form to the following listener in my controller:
#RequestMapping(value = "/confirmList", method = RequestMethod.POST)
public String confirmList(#ModelAttribute Catalog catalog, #ModelAttribute String numberOfItemsAdded) {
List<OrderItem> selectedItems = new ArrayList<OrderItem>();
for (OrderItem orderItem : catalog.getOrderItems()) {
if (orderItem.isAddedToCart()) {
selectedItems.add(orderItem);
}
}
//model.addAttribute("numberOfItemsAdded", selectedItems.size());
return "welcome";
}
That's it, execution flow does NOT even reach back my controller. Exhausting bug because I really do not understand what I am doing wrong here. Thank you in advance
EDIT:
Catalog.java
#Entity
#Table(name="Catalogs")
public class Catalog{
private long id; //generated value using hibernate ...
private String name; //column annotated by #Column
private String category;// column also annotated by #Column
private List<OrderItem> orderItems;// one to many mapping
//getters and setters here
}
I tested your code and I got HTTP 400 too. The thing is that what browser sends does not match whith what the controller method confirmList expects:
This is the form data I saw in Chrome's network tab:
id:1
name:the catalog
category:category
orderItems:[com.eej.ssba2.model.test.catalog.OrderItem#82ea8a, com.eej.ssba2.model.test.catalog.OrderItem#f441ae, com.eej.ssba2.model.test.catalog.OrderItem#40a13, com.eej.ssba2.model.test.catalog.OrderItem#1316c95, com.eej.ssba2.model.test.catalog.OrderItem#1cfc05a, com.eej.ssba2.model.test.catalog.OrderItem#5d725c, com.eej.ssba2.model.test.catalog.OrderItem#ff32b9, com.eej.ssba2.model.test.catalog.OrderItem#5b49a4, com.eej.ssba2.model.test.catalog.OrderItem#13faf31, com.eej.ssba2.model.test.catalog.OrderItem#6d64d]
orderItem.addedToCart:on
orderItem.addedToCart:on
orderItem.addedToCart:on
orderItem.addedToCart:on
action:Next
But controller cannot understand this, as OrderItems shows a toString() of each OrderItem instance and the addedToCart is not binded to any orderItem of the orderItems list.
You must modify your jsp this way:
<form:form method="POST" modelAttribute="catalog">
<form:hidden path="id"/>
<form:hidden path="name"/>
<form:hidden path="category"/>
<!-- form:hidden path="orderItems"/-->
<div id="products" class="row list-group">
<c:forEach var="orderItem" items="${catalog.orderItems}" varStatus="status">
<div class="item col-xs-4 col-lg-4">
<div class="thumbnail">
<img class="group list-group-image" src="http://placehold.it/400x250/000/fff" alt=""/>
<div class="caption">
<h4 class="group inner list-group-item-heading">
${orderItem.name}</h4>
<form:hidden path="orderItems[${status.index}].name" />
<p class="group inner list-group-item-text">
${orderItem.description}
<form:hidden path="orderItems[${status.index}].description" />
</p>
<div class="row">
<div class="col-xs-12 col-md-6">
<p class="lead">
R ${orderItem.price}</p>
<form:hidden path="orderItems[${status.index}].price" />
</div>
<div class="col-xs-12 col-md-6">
<label for="${orderItem.id}" class="btn btn-primary">Add to Cart <input
type="checkbox" id="${orderItem.id}" name="catalog.orderItems[${status.index}].addedToCart"
class="badgebox"><span class="badge">&check;</span></label>
</div>
</div>
</div>
</div>
</div>
</c:forEach>
</div>
<div class="row">
<div class="form-group">
<div class="col-sm-12 pull-right">
</div>
<div class="col-sm-2 pull-right">
<input type="submit"
class="btn btn-default btn-block btn-primary"
value="Next" name="action" formmethod="POST"
formaction="confirmList"/>
</div>
</div>
</div>
</form:form>
If you do so, you could see the message changes in Chrome's network tab (or the browser you are using). This is the form data right now:
id:1
name:the catalog
category:category
orderItems[0].name:OrderItemName#0
orderItems[0].description:OrderItemDesc#0
orderItems[0].price:0.0
orderItems[1].name:OrderItemName#1
orderItems[1].description:OrderItemDesc#1
orderItems[1].price:0.43645913001303904
orderItems[2].name:OrderItemName#2
orderItems[2].description:OrderItemDesc#2
orderItems[2].price:1.7151992716801088
orderItems[3].name:OrderItemName#3
orderItems[3].description:OrderItemDesc#3
orderItems[3].price:1.303683806806788
orderItems[4].name:OrderItemName#4
orderItems[4].description:OrderItemDesc#4
orderItems[4].price:2.507039003743686
orderItems[5].name:OrderItemName#5
orderItems[5].description:OrderItemDesc#5
orderItems[5].price:3.173744751378154
orderItems[6].name:OrderItemName#6
orderItems[6].description:OrderItemDesc#6
orderItems[6].price:3.183771167856446
catalog.orderItems[6].addedToCart:on
orderItems[7].name:OrderItemName#7
orderItems[7].description:OrderItemDesc#7
orderItems[7].price:6.73370053587355
catalog.orderItems[7].addedToCart:on
orderItems[8].name:OrderItemName#8
orderItems[8].description:OrderItemDesc#8
orderItems[8].price:2.0266022634803216
orderItems[9].name:OrderItemName#9
orderItems[9].description:OrderItemDesc#9
orderItems[9].price:5.251986962977732
catalog.orderItems[9].addedToCart:on
action:Next
And you would see now it returns a HTTP 200 as the request in fact reaches your controller. Delete the #ModelAttribute in your controller method too, as you have been suggested to:
#RequestMapping(value = "/confirmList", method = RequestMethod.POST)
public String confirmList(Catalog catalog, String numberOfItemsAdded) {
List<OrderItem> selectedItems = new ArrayList<OrderItem>();
for (OrderItem orderItem : catalog.getOrderItems()) {
if (orderItem.isAddedToCart()) {
selectedItems.add(orderItem);
}
}
//model.addAttribute("numberOfItemsAdded", selectedItems.size());
return "catalog";
}
try this extra handle in your Controller and Check your Console,
#ExceptionHandler(MissingServletRequestParameterException.class)
public void handleMissingRequestParams(MissingServletRequestParameterException ex) {
System.out.println();
System.out.println("------------------------MissingServletRequestParameterException------------------------");
System.out.println(" Parameter name:- "+ex.getParameterName());
System.out.println(" Parameter Type:- "+ex.getParameterType());
System.out.println(" Cause:- "+ex.getCause());
System.out.println(" LocalizedMessage:- "+ex.getLocalizedMessage());
System.out.println(" Message:- "+ex.getMessage());
System.out.println(" RootCause:- "+ex.getRootCause());
System.out.println("-------------------------------********************-----------------------------");
}
Edit
#kholofelo Maloma I can't see the variable numberOfItemsAdded in your jsp
AND i never used #ModelAttribute String numberOfItemsAdded to String parameter plz check this in documentation, I think it is used along with Bean, plz confirm
rather use #RequestParam
Read Documentation Here
It clears me
Sorry for above handle,
Note however that reference data and all other model content is not available to web views when request processing results in an Exception since the exception could be raised at any time making the content of the model unreliable. For this reason #ExceptionHandler methods do not provide access to a Model argument.

Resources