How to avoid redirect loop in spring web mvc - spring

public class ValidateSession extends HandlerInterceptorAdapter {
//before the actual handler will be executed
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
if(session.getAttribute("user")==null){
/*ModelAndView mav = new ModelAndView("/login/index");
throw new ModelAndViewDefiningException(mav);*/
ModelAndView mav = new ModelAndView();
mav.setViewName("redirect:/login/index.mars");
throw new ModelAndViewDefiningException(mav);
}
return true;
}
}
In my case if session is expired then user can't access my application, but i am stuck with redirection loop. although i have tried many possible ways but not luck :(

Don't associate the ValidateSession handler with the login/index.mars request. Use mvc interceptor. Exclusions possible since 3.2, I think.
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/yourpath/*"/>
<exclude-mapping path="/login/index.mars"/>
<bean class="org...ValidateSession " />
</mvc:interceptor>
</mvc:interceptors>

I know this is an old post but thought it may be of help since none of the above worked for me.
In the class implementing HandlerInterceptor:
response.sendRedirect("login?logout=true");
then in controller:
#RequestMapping(value = "login", method = RequestMethod.GET)
public String login(ModelMap modelmap, HttpServletRequest request, HttpSession httpSession, #RequestParam(required = false) String logout, #RequestParam(required = false) String reason, #RequestParam(required = false) String message) {
if (reason != null && MvcStatics.ERROR_MAP.get(reason) != null) {
ArrayList<String> errors = new ArrayList<>();
errors.add(MvcStatics.ERROR_MAP.get(reason));
modelmap.addAttribute("errors", errors);
}
if (logout != null && logout.equalsIgnoreCase("true")) {
httpSession.removeAttribute("loggedIn");
httpSession.removeAttribute("user");
httpSession.removeAttribute("token");
modelmap.addAttribute("message", "You have successfully been logged out.");
}}

i experienced the same issue. just remove the "redirect:" appended before "/login/index.mars"
ModelAndView mav = new ModelAndView();
mav.setViewName("redirect:/login/index.mars");
//to this and you redirect ll works fine
mav.setViewName("/login/index.mars");
throw new ModelAndViewDefiningException(mav);

Related

authenticated and anonymous for the same endpoind (spring security)

What i want is, for
localhost:8080/home -> should be open to only authenticated - home page after login
localhost:8080/home?msg=asdsada -> should be open to anonymous - for login errors like wrong password
This is endpoind:
#GetMapping(value = { "/home"})
public ModelAndView getLoginPage(
#RequestParam(value = "msg", required = false) String message) throws IOException
I tried to add this to security config of spring
.regexMatchers("/home").authenticated()
.regexMatchers("/home?msg=.*").permitAll()
So config became like this:
http
.authorizeRequests().antMatchers(anonymousEndpoints).anonymous()
.antMatchers(permittedEndpoints).permitAll()
.regexMatchers("/home").authenticated()
.regexMatchers("/home?msg=.*").anonymous()
.and()
.authorizeRequests().anyRequest().fullyAuthenticated()
But for wrong password, it does not go to endpoind
localhost:8080/home?msg=asdsada
For logged user, it can go to
localhost:8080/home
also it can go to
localhost:8080/home?msg=asdsada
What am I doing wrong? I can also use endpoind to check if logged in or not. Like:
But i want spring scurity to do this. Give 403 forbidden for example.
#GetMapping(value = { "/home"})
public ModelAndView getLoginPage(
#RequestParam(value = "msg", required = false) String message) throws IOException{
Authentication authentication = SecurityUtil.getAuthentication(false);
if (authentication != null) {
logger.info("User: {} already logged in, redirecting to dashboard", authentication.getName());
web.response.sendRedirect("/dashboard");
return null;
}
else{//not logged in
if (msg != null)//and msg is not null so like wrong password
//do smth
}
return null;
}
Don't configure the specific path in Spring Security Config, just analyze it in the controller method. In config set permitAll for this path, but add an authentication or principal parameter in the method signature:
#GetMapping(value = { "/home"})
public ModelAndView getLoginPage(#RequestParam(value = "msg", required = false) String message, Authentication authentication) throws IOException {
if (msg != null) {
...
} else if (!authentication.isAuthenticated()) {
...
}
...
}
P.S. Method arguments: https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-ann-arguments

handling session with spring mvc

Actually i am setting the attributes which are getting from bean class in modelandveiw method and trying to get that attribute in other modelandveiw method method but getting null values in
rs.getAttribute("customerId")
. Please help me.
#RequestMapping(value ="/insert",method= RequestMethod.Post)
public ModelAndView inserData(#ModelAttribute SavingBean savingBean,HttpServletRequest rs) {
HttpSession session = rs.getSession();
if (savingBean != null)
SavingBean saving = persionalService.insertData(savingBean);
int a = saving.getCustomerId();
rs.setAttribute("customerId",a );
System.out.println(saving.getDisgnProf());
List<SavingBean> list = new ArrayList<SavingBean>();
list.add(saving);
return new ModelAndView("welcome","list", list);
}
#RequestMapping(value ="/insertdata",method= RequestMethod.Post)
public ModelAndView check (#ModelAttribute SavingBean savingBean,HttpServletRequest rs) {
System.out.println(savingBean.getFirstName());
HttpSession session = rs.getSession();
System.out.println("abhishek" + rs.getAttribute("customerId"));
return null ;
}
You're not putting or getting anything from the session in this code. You're putting and getting attributes from rs, and rs is the request, not the session.
Yes you are putting it in the request rs, put it in session instead and you vill se it

Migrating to Spring MVC 4

We are migrating our mvc code to Spring 4. Previously we had a a method formBackingObject which we converted to get method initForm.
But trouble is - in previous controller which was extending SimpleFormController, formBackingObject was getting called even before submit method. We have now removed SimpleFormController. But initForm is getting called only only once on page load. It doesn't get called before submit. And there is some custom logic of creating user object and adding to UserProfileForm object.
Have you faced similar issue.
Old code
protected Object formBackingObject(HttpServletRequest request) throws Exception {
final UserProfileForm userProfileForm = new UserProfileForm();
final String id = request.getParameter("id");
if (id != null && !id.trim().equals("")) {
final User user = authenticationServices.findUser(ServletRequestUtils.getLongParameter(request, "id"));
userProfileForm.setUser(user);
} else {
final User user = new User();
userProfileForm.setUser(user);
}
return userProfileForm;
}
new code
#RequestMapping(method = RequestMethod.GET)
public String initForm(HttpServletRequest request, ModelMap model) throws Exception{
final UserProfileForm userProfileForm = new UserProfileForm();
final String id = request.getParameter("id");
if (id != null && !id.trim().equals("")) {
final User user = authenticationServices.findUser(ServletRequestUtils.getLongParameter(request, "id"));
userProfileForm.setUser(user);
} else {
final User user = new User();
userProfileForm.setUser(user);
}
addToModel(request, model);
model.addAttribute("userProfileForm", userProfileForm);
return "user-management/user-profile";
}
Create a method annotated with #ModelAttribute to fill your model.
#ModelAttribute("userProfileForm");
public UserProfileForm formBackingObject(#RequestParam(value="id", required=false) Long id) throws Exception{
final UserProfileForm userProfileForm = new UserProfileForm();
if (id != null) {
final User user = authenticationServices.findUser(id);
userProfileForm.setUser(user);
} else {
final User user = new User();
userProfileForm.setUser(user);
}
return userProfileForm;
}
#RequestMapping(method = RequestMethod.GET)
public String initForm() {
return "user-management/user-profile";
}
This way you can also use the #RequestParam annotation instead of pulling out parameters yourself.
See the reference guide for more information on the subject.
Certain inter-module dependencies are now optional at the Maven POM level where they were once required. For example, spring-tx and its dependence on spring-context. This may result in ClassNotFoundErrors or other similar problems for users that have been relying on transitive dependency management to pull in affected downstream spring-* . To resolve this problem, simply add the appropriate missing jars to your build configuration.

Spring MVC redirect with variables

I have following Spring MVC 3.2.4 method:
#RequestMapping(value = "/products/{product}", method = RequestMethod.POST)
public String update(Product product, #Valid #ModelAttribute("productForm") ProductForm productForm, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "products/view";
}
mapper.map(productForm, product);
productService.saveProduct(product);
return "redirect:/products/{product}";
}
After success it should redirect back user to detail of product. Problem is that instead of redirecting to page "/products/1" I am redirected to page "/products/Product [code=1234567890, name=Nejaky]". It looks like placeholder {product} is replaced by product.toString() instead of original ID from URL.
I am using built-in Spring Data converter:
<mvc:annotation-driven conversion-service="conversionService">
<mvc:argument-resolvers>
<bean class="org.springframework.data.web.PageableHandlerMethodArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />
<bean class="org.springframework.data.repository.support.DomainClassConverter">
<constructor-arg ref="conversionService" />
</bean>
What should I do to make it work correctly and redirect me back to "/products/1" without doing things like "redirect:/product" + product.getId()?
Our story starts in RedirectView source code, in the method replaceUriTemplateVariables.
protected StringBuilder replaceUriTemplateVariables(
String targetUrl, Map<String, Object> model, Map<String, String> currentUriVariables, String encodingScheme)
throws UnsupportedEncodingException {
StringBuilder result = new StringBuilder();
Matcher m = URI_TEMPLATE_VARIABLE_PATTERN.matcher(targetUrl);
int endLastMatch = 0;
while (m.find()) {
String name = m.group(1);
Object value = model.containsKey(name) ? model.remove(name) : currentUriVariables.get(name);
Assert.notNull(value, "Model has no value for '" + name + "'");
result.append(targetUrl.substring(endLastMatch, m.start()));
result.append(UriUtils.encodePathSegment(value.toString(), encodingScheme));
endLastMatch = m.end();
}
result.append(targetUrl.substring(endLastMatch, targetUrl.length()));
return result;
}
As you had predicted, the method uses value.toString() where value is your product object in the Model. No other component like a conversion system is involved here. Your options are as follows:
Use
"redirect:/product" + product.getId()
Add a model attribute called "productId" and use that in your view name
model.addAttribute("productId", product.getId());
"redirect:/product/{productId}"
Or use uri variables. I don't have information on those yet.
Ok, finally found reason for this. I had to annotate product param with #PathVariable. Wondering that it worked without it.
I know it's an old question but for anyone facing the same problem here is the answer
Just inject RedirectAttributes to your controller and use redirectAtrr.addAttribute([attrbuteName],[attributeValue])
#RequestMapping(value = "/products/{product}", method = RequestMethod.POST)
public String update(Product product,#Valid,#ModelAttribute("productForm") ProductForm productForm,BindingResult bindingResult,Model model,RedirectAttributes redirectAttr) {
if (bindingResult.hasErrors()) {
return "products/view";
}
mapper.map(productForm, product);
productService.saveProduct(product);
redirectAttr.addAttributte("productId",product.getId());
return "redirect:/products/{productId}";
}
Read documentation for more understanding.

Spring security perform validations for custom login form

I need to do some validations on the login form before calling the authenticationManager for authentication. Have been able to achieve it with help from one existing post - How to make extra validation in Spring Security login form?
Could someone please suggest me whether I am following the correct approach or missing out something? Particularly, I was not very clear as to how to show the error messages.
In the filter I use validator to perform validations on the login field and in case there are errors, I throw an Exception (which extends AuthenticationException) and encapsulate the Errors object. A getErrors() method is provided to the exception class to retrieve the errors.
Since in case of any authentication exception, the failure handler stores the exception in the session, so in my controller, I check for the exception stored in the session and if the exception is there, fill the binding result with the errors object retrieved from the my custom exception (after checking runtime instance of AuthenticationException)
The following are my code snaps -
LoginFilter class
public class UsernamePasswordLoginAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
#Autowired
private Validator loginValidator;
/* (non-Javadoc)
* #see org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
Login login = new Login();
login.setUserId(request.getParameter("userId"));
login.setPassword(request.getParameter("password"));
Errors errors = new BeanPropertyBindingResult(login, "login");
loginValidator.validate(login, errors);
if(errors.hasErrors()) {
throw new LoginAuthenticationValidationException("Authentication Validation Failure", errors);
}
return super.attemptAuthentication(request, response);
}
}
Controller
#Controller
public class LoginController {
#RequestMapping(value="/login", method = RequestMethod.GET)
public String loginPage(#ModelAttribute("login") Login login, BindingResult result, HttpServletRequest request) {
AuthenticationException excp = (AuthenticationException)
request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
if(excp != null) {
if (excp instanceof LoginAuthenticationValidationException) {
LoginAuthenticationValidationException loginExcp = (LoginAuthenticationValidationException) excp;
result.addAllErrors(loginExcp.getErrors());
}
}
return "login";
}
#ModelAttribute
public void initializeForm(ModelMap map) {
map.put("login", new Login());
}
This part in the controller to check for the instance of the Exception and then taking out the Errors object, does not look a clean approach. I am not sure whether this is the only way to handle it or someone has approached it in any other way? Please provide your suggestions.
Thanks!
#RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView signInPage(
#RequestParam(value = "error", required = false) String error,
#RequestParam(value = "logout", required = false) String logout) {
ModelAndView mav = new ModelAndView();
//Initially when you hit on login url then error and logout both null
if (error != null) {
mav.addObject("error", "Invalid username and password!");
}
if (logout != null) {
mav.addObject("msg", "You've been logged out successfully.");
}
mav.setViewName("login/login.jsp");
}
Now if in case login become unsuccessfull then it will again hit this url with error append in its url as in spring security file you set the failure url.
Spring security file: -authentication-failure-url="/login?error=1"
Then your URl become url/login?error=1
Then automatically signInPage method will call and with some error value.Now error is not null and you can set any string corresponding to url and we can show on jsp using these following tags:-
<c:if test="${not empty error}">
<div class="error">${error}</div>
</c:if>

Resources