Fallback URLs in Websphere Liberty Profile - websphere-liberty

I am on a team developing a single page web application with an associated REST API.
I wonder if anyone can help me? I am trying to find a way for our application to return the contents of index.html with a 200 response if certain URLs are accessed. For example the client wants to embed information in the URL but expects the content on index.html to be returned.
For example our single page web application is available on a single context root e.g: http://host:9082/webapp
We have rest endpoints available on http://host:9082/webapp/api/... These endpoints must not return index html, they must only return valid rest responses with the appropriate status code (400, 404, 200, 201 etc)
Java script is served from http://host:9082/webapp/js/... and there are other locations we don't want to fall back to index.html
However, if the client requests http://host:9082/webapp/resource/7/show we want index.html to be returned with status code 200. The client will then extract meaning from the URL to drive other REST requests.
So I tried to write a filter like the following:
#Override
public void doFilter(ServletRequest servletRequest,ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException
{
final HttpServletRequest request = (HttpServletRequest)servletRequest;
final HttpServletResponse response = (HttpServletResponse)servletResponse;
final String requestUri = request.getRequestURI();
if (!excluded(requestUri))
{
request.getRequestDispatcher(INDEX_HTML).forward(request, response);
}
else
{
filterChain.doFilter(servletRequest, servletResponse);
}
}
private boolean excluded(String requestUri)
{
for (String part : mExcludedUriParts)
{
if (requestUri.contains(part))
{
return true;
}
}
return false;
}
and enabled the filter in web.xml as following:
<filter>
<filter-name>FallbackFilter</filter-name>
<filter-class>com....http.filter.internal.FallbackFilter</filter-class>
<init-param>
<param-name>excludedUriParts</param-name>
<param-value>/api/,.js/,.png,.html,/apidocs/,/users/imgs/</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>FallbackFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
However this approach is quite fragile as the deployed needs to change the web.xml to match the available resources on the server which may of course change.
We also considered detecting 404's in the filterChain then modifying the response but Liberty did not allow this as the response has already been committed. We also considered using the request accept headers (i.e text/html) as the basis for whether or not to return index html, but we have other html files so this approach did not work either.
We basically want a way to allow some non existent locations on the server to return index.html with a 200 status code. Ideally we want to be informed of a 404 and control the response.
Is there a way to achieve this using filters or any other mechanism in Liberty?
Many thanks

I'm not certain of this, but if you wrap the response in a ServletResponseWrapper you may be able to intercept PrintWriter.flush() so setting the 404 does not commit the response, then the filter can work with it. There's an example of this used for something else here:
https://www.ibm.com/support/knowledgecenter/SSAW57_8.5.5/com.ibm.websphere.nd.iseries.doc/ae/twbs_jaxrs_handlers_servlet_filters.html

Related

Solution Spring Backend OAuth2 Client for both web apps as for native (mobile) apps

For the past days I have been trying to figuring out how to make OAuth2 work on a native app with the OAuth2 client consisting of a separate frontend application with a Spring backend. Good news! I figured out a way to make it work both as web app (on a browser) as on a native (mobile) app. Here I would like to share my findings and ask for any suggestions on possible improvements.
Where Spring works out of the box
Spring Oauth2 works out of the box for web apps. We add the dependency <artifactId>spring-security-oauth2-autoconfigure</artifactId>. We add the annotation #EnableOAuth2Client. Furthermore, we add the configuration. For an in detail tutorial I would like to refer you to this tutorial.
Where challenges start to arise
Spring works with a session cookie (JSESSIONID) to establish a session which is send to the frontend using a Set-Cookie header. In a mobile application this Set-Cookie header is not send back to the backend on subsequent requests. This means that on a mobile application the backend sees each request as a new session. To solve this, I implement a session header rather than a cookie. This header can be read and therefore added to the subsequent requests.
#Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
However, that solves only part of the problem. The frontend makes a request using window.location.href which makes it impossible to add custom headers (REST call cannot be used because it would make it impossible to redirect the caller to the authorization server login page, because the browser blocks this). The browser automatically adds cookies to calls made using window.location.href. That's why it works on browser, but not on a mobile application. Therefore, we need to modify Spring's OAuth2 process to be able to receive REST calls rather than a call using window.location.href.
The OAuth2 Client process in Spring
Following the Oauth2 process the frontend makes two calls to the backend:
Using window.location.href a call to be redirected to the Authorization server (e.g. Facebook, Google or your own authorization server).
Making a REST GET request with the code and state query parameter to retrieve an access token.
However, if Spring does not recognise the session (like on mobile phone) it creates a new OAuth2ClientContext class and therefore throws an error on the second call: InvalidRequestException("Possible CSRF detected - state parameter was required but no state could be found"); by the AuthorizationCodeAccessTokenProvider.class. The reason it throws this error is because the preservedState property is null on the request. This is nicely explained by this post's answer of #Nico de wit.
I created a visual of the Spring OAuth2 process which shows the box 'Context present in session?'. This is where it goes wrong as soon as you have retrieved the authorization code from logging into the authorization server. This is because further on in in the getParametersForToken box it checks the preservedState which is then null because it came from a new OAuth2ClientContext object (rather than the same object that was used when redirecting the first call to the page of the authorization server).
The solution
I solved this problem by extending OAuth2ClientContextFilter.class. This class is responsible for redirecting the user to the authorization server login page if no authorization code has been retrieved yet. Instead of redirecting, the custom class now sends back a 200 and the in the body an url to which the frontend needs to be redirected. Also the frontend can now make a REST call rather than using window.location.href to be redirected. That looks something like:
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
request.setAttribute(CURRENT_URI, this.calculateCurrentUri(request));
try {
chain.doFilter(servletRequest, servletResponse);
} catch (IOException var9) {
throw var9;
} catch (Exception var10) {
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10);
UserRedirectRequiredException redirect = (UserRedirectRequiredException)this.throwableAnalyzer.getFirstThrowableOfType(UserRedirectRequiredException.class, causeChain);
if (redirect == null) {
if (var10 instanceof ServletException) {
throw (ServletException)var10;
}
if (var10 instanceof RuntimeException) {
throw (RuntimeException)var10;
}
throw new NestedServletException("Unhandled exception", var10);
}
// The original code redirects the caller to the authorization page
// this.redirectUser(redirect, request, response);
// Instead we create the redirect Url from the Exception and add it to the body
String redirectUrl = createRedirectUrl(redirect);
response.setStatus(200);
response.getWriter().write(redirectUrlToJson(redirectUrl));
}
}
The createRedirectUrl contains some logic building the Url:
private String createRedirectUrl(UserRedirectRequiredException e) {
String redirectUri = e.getRedirectUri();
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(redirectUri);
Map<String, String> requestParams = e.getRequestParams();
Iterator it = requestParams.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> param = (Map.Entry)it.next();
builder.queryParam(param.getKey(), param.getValue());
}
if (e.getStateKey() != null) {
builder.queryParam("state", e.getStateKey());
}
return builder.build().encode().toUriString();
}
I hope it helps others in the future by implementing OAuth2 using Spring on web and mobile applications. Feel free to give feedback!
Regards,
Bart

