28

Background

I want to implement the design presented in this article.

It can be summarised by the diagram below: Security Architecture

  1. The client first authenticate with the IDP (OpenID Connect/OAuth2)
  2. The IDP returns an access token (opaque token with no user info)
  3. The client makes a call through the API gateway use the access token in the Authorization header
  4. The API gateway makes a request to the IDP with the Access Token
  5. The IDP verifies that the Access Token is valid and returns user information in JSON format
  6. The API Gateway store the user information in a JWT and sign it with a private key. The JWT is then passed to the downstream service which verifies the JWT using the public key
  7. If a service must call another service to fulfil the request it passes the JWT along which serves as authentication and authorisation for the request

What I have so far

I have most of that done using:

  • Spring cloud as a global framework
  • Spring boot to launch individual services
  • Netflix Zuul as the API gateway

I have also written a Zuul PRE filter that checks for an Access Token, contacts the IDP and create a JWT. The JWT is then added to the header for the request forwarded to the downstream service.

Problem

Now my question is quite specific to Zuul and its filters. If authentication fails in the API gateway for any reason, how can I can stop the routing and respond directly with a 401 without continuing the filter chain and forwarding the call?

At the moment if authentication fails the filter won't add the JWT to the header and the 401 will come from the downstream service. I was hoping my gateway could prevent this unnecessary call.

I tried to see how I could use com.netflix.zuul.context.RequestContextto do this but the documentation is quite poor and I couldn't find a way.

phoenix7360
  • 2,807
  • 6
  • 30
  • 41
  • 1
    Why aren't you using Spring Cloud Security for this? that provides this out-of-the-box for afaik. – M. Deinum May 12 '16 at 08:09
  • @M.Deinum I didn't think I could have enough control to implement this specific design. I need to have access token outside my secured network and JWT inside. I don't have much experience with Spring Cloud security. Do you think I could use it to achieve my design? – phoenix7360 May 12 '16 at 08:24
  • Spring Cloud Security only relays the same token to the down stream services. It does not have the ability to exchange or enhance tokens like @phoenix7360 is looking to do. However, it is a reasonable building block to work from. – Ryan J. McDonough Jan 07 '17 at 22:37

3 Answers3

13

You could try setting setSendZuulResponse(false) in the current context. This should not route the request. You could also call removeRouteHost() from the context, which would achieve the same. You could usesetResponseStatusCode to set the 401 status code.

mbragg02
  • 248
  • 5
  • 9
5

Add the following within your run method, it will solve this problem

ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
Michał Turczyn
  • 32,028
  • 14
  • 47
  • 69
Yongxin Zhang
  • 87
  • 1
  • 3
4

I know I am very late to answer. You can approach with prefilter of zuul. The steps you have to follow is given below.

 //1. create filter with type pre
 //2. Set the order of filter to greater than 5 because we need to run our filter after preDecoration filter of zuul.
 @Component
 public class CustomPreZuulFilter extends ZuulFilter {

  private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public Object run() {
    final RequestContext requestContext = RequestContext.getCurrentContext();
    logger.info("in zuul filter " + requestContext.getRequest().getRequestURI());
    byte[] encoded;
    try {
        encoded = Base64.encode("fooClientIdPassword:secret".getBytes("UTF-8"));
        requestContext.addZuulRequestHeader("Authorization", "Basic " + new String(encoded));

        final HttpServletRequest req = requestContext.getRequest();
        if (requestContext.getRequest().getHeader("Authorization") == null
                && !req.getContextPath().contains("login")) {
            requestContext.unset();
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());

        } else {
              //next logic
            }
        }

    } catch (final UnsupportedEncodingException e) {
        logger.error("Error occured in pre filter", e);
    }

    return null;
}



@Override
public boolean shouldFilter() {
    return true;
}

@Override
public int filterOrder() {
    return 6;
}

@Override
public String filterType() {
    return "pre";
}

}

requestContext.unset() will reset the RequestContext for the current threads active request, and you can provide a response status code.

noob_nerd
  • 531
  • 1
  • 6
  • 21
Vishnu KR
  • 718
  • 1
  • 8
  • 22