Multiple domains with spring mvc - java

Let's say I have an application that has to shorten URLs, but also do other things. (like google.com and goo.gl, or facebook.com and fb.me).
It will be easy to simply deploy two applications, but (for now) it's simpler to be just one. Using spring and spring-mvc. I have the following mappings:
#RequestMapping(value="/{shortUrlKey}", headers="Host=foo.br")
...
#RequestMapping(value="/{username}")
Alas, the headers annotation acts not as giving more specific information, but as restricting instead. So if I have these two, only the latter is invoked, even if I open it as http://foo.br/asdf. If leave only the former, it works for those coming from foo.br, and doesn't open anything if the host is different.
So, the questions:
how can I make two handlers for the same paths, but different URLs / Hosts
is it possible to resolve the host dynamically, with a property placeholder configurer (rather than hard-code it in the annotation)
Perhaps both would work if there is some pluggable mechanism for method resolution. Is there such?

My immediate suggestion would be to write a servlet filter (or a Spring HandlerInterceptor), which would take the host name from the request, prepend it to the original requested path, then forward on the request.
For example, given the requested URL http://goo.gl/my/path, the filter would forward to /goo.gl/my/path. The Spring MVC mappings would then have something to get their teeth into. The ant-style wildcard syntax (e.g. "**/my/path") or path-variable style (e.g. "{requestHost}/my/path" might be helpful there.
Alternatively, the filter could set a custom header or request attribute containing the requested host, but that's probably less flexible.
I'm not sure what you mean by the second part of your question, though.
Here's a working snippet:
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
if (request.getRequestURL().toString().contains(shortenerDomain)
&& !request.getRequestURI().startsWith(SHORT_URI_PREFIX)) {
request.getRequestDispatcher(SHORT_URI_PREFIX + request.getRequestURI())
.forward(request, response);
return false;
} else {
return true;
}
}

Based on your description, it sounds like you could have two controller methods with each domain header mapping:
#RequestMapping(value="/{shortUrlKey}", headers="Host=foo.br")
public void fooBr() { ... }
#RequestMapping(value="/{shortUrlKey}", headers="Host=bar.bz")
public void barBz() { ... }

Related

Disable servlet at build/package/deploy, enable at run-time?