Redirect after a POST vs redirect after a GET

I'm working on a Spring project. Here's my basic controller:
#Controller
public class Editor {
private static final String EDITOR_URL = "/editor";
#RequestMapping(value = EDITOR_URL, method = {POST, GET})
public ModelAndView edit(HttpServletResponse response,
HttpServletRequest request,
RedirectAttributes redirectAttributes,
#RequestParam Map<String, String> allRequestParams) {
// The code is trimmed to keep it short
// It doesn't really matter where it gets the URL, it works fine
String redirectURL = getRedirectUrl();
// redirectURL is going to be /editor/pad.html
return new ModelAndView("redirect:" + redirectUrl);
}
From web.xml:
<servlet-mapping>
<servlet-name>edm</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
I have jetty embedded and I'm trying an integration test:
#Test
public void redirectToEditPadSuccess() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(END_POINT + "/edm/editor")
.queryParam("param1", "val1")
.queryParam("param2", "val2");
HttpEntity<?> entity = new HttpEntity<>(requestHeaders);
HttpEntity<String> response = restTemplate.exchange(
builder.build().encode().toUri(),
HttpMethod.POST,
entity,
String.class);
HttpHeaders httpResponseHeaders = response.getHeaders();
List<String> httpReponseLocationHeader = httpResponseHeaders.get("Location");
assertTrue(httpReponseLocationHeader.size() == 1);
String redirectLocation = httpReponseLocationHeader.get(0);
URL redirectURL = new URL(redirectLocation);
assertEquals("/edm/editor/pad.html", redirectURL.getPath());
}
So when I execute the above it works fine and I get a green OK sign.
Now, the controller accepts both POST and GET methods. If I execute the test using GET method (replacing HttpMethod.POST with HttpMethod.GET), the result is going to be a 404.
The logs reveal:
WARN org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/edm/editor/pad.html] in DispatcherServlet with name 'edm'
I tried to debug the application up to the DispatcherServlet and weird thing is that with GET, after the 302/redirect response the Dispatcher is being called again and turns this to a 200 - no idea how and why.
I'm going to try and explain what is going on, and then provide a solution.
First let's forget that you're running a rest case, and assume that the request is coming from a browser.
Scenario 1 : Browser issues a GET request, and the server responds with a redirect.
In this case, the browser reads the response status code as 302 and makes another request using the Location response header. The user sees a quick reload but doesn't notice anything wrong.
Scenario 2 : Browser issues a POST request, and the server responds with a redirect.
In this case, the browser does follow the response code and does issue a redirect, but, the second request is a GET request, and the original request body is lost in the second request. This is because strictly by HTTP standards, the browser cannot "re-post" data to the server, without an explicit request by the user. (Some browsers will prompt the user and ask them if they want to re-post)
Now in your code, RestTemplate is using what I presume to be a default HttpClientFactory, most likely this one: https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java.
This is how RestTemplate is handling the above two scenarios:
Scenario 1 : Rest Template issues a GET request, and the server responds with a redirect.
Here the Rest Template instance will work exactly as a browser would. That's the reason why two requests are being made, and the second one is looking for /edm/editor/pad.html
Scenario 2 : Rest Template issues a POST request, and the server responds with a redirect.
In this case, Rest Template will stop after the first call, because it cannot automatically override your request method and change it to GET, and it cannot prompt you for permission, like a browser would.
Solution: When creating an instance of RestTemplate, pass it an overridden version of the client factory, something like
new RestTemplate(new SimpleClientHttpRequestFactory() {
protected void prepareConnection(HttpURLConnection conn, String httpMethod) throws IOException {
super.prepareConnection(conn, httpMethod);
conn.setInstanceFollowRedirects(false);
}
});
This will instruct rest template to stop after the first request.
Sorry for the lengthy answer, but I hope this clarifies things.

