Here is my code:
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.StrutsStatics;
#SuppressWarnings("serial")
public class PostOnlyInterceptor extends AbstractInterceptor {
#Override
public String intercept(ActionInvocation ai) throws Exception {
final ActionContext context = ai.getInvocationContext();
HttpServletRequest request = (HttpServletRequest) context.get(StrutsStatics.HTTP_REQUEST);
if (!request.getMethod().equals("POST")) {
return Action.ERROR;
}
return ai.invoke();
}
}
I am using this interceptor to avoid 'GET' method requests for security reasons. But when I am calling it by using chain action method: request.getMethod() returns GET request.
So how to handle this situation?
Beware of Action Chaining, that is discouraged:
As a rule, Action Chaining is not recommended. First explore other options, such as the Redirect After Post technique.
But if you already are using it and can't change, you can bypass the POST check from within your Interceptor by checking if the result is of type chain:
public String intercept(ActionInvocation ai) throws Exception {
final ActionContext context = ai.getInvocationContext();
HttpServletRequest request = (HttpServletRequest)
context.get(StrutsStatics.HTTP_REQUEST);
boolean isChainResult = ai.getResult() != null
&& ActionChainResult.class.isAssignableFrom(ai.getResult().getClass());
if (!request.getMethod().equals("POST") && !isChainResult) {
return Action.ERROR;
}
return ai.invoke();
}
Related
Is there with Spring (boot) a way to check if a REST request contains a parameter not explicitly declared by the called REST method?
With the required flag we can force the client to include a certain parameter in the request. I am looking for a similar way to disallow the client to send a parameter that is not explicity mentioned in the declaration of the controller method:
#RequestMapping("/hello")
public String hello(#RequestParam(value = "name") String name) {
//throw an exception if a REST client calls this method and
// sends a parameter with a name other than "name"
//otherwise run this method's logic
}
For example calling
curl "localhost:8080/hello?name=world&city=London"
should result in a 4xx answer.
One option would be to explicitly check for unexpected parameters:
#RequestMapping("/hello")
public String hello(#RequestParam Map<String,String> allRequestParams) {
//throw an exception if allRequestParams contains a key that we cannot process here
//otherwise run this method's logic
}
But is it also possible to achieve the same result while keeping the same convenient #RequestParam usage as in the first example?
EDIT: Sorry, I do not see any connection to this question. The other question is about annotation processing at runtime. My question is about the behaviour of Spring's REST engine. Am I missing something?
EDIT: Based on the answers, I have written this HandlerInterceptor:
#Component
public class TooManyParamatersHandlerInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod m = (HandlerMethod) handler;
if (m.getMethod().getName().equals("error")) {
return true;
}
List<String> allowedParameters = Stream.of(m.getMethodParameters())
.flatMap(p -> Stream.of(p.getParameterAnnotation(RequestParam.class)))
.filter(Objects::nonNull)
.map(RequestParam::name).collect(Collectors.toList());
ArrayList<String> actualParameters = Collections.list(request.getParameterNames());
actualParameters.removeAll(allowedParameters);
if (!actualParameters.isEmpty()) {
throw new org.springframework.web.bind.ServletRequestBindingException(
"unexpected parameter: " + actualParameters);
}
return true;
}
}
In this case you required HandlerInterceptor or HandlerInterceptorAdapter, override the preHandle method
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//request param validation validation
return true; //or throw exception
}
ServletRequest.getParameterMap() returns a map of key-values of the request parameters.
You can do it by ContainerRequestFilter feature which is added from JavaEE 7 that lets you access the resource class and resource method matched by the current request and make you to do your desire action when that have not been matched.
You can read more here :
https://docs.oracle.com/javaee/7/api/javax/ws/rs/container/ResourceInfo.html
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.Provider;
#Provider
public class RequestParamFilter implements ContainerRequestFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Set<String> acceptedParamList = new HashSet<String>();
Method method = resourceInfo.getResourceMethod();
for (Annotation[] annos : method.getParameterAnnotations()) {
for (Annotation anno : annos) {
if (anno instanceof QueryParam) {
acceptedParamList.add(((QueryParam) anno).value());
}
}
}
MultivaluedMap<String, String> queryParams = requestContext.getUriInfo().getQueryParameters();
for (String param : queryParams .keySet()) {
if (!acceptedParamList.contains(param)) {
requestContext.abortWith(Response.status(Status.BAD_REQUEST).entity("Unexpected paramter found : "+param).build());
}
}
}
}
P.N : Filters are cost in your application speed most of the times, Specially if you have complex chains in it!
I recommend to use it in this case (and similar cases) because of most of the those requests should not be reached to the server application at all.
I hope this helps you and Happy coding! =)
As far as I know, you cannot simply disallow parameters using Spring. Honestly, this issue is rather questionable and unnecessary and I think it's an antipattern.
However, Spring provides with each mapping the HttpServletRequest and HttpServletResponse objects to the controller method signature. Use the method HttpServletRequest::getParameterMap to receive the Map of the passed parameters for the further iteration and validation.
#RequestMapping("/hello")
public String hello(RequestParam(value = "name") String name, HttpServletRequest request, HttpServletResponse response) {
final Map<String, String[]> parameterMap = request.getParameterMap();
// logics
}
Passing those object to only to the #RequestMapping("/hello") allows performing the validation only to the selected mapping. If you want to define this behavior globally, I suggest you use HandlerInterceptor::preHandle as answered here.
If you make the hello parameter required=true, then you can just check the size of the Map whether is equal to 1 or not.
I am working on a messy Struts 1 application that makes use of a custom context class to store values throughout the application. Basically it is only used to store session scope variables. I guess the reason that this custom class is used is so that other classes which do not have access to the http session can still get and set the session variables.
Anyways, for the most part this works just fine. The custom context is used throughout the Actions and service classes to share variables with no problem. However, I just discovered that things do not work out so nicely using this custom context inside of an Http Filter! It appears that it randomly will pull the value from a different session. And by session, I actually mean thread, since this custom context uses ThreadLocale to do it's dirty work.
Take a look
package com.zero.alpha.common;
import java.io.Serializable;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
public final class CustomContext implements Serializable {
private static final long serialVersionUID = 400312938676062620L;
private static ThreadLocal<CustomContext> local = new ThreadLocal() {
protected CustomContext initialValue() {
return new CustomContext("0", "0", Locale.getDefault());
}
};
private String dscId;
private String sessionId;
private Locale locale;
private Map<String, Serializable> generalArea;
public CustomContext(String dscId, String sessionId, Locale locale) {
this.dscId = dscId;
this.sessionId = sessionId;
if (locale != null) {
this.locale = locale;
} else {
this.locale = Locale.getDefault();
}
this.generalArea = new Hashtable();
}
public static CustomContext get() {
return ((CustomContext) local.get());
}
public static void set(CustomContext context) {
local.set(context);
}
public String getDscId() {
return this.dscId;
}
public String getSessionId() {
return this.sessionId;
}
public Locale getLocale() {
return this.locale;
}
public Serializable getGeneralArea(String key) {
return ((Serializable) this.generalArea.get(key));
}
public Serializable putGeneralArea(String key, Serializable value) {
return ((Serializable) this.generalArea.put(key, value));
}
public void clearGeneralArea() {
this.generalArea.clear();
}
public Serializable removeGeneralArea(String key) {
return ((Serializable) this.generalArea.remove(key));
}
}
Again, this seems to work just fine and dandy inside every other class other than a filter. Let me show you the filter where it messes up.
package com.zero.alpha.myapp.common.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.zero.alpha.common.CustomContext;
import com.zero.alpha.myapp.utility.CommonConstants;
import com.zero.alpha.myapp.utility.CommonHelpers;
import com.zero.alpha.myapp.UserDomain;
public class LoginFilter implements Filter {
public LoginFilter() {
}
public void init(FilterConfig config) throws ServletException {}
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
// Don't use the login filter during a login or logout request
if (req.getServletPath().equals("/login.do")
|| req.getServletPath().equals("/login-submit.do")
|| req.getServletPath().equals("/logout.do")) {
chain.doFilter(request, response);
} else {
doFilter(req, (HttpServletResponse) response, chain);
}
}
protected void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpSession session = request.getSession(false);
// This is the problem right here. Sometimes this will grab the value of a different user currently logged in
UserDomain user = (UserDomain) CustomContext.get()
.getGeneralArea(CommonConstants.ContextKey.USER_SESSION);
if (session == null || user == null) {
// Unauthorized
response.sendRedirect(loginPage);
} else {
// Authorized
session.setAttribute("userInfo", CommonHelpers.getUserDisplay(user));
chain.doFilter(request, response);
}
}
}
When the custom context is used to grab the user in the doFilter method, it will randomly grab the user object from another logged in user. Obviously not a good situation!
The only time this happens is after some activity from a different logged in user. I could sit there all day and keep refreshing user A's session and there wouldn't be an issue. However, after taking some action as user B and then refreshing user A's session again, it will usually be swapped. But then if I refresh user A's session again, things are back to normal.
I've noticed this happens extremely more frequently when the application is actually deployed to a remote development tomcat server. It still happens when running locally, but not nearly as often as when deployed remotely. It happens almost 100% of the time remotely.
I have examined the session variable inside of the filter, and there doesn't appear to be an issue with the session itself. I have confirmed that the session id is still correct even when the incorrect user is pulled from the ThreadLocale variable.
Can anyone help me out? Thanks.
Your strategy is irretrievably flawed. Unless your servlet engine is single-threaded, you cannot rely on the same thread to handle every request in a given session. Moreover, and more relevant to the problem stated, even when the same thread to handle one request in a given session is also used to handle the next request in that session, it is not safe to assume that it does not handle a request belonging to a different session in between. There is no guarantee of thread / session binding.
Anyway, you seem to be missing the obvious. If you want to store session-scope variables, then store them in the session. That's what session attributes are for. See HttpSession.setAttribute() and HttpSession.getAttribute() -- you can use these to set and retrieve your context object.
I have an object with state and non-serializable fields, like threads, and I would to invoke functions on it like one would do it through RMI but through http. I don't want to scale and I am in an isolated network. I am currently using Jetty, like this:
public class ObjectHandler extends AbstractHandler {
MyStatefulObject obj;
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String action = request.getParameter("action");
switch (action) {
case "method1":
obj.method1(request.getParameter("some-parameter"));
break;
case "method2":
obj.method2(request.getParameter("some-other-parameter"));
break;
}
baseRequest.setHandled(true);
}
}
which is kind of weird. I would like to use something like Servlets, and use the different methods to tell apart the action to do, or use JAX-RS to use the calling url to tell apart the action to do. But both of those methods are stateless, that is, I cannot pass an object to a servlet, and, at least with jersey, the construction was made with the class, not with and instance of it, so I could not control the construction of the MyStatefulObject object. So, is there a library for, let's say, annotate an object and pass it to a server instance and start listening to requests? I would like to make something like this:
#Path("/")
public class MyStatefulObject {
MyStatefulObject(Parameter param1, Param) {
//some building stuff
}
#POST
#Path("/path1")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8")
void method1(Parameter param) {}
#POST
#Path("/path2")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON + "; charset=UTF-8")
Object method2(Parameter param) {
return new Object();
}
}
while outside I would have:
Server server = new Server(8081);
server.setHandler(new MyStatefulObject(param));
server.start();
server.join();
Is there a library that makes me able to do that? as I say before, I don't want to scale (this is running in a small network) and there is no security concerns. I just want to "publish" an object.
In the end, Jersey does allow stateful objects to be published, using the ResourceConfig class with an object (as opposed with a Class, which is the use I found more frequently). Funny cause in this question they want to do the exact opposite. We simply register an object in the ResourceConfig.
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.net.URI;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import javax.inject.Singleton;
#Path("calculator")
public class Calculator {
int i = -1;
public Calculator(int i) {
this.i = i;
}
#GET
#Path("increment")
#Produces(MediaType.APPLICATION_JSON)
public String increment() {
i = i + 1;
return "" + i;
}
public static void main(String[] args) throws Exception {
ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(new Calculator(10));
Server server = JettyHttpContainerFactory.createServer(new URI("http://localhost:8080"), resourceConfig);
server.start();
}
}
In play 2.3, how can I automatically reject (return BadRequest) all incoming requests that are not of type application/json? Is there an annotation type like for BodyParsers?
I don't want to add an extra check:
#BodyParser.Of(BodyParser.Json.class)
public static Result sendMessage() {
JsonNode requestBody = request().body().asJson();
if (requestBody == null) {
return badRequest("Bad Request: Not JSON request");
}
return ok();
}
Probably the most flexible way is creating own interceptor - a.k.a. Action composition
Sample RequiredJson.java (let's place it in new annotations package)
package annotations;
import com.fasterxml.jackson.databind.JsonNode;
import play.libs.F;
import play.mvc.Http;
import play.mvc.Result;
public class RequiredJson extends play.mvc.Action.Simple {
#Override
public F.Promise<Result> call(Http.Context ctx) throws Throwable {
boolean hasCorrectType = ctx.request().getHeader("Content-Type") != null && ctx.request().getHeader("Content-Type").equals("application/json");
JsonNode json = ctx.request().body().asJson();
if (!hasCorrectType || json == null) {
return F.Promise.<Result>pure(badRequest("I want JSON!"));
}
return delegate.call(ctx);
}
}
So you can use this annotation for whole controller(s) or for selected action(s) only like:
#With(annotations.RequiredJson.class)
Result: if Content-Type isn't valid or if incoming data isn't valid JSON it returns badRequest, otherwise it calls the requested action as usually.
I am using the Jersey client to run some integration tests against my service. However, one of my calls sends a redirect. I am expecting to get a redirect but when Jersey Client gets the redirect it errors out with a com.sun.jersey.api.client.UniformInterfaceException. Is there some way to make it accept the response with the redirect and just let me know what it got?
You can catch UniformInterfaceException which provides response field containing all the details.
You could additionally write some hamcrest matchers to express your expectations:
import static javax.ws.rs.core.Response.Status.*;
import static org.junit.rules.ExpectedException.*;
import javax.ws.rs.core.Response.Status;
import org.hamcrest.*;
import org.junit.*;
import org.junit.rules.ExpectedException;
import com.sun.jersey.api.client.*;
public class MyResourceShould {
#Rule
public ExpectedException unsuccessfulResponse = none();
private WebResource resource;
#Before
public void setUp() {
Client client = Client.create();
client.setFollowRedirects(false);
resource = client.resource("http://example.com");
}
#Test
public void reportMovedPermanently() {
unsuccessfulResponse.expect(statusCode(MOVED_PERMANENTLY));
resource.path("redirecting").get(String.class);
}
public static Matcher<UniformInterfaceException> statusCode(Status status) {
return new UniformInterfaceExceptionResponseStatusMatcher(status);
}
}
class UniformInterfaceExceptionResponseStatusMatcher extends TypeSafeMatcher<UniformInterfaceException> {
private final int statusCode;
public UniformInterfaceExceptionResponseStatusMatcher(Status status) {
this.statusCode = status.getStatusCode();
}
public void describeTo(Description description) {
description.appendText("response with status ").appendValue(statusCode);
}
#Override
protected boolean matchesSafely(UniformInterfaceException exception) {
return exception.getResponse().getStatus() == statusCode;
}
}
Also note that follow redirects (in setUp method) should be set to false in order to get UniformInterfaceException instead of following redirect (if one is specified in Location header).