Is there a way to change http request method in netflix zuul routing filter? - netflix-zuul

I'm trying to trasform http GET method call from legacy api server built with MVC1 pattern to new restful api server without any change of front-end source code using netflix zuul and eureka.
I added zuul pre filter transforming legacy url to restful convention url working after PreDecorationFilter and it works fine.
But now I'm facing problem converting the GET method to proper method like POST, PUT, DELETE by distinguising url so that the requests are properly mapped in spring controller via #GetMapping/#PostMapping/#PutMapping/#DeleteMapping.
I looked into SimpleRoutingFilter that handles HttpClient but
Because of environmental constraint, I have to use eureka service id to route to the new api server and that means I should use RibbonRoutingFilter which is quite complicated to find out a right place to this operation in.
So, is this possible to change http method or make new http request before RibbonRoutingFilter?
If possible can you please suggest where is the right place to do that or some reference?
Many thanks!
======================================================================
Milenko Jevremovic,
Would you please tell me more detail about using Feign?
I defiend #FeignClient like below
#PostMapping(value = "{url"}, consumes = "application/json")
ResponseEntity<?> postMethod(#PathVariable("url") String url);
and to get query parameters to request body for POST In zuul pre filter,
after transform logic from GET request url to POST new restful url ...
byte[] bytes = objectMapper.writeValueAsBytes(ctx.get("requestQueryParams"));
ctx.setRequests(new HttpServletRequestWrapper(request) {
#Override ..getMethod
#Override ..getContentLength
#Override ..getConentLengthLong
#Override
public ServletInputStream getInputStream() {
return new ServletInputStreamWrapper(bytes);
}
}
ResponseEntity<?> response feignClient.post(transformedNewApiUri);
and set RequestContext code that you suggested ....
and controller of new api server is like,
#PostMapping
ResponseEntity<model> post(#RequestBody req..)
It comes to controller fine but when I see the http request in post method of controller,
There is no request body for parameters.
(HttpServleterRequest getInputStream shows empty)
The request data set in zuul pre filter by HttpServletRequestWrapper is
not used in Feign maybe...?
Would you please get me more idea setting request body when changing GET query
to POST constructor for using Feign?

It is not possible to change method of HttpServletRequest, but it's possible to replace request in RequestContext. HttpServletRequestWrapper appears to be very helpful:
static class PostHttpServletRequest extends HttpServletRequestWrapper {
public PostHttpServletRequest(HttpServletRequest request) {
super(request);
}
#Override
public String getMethod() {
return "POST";
}
}
So method run can be rewritten as following:
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletRequest requestWrapper = new PostHttpServletRequest(request);
ctx.setRequest(requestWrapper);
return null;
}

After doing some research did not find any built in solution.
But what comes in my mind you can use Feign client in your Pre filter, get the response, set the response and return it immediately to client from your Pre filter.
You can set Feign client url or your service id, like it is explained in the docs, it uses ribbon as well .
Change response in your run method like:
...
RequestContext ctx = RequestContext.getCurrentContext();
ctx.setResponseStatusCode(your_code);
ctx.setResponseBody(new_body);
ctx.setSendZuulResponse(false);
return null

Related

ASP.Net Web API: Regarding web api action calling url or end point url

i am new in asp.net web api. just reading a article on web api from this url http://www.c-sharpcorner.com/article/remote-bind-kendo-grid-using-angular-js-and-Asp-Net-web-api/
now see this code
[RoutePrefix("api/EmployeeList")]
public class EmployeeDetailsController : ApiController
{
[HttpGet]
[Route("List")]
public HttpResponseMessage EmployeeList()
{
try
{
List<Employee> _emp = new List<Employee>();
_emp.Add(new Employee(1, "Bobb", "Ross"));
_emp.Add(new Employee(2, "Pradeep", "Raj"));
_emp.Add(new Employee(3, "Arun", "Kumar"));
return Request.CreateResponse(HttpStatusCode.OK, _emp, Configuration.Formatters.JsonFormatter);
}
catch (Exception ex)
{
return Request.CreateResponse(HttpStatusCode.OK, ex.Message, Configuration.Formatters.JsonFormatter);
}
}
}
as per my understanding the requesting url should be /api/EmployeeList/List but if anyone look the above image then must notice different url api/Employee/GetEmployeeList is being used to call list action method. so i just like to know the reason why different url is getting issued to call List action function ?
also do not understand how this url api/Employee/GetEmployeeList can work in this situation because controller name is EmployeeDetailsController but RoutePrefix has been used to address it api/EmployeeList and action method name is EmployeeList() which has been change to List..........so some one tell me how this url api/Employee/GetEmployeeList can invoke list action of web api ?
please discuss in detail. thanks
Did you activate AttributeRouting? If not, standard routing is in place and your current attributes will be ignored.
You need to do this in the WebApi registration process, like this:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
Then, you can remove any method call like this:
config.Routes.MapHttpRoute
to disable conventional routing.
BTW, the call api/Employee/GetEmployeeList is valid because Employee is the name of your controller, Get is the verb and EmployeeList is the name of the method.
Did you enable attribute routing? You do this by default in webapiconfig.cs by adding this line of code:
config.MapHttpAttributeRoutes();
#Monojit-Sarkar. The URL the user showed in postman did not also match the code. In the code, these are the users we expect to see:
_emp.Add(new Employee(1, "Bobb", "Ross"));
_emp.Add(new Employee(2, "Pradeep", "Raj"));
_emp.Add(new Employee(3, "Arun", "Kumar"));
But the results in postman are different as shown in the image the user posted. So something is disconnected from the article/image and the sample code.

Spring Zuul API Gateway with Spring Session / Redis Authenticate and Route in same Request

I have been really been searching high and low for the last few days on how to do this and have finally decided to admit defeat and ask for help, please!!!
I have followed Dr Dave Syer's tutorial on Angular and Spring Security specifically the Zuul Proxy as an api gateway and using Spring Session with Redis (https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/double#_sso_with_oauth2_angular_js_and_spring_security_part_v)
The issue I am having is that I am calling resource rest services via the gateway from an external application with the following header:
String plainCreds = "user:password";
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = Base64.getEncoder().encode(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);
to be authenticated and then routed by zuul and then the resource to have access to the authenticated session via redis.
The issue is that the session seems to only commit to redis in the gateway after the request has responded. So what is happening is that when I call a resource service with the header, I can see the successful authentication occurring in the gateway and session being created, however I am getting a 403 in the resource due to the session not being in redis after its been routed via zuul.
However if I get the error, grab the session id and add it to the header and try again it works because now my authenticated session is available for the resource project after its been routed.
Please could someone point me in the direction of how I go about getting my calls via the gateway to authenticate and route in the same request please?
Thanks
Justin
I followed Justin Taylor's posts on different pages so this is his solution. It makes me sense to have solution with source code here:
Make Spring Session commit eagerly - since spring-session v1.0 there is annotation property #EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) which saves session data into Redis immediately. Documentation here.
Simple Zuul filter for adding session into current request's header:
#Component
public class SessionSavingZuulPreFilter extends ZuulFilter {
#Autowired
private SessionRepository repository;
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
Session session = repository.getSession(httpSession.getId());
context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());
log.info("ZuulPreFilter session proxy: {}", session.getId());
return null;
}
}
Once more - this is not my solution - credentials go to Justin Taylor.
I am so sorry about the delayed response here, one of the great things about South Africa is our great telecoms hehe, I have had no internet at home for a while and my source code for this is on my home pc.
Yes Steve is on the right track. There are two issues that you need to be resolve here:
Spring session only commits the authenticated session to redis on response to the initial incoming request. So the first step is to follow that link steve provided to ensure spring session commits to redis whenever the session changes.
Zuul doesn't propagate this newly authenticated session on the initial routing. So what you need to do is to use a zuul pre filter (lots of examples around) that gets the authenticated session id and then adds it to the zuul request to the resource behind the gateway. You will see a setter method on the zuul request to set the session id.
If you don't do this, you will need to do two calls, one to authenticate and get a valid session id which would be in redis from spring session, and then the subsequent call with your authenticated session id.
I did battle with this for a while, but when I got it working it was spot on. I extended this solution to not only work for http basic, but added in a jwt token implementation.
Hopefully this helps, as soon as I am connected at home I can post the source.
Good Luck!
Justin
My APIGateway (Zuul) is proxied by Apache Httpd and protected by Mellon module (SAML 2.0). After a successfully authentication on the identity provider, mellon module inject correctly some headers read into the SAML response, but the first request fails with a 403 status code.
I'm also using SpringSecurity, to solve the problem I'm using a simple filter added on the security filter chain that ensure the correct creation of SecurityContext:
#Component
public class MellonFilter extends OncePerRequestFilter {
private final Logger log = LoggerFactory.getLogger(MellonFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String mellonId=req.getHeader("mellon-nameid");
if(mellonId==null||mellonId.isEmpty())
;//do filterchain
else {
UserWithRoles userWithRoles = new UserWithRoles();
userWithRoles.setUsername(mellonId);
SilUserDetails details = new SilUserDetails(userWithRoles);
SilAuthenticationPrincipal silPrincipal = null;
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("Some roles");
silPrincipal = new SilAuthenticationPrincipal(details, true, authorities);
SecurityContextHolder.clearContext();
SecurityContextHolder.getContext().setAuthentication(silPrincipal);
}
filterChain.doFilter(req,httpServletResponse);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
if(SecurityContextHolder.getContext().getAuthentication()!=null&&SecurityContextHolder.getContext().getAuthentication() instanceof SilAuthenticationPrincipal)
return true;
return false;
}
}
Then I need a ZuulFilter to save the session (on Redis) and to propagate the actual session id:
public class ZuulSessionCookieFilter extends ZuulFilter {
private final Logger log = LoggerFactory.getLogger(ZuulSessionCookieFilter.class);
#Autowired
private SessionRepository repository;
#Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
httpSession.setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext()
);
Session session = repository.findById(httpSession.getId());
context.addZuulRequestHeader("cookie", "SESSION=" + base64Encode(httpSession.getId()));
log.debug("ZuulPreFilter session proxy: {} and {}", session.getId(),httpSession.getId());
return null;
}
private static String base64Encode(String value) {
byte[] encodedCookieBytes = Base64.getEncoder().encode(value.getBytes());
return new String(encodedCookieBytes);
}
}
I hope this solution will be helpful to everyone.