How to modify Spring controller to prevent downloading a file that hasn't changed

I have an implementation similar to this one that allows the caller to ask for a specific file from the filesystem. The file is one that doesn't change frequently and is being downloaded by a mobile application. Is there a way I can change this to use the 304 - Not Modified HTTP status code so the client wouldn't have to download the file when it hasn't changed? If so, would the interface to this method have to change in some way to accept a date? Is there some standard way to implement this?
#RequestMapping(value = "/files/datafile", method = RequestMethod.GET)
public void getDataFile(HttpServletResponse response) {
try {
// get your file as InputStream
InputStream is = ...;
// copy it to response's OutputStream
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
log.info("Error writing file to output stream. Filename was '{}'", fileName, ex);
throw new RuntimeException("IOError writing file to output stream");
}
}
Use Springs ETag Header support.
You just need to add this filter to your web.xml
<filter>
<filter-name>etagFilter</filter-name>
<filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>etagFilter</filter-name>
<servlet-name>petclinic</servlet-name>
</filter-mapping>
Or you do it more sophisticated in your controller:
Using Spring support, but then you need to rewrite it so that it return a ResponseEntity instead of using HttpResponse directly, like described in Spring Reference Chapter 21.14.3 Support for the Cache-Control, ETag and Last-Modified response headers in Controllers.
Of course you could also set the etag header directly to your HttpResponse: RFC 2616 Chapter 14.19 ETag

