I'm working on a website using JAVA Spring mvc. I have a functionality that requires two controllers. First of all, the request is handled by controller1 who redirect it to controller2 using a return new ModelAndView ("redirect:controller2.htm"). All is working fine. However,I would like to block the direct access to the controller2 ( block a call from the url "controller2.htm") because the controller2's form needs data from the controller1.I want that the only case in which controller2 is used is the redirection from controller1. I would like a solution without annotations.Thanks in advance for your help.
Here is the code :
Controller1:
public class controller1 extends SimpleFormController implements Serializable {
private PersonManager pManager ;
#Override
public ModelAndView onSubmit(Object command) {
CommandPerson cmd = (CommandPerson) command;
Person p = null;
String viewName = "redirect:controller2.htm";
try {
p = pManager.getPersonbyID(cmd.getID());
} catch (EmptyResultDataAccessException ex) {
viewName="NosuchPerson";
}
ModelAndView mav = new ModelAndView(viewName);
mav.addObject("ID",cmd.getID());
return mav;
}
controller2:
public class controller2 extends SimpleFormController implements Serializable {
private PersonManager pManager ;
#Override
public ModelAndView onSubmit (Object command) throws ServletException, IOException {
Person p = (Person) command;
Map<String,Object> model = new HashMap<String,Object>();
pManager.UpdatePerson(p);
model.put("person", p);
return new ModelAndView("SuccesfulUpdate","model",model);
}
protected Object formBackingObject(HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException {
String id = request.getParameter("ID");
if(id==null) {
response.sendRedirect("controller1.htm");
return null;
} else{
Personne p = pManager.getPersonbyID(id);
return p;
}}
If the url "controller2.htm" is called directly the ID parameter will be null,and as the formBackingObject() is the first method executed when the request is being handled I thought I could make a redirection in it , but it didn't work as I'm redirected to the controller2's form being empty.
Your problem is very near to the Cross Site Request Forgery protection, so the same general solution should apply.
You just have to generate a random token in Controller1, put it in model under an arbitrary name (say "_csrf") and also store its value in session. Then in Controller2 you test that :
the parameter request _csrf is present in request
it is equal to the value stored in session
and immediately remove the _csrf value from the session.
If both requirements are met, it is highly probable that Controller2 was called via a redirect from Controller1, as nobody else should be able to guess the value
I finally found the solution. I overrided the showForm method . In this method , I could test if the parameter exists in the request . If it exists the formBackingObject method is called and the controller2's form is displayed else there is a redirection to controller1.
Controller1:
public class controller1 extends SimpleFormController implements Serializable {
private PersonManager pManager ;
#Override
public ModelAndView onSubmit(Object command) {
CommandPerson cmd = (CommandPerson) command;
Person p = null;
String viewName = "redirect:controller2.htm";
try {
p = pManager.getPersonbyID(cmd.getID());
} catch (EmptyResultDataAccessException ex) {
viewName="NosuchPerson";
}
ModelAndView mav = new ModelAndView(viewName);
mav.addObject("ID",cmd.getID());
return mav;
}
Controller2:
public class controller2 extends SimpleFormController implements Serializable {
private PersonManager pManager ;
#Override
public ModelAndView onSubmit (Object command) throws ServletException, IOException {
Person p = (Person) command;
Map<String,Object> model = new HashMap<String,Object>();
pManager.UpdatePerson(p);
model.put("person", p);
return new ModelAndView("SuccesfulUpdate","model",model);
}
protected Object formBackingObject(HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException {
String id = request.getParameter("ID");
if(id==null) {
response.sendRedirect("controller1.htm");
return null;
} else{
Personne p = pManager.getPersonbyID(id);
return p;
}}
protected ModelAndView showForm(HttpServletRequest request, HttpServletResponse response, BindException errors)throws Exception {
String id = request.getParameter("ID");
if (id==null) return new ModelAndView("redirect:controller1.htm");
else{
Personn p = (Personne) formBackingObject(request, response);
return new ModelAndView("UpdatePersonForm","Person",p);
}}
Related
I'm a new javaer. Recently I'm working on a new springboot project, and I want to print request body before it enter mvc controller. (To be exact, I want to print request body of post request with contentType:"application/json")
I use a requestWrapper as below.
public class MyRequestWrapper extends HttpServletRequestWrapper {
private byte[] cachedBody = new byte[]{};
private InputStream input = null;
public MyRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
if (request.getContentType() != null && (request.getContentType().contains("multipart/") ||
request.getContentType().contains("/x-www-form-urlencoded"))) {
cachedBody = new byte[]{};
input = request.getInputStream();
} else {
cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
input = new ByteArrayInputStream(cachedBody);
}
}
#Override
public ServletInputStream getInputStream() {
return new ServletInputStream() {
#Override
public int read() throws IOException {
return input.read();
}
}
}
public String getBody() {
return new String(cachedBody);
}
Then, I use a filter to print the request content.
#WebFilter(filterName = "RequestResponseFilter", urlPatterns = "/*", asyncSupported = true)
public class RequestResponseFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
MyRequestWrapper requestWrapper = (MyRequestWrapper) request;
......
System.out.println(requestWrapper.getBody());
......
chain.doFilter(requestWrapper, response);
}
}
Below is my controller.
#PostMapping(value="/test")
public ResponseData<String> test(
#RequestParam("id") String id,
#RequestParam("value") String value) {
ResponseData<String> result = new ResponseData<>();
result.setData(id + value);
result.setCode(Constants.CODE_SUCCESS);
return result;
}
However, when I use postman to test my code, it didn't work well. If I use post method and pass param with content-type:"application/x-www-form-urlencoded", it throws "org.springframework.web.bind.MissingServletRequestParameterException".
What confuse me is that, if I pass param with content-type:"multipart/form-data", it work well.
Besides, I have tried CachedBodyHttpServletRequest which provided by spring. But it couldn't get request content until the request enter controller.
Why the mvc controller failed to get param with annotation #RequestParam? And how can I fix it?
u can get param & body like this
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// param
request.getParameterMap().forEach((k, v) -> System.out.println(k + " : " + v[0]));
// body
byte[] array = StreamUtils.copyToByteArray(request.getInputStream());
System.out.println(new String(array, StandardCharsets.UTF_8));
}
but if u upload file with multipart/form-data then file content can't cast to String, u need tools to resolve it, something like this
if (contentType.startsWith("multipart/form-data")) {
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
StandardMultipartHttpServletRequest req = (StandardMultipartHttpServletRequest)resolver.resolveMultipart((HttpServletRequest) request);
System.out.println(req.getMultiFileMap());
System.out.println(req.getParameterMap());
}
I have a Java project and I'm using Servlet in order to handle http requests.
I also using Spring
When I receive a request to create a new object (for example an account), I would like also to return the “location” header with the GET URL of the newly created object.
for example: location: /accounts/1000
I understand the header are added to the Servlet filter (correct me if Im wrong)
public class ApiLogFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger("apilogger");
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = ((HttpServletResponse) servletResponse);
httpServletResponse.addHeader( "Location","the location value");
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
String queryString = httpServletRequest.getQueryString() != null ? httpServletRequest.getQueryString() : "N/A";
String logMessage = "URL: " + httpServletRequest.getRequestURL() + ", Query String: " + queryString + ", Response Status: " + httpServletResponse.getStatus() ;
LOGGER.info(logMessage);
}
}
#Override
public void destroy() {
}
}
But I don't understand how to get the location value from the API
#RequestMapping("/accounts")
public class IgnoreRuleController {
private AccountService accountService;
public void setIgnoreRuleService(IgnoreRuleService ignoreRuleService) {
this.accountService = ignoreRuleService;
}
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public String createAccount(#RequestBody Account account) {
return new Gson().toJson(accountService.createAccount(account));
}
}
I found solution here
http://learningviacode.blogspot.com/2013/07/post-with-location.html
you didn't need to do anything with the filter.
in the api itself:
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> createIgnoreRule(#RequestBody IgnoreRule ignoreRule) {
String response = new Gson().toJson(ignoreRuleService.createIgnoreRule(ignoreRule));
final URI location = ServletUriComponentsBuilder
.fromCurrentServletMapping().path("/ignore_rules/{id}").build()
.expand(ignoreRule.getId()).toUri();
final HttpHeaders headers = new HttpHeaders();
headers.setLocation(location);
final ResponseEntity<String> entity = new ResponseEntity<>(response, headers, HttpStatus.CREATED);
return entity;
}
It's very simple, you can pass the header directly throw your method signature:
#RequestMapping(value="/create-account", method = RequestMethod.POST)
#ResponseBody
public String createAccount(#RequestHeader HttpHeaders httpHeader, #RequestBody Account account) {
var s = httpHeader.get("Location");
System.out.println(s.get(0));
return ...
}
In fact you can pass the whole request also which contains everything (Headers, Body, ...):
#RequestMapping(value="/create-account", method = RequestMethod.POST)
#ResponseBody
public String createAccount(HttpServletRequest httpRequest, #RequestBody Account account) {
var s = httpRequest.getHeader("Location");
System.out.println(s);
return ....
}
I have made a user login interface where the user gets authenticated using spring security.
I have made an AuthenticationSuccessHandler which redirects the user to a new page.
I also want to implement a loginController in order to get the name of user logged in as well as displaying error messages for wrong credentials. Here is my Handler code :
public class MySimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
protected final Log logger = LogFactory.getLog(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
protected MySimpleUrlAuthenticationSuccessHandler() {
super();
}
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
protected void handle(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws IOException {
final String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
logger.debug("Response has already been committed. Unable to redirect to " + targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineTargetUrl(final Authentication authentication) {
boolean isUser = false;
boolean isAdmin = false;
final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (final GrantedAuthority grantedAuthority : authorities) {
if (grantedAuthority.getAuthority().equals("ROLE_USER")) {
isUser = true;
break;
} else if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
isAdmin = true;
break;
}
}
if (isUser) {
return "/static_htm.html";
} else if (isAdmin) {
return "/console.html";
} else {
throw new IllegalStateException();
}
}
And my controller code :
#Controller
public class HelloController {
#RequestMapping(value="/login", method = RequestMethod.GET)
public String printWelcome(ModelMap model, Principal principal ) {
String name = principal.getName();
model.addAttribute("username", name);
model.addAttribute("message", "Spring Security Hello World");
return "static_htm"; //page after successful login
}
#RequestMapping(value="/login", method = RequestMethod.GET)
public String login(ModelMap model) {
return "login"; //login page
}
#RequestMapping(value="/loginfailed", method = RequestMethod.GET)
public String loginerror(ModelMap model) {
//String errormessage = resources.getMessage("login.error", null, null);
model.addAttribute("error", "true");
return "login"; //login page
}
}
The handler works fine but I am not able to get the user name as well as the error message. What should I do to make both the handler and controller work together ?
Any suggestions are greatly appreciated.
From your question I presume you want to get the user name in the login controller.
If not so, feel free to disregard my answer.
You may have gotten it backward actually.
Success handler is somewhat like a custom implementation of "default-target-url".
So it is actually executed after login controller...
When login is successful, and there's no previously requested path (this is implemented by SavedRequestAwareAuthenticationSuccessHandler) then the request will be sent to the "default-target-url".
Or when there's a custom success handler, the success handler will determine the path it goes to.
I am working to pass data from one controller to another.
I have one class that is annotated with #ControllerAdvice that is used to handle all exception of application.
I am processing exception and adding them to custom class then in ModelAndView I am adding that and passing to another controller using redirect.
And in that controller I want that added object but I don't have much idea about it how to get that object. I have tried some trick but did not get success.
Code:
ExceptionHandler class:
#ControllerAdvice
public class DefaultExceptionHandler {
#Autowired
private CPro cPro;
private static final Logger LOG = LoggerFactory.getLogger(DefaultExceptionHandler.class);
#RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
#ExceptionHandler(Exception.class)
#ResponseStatus(value = INTERNAL_SERVER_ERROR)
#ResponseBody
public ModelAndView handleException(Exception ex) {
ModelAndView modelAndView = new ModelAndView("redirect:/");
String exceptionType = ex.getClass().getSimpleName();
DefaultExceptionHandler.LOG.error("Internal Server Exception", ex);
ErrorResponse response = new ErrorResponse();
if (ex.getCause() != null) {
response.addSimpleError(exceptionType, ex.getCause().getMessage(), cPro.getProName());
} else {
response.addSimpleError(exceptionType, ex.getMessage(), cPro.getProName());
}
modelAndView.addObject("processingException", response);
return modelAndView;
}
}
my home controller:
#RequestMapping(value = "/", method = RequestMethod.GET)
public String getHomePage(#ModelAttribute("processingException") ErrorResponse errorResponse, Model model) {
// I want to get object data of processingException added in exception handler using ModelAndView
model.addAttribute("processingException", errorResponse.getError() == null ? null : errorResponse);
return "upscale"; //here upscale.html redirection
}
Does anyone have idea that how to get that object data in my controller ?
Thanks.
After a lot googling and searching various forums and article, I found some solution. I have combined data and code of various forums I have made my requirement fulfill.
We can use FlashMap for that. Just get context of request and add FlashMap and add other data to FlashMap as well.
Code:
#ControllerAdvice
public class DefaultExceptionHandler {
#Autowired
private CPro cPro;
private static final Logger LOG = LoggerFactory.getLogger(DefaultExceptionHandler.class);
#ExceptionHandler(Exception.class)
public String handleException(Exception ex, HttpServletRequest request) throws IOException {
DefaultExceptionHandler.LOG.error("Internal Server Exception", ex);
String exceptionType = ex.getClass().getSimpleName();
ErrorResponse response = new ErrorResponse();
if (ex.getCause() != null) {
response.addError(exceptionType, ex.getCause().getMessage(), cPro.getProName());
} else {
response.addError(exceptionType, ex.getMessage(), cPro.getProName());
}
FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(request);
if (outputFlashMap != null) {
outputFlashMap.put("processingException", response);
}
return "redirect:/";
}
}
and other hand, in controller use ModelAttribute to get data that is sent from exception handler method.
code:
#RequestMapping(value = "/", method = RequestMethod.GET)
public String getHomePage(Model model, #ModelAttribute("processingException") Object processingException) {
if (processingException instanceof ErrorResponse) {
model.addAttribute("processingException", ((ErrorResponse) processingException).getError());
} else {
model.addAttribute("processingException", null);
}
return "upscale"; //here upscale.html redirection
}
After all bingo.. Done my work.
If anyone have still better idea on it then still welcome..
Thanks guys.
You could make a workaround like this:
public ModelAndView handleException(Exception ex, HttpServletRequest req) {
//...
ModelAndView modelAndView = new ModelAndView("forward:/");
//...
req.setAttribute("processingException", response);
Then in your Controller Method you have access to HttpServletRequest and get the Attribute (Object):
public String getHomePage(#ModelAttribute("processingException", HttpServletRequest req)
{
//....
req.getAttribute("processingException");
I'm new to Spring MVC. I'm getting errors on the following (not sure yet what;s the full scope of info requierd to assist me):
Working fine:
#RequestMapping(value = "startpage.do")
public ModelAndView startpage(HttpServletRequest req, HttpServletResponse res) {
.
.
ModelAndView mv = new ModelAndView("startpage");
mv.getModelMap().addAttribute("loginPage", loginPage);
return mv;
But failing:
#RequestMapping(value = "somecontroller.do")
public ModelAndView ftcontroller(HttpServletRequest req, HttpServletResponse res, ModelAndView mav) {.. ...
ModelAndView mv = new ModelAndView("startpage");
mv.getModelMap().addAttribute("loginPage", loginPage);
return mav;
As you can see, same code, different request mapping. Could it be that this is consuing the MVC somehow to get confused?
The error I'm getting is:
java.lang.NullPointerException
at jsp_servlet._web_45_inf._jsp.__somecontroller._jspService(__ftcontroller.java:103)
at weblogic.servlet.jsp.JspBase.service(JspBase.java:34)
at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:292)
Truncated. see log file for complete stacktrace
The biz logic is a login screen "startpage.do" when Login is submitted, it can fail (in which case, startup.do should be retuend once again, in other case, a differn page should be returned.
if there is better way to implement this, I'll be happy to hear that.
(It should be noted that I'm trying to plug-in Spring MVC into an existing project, so I'm trying to make as little as possible changes, and hence not using spring:form etc..)
More info:
The JSP startpage.jsp (resolved from "startpage") has a form with target="somecontroller.do".
Full controller code:
class
{
public static void main(String[] args)
{
#RequestMapping(value = "startpage.do")
public ModelAndView startpage(HttpServletRequest req, HttpServletResponse res) {
System.out.println(">>>>>>HomeController: Passing through (Get Type)...");
LoginPage loginPage = new LoginPage();
ModelAndView mv = new ModelAndView("startpage");
mv.getModelMap().addAttribute("loginPage", loginPage);
return mv;
}
#RequestMapping(value = "somecontroller.do")
public ModelAndView ftcontroller(HttpServletRequest req, HttpServletResponse res)
throws Exception {
// Parsing for login request;
String sUsername = req.getParameter(USER_ID);
String sUserPassword = req.getParameter(PASSWORD);
AbstractResponseDataComponent returnedResponse = new LoginCommand().login(sUsername, sUserPassword);
String returnedView = GlobalConstants.EMPTY_STRING;
JstlView view = new JstlView();
Map model = new HashMap();
if (returnedResponse.isSuccessful())
{
view.setUrl("somecontroller");
model.put("loginResponse", (LoginResponse) returnedResponse);
} else
{
view.setUrl("startpage");
model.put("loginPage", (LoginPage) returnedResponse);
}
return new ModelAndView(view, model);
}
}
}
BTW: when using Spring 3.0 make your method signature more clean:
instead of
public ModelAndView ftcontroller(HttpServletRequest req, HttpServletResponse res)
throws Exception {
String sUsername = req.getParameter(USER_ID);
String sUserPassword = req.getParameter(PASSWORD);
...
do it in the spring 3.0 way:
public ModelAndView ftcontroller(
#RequestParam(USER_ID) String sUsername,
#RequestParam(PASSWORD) String sUserPassword)
throws Exception {
...
For your second question:
if there is better way to implement
this, I'll be happy to hear that.
Are you looking for something like this:
#RequestMapping(value = "startpage.do")
public String startpage() {
...
if (loginFailed) {
return "redirect:startpage.do");
} else {
return "redirect:somecontroller.do");
}
}
(I prefere redirects, because I assume the that login methods has some sideeffects.)
I belive the cause of your exception is the JstlView.
Try not to use the JstlView direct, instad pass the view name as String.
final String viewName;
Map model = new HashMap();
if (returnedResponse.isSuccessful())
{
viewName = "somecontroller";
model.put("loginResponse", (LoginResponse) returnedResponse);
} else
{
viewName = "startpage";
model.put("loginPage", (LoginPage) returnedResponse);
}
return new ModelAndView(viewName, model);