zuul return 302 with location header - spring-boot

I have a simple use case, where I need to send 302 HTTP status with the Location header if the request comes to the gateway when the url contains /logout. This has to happen without routing to the back-end service.
Below is my zuul fillter:
public class LogoutFillter extends ZuulFilter{
#Override
public boolean shouldFilter() {
if(RequestContext.getCurrentContext().getRequest().getRequestURI().toLowerCase().contains("/logout")){
return true;
}else{
return false;
}
}
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 3;
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession excistingSession = context.getRequest().getSession(false);
if(excistingSession != null){
excistingSession.invalidate();
context.unset()
//context.addZuulResponseHeader("Location", "/abc/def/logout.do"); //notworking
context.setResponseStatusCode(302);
}
return null;
}
}
I have tried to do this like below:
HttpServletResponse response = context.getResponse();
response.setStatus(302)
response.setHeader("Location", "/abc/logout.to");
context.unset();
context.setResponse(response);
However, that didn't work either. Any suggestion would be appreciated.

Finally I managed to resolve the issue. Below is my code
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession excistingSession = context.getRequest().getSession(false);
if(excistingSession != null){
excistingSession.invalidate();
context.setSendZuulResponse(false);
context.addZuulResponseHeader("Location", "/abc/def/logout.do");
context.setResponseStatusCode(HttpServletResponse.SC_MOVED_TEMPORARILY);
}
return null;
}
In the above code setSendZuulResponse(false) will stop the routing to the back-end service, and the addZuulResponseHeader will add the response header.

Related

How to send response from zuul pre type filter?

I am using Netflix zuul as api gateway. In a pre type Authentication filter, I am validating jwt tokens. If the token is expired or invalid, the request is not forwarded to underlying services. The filter should block the request and send a response to the client. The http response status code should be 403.
But when i send a http request using an expired token, my authentication filter is not sending response to the client. The http response status code is 500. What is the issue and how to resolve it? How to set the response status code to 403 and send the response to the client? I am attaching the code snippet of my authentication filter class.
#Component
#RequiredArgsConstructor
public class AuthenticationFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
private final GatewayTokenService gatewayTokenService;
private final LogHelperService logHelperService;
private final JwtUtils jwtUtils;
private final AESUtils aesUtils;
private final AuthenticationErrorLogService authenticationErrorLogService;
private final ErrorLogService errorLogService;
private ResponseModelDTO responseModelDTO;
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 2;
}
#Override
public boolean shouldFilter() {
return true;
}
#SneakyThrows
#Override
public Object run(){
RequestContext context = RequestContext.getCurrentContext();
try{
String authHeader = logHelperService.getHeaderFromRequestContext(HeaderName.AUTHORIZATION);
if (authHeader != null) {
String encrypted_jwt = jwtUtils.parseToken(authHeader, TokenType.BEARER);
if(gatewayTokenService.isJwtTokenBlackListed(encrypted_jwt))
throw new BlacklistedTokenException("token is blacklisted!");
else{
if(jwtUtils.isJwtTokenValid(aesUtils.decrypt(encrypted_jwt))) {
logger.info("Token is ok!");
return null;
}
}
}
return null;
}
catch (TokenDecryptionException | BlacklistedTokenException | ExpiredJwtException | SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e){
authenticationErrorLogService.saveAuthenticationErrorLog(e);
context.setSendZuulResponse(false); // blocks the request
responseModelDTO = ResponseModelDTO.builder()
.status(Status.ERROR.getValue())
.message("Invalid Token")
.correlation_id(getZuulCorrelationId())
.data(null).build();
context.setResponseBody(new ObjectMapper().writeValueAsString(responseModelDTO));
context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
return null;
}
catch (Exception e){
errorLogService.saveLogs(e, ExceptionType.Handled_Exception);
context.setSendZuulResponse(false);
responseModelDTO = ResponseModelDTO.builder()
.status(Status.ERROR.getValue())
.message("Invalid Token")
.correlation_id(getZuulCorrelationId())
.data(null).build();
context.setResponseBody(new ObjectMapper().writeValueAsString(responseModelDTO));
context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
return null;
}
}
private String getZuulCorrelationId(){
RequestContext context = RequestContext.getCurrentContext();
return context.getZuulRequestHeaders().get("x-correlation-id");
}
}

Read custom header value from the response

