Create HttpServletResponse object in Zuul custom filter - spring-boot

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;
}

Related

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) {
}
}
}

How to send message using websocket whenever an API is called using springboot?

I have a simple controller which return name . I have websocket handler which return message to client as: Hey there, presentation recieved from user. whenever http://localhost:8080/sample is called, i need to display the above message to <ws://localhost:8080/presentation>, using https://websocketking.com/ to connect to websocket.
#RestController
public class WebController {
#RequestMapping("/sample")
public SampleResponse Sample(#RequestParam(value = "name",
defaultValue = "Robot") String name) {
SampleResponse response = new SampleResponse();
response.setId(1);
response.setMessage("Your name is "+name);
return response;
}
}
#Component
public class WebSocketHandler extends AbstractWebSocketHandler {
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
System.out.println("New Text Message Received from presetation");
String payload = message.getPayload();
System.out.println(payload);
session.sendMessage(new TextMessage("Hey there, presentation recieved from user"));
}
}
public class WebSocketConfiguration implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketHandler(), "/presentation").setAllowedOrigins("*");
}
}

Spring Boot escape characters at Request Body for XSS protection

I'm trying to secure my spring boot application using a XSSFilter like this:
public class XSSFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException { }
#Override
public void destroy() { }
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new XSSRequestWrapper((HttpServletRequest) request), response);
}
}
And the wrapper:
public class XSSRequestWrapper extends HttpServletRequestWrapper {
public XSSRequestWrapper(HttpServletRequest servletRequest) {
super(servletRequest);
}
#Override
public String[] getParameterValues(String parameter) {
String[] values = super.getParameterValues(parameter);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = replaceXSSCharacters((values[i]));
}
return encodedValues;
}
private String replaceXSSCharacters(String value) {
if (value == null) {
return null;
}
return value
.replace("&","&")
.replace("<", "<")
.replace(">",">")
.replace("\"",""")
.replace("'","'");
}
#Override
public String getParameter(String parameter) {
return replaceXSSCharacters(super.getParameter(parameter));
}
#Override
public String getHeader(String name) {
return replaceXSSCharacters(super.getHeader(name));
}
}
The problem is, that only secures the Request parameters and Headers, not the Request body, and sometimes my Controller receive data using #RequestBody.
So, if i submit to my controller a json like this:
{"name":"<script>alert('hello!')</script>"}
The html chars at the name property doesn't get escaped like i need. How can i escape the RequestBody?
EDIT:
This is different from the "duplicated" question. My question is very Specific. How to escape characters on Request Body.
To remove XSS characters you just override AbstractJackson2HttpMessageConverter - this converter has responsibility to read request.inputStream to RequestBody object
#Component
public class XSSRequestBodyConverter extends AbstractJackson2HttpMessageConverter {
public XSSRequestBodyConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
#Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
Object requestBody = super.read(type, contextClass, inputMessage);
//Remove xss from requestBody here
String requestInStr = objectMapper.writeValueAsString(requestBody);
return objectMapper.readValue(replaceXSSCharacters(requestInStr), Object.class);
}
}
I resolved with a custom class:
#Configuration
public class AntiXSSConfig {
#Autowired()
public void configeJackson(ObjectMapper mapper) {
mapper.getFactory().setCharacterEscapes(new HTMLCharacterEscapes());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public static class HTMLCharacterEscapes extends JsonpCharacterEscapes {
#Override
public int[] getEscapeCodesForAscii() {
int[] asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();
// and force escaping of a few others:
asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['"'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM;
return asciiEscapes;
}
#Override
public SerializableString getEscapeSequence(int ch) {
switch (ch) {
case '&' : return new SerializedString("&");
case '<' : return new SerializedString("<");
case '>' : return new SerializedString(">");
case '\"' : return new SerializedString(""");
case '\'' : return new SerializedString("'");
default : return super.getEscapeSequence(ch);
}
}
}
}
It covers all the cases.
Have a local String field in XSSRequestWrapper which holds the cleaned-up body (probably not suitable for large bodies).
Populate this field in the constructor by reading request.getInputStream() and cleaning up the body the same way as parameters.
Override getInputStream and getReader methods of HttpServletRequestWrapper, and construct an InputStream (string -> byte array -> ByteArrayInputStream) and Reader (StringReader) from the String field and return them respectively. Maybe cache the constructed InputStream and Reader objects for better performance for when the methods are called repeatedly.
You may also be interested in cleaning up JSON when it is being deserialized into Java object.

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 ?

Make simple servlet filter work with #ControllerAdvice

