Spring MVC and X-HTTP-Method-Override parameter - java

I'm using Spring MVC, and I have a function to update a user's profile:
#RequestMapping(value = "/{userName}" + EndPoints.USER_PROFILE,
method = RequestMethod.PUT)
public #ResponseBody ResponseEntity<?> updateUserProfile(
#PathVariable String userName, #RequestBody UserProfileDto userProfileDto) {
// Process update user's profile
}
I've started using JMeter, and for some reason they have a problem with sending a PUT request with a body (either in a request body or using a request parameter hack).
I know that in Jersey you can add a filter to process the X-HTTP-Method-Override request parameter, so that you can send a POST request and override it using the header parameter.
Is there any way to do this in Spring MVC?
Thanks!

Spring MVC has the HiddenHttpMethodFilter which allows you to include a request parameter (_method) to override the http method. You just need to add the filter into your filter chain in web.xml.
I'm not aware of an out-of-the-box solution to use the X-HTTP-Method-Override header, but you can create a filter similar to the HiddenHttpMethodFilter yourself which uses the header to change the value rather than the request parameter.

You can use this class as a filter:
public class HttpMethodOverrideHeaderFilter extends OncePerRequestFilter {
private static final String X_HTTP_METHOD_OVERRIDE_HEADER = "X-HTTP-Method-Override";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String headerValue = request.getHeader(X_HTTP_METHOD_OVERRIDE_HEADER);
if (RequestMethod.POST.name().equals(request.getMethod()) && StringUtils.hasLength(headerValue)) {
String method = headerValue.toUpperCase(Locale.ENGLISH);
HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
filterChain.doFilter(wrapper, response);
}
else {
filterChain.doFilter(request, response);
}
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
#Override
public String getMethod() {
return this.method;
}
}
}
Source: http://blogs.isostech.com/web-application-development/put-delete-requests-yui3-spring-mvc/

Related

How to log a username in Spring MVC POST request

My goal is to log the incoming http requests to my Spring (5.0.7) MVC Web / Spring security (4.2.3) application. I want to save the requestdata in a database, containing IP, request method, headers, body and the URL. The critical requests are the login attempts so I need to fetch the POST request to the /login URL.
Therefore I wrote a filter to get this done because an interceptor is applied after the filter chain.
I looked at the solution at this SO question and I also tried the variant with an interceptor.
WebAppInitializer
public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
...
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
...
filterRegistration = servletContext.addFilter("logFilter", new APILoggingFilter() );
String[] mappings = new String[] {"/login", "/logout", "/data"};
filterRegistration.addMappingForUrlPatterns(null, false, mappings);
}
}
LoggingFilter
public class APILoggingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
boolean isFirstRequest = !isAsyncDispatch(request);
HttpServletRequest requestToUse = request;
HttpServletResponse responseToUse = response;
if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
requestToUse = new ContentCachingRequestWrapper(request);
}
if (isFirstRequest && !(response instanceof ContentCachingResponseWrapper)) {
responseToUse = new ContentCachingResponseWrapper(response);
}
filterChain.doFilter(requestToUse, responseToUse);
String user = SecurityContextHolder.getContext().getAuthentication().getName();
// This is were the logging to the database should take place
if (!isAsyncStarted(request)) {
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(responseToUse, ContentCachingResponseWrapper.class);
responseWrapper.copyBodyToResponse();
}
}
#Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
}
log4j.properties
log4j.logger.org.springframework=INFO
log4j.logger.org.springframework.web.filter=DEBUG
With this code I am able to log all request to the database with almost all the data I wanted. I see GET requests and POST requests.
The problem or question is: why is it not possible to see the username? I tried to get the username via
request.getRemoteUser();
and with
String user = SecurityContextHolder.getContext().getAuthentication().getName();
It is always null. And here is the curious thing. If I disable the second entry in my log4j.properties (log4j.logger.org.springframework.web.filter=DEBUG) then I always get the username with both options BUT I never fetch a POST request anymore only GET requests.
How do I achieve both goals? Fetch all requests AND get the username?

log request body string in RestController ExceptionHandler