When I send request from the Soap UI under raw response tab I see the following result(find attachment). Now in AOP controller I want to read this header value which is marked as red. How it is possible? Thanks in advance.
In my application to send soap requests I have WebServiceTemplate. I applied custom interceptor WebServiceInterceptor (which implements ClientInterceptor interface) on this web service template. In overridden afterCompletion method, which injects MessageContext, I was able to take this property from the SaajMessageHeader.
Here is what code looks like:
#Configuration
public class MyWebServiceConfig {
#Bean(name = "myWSClient")
public WebServiceTemplate myWSClient() throws Exception {
WebServiceTemplate template = new WebServiceTemplate();
...
WebServiceInterceptor[] interceptors = { new WebServiceInterceptor() };
template.setInterceptors(interceptors);
return template;
}
private static class WebServiceInterceptor implements ClientInterceptor {
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
...
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException {
try {
SaajSoapMessage message = (SaajSoapMessage) messageContext.getResponse();
String []traceId = message.getSaajMessage().getMimeHeaders().getHeader("ITRACING_TRACE_ID");
if(traceId != null && traceId.length > 0){
process.setTraceId(traceId[0]);
}
} catch (Exception e) {
}
}
}

Create HttpServletResponse object in Zuul custom filter

I have a Zuul custom filter of type PRE_TYPE.
When I receive the request I want to prevent its routing and instead send a response, in this case a SOAP message, since I am simulating a web service response.
My custom filter:
#Component
public class CustomFilter extends ZuulFilter {
private ThreadLocal<byte[]> buffers;
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = getCurrentContext();
ctx.unset();
String s= "<soap:Envelope xmlns:......</soap:Envelope>";
}
#Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
#Override
public int filterOrder() {
return 0;
}
}
I need to create a HttpServletResponse and fill it with my response and write it to the output stream, so the client receives that response.
How can I create the servletresponse object?
Try something like this:
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.setSendZuulResponse(false);
ctx.setResponseBody("<soap:Envelope xmlns:......</soap:Envelope>");
ctx.setResponseStatusCode(...);
return null;
}

How to make Zuul dynamic routing based on HTTP method and resolve target host by 'serviceId'?

How to make Zuul dynamic routing based on HTTP method (GET/POST/PUT...)?
For example, when you need to route the POST request to the different host instead of the default one described in 'zuul.routes.*'...
zuul:
routes:
first-service:
path: /first/**
serviceId: first-service
stripPrefix: false
second-service:
path: /second/**
serviceId: second-service
stripPrefix: false
I.e. when we request 'GET /first' then Zuul route the request to the 'first-service', but if we request 'POST /first' then Zuul route the request to the 'second-service'.
To implement dynamic routing based on HTTP method we can create a custom 'route' type ZuulFilter and resolve 'serviceId' through DiscoveryClient. Fore example:
#Component
public class PostFilter extends ZuulFilter {
private static final String REQUEST_PATH = "/first";
private static final String TARGET_SERVICE = "second-service";
private static final String HTTP_METHOD = "POST";
private final DiscoveryClient discoveryClient;
public PostOrdersFilter(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
#Override
public String filterType() {
return "route";
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String method = request.getMethod();
String requestURI = request.getRequestURI();
return HTTP_METHOD.equalsIgnoreCase(method) && requestURI.startsWith(REQUEST_PATH);
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
List<ServiceInstance> instances = discoveryClient.getInstances(TARGET_SERVICE);
try {
if (instances != null && instances.size() > 0) {
context.setRouteHost(instances.get(0).getUri().toURL());
} else {
throw new IllegalStateException("Target service instance not found!");
}
} catch (Exception e) {
throw new IllegalArgumentException("Couldn't get service URL!", e);
}
return null;
}
}
#Cepr0's solution is right. Here I am proposing just a simpler way (without service discovery). Assuming you have that route:
zuul:
routes:
first:
path: /first/**
# No need for service id or url
Then you can route requests for '/first' route in 'route' type filter just by setting location to request context.
#Component
public class RoutingFilter extends ZuulFilter {
#Override
public String filterType() {
return ROUTE_TYPE;
}
#Override
public int filterOrder() {
return 0;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() throws ZuulException {
/* Routing logic goes here */
URL location = getRightLocationForRequest();
ctx.setRouteHost(location);
return null;
}
}

Zuul redirect to external link

Using zuul is possible to redirect a request to an external link like http://www.google.com ?
I have this scenario.
In a webpage there are a bunch of links pointing to a several websites. When you click to one of these zuul checks if you have the permission to visit this page and redirect the browser to the external link.
I've created a route filter.
public class TestZuulFilter extends ZuulFilter {
#Override
public String filterType() {
return "route";
}
#Override
public int filterOrder() {
return 5;
}
#Override
public boolean shouldFilter() {
// ... filter logic ...
}
#Override
public Object run() {
// ... permission check ...
RequestContext ctx = RequestContext.getCurrentContext();
//todo redirect
}
}
How can i redirect the user browser to google.com ?
Thank you.
Update 20/09/2016
I've managed to solve my problem changing filter type from "pre" to "post" and adding the Location HTTP header to the response.
public class TestZuulFilter extends ZuulFilter {
#Override
public String filterType() {
return "post";
}
#Override
public int filterOrder() {
return 5;
}
#Override
public boolean shouldFilter() {
// ... filter logic ...
}
#Override
public Object run() {
// ... permission check ...
RequestContext ctx = RequestContext.getCurrentContext();
//redirect
HttpServletResponse response = ctx.getResponse();
response.setStatus(HttpServletResponse.SC_FOUND);
response.setHeader("Location", "http://www.google.com");
return null;
}
}
Now it works, but is this the right way to do it ?

Resources