Lets say I have a simple "Hello world" type servlet, configured with the annotation #WebServlet("/hello").
I want to disable it for build/deployment, so it will not be possible to "call" the servlet. How would I do that?
Then, through a configuration file, I want to be able to enable the servlet at run-time, so it can be used by a client. How would I do that?
Is either of these possible?
You can't enable servlets during runtime via standard API. It can at most only be enabled during build time in web.xml or during deploy time by ServletContext#addServlet(). Your best bet is to always enable it and control it on a per-request basis. You can use a servlet filter for this.
First give the servlet a name.
#WebServlet(urlPatterns="/hello", name="yourServlet")
public class YourServlet extends HttpServlet {
// ...
}
So that you can easily map a filter directly to it without worrying about servlet's URL patterns.
#WebFilter(servletNames="yourServlet")
public class YourFilter implements Filter {
// ...
}
In your filter, just decide whether to continue the chain, or to return a 404 based on your configuration file setting.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (isYourConfigurationFileSettingSet()) {
chain.doFilter(request, response);
} else {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
The isYourConfigurationFileSettingSet() part can't be answered in detail based on the information provided so far. In case you actually also couldn't figure out that, then head to Where to place and how to read configuration resource files in servlet based application?
Instead of defining the servlet through an annotation, do it in the web.xml file. Different versions of this file may allow you to have the servlets enabled or not.
The version of web.xml to use should be selected at build and deployment time. Maybe by a Maven profile or similar. Take a look at the following link for some ideas on that: https://maven.apache.org/guides/mini/guide-building-for-different-environments.html
If you want truly run-time control, then you may have to do a little custom coding. A filter (or, I suppose, the servlet itself) could check the value of a property and return a response with an HTTP error code (I suppose 403 would be vaguely appropriate; 404 less so, but if you want it to appear as though the servlet didn't exist in that configuration, it would work...)

how can i convince spring 4.2 to pass OPTIONS request through to the controller

we have are using spring mvc with #RestController annotations on our controllers, and we're handling authorization in the controller. we use the same code to set the allowed methods in responses to CORS pre-flight request. to achieve this, we have:
<init-param>
<param-name>dispatchOptionsRequest</param-name>
<param-value>true</param-value>
</init-param>
in the configuration of the dispatcher servlet, and then we have:
#RequestMapping(value="/some/collections", method=RequestMethod.OPTIONS)
public void collectionOptions(
HttpServletRequest req,
HttpServletResponse res) {
List<RequestMethod> methods = new ArrayList<>();
// check actual permissions, add the appropriate methods
CORS.setAllowedMethodHeaders(res,methods);
}
we also have an interceptor that do basic checks on CORS pre-flight to see if the origin can possibly have any permissions at all.
we do it like this mostly because permissions for some requests actually depend on #RequestParams, i.e.:
OPTIONS /api/collections?userId=122
might be allowed if you have administrative privileges OR you actually the user with the ID 122. also, we have API keys, so
OPTIONS /api/collections?userId=122&apiKey=ABC
might be OK for one origin, but not for another one.
this works fine, but spring 4.2 now decides wether or not it handles an OPTIONS request, via a call to:
CorsUtils.isCorsRequest(request);
in AbstractHandlerMapping and then returns
HandlerInterceptor[] interceptors = chain.getInterceptors();
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
instead of the HandlerMethod ...
what we would need is some way to tell spring to let the controller handle OPTIONS requests, no matter what preflight request handlers are present.
We don't seem to be able to find a point where we can EITHER tell the built-in CORS handling to be quiet or to configure some subclass somewhere that would allow us to bypass the newly added code in:
AbstractHandlerMapping.getHandler(HSR request)
Is this in any way possible? Wouldn't it be nice for a feature like this to be quiet until I actively enable it (through WebMvcConfigurerAdapter or via those #CrossOrigin annotations)?
-------- EDIT -------------
the HTTP standard says the following about the OPTIONS method:
The OPTIONS method represents a request for information about the communication options available on the request/response chain identified by the Request-URI. This method allows the client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval.
thinking beyong just CORS, i think that intercepting a CORS options call although a corresponding method is mapped on the controller is not the right way to go. yes, CORS is one thing you can do with an OPTIONS call. but it is by no means the only one.
if there is nothing mapped and if a handler method is mapped with a different request method and the #CrossOrigin annotation, the assumptions that i want the built in CORS support to be triggered would be fine, but i do not think that any request that has the origin header set should automatically ONLY go to the CORS handlers.
I just convinced Spring 4.3 to pass CORS preflights to my controller by adding a custom handler mapping:
public class CorsNoopHandlerMapping extends RequestMappingHandlerMapping {
public CorsNoopHandlerMapping() {
setOrder(0); // Make it override the default handler mapping.
}
#Override
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, CorsConfiguration config) {
return chain; // Return the same chain it uses for everything else.
}
}
Note: you'll still need to tell Spring to dispatch OPTIONS requests to your controller to begin with - that is, set dispatchOptionsRequest to true in dispatcherServlet like it says in this question.
WHY IT WORKS
Sébastien's above answer suggests using your own CorsProcessor. As far as I can tell, this will still not use your controller as the handler; it will just pass a different CorsProcessor to its own CORS handler.
By default, it looks like the method AbstractHandlerMapping#getCorsHandlerExecutionChain will throw out your controller when it detects a preflight. Instead of using your controller as the handler, it instantiates a new PreFlightHandler and uses that instead. See Spring source code. This is the problematic line:
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
What it's doing here is rebuilding the execution chain with a PreFlightHandler instead of your controller. This is not what we want, so we can override it to return the input chain.
As suggested by zeroflagl, I also think that this is not a good idea to mix access control and CORS. And you should keep in mind that only preflight CORS requests are OPTIONS ones.
If you need to customize Spring CORS handling, you can use AbstractHandlerMapping#setCorsProcessor() to provide your own implementation that could eventually extend DefaultCorsProcessor.
Please notice that by default, CORS processing is enabled but no remote origin is allowed so customizing CorsProcessor is rarely needed. More info on this blog post.
Your access control check could be done as a regular HandlerInterceptor since a successful CORS preflight request will be followed by an actual request.

Using only one servlet

I'am making a web page with a login system and backoffice page. The problem is, both use the method "doPost" (the login use to autenticate and the backoffice use to insert data in db). How can I use only one servlet for both? I'am asking this because both use doPost, so I made two servlet's.
In case you want to use a single servlet, you should implement Front Controller Pattern. For this, you will parse the request URL and decide which action should be performed:
public class MySingleServlet extends Servlet {
#Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String url = request.getPathInfo();
//returns the action to handle
Action action = ActionFactory.getAction(url);
action.process(request, response);
}
}
This involves an Action interface/abstract class and an ActionFactory that will parse the URL and return the right implementation to handle the actions to do.
Another more naive and harder-to-maintain implementation is by sending an action parameter. This may be a problem because an attacker may use a proxy and change the action parameter before sending the request to the URL. If this is a recognized valid action, and the attacker knows what to send, then you're in trouble.
Note that there are MVC frameworks that already implement Front Controller Pattern like Spring MVC and JSF, so there's no need to reinvent the wheel unless it is for learning purposes (otherwise, you should use a library that already implements this).
You could add an extra parameter (e.g. action) in your post method
retrieved from a hidden form field, if you are using forms, or
added with a simple &action='value' to your request if using xml http request
and based on its value perform the appropriate actions:
if (action.equals("auth"))
{
// authenticate
}
else if (action.equals("backoffice"))
{
// db update
}
You can get pathInfo from request object based on that you route the request.

