Strip prefix to remove the environment passed in URL - netflix-zuul

My frontend application attaches an environment attribute as a prefix in the URI -- I am having difficulty getting Zuul to strip this prefix.
For example, the frontend application is making a request to Zuul proxy locally http://localhost:9080/local/domains/metrs/subdomains/medicare/base-templates/. Zuul needs to strip out the environment "local" as seen in the URL and then forward to the web service located (with the environment "local" stripped) at http://localhost:9090/icews/admin/domains/metrs/subdomains/medicare/base-templates/
Unfortunately, I get a 404 error because the prefix "local" is not being stripped -- when reading the logs on the server hosting the web service, I still see the environment "local" forwarded in the call.
Here is my Zuul configuration:
ice.ws.local.url=http://localhost:9090/icews/admin
ice.ws.dev.url=http://developmentserver/icews/admin
zuul.routes.base-templates.path=/local/**/base-templates/**
zuul.routes.base-templates.url=${ice.ws.local.url}
zuul.routes.base-templates.strip-prefix=true
zuul.routes.base-templates.path=/dev/**/base-templates/**
zuul.routes.base-templates.url=${ice.ws.dev.url}
zuul.routes.base-templates.strip-prefix=true
UPDATE
I went ahead and used the approach as outlined here: https://github.com/spring-cloud/spring-cloud-netflix/issues/1893 and https://github.com/spring-cloud/spring-cloud-netflix/issues/2408

I did not use the strip-prefix in the application.properties but instead followed the approach outlined here https://github.com/spring-cloud/spring-cloud-netflix/issues/1893
Specifically for my case where I have a frontend that needs to route on different environments (each being a different microservice), I have another Zuul filter that runs in order 6 (PRE_DECORATION_FILTER_ORDER + 1):
#Component
public class PostDecorFilter extends ZuulFilter {
#Value("${zuul.PostSendResponseCustomFilter.post.disable:false}")
private boolean isDisableFilter;
#Override
public int filterOrder() {
// run this filter after zuul..PreDecorationFilter
return PRE_DECORATION_FILTER_ORDER + 1;
}
#Override
public String filterType() {
return PRE_TYPE;
}
#Override
public boolean shouldFilter() {
return !isDisableFilter;
}
/**
* Add custom response processing here
*
* #return
*/
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
// environment is added by frontend Angular app, e.g. dev, dev-t, qa, qa-t
String environment = RequestContext.getCurrentContext().getRequest().getRequestURI().split("/")[1];
if ( "dev".equals(environment) || "dev-t".equals(environment) || "qa".equals(environment) || "qa-t".equals(environment) ) {
// e.g. /dev/some/resource/
String requestUriWithEnvironment = RequestContext.getCurrentContext().getRequest().getRequestURI();
// e.g. /some/resource/
String requestUriWithoutEnvironment =
requestUriWithEnvironment.substring(environment.length() + 1);
context.put(REQUEST_URI_KEY, requestUriWithoutEnvironment);
}
return null;
}
I also had to use a CustomRouteLocator as outlined here: https://github.com/spring-cloud/spring-cloud-netflix/issues/2408
The reason is that the paths in application.properties are
ice.ws.local.url=http://localhost:9090/icews/admin
ice.ws.dev.url=http://developmentserver/icews/admin
zuul.routes.base-templates.path=/local/**/base-templates/**
zuul.routes.base-templates.url=${ice.ws.local.url}
zuul.routes.base-templates.path=/dev/**/base-templates/**
zuul.routes.base-templates.url=${ice.ws.dev.url}
Essentially, I was experiencing the same behavior as outlined here: "it seems that SimpleRouteLocator iterates over all routes, not in the order they're in config file and just pick the first match" https://github.com/spring-cloud/spring-cloud-netflix/issues/2408

Related

Using dynamic URL for Spring ReactiveFeignClient

I'm using ReactiveFeignClient from Playtika
I need to use dynamic URL especially for the host part because I want to use the same interface for several services that have the same request and response formats, but different host. The URLs on each service can have different host name and prefix, but all have the same suffix.
For example:
http://localhost:3001/games/purchase
http://localhost:3002/gadgets/phone/purchase
Actually I don't know whether it has the same behavior as non-reactive feign client. I follow the suggestion on How can I change the feign URL during the runtime?.
Here's the client interface.
#ReactiveFeignClient(
name = "dummy",
configuration = TransactionClient.Configuration.class
)
public interface TransactionClient {
// #PostMapping("/purchase") // Using #PostMapping and #RequestLine both don't work
#RequestLine("POST /purchase")
Mono<PurchaseResponseDto> doPurchase(
URI baseUrl,
#Valid #RequestBody PurchaseRequestDto requestDTO
);
#RequiredArgsConstructor
class Configuration {
#Bean
public ReactiveStatusHandler reactiveStatusHandler() {
return new CustomStatusHandler();
}
}
}
And here's the auto configuration
#Configuration
public class TransactionClientServiceAutoConfiguration {
#Bean
public Contract useFeignAnnotations() {
return new Contract.Default();
}
#Bean
#LoadBalanced
public TransactionClient botRemoteClient() {
return Feign.builder().target(Target.EmptyTarget.create(TransactionClient.class));
}
}
However, I got error indicating that no service with name dummy. It's just a dummy name indeed, because the name parameter is required for #ReactiveFeignClient annotation and I want to use the interface for multiple services.
How to make dynamic url possible for #ReactiveFeignClient
From reactive feign github I found this snippet:
IcecreamServiceApi client =
WebReactiveFeign //WebClient based reactive feign
.<IcecreamServiceApi>builder()
.target(IcecreamServiceApi.class, "http://www.myUrl.com")
You can change the url by creating a new instance of the client. Found no other way. Also, I added both #PostMapping and #RequestLine("POST") to the feign interface since I couldn't get the contracts option to work.
Sharing this for posterity or until a better version comes along.

Where to set API host schema in Swashbuckle?

I'm trying to figure out how to fix an issue I have going on with Swagger displaying on our STAGE and PROD servers. I believe my issue is the fact that we have Load Balancer that uses HTTPS to get to but once you get there all the sites are served over HTTP. I believe that is the reason I'm getting
Can't read from server. It may not have the appropriate access-control-origin settings.
So I found the following on Swagger and I'm thinking I just need to set my Schema to HTTP but I'm not 100% sure where to make this change? Do I add it to the SwaggerConfig.cs file?
https://swagger.io/docs/specification/2-0/api-host-and-base-path/
This is what I have in my SwaggerConfig.cs file:
public class SwaggerConfig
{
//public static void Register(HttpConfiguration config)
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.ApiKey("Api-Token")
.Description("API Key for accessing secure APIs")
.Name("Api-Token")
.In("header");
c.SingleApiVersion("v1", "WorkdayAPI");
c.IncludeXmlComments(string.Format(#"{0}\bin\WorkdayApi.XML",
System.AppDomain.CurrentDomain.BaseDirectory));
// If you want the output Swagger docs to be indented properly, enable the "PrettyPrint" option.
c.PrettyPrint();
})
.EnableSwaggerUi(c =>
{
// If your API supports ApiKey, you can override the default values.
// "apiKeyIn" can either be "query" or "header
c.EnableApiKeySupport("Api-Token", "header");
});
}
}

Zuul reverse proxy with Keycloak server

I'm configuring a Spring Cloud (Angel.SR6) application using the Zuul reverse proxy utility, in order to hide the internal service ports. My zuul (edge) service is published in the 8765 port and my organizations service is in the 8083 one. Everything goes smoothly when I access the application with no security, http://localhost:8765/organization/organizations returns the JSON with all the organizations.
However, now I want to integrate a Keycloak SSO (OAuth2) server for authorization purposes. I have added the Spring Security adapter in my organization service and configured it to authenticate in http://localhost:8080/auth. Everything goes well, except that zuul performs a redirection instead of proxying. So when authentication is successful, I get redirected to http://localhost:8083/organizations instead of http://localhost:8765/organization/organizations. Here there are my browser requests:
That's because the keycloak adapter creates a token verification endpoint in the http://localhost:8083/sso/login, from which it performs a redirection to the authorization server in order to validate the token. When authorization server acknowledges it, a redirection is sent to the organization service, with the /organization path, so the end url being loaded is http://localhost:8083/organizations. But I would like the first requested url to be loaded instead.
Which choice do I have?
Recently I've had the same problem. I've solved it by:
Add to application.properties in Zuul
zuul.sensitive-headers=Cookie,Set-Cookie
Introduce KeycloakFilterRoute in Zuul
class KeycloakFilterRoute extends ZuulFilter {
private static final String AUTHORIZATION_HEADER = "authorization";
#Override
public String filterType() {
return "route";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.getRequest().getHeader(AUTHORIZATION_HEADER) == null) {
addKeycloakTokenToHeader(ctx);
}
return null;
}
private void addKeycloakTokenToHeader(RequestContext ctx) {
RefreshableKeycloakSecurityContext securityContext = getRefreshableKeycloakSecurityContext(ctx);
if (securityContext != null) {
ctx.addZuulRequestHeader(AUTHORIZATION_HEADER, buildBearerToken(securityContext));
}
}
private RefreshableKeycloakSecurityContext getRefreshableKeycloakSecurityContext(RequestContext ctx) {
if (ctx.getRequest().getUserPrincipal() instanceof KeycloakAuthenticationToken) {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) ctx.getRequest().getUserPrincipal();
return (RefreshableKeycloakSecurityContext) token.getCredentials();
}
return null;
}
private String buildBearerToken(RefreshableKeycloakSecurityContext securityContext) {
return "Bearer " + securityContext.getTokenString();
}
}
(Migrated from comment to answer)
I ended up making a Github project in order to explain my problem to the keycloak team, and got a pull request from one of the development team members trying to help me out. Following their recommendations, I came into the conclusion that zuul is good to hide stateless services (bearer only ones), but not the ones that user directly interacts with. Here it is the whole thread in the mailing list.

Multi-tenant ServiceStack API, same deployment to respond to requests on different hostnames?

We're creating APIs using ServiceStack that are multi-tenant. We want to do DNS-based load-balancing and routing, rather than stitch things up via a reverse proxy (like nginx or haproxy).
We have Request DTOs that have a Tenant parameter. ServiceStack (and its SwaggerFeature) allow us to define custom routes, and document the DTOs such that we can read values from path, query, headers, or body.
How do we (best) wire things so that DTO properties can read values from a hostname pattern as well? So, make the Route take values from matching out of the hostname as well as the path?
We'd like to have URLs like
https://{tenant}.{DNS zone for environment}/{rest of path with tokens}
Also - out DNS zone will vary depending which environment we're in - for non-production we use (say) testing-foobar.com, and production we use real-live.com. Ideally we'd be able to support both with a single route declaration (and we prefer decorating the Request DTO instead of imperative declaration at run-time AppHost.Init).
I solved this just this week, on a existing multi-tenant system which uses .NET security principals to deal with the user permissions and tenants. I used a custom ServiceRunner to select the tenant and set up the security. Your approach to multi-tenant is different, but using a ServiceRunner still seems a valid approach.
You'd end up with something like this:
public class MyServiceRunner<T> : ServiceRunner<T>
{
public MyServiceRunner(IAppHost appHost, ActionContext actionContext)
: base(appHost, actionContext)
{}
public override void BeforeEachRequest(IRequestContext requestContext, T request)
{
// Set backend authentication before the requests are processed.
if(request instanceof ITenantRequest)
{
Uri uri = new Uri(requestContext.AbsoluteUri);
string tenant = uri.Host; // Or whatever logic you need...
((ITenantRequest).Tenant = tenant;
}
}
}
public class MyAppHost : AppHostBase
{
public MyAppHost() : base("My Web Services", typeof(MyService).Assembly) { }
public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
return new MyServiceRunner<TRequest>(this, actionContext);
}
public override void Configure(Container container)
{
...
}
}
Perhaps the Requests filtering approach is somehow better, but this does the job for us.

Alfresco SDK code hanging at AuthenticationUtils.startSession

i am testing the code from SDK to call Alfresco on bitNami Alresco 4.0.e-0 server with a webapp that is located on same tomcat server as Alfresco. The code hangs at the very first call to AuthenticationUtils to get session. I pretty am sure i supplied the standard bitNami Alfresco user and password for this. Did i miss any libraries? I put most available dependencies as my local maven repositories and code compiles well.
the following is code from SDK without Alfresco license as i could not format the code with it:
package org.alfresco.sample.webservice;
import org.alfresco.webservice.repository.RepositoryServiceSoapBindingStub;
import org.alfresco.webservice.types.Store;
import org.alfresco.webservice.util.AuthenticationUtils;
import org.alfresco.webservice.util.WebServiceFactory;
public class GetStores extends SamplesBase
{
/**
* Connect to the respository and print out the names of the available
*
* #param args
*/
public static void main(String[] args)
throws Exception
{
// Start the session
AuthenticationUtils.startSession(USERNAME, PASSWORD);
try
{
// Get the respoitory service
RepositoryServiceSoapBindingStub repositoryService = WebServiceFactory.getRepositoryService();
// Get array of stores available in the repository
Store[] stores = repositoryService.getStores();
if (stores == null)
{
// NOTE: empty array are returned as a null object, this is a issue with the generated web service code.
System.out.println("There are no stores avilable in the repository.");
}
else
{
// Output the names of all the stores available in the repository
System.out.println("The following stores are available in the repository:");
for (Store store : stores)
{
System.out.println(store.getScheme() + "://" + store.getAddress());
}
}
}
finally
{
// End the session
AuthenticationUtils.endSession();
}
}
}
The WebServiceFactory uses
http://localhost:8080/alfresco/api
as default endpoint.You can change the endpoint by providing a file called webserviceclient.properties on the classpath under alfresco (the resource path: alfresco/webserviceclient.properties)
The properties file must offer a property called repository.location, which specifies the endpoint URL. Since you are using a bitnami Alfresco instance, it is probably running on port 80. The file should contain the following property entry:
repository.location=http://localhost:80/alfresco/api

Resources