ASPNET 5: How to receive request parameter as raw content no matter what the Content-Type header is set to?

I'm building an API where the client can send me a blob of data and I save it somewhere for retrieval later. I don't care what the content-type is. I just want to get the raw data every time, no matter what they set Content-Type to. I know this can be achieved in older versions of ASPNET by accessing the Request object, but this does not seem possible with ASPNET 5, nor would I want to do this because it would make unit testing impossible (Request is not dependency injected).
I've seen some mentions of the IInputFormatter and IOutputFormatter interfaces, but those seem to attach to a specific Content-Type. I think I'm looking for something that's more like the [FromBody] attribute, but that gives me raw output. Something like [RawBody]. I've done quite a bit of searching and can't seem to find any simple solutions for this.
you can create CustomAttribute and read Request.InputStream with StreamReader Save stream data where ever you want then reset InputStream position to 0
httpContext.Request.InputStream.Position = 0;
I used it in Custom AuthorizeAttribute but you can use use Custom ActionFilter and use Request from filterContext you can google for "Custom Attribute in MVC c#" you can also do that in ActionResult.
If you are still looking for solution to get Request Object here is the solution
Create MessageHandler
public class MessageHandler1 : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpContextWrapper context = (HttpContextWrapper)request.Properties.ElementAt(0).Value;
// Call the inner handler.
var response = await base.SendAsync(request, cancellationToken);
return response;
}
}
add Entry to WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.MessageHandlers.Add(new MessageHandler1());
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Now you can access row Request in MessageHandler1

