I am facing this issue while trying to redirect a user from a login page (Spring) to a url that contains special german letters (ä, ö, ü, ß). This is likely not limited to them, but these are important at the moment.
The workflow looks like this:
User goes to a login page with a redirectUrl request param, for example: http://www.example.com/login.do?redirectUrl=http%3A%2F%2Fwww.example.com%2F%25C3%25B6l%2F, where %25C3%25B6l translates to öl
After logging in successfully, the user should be redirected to http://www.example.com/öl/. However, the actual URL is http://www.example.com/�l/, for some strange reason.
Looking at the URL Redirect trace, seems like (Spring?) encodes the ö to %F6 (Unicode?) instead of %C3%B6 (UTF-8).
The spring container (Tomcat) is all set to have UTF-8 encodings.
I have also tried encoding and decoding the URL to and from ISO-8859-1 and UTF-8, but without luck.
What I have also tried and seemed to work was to manually convert the special letters to their UTF-8 encoding (e.g. string.replace("ö", "%C3%B6");" and pass the url in that form. However, this is ugly and I would prefer not to do that.
Any suggestions on how to properly handle this?
Thanks.
Assumed you are in context of a Spring MVC application, redirects can be implemented using RedirectView or simply with the prefix redirect: as return value of a controller method. Catch it as RequestParam during form initialization and keep it in the session as shown in the following example:
Model:
public class LoginForm implements Serializable {
private String username;
private String password;
private String redirectUrl;
// getters and setters omitted
}
View:
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form:form method="POST" modelAttribute="loginForm" acceptCharset="UTF-8" action="login/doLogin">
<table>
<tr>
<td><form:label path="username">User Name</form:label></td>
<td><form:input path="username"/></td>
</tr>
<tr>
<td><form:label path="password">Password</form:label></td>
<td><form:password path="password"/></td>
</tr>
</table>
<div>
<input type="submit" value="Submit"/>
</div>
</form:form>
</body>
</html>
Controller:
#Controller
#RequestMapping("login")
#SessionAttributes(LoginController.FORM_NAME)
public class LoginController {
static final String FORM_NAME = "loginForm";
private static final String DEFAULT_REDIRECT_URL = "https://google.com";
#ModelAttribute(FORM_NAME)
public LoginForm createForm() {
return new LoginForm();
}
#GetMapping
public String showForm(Model model, #ModelAttribute(FORM_NAME) LoginForm loginForm, #RequestParam(required = false) String redirectUrl) {
loginForm.setRedirectUrl(redirectUrl);
model.addAttribute(FORM_NAME, loginForm);
return "login/login-form";
}
#PostMapping("/doLogin")
public String processFormCredentials(LoginForm loginForm) {
// input validation omitted ...
return "redirect:" + (loginForm.getRedirectUrl() != null ? loginForm.getRedirectUrl() : DEFAULT_REDIRECT_URL);
}
}
Using this code, the redirect with umlauts works fine on Tomcat - http://<url>/login?redirectUrl=http%3A%2F%2Fwww.example.com%2F%25C3%25B6l%2F redirects on form submission to http://www.example.com/öl/.
Related
My current controller is returning to an HTML page. I want to send some data (a DTO) which can be received at front end, like an ajax response (like it happens in ajax + RestController). But this is not a RestController, it is a normal #controller returning HTML. I don't know even if it is possible to send DTO with html. There isn't much code to look at. I am just looking for feasibility. Do let me know if additional info is required. Thanks
Edit
Here is some code for reference. login.html is the page I am returning to. I want to send some additional info with this return ( in the form of a DTO ).
#Controller
public class LoginUserController {
#RequestMapping(value = {"/loginuser", "/loginuser?error"})
public String login() {
return "/login";
}
}
If you want to return view only i.e jsp in your case . You could put DTO object in modelMap and used inside jsp . Or simply if you want to return JSON only you could use #ResponseBody .
#RequestMapping(value = {"/loginuser", "/loginuser?error"})
public String login(ModelMap model) {
YourDTO dtoObject = // get DTO;
model.put("dtoObject",dtoObject)
return "/login";
}
class YourDTO {
private int id;
private String name;
// getters setters
}
Inside login JSP you can usedtoObject .
Name <c:out value="${dtoObject.name}" ></c:out> // Using JSTL
Or In Html file
<input type="text" id="name" value='${dtoObject.name}'/>
Seems like you're trying to build a spring boot web app, here is the bare minimum what you need to do:
Make sure your maven dependencies have spring-boot-starter-thymeleaf along with spring-boot-starter-webapp in the pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Configure/write your controller like this :
#Controller
public class LoginUserController {
#GetMapping(value = {"/loginuser", "/loginuser?error"})
public String login( Model model) {
model.addAttribute("message", "Aww snaps! something went wrong...");
return "login";
}
}
Ensure you have src/main/resources/templates/login.html which may look like this:
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="${message}" />
</body>
</html>
You can use any other viewing technology of your choice instead of Thymeleaf. Basically the viewing technology parses the HTML template, looks for expressions like
th:text
to reslove the value of
${message}
with the data that you've put in the model in Controller.
Hope this helps!
you can use following code
#Controller
public class LoginUserController {
#GetMapping(value = {"/loginuser", "/loginuser?error"})
public String login( Model model) {
organizationDTO = organizationService.getOrganizationDTO(id);
model.addAttribute("organizationDTO", organizationDTO);
return "/login";
}
}
and use below code in HTML page using JSTL
<div class="row">
<p class="col-md-4">${organizationDTO."your value"}</p>
</div>
eg below
<div class="row">
<label class="col-md-2">Id</label>
<p class="col-md-4">${organizationDTO.id}</p>
<label class="col-md-2">Name</label>
<p class="col-md-4">${organizationDTO.name}</p>
</div>
I am using SpringBoot with Thymeleaf to build a trivial example to help me learn the two technologies.
I am basing my example on THIS GUIDE
The entity is a Greeting which has an Id and a Content.
I create the Greeting just fine, and I can list all the Greetings I've created.
I then wanted to add a delete option against each Greeting in the list page. When clicking delete, I want the object to be deleted and the list page served up again.
Alas, when I load the list page I get this error:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'greeting' available as request attribute
Controller and respository objects
#Controller
public class GreetingController {
#Autowired
GreetingRepo gr;
#RequestMapping(value="/greeting/list", method=RequestMethod.GET)
public String greetingsForm(Model model) {
model.addAttribute("greetings", gr.findAll());
return "greeting/list";
}
#RequestMapping(value="/greeting/delete", method=RequestMethod.POST)
public String deleteGreeting(#ModelAttribute("greeting") Greeting greeting) {
gr.delete(greeting);
return "redirect:/greeting/list";
}
}
#RepositoryRestResource
interface GreetingRepo extends JpaRepository<Greeting, Long> {
}
List.html page:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Getting Started: Handling Form Submission</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>Result</h1>
<div th:each="greeting : ${greetings}">
<p th:text="'id: ' + ${greeting.id}" />
<p th:text="'content: ' + ${greeting.content}" />
<form action="#" th:action="#{/greeting/delete}" th:object="${greeting}" method="post">
<input type="hidden" th:field="*{id}"/>
<input type="hidden" th:field="*{content}"/>
<input type="Submit" value="Delete"/>
</form>
</div>
Add another
Show All
</body>
</html>
Would appreciate a push in the right direction here :-)
I think you miss this line .
model.addAttribute("greeting", new Greeting());
#RequestMapping(value="/greeting/list", method=RequestMethod.GET)
public String greetingsForm(Model model) {
model.addAttribute("greeting", new Greeting());
model.addAttribute("greetings", gr.findAll());
return "greeting/list";
}
you dont have to use a form to delete the greeting you can do it very easily with this approach. hide the id of the greeting within the url. so you dont need to use a form and hidden tags. and annotate the controller method with following approach, to accept incoming id of the greeting.
replace the current form with given html code and replace the delete method in the controller as well.
<a th:href="#{/greeting/{id}/delete(id=${greeting.id})}" th:text="delete"></a>
#RequestMapping(value="/greeting/{id}/delete",method=RequestMethod.GET)
public String deleteGreeting(#PathVariable int id) {
gr.delete(id);
return "redirect:/greeting/list";
}
edit:- since you need the object to be present within the controller
you can use findOne method to fetch the object from the given id.check out the following example.
<a th:href="#{/greeting/{id}/edit(id=${greeting.id})}" th:text="edit"></a>
#RequestMapping(value="/greeting/{id}/edit",method=RequestMethod.GET)
public String Edit(#PathVariable int id){
greeting greetingob = gr.findOne(id);
return "edit";
}
I am redirecting my request from one controller to another, but while doing that I am loosing my session attributes. But I want to use the session only so that I can verify if user is logged in or not. Whole condition is explained below:
Login.jsp
This is the page in which user gives its username and password and the request goes to "LoginController" .
<%# page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Login</title>
</head>
<body>
${loginError }
<h1>Login Details</h1>
<form action="${pageContext.request.contextPath }/login" method="post">
<table>
<tr>
<td>Username</td>
<td><input type="text" name="userId" ></td>
</tr>
<tr>
<td>Password</td>
<td><input type="password" name="password" ></td>
</tr>
<tr>
<td><input type="submit" name="Submit" value="Submit"></td>
</tr>
</table>
</form>
</body>
</html>
LoginController.java
As the action was "login" using post method so the request maps to "verifyLogin()" method.
I have checked for the customer name it is coming after calling "loginCustomer()" method of "CustomerService" class.
But when I set it in session attributes and redirect it to url which match up to "HomeController" method I am losing that session attributes their.
#Controller
#RequestMapping(value="/login")
public class LoginController {
#Autowired
private CustomerService customerService;
#RequestMapping(method=RequestMethod.GET)
public String showLoginForm(){
return "login";
}
#RequestMapping(method=RequestMethod.POST)
public String verifyLogin(#RequestParam String userId, #RequestParam String password, HttpSession session, Model model){
Customer customer = customerService.loginCustomer(userId, password);
if(customer==null){
model.addAttribute("loginError", "Username or password is not correct");
return "login";
}
session.addAttribute("loggedInUser", customer);
return "redirect:/";
}
#RequestMapping(value="logout", method=RequestMethod.GET)
public String logoutUser(HttpSession session){
session.removeAttribute("loggedInUser");
return "login";
}
#ExceptionHandler(Exception.class)
public ModelAndView handleException(){
ModelAndView mav = new ModelAndView();
mav.addObject("loginError", "Usrname or password is not correct");
mav.setViewName("login");
return mav;
}
}
HomeController.java
The request mapped to "home()" method. This method is also called when application loads up, and when user logged in correctly then also it is called.
Here in this method I am loosing my session attribute, but I want it in the page were this method maps i.e. home.jsp the return value of this method.
As session attribute is not available in this method so its not available in the home.jsp page also.
Please help.
#Controller
public class HomeController {
#Autowired
private ProductService productService;
#Autowired
private CategoryService categoryService;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Model model) {
List<Product> products = productService.getFeaturedProducts();
List<Category> categories = categoryService.getAllCategories();
model.addAttribute("featuredProduct", products);
model.addAttribute("categories",categories);
return "home";
}
}
I would suggest to go for RedirectAttributes.
redirectAttrs.addFlashAttribute("loggedInUser", customer);
Further reading: -Flash Attribute
My first guess would be that your session Id cookie might get lost between login and next page. (could be because the Web app is configured not to use cookies or the cookie is set for the wrong domain)
To verify check in your browser that:
After successful login the server sets the session cookie for the current host (chrome or Firefox Dev tools can help you here)
And also that your Browser sends the same cookie in the next request for the homepage.
If this does not help please also include the set cookie and the cookie headers in your reply
I've used session with the help plain old HttpServletRequest request and that seemed to serve the purpose for me.
#RequestMapping(method=RequestMethod.POST)
public String verifyLogin(#RequestParam String userId, #RequestParam String password, HttpServletRequest request, Model model){
Customer customer = customerService.loginCustomer(userId, password);
HttpSession session = request.getSession(false);
if (session == null) {
session = request.getSession();
}
if(customer==null){
model.addAttribute("loginError", "Username or password is not correct");
return "login";
}
session.addAttribute("loggedInUser", customer);
return "redirect:/";
}
By this way, you can access the session values through the request object in both your controller and jsp page.
In one of my JSPs I get the user to enter some details about themselves. When they click submit I want the page that it redirects to, to remember the variable and then print it out.
e.g.
(register.jsp) Username: Barney
(welcome.jsp) Welcome Barney
(register.jsp) Username: Vernon
(welcome.jsp) Hello Vernon
Current code:
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String register(ModelMap map, HttpSession session,
#RequestParam(value="givenUser") String givenUser) {
session.setAttribute("ans", givenUser);
map.addAttribute("displayAnswer", givenUser);
map.put("givenUser", givenUser);
return "register";
}
}
Request mapping for welcome:
#RequestMapping(value = "/welcome", method = RequestMethod.GET)
public String welcome() {
return "welcome";
}
and then in welcome.jsp I have: Hello ${givenAnswer}
register.jsp which once the link inside is clicked, goes to welcome.jsp
this is register.jsp
<%#taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%# page isELIgnored="false" %>
<html>
<head>
<title>SPRING ####### WEB</title>
</head>
<body>
<h2>SUCCESSFUL REDIRECTION!!!</h2>
<p>Register</p>
<form action="/HelloSpring/welcome" method="post">
Username: <input type="text" name="givenUser" >
Firstname: <input type="text" name="givenPassword">
Surname: <input type="text" name="givenSurname" >
Password: <input type="password" name="givenPassword">
<input type="submit" value="Submit">
</form>
</body>
</html>
welcome.jsp
<%#taglib uri="http://www.springframework.org/tags/form" prefix="spring"%>
<%# page isELIgnored="false" %>
<html>
<head>
<title>SPRING ####### WEB</title>
</head>
<body>
<h2>Spring Page Redirection</h2>
<p>Welcome ${givenUser}, your password is ${givenPassword}</p>
</body>
</html>
so my question is what have i not included because it currently just fires a blank when the user submits details
You can post the userinput attribute to the Controller and then send the value back in ModelMap.
Example:
#RequestMapping("/hello")
public String hello1(ModelMap map,#RequestParam String userInput){
/*your code goes here*/
map.put("userInput",userInput);
return "jsp1"; //first jsp name
}
and use it in jsp using expression language.
Hello {userInput}!!
The name in a <input> tag is what is used as request parameter name on form submission.
You should at least change your controller code to use correct parameter name :
#RequestMapping(value = "/answer", method = RequestMethod.GET)
public String answer(ModelMap map, HttpSession session,
#RequestParam("howareyou") String givenAnswer) {
session.setAttribute("ans", givenAnswer);
map.addAttribute("displayAnswer", givenAnswer);
map.put("givenAnswer", givenAnswer);
return "answer";
}
}
I also removed unused parameter req since Spring gives you directly the HttpSession. And it is normally no use to add the same object twice in the model. But I assume you were not sure of what was correct and tried both - any of them is correct and is enough :-)
SUCCESS! apparently it needed ${ param.elc }. why is this not on any tutorials and how come no one knew about it? im a bit skeptical still but it's doing as required.
thanks to all who helped!
Is there an easy way in Spring MVC 3.x to display form error messages (obtained by JSR303 validation), before submiting the form ?
Consider the example code at the end of this post.
The end-user is supposed to edit forms in which the initial data is already invalid.
Errors are properly displayed when the form is submitted (POST method), but not on initial form display (GET method).
Is there an easy way to display the errors on initial form display (GET method) ? (Is there a way to re-use the form:errors tag for this purpose?)
JSP View form1.jsp:
<%# page session="true" %>
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%# taglib uri="http://www.springframework.org/tags" prefix="s" %>
<%# taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<html><body>
<form:form commandName="form1" method="post">
<s:bind path="*">
<c:if test="${status.error}">
<div style="color: red;">There are errors:</div>
<form:errors /><br />
</c:if>
</s:bind>
<form:label path="field1" >Field1:</form:label>
<form:input path="field1" />
<form:errors path="field1" cssStyle="color: red;"/>
<br />
<form:label path="field2" >Field2:</form:label>
<form:input path="field2" />
<form:errors path="field2" cssStyle="color: red;"/>
<br />
<form:button name="submit">Ok</form:button>
</form:form>
</body></html>
Controller:
#Controller #SessionAttributes("form1")
public class Form1Controller {
#ModelAttribute("form1") public Form1Bean createForm1() { return new Form1Bean(); }
#RequestMapping(value = "/form1/edit", method = RequestMethod.GET)
public String getEdit(Model model) {
// here we pretend to get form1 from database, and store it in session.
// form1 in database may have invalid field values.
// Perform a JSR-303 validation here ?
// MAIN QUESTION: What's the easy way to add errors to model and display them ?
return "/form1";
}
#RequestMapping(value = "/form1/edit", method = RequestMethod.POST)
public String postEdit(#Valid #ModelAttribute("form1") Form1Bean form1, BindingResult result, Model model) {
if (result.hasErrors()) {
return "/form1";
} else {
return "redirect:/done";
}
}
}
Backing Bean:
public class Form1Bean {
#Size(min=4,max=10) private String field1; // getters and setters ommited for brevity
#Size(min=4,max=10) private String field2;
public Form1Bean() {
this.field1 = "bad"; this.field2="good"; // start with an invalid field1
}
//...
}
Edit: After the interpreting the answer from #jb-nizet, here is the complete controller source:
#Controller #SessionAttributes("form1")
public class Form1Controller {
#Autowired org.springframework.validation.Validator validator;
#ModelAttribute("form1") public Form1Bean createForm1() { return new Form1Bean(); }
#RequestMapping(value = "/form1/edit", method = RequestMethod.GET)
public String getEdit(#ModelAttribute("form1") Form1Bean form1, Errors errors, Model model) {
// here we pretend to get form1 from database, and store it in session.
// form1 in database may have invalid field values.
validator.validate(form1, errors);
return "/form1";
}
#RequestMapping(value = "/form1/edit", method = RequestMethod.POST)
public String postEdit(#Valid #ModelAttribute("form1") Form1Bean form1, BindingResult result, Model model) {
if (result.hasErrors()) {
return "/form1";
} else {
return "redirect:/done";
}
}
}
Made a few tests, and it seems to work! Than you #jb-nizet
Not tested, but as I understand the documentation, you would just have to inject an object of type org.springframework.validation.Validator in your controller, create and bind an Errors instance as explained in Add Error on GET METHOD Spring annotated controller, and invoke the validator's validate() method, passing the form and the Errors as argument.