REST API for registration

We want to implement a public RESTful API integrated in our software (written in java) that might be used by various clients to build small e-commerce apps (e.g. for Android or iPhone). This API includes getting a list of products, categories, shopping cart support, etc.
We need to provide an API that will allow user registration and couple of other sensitive functions. How should we protect this API against spam and bruteforcing? In the standard product we use reCAPTCHA. Any alternative for the REST counterpart?
First, think of separation of concerns. What is the purpose of REST API?
A REST API should do offer a service to the client. Client sends a request via REST protocol, and gets a response for its request. In code, this looks something like:
#GET
public Response getClientInfo(#QueryParam("clientId") Integer clientId) {
ClientDTO clientDTO = database.getClientInfo(clientId);
return ResponseWrapper.wrap(clientDTO);
}
Now, you want your REST method doing ONLY this and nothing else. Otherwise, you would put block-bruteforce-and-spam-logic in your REST method and you would get a mess of the code that is not extensible, hard to version, etc. If you want to change your, e.g. blacklisting policy you would have to change each and every REST method, and it's bulky. If you want to check the calls before the make it to REST methods, then take a look at Filters. Every request and response pass through a chain of filters and could be check for misuse of the server.
I don't know what is your technology stack is, but I would suggest looking into these:
JBoss AS7.
DeltaSpike (enables you powerful Interceptors that will check user rights and execution rights before the execution of the REST method).
for example:
#LoggedInUser
#GET
public Response getClientInfo(...) {
...
}
This security annotation #LoggedInUser (which, by the way, you define) will give sign to an Interceptor to check this security constraint, e.g.
#Secures (built in annotation)
#LoggedInUser
public boolean hasRight(Identity identity) {
return identity.isLoggedIn(); //or if he is in certain group of users
}
Context and Dependency Injection context (used in DeltaSpike).
JBoss Filters (a filter chain where you can create your own filter that, for example, checks if some IP is trying to send multiple calls within a very short period ~ 10 lines of code).
An example of the Filter
#Startup
#ApplicationScoped
#Filter(around= "org.jboss.seam.web.ajax4jsfFilter")
public class IPTrackerFilter extends AbstractFilter {
//IPTracker is your #ApplicationScoped bean that remembers all IP addresses accessing the application.
#Inject
private IPTracker fIPTracker;
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
if (!(req instanceof HttpServletRequest)) {
chain.doFilter(req, res);
return;
}
final String ipAddress= ((HttpServletRequest)req).getRemoteAddr();
if (fIPTracker.isBlackListed(ipAddress)) {
//implement error message here
sendErrorMessage(response);
return;
} else {
//all good, continue
chain.doFilter(req, res);
}
}
}
PS. I gave you the link for DeltaSpike, for others is really easy to find. Also, if you find DeltaSpike to obscure, try with JBoss Seam Security Framework.

Alternative of URL parameter for deciding which method to call

Right now based on the site name in the URL parameter, we decide the appropriate actions to take(method calls etc) in the Java (Standard Jsp/Servlet web applications). For example, the request would be something like www.oursite.com?site=Ohio
Wondering what would be the alternative of doing this without having to provide URL parameter.
You could use POST instead of GET.
GET appends request parameters to the end of the URL.
POST sends encoded data using a form.
http://www.tutorialspoint.com/jsp/jsp_form_processing.htm
Why not just code it into the path?
www.oursite.com/Ohio
If you're just using straight servlet api, you can just do something of this nature:
String path = request.getPathInfo();
String site = path.split("/")[0];
That being said, most web frameworks have some support for helping with this.
For example, in spring mvc:
#RequestMapping(value="/{site}/blah/blah", method=RequestMethod.GET)
public ModelAndView blahBlah(HttpServletRequest req,
HttpServletResponse resp,
#PathVariable("site") String site) {
// do stuff here
}
Of course you could do this at the controller level too if all your methods need that sort of mapping:
#Controller
#RequestMapping(value="/{site}")
public class MyController {
#RequestMapping(value="/blah/blah", method=RequestMethod.GET)
public ModelAndView blahBlah(HttpServletRequest req,
HttpServletResponse resp,
#PathVariable("site") String site) {
// do stuff here
}
}
I believe this is cleaner than a query param, though it still shows up in your URL. There's other, more complex methods like using apache's reverse proxying and virtual host capabilities to switch based on site names. You could do something at login, and store the site in session. It all depends on your requirements.
You could use an alternate URL, like ohio.oursite.com. This process could be automated by having your server respond to *.oursite.com. I would probably set up a filter that looked at what the subdomain was and compared that with a predefined list of allowed sites. If it didn't exist, you could redirect back to the main (www) site. If it did, you could set a request attribute that you could use in a similar way that you currently use the request parameter now.

Categories