Passing Authentication Token with Breeze query

My server side breeze api calls require me to validate the token value provided with each call before returning any data. To achieve this, i am passing TokenId with each Entity Query using withParameters function of breeze Entity Query and specify the parameter on my server side controller action as illustrated below.
Following is how i am doing it right now:
Client Side
function GetCustomers(){
return breeze.EntityQuery.from('Customers')
.withParameters({ TokenId: 'token value' })
.using(entityManager).execute()
}
Server Side
[HttpGet]
public IQueryable<Customer> Customers(string TokenId)
{
//server side logic
}
This for some reason looks to me can be simplified using some configuration on Breeze Entity Manager which automatically adds the parameter value to every query sent from client. And also, on server side, i don't think its necessary to have tokenId parameter defined on each controller method. This should be easy to solve with Action Filters on the server side.
Can someone point me in right direction if it is possible and how to do this?
Thanks
You should always pass custom authentication information, such as tokens, in the headers.
If you can pass it as a custom header you can search for 'define custom headers breeze.js' to see how that can be done.
A good example -
http://breeze.github.io/doc-js/server-ajaxadapter.html
Also keep in mind that if you put the token in the query string that is persisted by most of the servers that your call is crossing over, making it much easier to find tokens for your application if an evil-doer were to download the logs from that server.
Also keep in mind that if you are using cross-domain requests (CORS) you will need to enable the custom header on the server side that is receiving the call.
To give an example of the answer above:
// get the current default Breeze AJAX adapter
var ajaxAdapter: any = breeze.config.getAdapterInstance('ajax');
// set fixed headers
ajaxAdapter.defaultSettings = {
headers: {
"Bearer": this.bearerToken
}
};
Note also that if you are using ASP.net Identity (which includes a cookie based authentication), you must include this code so that the asp.net pipeline does NOT use that cookie:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new Ask.Filters.RequireHttpsAttribute());
//This forces http header authentication which is required for web api calls.
config.SuppressDefaultHostAuthentication();
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Note also that you should use only https, with this filter:
public class RequireHttpsAttribute : AuthorizationFilterAttribute
{
public int Port { get; set; }
public RequireHttpsAttribute()
{
Port = 443;
}
public override void OnAuthorization(HttpActionContext actionContext)
{
var request = actionContext.Request;
if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
var response = new HttpResponseMessage();
if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Head)
{
var uri = new UriBuilder(request.RequestUri);
uri.Scheme = Uri.UriSchemeHttps;
uri.Port = this.Port;
response.StatusCode = HttpStatusCode.Found;
response.Headers.Location = uri.Uri;
}
else
{
response.StatusCode = HttpStatusCode.Forbidden;
}
actionContext.Response = response;
}
else
{
base.OnAuthorization(actionContext);
}
}
}
And finally, make sure you use the [Authorize] and [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)] attribute on your controllers.
See the Full Sample and article on asp.net
I suggest using oAuth tokens in the header. I am currently using Auth0, but there are many others including Google Firebase. BreezeJS has been expanded such that it will accept AuthHttp as it's HTTP client for all calls. AuthHttp will automatically add a bearer token to the header of every call to the server.
One the server side it's a matter of adding the proper oAuth client lib (via Nuget) which automatically parses out the bearer token, validates it against the oAuth server, and makes claims available for use within your API call.
For example, in my implementation my api code will receive a claim that contains the users id, which I can then validate against my db and use to filter all actions. This keeps user B from loading user A's data by directly calling the api in a browser.
For example, all API calls are first validated by the oAuth subsystem. Once my api code is executed I know the caller has been validated and I use the passed claims (user id) to access only the calling users data - REGARDLESS what was actually requested by the API call parameters (which can be forged as easily as editing a URL string).
A simple example of this would be exposing your UserSet in the API. If you don't filter by the user claim id you must realize that every user in your system will be accessible by constructing a simple URL in a browser.
Here I filter out and return only the user record identified by the user id in the auth claim. Note I use #if AUTH simply for testing as PROD will always have AUTH turned on.
#if AUTH
[Authorize]
#endif
[HttpGet]
[EnableBreezeQuery(MaxExpansionDepth = 5)]
public IQueryable<User> UserSet()
{
#if AUTH
Guid guid = userGuid();
return _efContext.Context.UserSet.Where(x => x.active && x.guid == guid);
#else
return _efContext.Context.UserSet;
#endif
}
All of this sounds complicated, but it really is quite easy to implement.
Thanks,
Mike

Redirect after EndInvoke in ASP.NET MVC

Hi have the following code on my view (JQuery):
$.post('<%=Url.Content("~/Customer/StartLongRunningProcess")%>');
Wich invokes an asynchronous call (C#):
public void StartLongRunningProcess()
{
ProcessTask processTask = new ProcessTask();
processTask.BeginInvoke(new AsyncCallback(EndLongRunningProcess), processTask);
}
Finally, the result of the call:
public void EndLongRunningProcess(IAsyncResult result)
{
ProcessTask processTask = (ProcessTask)result.AsyncState;
string id = processTask.EndInvoke(result);
RedirectToAction("~/SubscriptionList/SubscribedIndex/" + id);
}
The redirect is ignored. Response.Redirect also fails, since the HTTP headers has been already sent. I've tried change the window.location from javascript, this works, but I'm unable to pass the parameter id by ViewData. Any idea to resolve this?
Are you sure the headers have already been sent? I'm not really up on asynchronous controllers, but I would doubt that it would start sending any headers right away. My first thought would be that a redirect response to an ajax call isn't handled by the browser. You will probably need to implement some logic that sends back a result with the URL and have your success delegate in jQuery look for that piece of data and then do the redirect through javascript (i.e. window.location).
HTH

Resources