How to control the web context of Spring Hateoas generated links?

We are building an API and are using Spring RestControllers and Spring HATEOAS.
When the war file is deployed to a container and a GET request is made to http://localhost:8080/placesapi-packaged-war-1.0.0-SNAPSHOT/places, the HATEOAS links look like this:
{
"links" : [ {
"rel" : "self",
"href" : "http://localhost:8080/placesapi-packaged-war-1.0.0-SNAPSHOT/places",
"lastModified" : "292269055-12-02T16:47:04Z"
} ]
}
in that the web context is that of the deployed application (eg: placesapi-packaged-war-1.0.0-SNAPSHOT)
In a real runtime environment (UAT and beyond), the container is likely to be sat behind a http server such as Apache where a virtual host or similar fronts the web application. Something like this:
<VirtualHost Nathans-MacBook-Pro.local>
ServerName Nathans-MacBook-Pro.local
<Proxy *>
AddDefaultCharset Off
Order deny,allow
Allow from all
</Proxy>
ProxyPass / ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/
ProxyPassReverse / ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/
</VirtualHost>
Using the above, when we make a GET request to http://nathans-macbook-pro.local/places, the resultant response looks like this:
{
"links": [ {
"rel": "self",
"href": "http://nathans-macbook-pro.local/placesapi-packaged-war-1.0.0-SNAPSHOT/places",
"lastModified": "292269055-12-02T16:47:04Z"
} ]
}
It's wrong because the link in the response contains the web app context, and if a client were to follow that link they would get a 404
Does anyone know how to control the behaviour of Spring HATEOAS in this respect? Basically I need to be able to control the web context name that it generates within links.
I did a bit of poking around and can see that with a custom header X-Forwarded-Host you can control the host and port, but I couldn't see anything similar to be able to control the context.
Other options we've considered involve either deploying the app to the ROOT context or to a fixed named context, and then set up our virtual host accordingly. However, these feel like compromises rather than solutions because ideally we would like to host several versions of the application on the same container (eg: placesapi-packaged-war-1.0.0-RELEASE, placesapi-packaged-war-1.0.1-RELEASE, placesapi-packaged-war-2.0.0-RELEASE etc) and have the virtual host forward to the correct app based on http request header.
Any thoughts on this would be very much appreciated,
Cheers
Nathan
First, in case you weren't aware, you can control the context of the web application (under Tomcat at least) by creating webapp/META-INF/context.xml containing the line:
<Context path="/" />
... which will make set the application context to be the same as what you are using (/).
However, that wasn't your question. I posed a similar question a little while back. As a result, from what I can gather, there's no out-of-the-box mechanism for controlling the generated links manually. Instead I created my own modified version of ControllerLinkBuilder, which built up the base of the URL using properties defined in application.properties. If setting the context on your application itself is not an option (i.e. if you're running multiple versions under the same Tomcat instance) then I think that this is your only option, if ControllerLinkBuilder is not building up your URLs correctly.
Had a very similar problem. We wanted our public URL to be x.com/store and internally our context path for hosts in a cluster was host/our-api. All the URLS being generated contained x.com/our-api and not x.com/store and were unresolvable from the public dirty internet.
First just a note, the reason we got x.com was because our reverse-proxy does NOT rewrite the HOST header. If it did we'd need to add an X-Forwarded-Host header set to x.com so HATEOAS link builder would generate the correct host. This was specific to our reverse-proxy.
As far as getting the paths to work...we did NOT want to use a custom ControllerLinkBuilder. Instead we rewrite the context in a servlet filter. Before i share that code, i want to bring up the trickiest thing. We wanted our api to generate useable links when going directly to the tomcat nodes hosting the war, thus urls should be host/our-api instead of host/store. In order to do this the reverse-proxy needs to give a hint to the web app that the request came through the reverse-proxy. You can do this with headers, etc. Specifically for us, we could ONLY modify the request url, so we changed our load balancer to rewrite x.com/store to host/our-api/store this extra /store let us know that the request came through the reverse-proxy, and thus needed to be using the public context root. Again you can use another identifier (custom header, presence of X-Forwared-Host, etc) to detect the situation..or you may not care about having individual nodes give back usable URLs (but it's really nice for testing).
public class ContextRewriteFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest req, ServletResponse res, final FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
//There's no cleanup to perform so no need for try/finally
chain.doFilter(new ContextRewriterHttpServletRequestWrapper(request), res);
}
private static class ContextRewriterHttpServletRequestWrapper extends HttpServletRequestWrapper {
//I'm not totally certain storing/caching these once is ok..but i can't think of a situation
//where the data would be changed in the wrapped request
private final String context;
private final String requestURI;
private final String servletPath;
public ContextRewriterHttpServletRequestWrapper(HttpServletRequest request){
super(request);
String originalRequestURI = super.getRequestURI();
//If this came from the load balancer which we know BECAUSE of the our-api/store root, rewrite it to just be from /store which is the public facing context root
if(originalRequestURI.startsWith("/our-api/store")){
requestURI = "/store" + originalRequestURI.substring(25);
}
else {
//otherwise it's just a standard request
requestURI = originalRequestURI;
}
int endOfContext = requestURI.indexOf("/", 1);
//If there's no / after the first one..then the request didn't contain it (ie /store vs /store/)
//in such a case the context is the request is the context so just use that
context = endOfContext == -1 ? requestURI : requestURI.substring(0, endOfContext);
String sPath = super.getServletPath();
//If the servlet path starts with /store then this request came from the load balancer
//so we need to pull out the /store as that's the context root...not part of the servlet path
if(sPath.startsWith("/store")) {
sPath = sPath.substring(6);
}
//I think this complies with the spec
servletPath = StringUtils.isEmpty(sPath) ? "/" : sPath;
}
#Override
public String getContextPath(){
return context;
}
#Override
public String getRequestURI(){
return requestURI;
}
#Override
public String getServletPath(){
return servletPath;
}
}
}
It's a hack, and if anything depends on knowing the REAL context path in the request it will probably error out...but it's been working nicely for us.
ProxyPass /placesapi-packaged-war-1.0.0-SNAPSHOT
ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/
ProxyPassReverse /placesapi-packaged-war-1.0.0-SNAPSHOT
ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/