the final goal:
log request body string in RestController's #ExceptionHandler.
explanations
By default, when request is invalid json, springboot throws a HttpMessageNotReadableException, but the message is very generic, and not including specific request body. This makes investigating hard. On the other hand, I can log every request string using Filters, but this way logs will be flooded with too many success ones. I only want to log the request when it is invalid. What I really want is in #ExceptionHandler I'll get that string(previously got somewhere) and log as ERROR.
To illustrate the problem, I created a demo project in github.
the controller:
#RestController
public class GreetController {
protected static final Logger log = LogManager.getLogger();
#PostMapping("/")
public String greet(#RequestBody final WelcomeMessage msg) {
// if controller successfully returned (valid request),
// then don't want any request body logged
return "Hello " + msg.from;
}
#ExceptionHandler({HttpMessageNotReadableException.class})
public String addStudent(HttpMessageNotReadableException e) {
// this is what I really want!
log.error("{the request body string got somewhere, such as Filters }");
return "greeting from #ExceptionHandler";
}
}
the client
valid request
curl -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message":"nice to meet you!"}'
invalid request(invalid json)
curl -H "Content-Type: application/json" http://localhost:8080 --data '{"from":"jim","message""nice to meet you!"}'
I once tried HandlerInterceptor but will get some error like
'java.lang.IllegalStateException: Cannot call getInputStream() after
getReader() has already been called for the current request'.
after some searching 1 2, I decided to use Filter with ContentCachingRequestWrapper.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(httpServletRequest);
chain.doFilter(cachedRequest, response);
String requestBody = IOUtils.toString(cachedRequest.getContentAsByteArray(), cachedRequest.getCharacterEncoding());
log.info(requestBody);
}
This code works well except that the log is after the RestController. if I change the order:
String requestBody = IOUtils.toString(cachedRequest.getReader());
log.info(requestBody);
chain.doFilter(cachedRequest, response);
Works for invalid request, but when request is valid, got following exception:
com.example.demo.GreetController : Required request body is
missing: public java.lang.String
com.example.demo.GreetController.greet(com.example.demo.WelcomeMessage)
I also tried getContentAsByteArray, getInputStream and getReader methods since some tutorials say the framework checks for specific method call.
Tried CommonsRequestLoggingFilter as suggested by #M. Deinum.
But all in vain.
Now I'm bit confused. Can anyone explain the executing order of RestController and Filter, when request is valid and invalid?
Is there any easier way(less code) to achive my ultimate goal? thanks!
I'm using springboot 2.6.3, jdk11.
Create a filter that wraps your request in a ContentCachingRequestWrapper (nothing more nothing less).
Use the HttpServletRequest as a parameter in your exception handling method as an argument
Check if instance of ContentCachingRequestWrapper
Use the getContentAsByteArray to get the content.
Something like this.
public class CachingFilter extends OncePerRequestFilter {
protected abstract void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(new ContentCachingRequestWrapper(request), new ContentCachingResponseWrapper(response));
}
NOTE: I wrapped the response as well, just in case you wanted that as well.
Now in your exception handling method use the HttpServletRequest as an argument and use that to your advantage.
#ExceptionHandler({HttpMessageNotReadableException.class})
public String addStudent(HttpMessageNotReadableException e, HttpServletRequest req) {
if (req instanceof ContentCachingRequestWrapper) {
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) req;
log.error(new String(wrapper.getContentAsByteArray()));
}
return "greeting from #ExceptionHandler";
}
It could be that multiple filters add a wrapper to the HttpServletRequest so you might need to iterate over those wrappers, you could also use this
private Optional<ContentCachingRequestWrapper> findWrapper(ServletRequest req) {
ServletRequest reqToUse = req;
while (reqToUse instanceof ServletRequestWrapper) {
if (reqToUse instanceof ContentCachingRequestWrapper) {
return Optional.of((ContentCachingRequestWrapper) reqToUse);
}
reqToUse = ((ServletRequestWrapper) reqToUse).getRequest();
}
return Optional.empty();
}
Your exception handler would then look something like this
#ExceptionHandler({HttpMessageNotReadableException.class})
public String addStudent(HttpMessageNotReadableException e, HttpServletRequest req) {
Optional<ContentCachingRequestWrapper) wrapper = findWrapper(req);
wrapper.ifPresent(it -> log.error(new String(it.getContentAsByteArray())));
return "greeting from #ExceptionHandler";
}
But that might depend on your filter order and if there are multiple filters adding wrappers.
Following #M.Deinum's comments, I solved and hope useful for others:
Add a Filter
public class MyFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper(httpServletRequest);
chain.doFilter(cachedRequest, response);
}
}
Inject the ContentCachingRequestWrapper in ExceptionHandler
#ExceptionHandler({ HttpMessageNotReadableException.class })
public String addStudent(HttpMessageNotReadableException e, ContentCachingRequestWrapper cachedRequest) {
log.error(e.getMessage());
try {
String requestBody = IOUtils.toString(cachedRequest.getContentAsByteArray(), cachedRequest.getCharacterEncoding());
log.error(requestBody);
} catch (IOException ex) {
ex.printStackTrace();
}
return "greeting from #ExceptionHandler";
}