I've a simple filter just to check if a request contains a special header with static key - no user auth - just to protect endpoints. The idea is to throw an AccessForbiddenException if the key does not match which then will be mapped to response with a class annotated with #ControllerAdvice. However I can't make it work. My #ExceptionHandler isn't called.
ClientKeyFilter
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Controller
import javax.servlet.*
import javax.servlet.http.HttpServletRequest
#Controller //I know that #Component might be here
public class ClientKeyFilter implements Filter {
#Value('${CLIENT_KEY}')
String clientKey
public void init(FilterConfig filterConfig) {}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
req = (HttpServletRequest) req
def reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey)) {
throw new AccessForbiddenException('Invalid API key')
}
chain.doFilter(req, res)
}
public void destroy() {}
}
AccessForbiddenException
public class AccessForbiddenException extends RuntimeException {
AccessForbiddenException(String message) {
super(message)
}
}
ExceptionController
#ControllerAdvice
class ExceptionController {
static final Logger logger = LoggerFactory.getLogger(ExceptionController)
#ExceptionHandler(AccessForbiddenException)
public ResponseEntity handleException(HttpServletRequest request, AccessForbiddenException e) {
logger.error('Caught exception.', e)
return new ResponseEntity<>(e.getMessage(), I_AM_A_TEAPOT)
}
}
Where I'm wrong? Can simple servlet filter work with spring-boot's exception mapping?
As specified by the java servlet specification Filters execute always before a Servlet is invoked. Now a #ControllerAdvice is only useful for controller which are executed inside the DispatcherServlet. So using a Filter and expecting a #ControllerAdvice or in this case the #ExceptionHandler, to be invoked isn't going to happen.
You need to either put the same logic in the filter (for writing a JSON response) or instead of a filter use a HandlerInterceptor which does this check. The easiest way is to extend the HandlerInterceptorAdapter and just override and implement the preHandle method and put the logic from the filter into that method.
public class ClientKeyInterceptor extends HandlerInterceptorAdapter {
#Value('${CLIENT_KEY}')
String clientKey
#Override
public boolean preHandle(ServletRequest req, ServletResponse res, Object handler) {
String reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey)) {
throw new AccessForbiddenException('Invalid API key')
}
return true;
}
}
You can't use #ControllerAdvice, because it gets called in case of an exception in some controller, but your ClientKeyFilter is not a #Controller.
You should replace the #Controller annotation with the #Component and just set response body and status like this:
#Component
public class ClientKeyFilter implements Filter {
#Value('${CLIENT_KEY}')
String clientKey
public void init(FilterConfig filterConfig) {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String reqClientKey = request.getHeader("Client-Key");
if (!clientKey.equals(reqClientKey)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid API key");
return;
}
chain.doFilter(req, res);
}
public void destroy() {
}
}
Servlet Filters in Java classes are used for the following purposes:
To check requests from client before they access resources at backend.
To check responses from server before sent back to the client.
Exception throw from Filter may not be catch by #ControllerAdvice because in may not reach DispatcherServlet. I am handling in my project as below:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
String token = null;
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && (bearerToken.contains("Bearer "))) {
if (bearerToken.startsWith("Bearer "))
token = bearerToken.substring(7, bearerToken.length());
try {
AuthenticationInfo authInfo = TokenHandler.validateToken(token);
logger.debug("Found id:{}", authInfo.getId());
authInfo.uri = request.getRequestURI();
AuthPersistenceBean persistentBean = new AuthPersistenceBean(authInfo);
SecurityContextHolder.getContext().setAuthentication(persistentBean);
logger.debug("Found id:'{}', added into SecurityContextHolder", authInfo.getId());
} catch (AuthenticationException authException) {
logger.error("User Unauthorized: Invalid token provided");
raiseException(request, response);
return;
} catch (Exception e) {
raiseException(request, response);
return;
}
// Wrapping the error response
private void raiseException(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ApiError apiError = new ApiError(HttpStatus.UNAUTHORIZED);
apiError.setMessage("User Unauthorized: Invalid token provided");
apiError.setPath(request.getRequestURI());
byte[] body = new ObjectMapper().writeValueAsBytes(apiError);
response.getOutputStream().write(body);
}
// ApiError class
public class ApiError {
// 4xx and 5xx
private HttpStatus status;
// holds a user-friendly message about the error.
private String message;
// holds a system message describing the error in more detail.
private String debugMessage;
// returns the part of this request's URL
private String path;
public ApiError(HttpStatus status) {
this();
this.status = status;
}
//setter and getters

Resources