Redirection in spring MVC with loadbalacer

I have a spring MVC application which is hosted on a server S. All the calls from a client C goes to S via loadbalancer L. The url that load balancer uses internally for server S is internalurl.com and the url that client uses is xyz.com.
The issue, is when I hit a url say xyz.com/admin/ on my mozilla browser, I expect it to get redirected to xyz.com/admin/home.html. But instead, it is redirecting to internalurl.com/admin/home.html and since internalurl.com is internal to load-balancer, I get "Network Access Message: The website cannot be found" on my browser with "internalurl.com/admin/home.html" in my browser addressbar
For redirection, I am using :
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String strReferer = request.getHeader("Referer");
String strConetextPath = request.getContextPath();
if (strReferer != null && !strReferer.contains(strConetextPath)) {
response.sendRedirect("/requestError.html");
}
// redirect to home.html in case user tries to access /admin/
// in case user is not already logged-in, user will be redirected to login.html
else if (request.getRequestURI().equalsIgnoreCase("/admin/")) {
response.sendRedirect("home.html");
}else
chain.doFilter(req, res);
}
Thanks in advance.
Its not an issue with SPring MVC at all. It shall be handled at the Proxy / Load balancer.
For any redirects happening at the Internal server level will have the internal server URL, which will not be known to the Client / Browser. As Client is aware of only the Web server (as u mentioned XYZ.com).
It’s a typical issue which is handled by the proxy or the load balancer using reverse proxy concept.
For e.g.: In case of Apache HTTP, we use ProxyPassReverse configuration, which would transform the all internal server url headers to point to XYZ.com before responding to the browser. This will ensure that browser calls only xyz.com
Hope it helps.
Thanks, JP

Resources