How to add values to the header from a filter in Spring Security?

I am developing a web service with JWT in spring security and spring session. My intention is to add a filter that validates the JWT, extract its JTI and add it in the header service as "x-auth-token". The Json Web Token JTI matches the "Session_id" that generates spring session when a new user is authenticated (it can see with RequestContextHolder.currentRequestAttributes().GetSessionId()). This was put in the Json Web Token JTI when the user was authenticated.
I already have the filter that validates the JWT, however, I will not put it here now for simplicity. What I will put will be only the filter class with the written method doFilter.
What I'm trying to do is add a value to the header, something like this:
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
/*
* In this part I validate the token and extract the JTI, which is equal to the session_id of spring session.
* Suppose that JTI = 71b0b8c1-1eac-46ce-80b6-f14c2e08c0de
*/
//I want to do something like this:
request.addHeader("x-auth-token", "71b0b8c1-1eac-46ce-80b6-f14c2e08c0de");
chain.doFilter(request, response);
}
}
This way the spring session token will not be inserted by the user, but by the filter once it is extracted from the JWT.
I have tried to do it through a class that extends from HttpServletRequestWrapper, like this:
public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
/**
* construct a wrapper for this request
*
* #param request
*/
public HeaderMapRequestWrapper(HttpServletRequest request) {
super(request);
}
private Map<String, String> headerMap = new HashMap<String, String>();
/**
* add a header with given name and value
*
* #param name
* #param value
*/
public void addHeader(String name, String value) {
headerMap.put(name, value);
}
#Override
public String getHeader(String name) {
String headerValue = super.getHeader(name);
if (headerMap.containsKey(name)) {
headerValue = headerMap.get(name);
}
return headerValue;
}
/**
* get the Header names
*/
#Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
for (String name : headerMap.keySet()) {
names.add(name);
}
return Collections.enumeration(names);
}
#Override
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if (headerMap.containsKey(name)) {
values.add(headerMap.get(name));
}
return Collections.enumeration(values);
}
}
and defining the doFilter method like this:
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
/*
* In this part I validate the token and extract the JTI, which is equal to the session_id of spring session.
* Suppose that JTI = 71b0b8c1-1eac-46ce-80b6-f14c2e08c0de
*/
HttpServletRequest r = (HttpServletRequest) request;
HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(r);
requestWrapper.addHeader("x-auth-token", "71b0b8c1-1eac-46ce-80b6-f14c2e08c0de");
chain.doFilter(requestWrapper, response);
}
However it does not work, I do not know if I have missed something or is not the way to do it.
Edit 02/10/2017:
When I run the service spring recognizes that there is no token (x-auth-token) in the header and automatically sends me the filter to authenticate the new user, which causes a Forbidden error because there is no user and password.
If I send the token (x-auth-token) from the beginning in the header everything works fine.
Edit 05/10/2017:
I have created an second filter to check the value that was added by the first filer in the header. The first filter does not receive the value "x-auth-token" from the ServletRequest, it adds it with "requestWrapper".
The second filter was added to configuration class like this:
.addFilterAfter (getCustomFilter (),
UsernamePasswordAuthenticationFilter.class)
.addFilterAfter
(getCustomFilter2 (), UsernamePasswordAuthenticationFilter.class)
where getCustomFilter () and getCustomFilter2 () were created using a bean like this:
#Bean
public CustomFilter getCustomFilter(){
return new CustomFilter();
}
#Bean
public CustomFilter2 getCustomFilter2(){
return new CustomFilter2();
}
The second filter is defined as follows:
public class CustomFilter2 implements Filter {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println("Result: " + req.getHeader("x-auth-token"));
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
When I run the service spring recognizes that there is no token (x-auth-token) in the original header and automatically sends me the filter to authenticate the new user.
I think the problem is the order in which spring session is executed.
How can I call spring session after our filters?
The configuration class is the follows:
#Configuration
#EnableWebSecurity
public class SeguridadConfiguracion extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("seguridadServicio")
private UserDetailsService objSeguridadServicio;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(objSeguridadServicio);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").hasAnyAuthority("ComisionadoSI","GerenciaSI")
.anyRequest().authenticated()
.and()
.logout().clearAuthentication(true)
.invalidateHttpSession(true)
.and()
.formLogin()
.and()
.httpBasic()
.and()
.addFilterAfter(getCustomFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(getCustomFilter2(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable();
}
#Bean
public CustomFilter getCustomFilter(){
return new CustomFilter();
}
#Bean
public CustomFilter2 getCustomFilter2(){
return new CustomFilter2();
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
}
We can do this via addingAttribute in the filter
httpServletRequest.setAttribute("key name","value");
And in Controller we can access them via #RequestAttribute
Have you verified the order of your filters to make sure this filter is executed in the correct order?
Update to add more info:
Looking at the spring docs: https://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-rest
You enable spring session by adding the annotation #EnableRedisHttpSession.
The #EnableRedisHttpSession annotation creates a Spring Bean with the
name of springSessionRepositoryFilter that implements Filter. The
filter is what is in charge of replacing the HttpSession
implementation to be backed by Spring Session. In this instance Spring
Session is backed by Redis.
According to this the springSessionRepositoryFilter is an instance of springSessionRepositoryFilter: https://docs.spring.io/spring-session/docs/current/api/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.html
RedisHttpSessionConfiguration exposes the SessionRepositoryFilter as a bean named
"springSessionRepositoryFilter". In order to use this a single
RedisConnectionFactory must be exposed as a Bean.
Based on that, I think you need to add your filter before the SessionRepositoryFilter like:
.addFilterAfter(getCustomFilter(), SessionRepositoryFilter.class)
.addFilterAfter(getCustomFilter2(), SessionRepositoryFilter.class)

Spring MVC - calling method on every admin pages

I'm new to spring mvc , I'm working on a web project admin panel.
Here is some example of my admin pages controllers :
#Controller
#RequestMapping("/admin/article/**")
public class ArticleController {
private ArticleDao articleDao;
private String fileName;
private String baseUrl;
public ArticleController() {
articleDao = ArticleDaoFactory.create();
}
#RequestMapping(value = "/admin/article",method = RequestMethod.GET)
public String doGet(ModelMap model,HttpServletRequest request,ArticleForm articleForm) {
//some codes
}
#RequestMapping(value = "/admin/article/add",method = RequestMethod.GET)
public String doGetAdd(ModelMap model,ArticleForm articleForm) {
model.addAttribute("article", articleForm);
return "admin/articleAdd";
}
#RequestMapping(value = "/admin/article/add",method = RequestMethod.POST)
public String doPost(#ModelAttribute ArticleForm article, BindingResult result ,ModelMap model){
//some codes
}
#RequestMapping(value = "/admin/article/edit/{id}",method = RequestMethod.GET)
public String getEdit(ModelMap model, #PathVariable("id") int id) {
//some codes
}
#RequestMapping(value = "/admin/article/edit/{id}",method = RequestMethod.POST)
public String postEdit(ModelMap model, #PathVariable("id") int id, ArticleForm article, BindingResult result) {
//some codes
}
#RequestMapping(value = "/admin/article/delete/{id}",method = RequestMethod.GET)
public void getDelete(ModelMap model, #PathVariable("id") int id, HttpServletResponse response) {
//some codes
}
}
now I need another mapping in another contoller named AdminController (for example) to Authenticate admin and bring him to login page if he is not logged in. for sure Authenthication is one example, I might want to use more classes on every admin page.
Note that my authenthication class needs request and session references (and for sure my other classes will need other references created by spring)
I got to know that I can not get HttpServletRequest and ... using a constructor method so I wrote another request mapping to call a method.
Eventhough I can set my properties this way ,but I can not use this method on every admin url.
#Controller
#RequestMapping(value = "/admin/**",method = RequestMethod.GET)
public class AdminController {
Authentication authentication;
HttpServletRequest request;
HttpSession session;
HttpServletResponse response;
public void checkAndSet(HttpSession session,HttpServletRequest request,HttpServletResponse response) {
authentication = new Authentication(session,request);
this.request = request;
this.session = session;
this.response = response;
if(!authentication.isLoggedIn()){
System.out.println(" I'm not logged in");
response.setHeader("Location","/admin/login");
}
}
So I need some suggestion on how to write a request mapping in a controller to call a method on every other controllers that are 'admin' page child ?
FYI : I'm not thinking for spring security for this.
thanks;
I think you can do it by implementing a servlet filter.
For example :
public class AuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String url = request.getServletPath();
HttpSession session = request.getSession(false);
Authentication authentication = new Authentication(session,request);
if (isAdminUrl(url) && !authentication.isLoggedIn()) {
res.sendRedirect/admin/login");
}
chain.doFilter(req, res);
}
}
And then, you have to implement the method isAdminUrl(String url) to determine if you want to apply your filter.
Otherwise, I strongly recommend you to take a look at Spring Security

Servlet filter "returning" an object for later use

I'm implementing security in my RESTful webservice, and I'm thinking of creating a filter that checks if the Authorization header is valid or not, and this check is done by sending the token to a third party endpoint. If the token is valid, the third party endpoint has to send me a response that contains information regarding the token's expiration, client id, scope, and other stuff. The logic, then, is this:
#Override
public void doFilter(
final ServletRequest request,
final ServletResponse response,
final FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
header = req.getHeader("Authorization");
EndpointResponse eResponse = Endpoint.validate(header);
if(eResponse.valid())){
chain.doFilter(...);
return eResponse; //or equivalent
}else{
HttpServletResponse res = HttpServletResponse(response);
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
...
}
}
Then, in a DAO class, I will use the eResponse like this
public final class DAO{
public void checks(){
if(eResponse.scope() == "ADMIN"){
...
}else{
...
}
}
}
Is there a way to inject or return an object after the filter does the validation? Hopefully, without using spring or hibernate, since I can't use these at my job.
-EDIT-
The way I'm accessing the DAO would be like this
#Path("")
public class CertificationService {
#GET
#Produces(CertificationApplication.SUPPORTED_REPRESENTATIONS)
#Path(CertificationConstants.URL_PATH)
public Response getCertificationByUpId(String upId) throws CertificationException {
ResponseBuilder response;
try{
response = Response.ok(DAO.findCertificationByUPID(upId));
} catch (CertificationException e) {
response = handleException(e);
}
return response.build();
}
}
The findCertificationByUPID method would have to call the checks() method I declared above.
Try placing the object on the request using setAttribute():
request.setAttribute("auth", eResponse);
Then your controller can grab the object using
EndpointResponse eResponse = (EndpointResponse) request.getAttribute("auth");
and do whatever you like with it (including passing it to the DAO):
dao.checks(eResponse);
where DAO is like what you have above, but with
public void checks(EndpointResponse eResponse) { ... }
instead.
If you prefer to keep the EndpointResponse out of the DAO, you can do
public void checks(String role) { ... }
or similar.

Categories