How to set implementation of OncePerRequestFilter to filter only one endpoint - spring

Im using this Filter to log my requests and responses, it works very well, but I noticed that I didnt need actualy this filter for all my endpoints - it will be more efficient and enough to filtering and logging requests from only one endpoint.
Is it possible without making if statement in afterRequest method?
Im searching this a lot, but almost every example is with spring security :(
#Slf4j
#Component
public class RequestAndResponseLoggingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (isAsyncDispatch(request)) {
filterChain.doFilter(request, response);
} else {
doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
}
}
protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {
beforeRequest(request, response);
try {
filterChain.doFilter(request, response);
}
finally {
afterRequest(request, response);
response.copyBodyToResponse();
}
}
protected void beforeRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (log.isInfoEnabled()) {
val address = request.getRemoteAddr();
val queryString = request.getQueryString();
if (queryString == null) {
log.info("{}> {} {}", address, request.getMethod(), request.getRequestURI());
} else {
log.info("{}> {} {}?{}", address, request.getMethod(), request.getRequestURI(), queryString);
}
Collections.list(request.getHeaderNames()).forEach(headerName ->
Collections.list(request.getHeaders(headerName)).forEach(headerValue ->
log.info("{}> {}: {}", address, headerName, headerValue)));
val content = request.getContentAsByteArray();
if (content.length > 0) {
log.info("{}>", address);
try {
val contentString = new String(content, request.getCharacterEncoding());
Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> log.info("{}> {}", address, line));
} catch (UnsupportedEncodingException e) {
log.info("{}> [{} bytes body]", address, content.length);
}
}
log.info("{}>", address);
}
}
protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (log.isInfoEnabled()) {
val address = request.getRemoteAddr();
val status = response.getStatus();
log.info("{}< {} {}", address, status, HttpStatus.valueOf(status).getReasonPhrase());
response.getHeaderNames().forEach(headerName ->
response.getHeaders(headerName).forEach(headerValue ->
log.info("{}< {}: {}", address, headerName, headerValue)));
val content = response.getContentAsByteArray();
if (content.length > 0) {
log.info("{}<", address);
try {
val contentString = new String(content, request.getCharacterEncoding());
Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> log.info("{}< {}", address, line));
} catch (UnsupportedEncodingException e) {
log.info("{}< [{} bytes body]", address, content.length);
}
}
}
}
private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
if (request instanceof ContentCachingRequestWrapper) {
return (ContentCachingRequestWrapper) request;
} else {
return new ContentCachingRequestWrapper(request);
}
}
private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
if (response instanceof ContentCachingResponseWrapper) {
return (ContentCachingResponseWrapper) response;
} else {
return new ContentCachingResponseWrapper(response);
}
}
}

Related

How to catch spring boot AccessDeniedException when using #PreAuthorize

I am trying to catch AccessDeniedException, so I can display the AccessDenied error message in response, instead of Internal Server Error(s) while executing query
But, when I try to debug the method, try/catch block doesn't catch the AccessDeniedException thrown by filterChain.doFilter(request, response);
I tried to use recommended #ExceptionHandler , but they did not help either.
Current response:
{
"errors": [
{
"message": "Internal Server Error(s) while executing query"
}
]
}
Expected response:
{
"errors": [
{
"message": "Access denied."
}
]
}
Here is my code:
#Component
public class JwtFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(#NotNull HttpServletRequest request, #NotNull HttpServletResponse response, #NotNull FilterChain filterChain) {
Optional<HttpServletRequest> optReq = Optional.of(request);
String authToken = optReq
.map(req -> req.getHeader("Authorization"))
.filter(token -> !token.isEmpty())
.map(token -> token.replace("Bearer ", ""))
.orElse(null);
if (authToken != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//process token
} else {
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | ServletException | IOException e) {
String test = "test";
}
}
}
}
Here is the mutation where #Preauthorized is used :
#Component
#PreAuthorize("hasAnyRole('CREATOR','INFLUENCER','INFLUENCER_TEAMMATE')")
public class ProducerSharedMutations implements GraphQLMutationResolver {
private final WidgetService widgetService;
public ProducerSharedMutations(WidgetService widgetService) {
this.widgetService = widgetService;
}
public Widget addWidget(WidgetInput widgetInput){
return widgetService.add(widgetInput);
}
}

Spring boot application filter response body

I am working on a spring boot application. I want to modify the response of the request by request body field "Id".
I have implemented below, but still getting just the name in the output while implementing.Any suggestions on implementing below would be helpful:
Below is the requestBody:
{
"id" : "123"
}
In response, I want to append that field to response id(fieldname from request body).
responseBody:
{
"name" : "foo123" //name + id from request
}
MyCustomFilter:
public class TestFilter implements Filter {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintStream ps = new PrintStream(baos);
MultiReadHttpServletRequest wrapper = new MultiReadHttpServletRequest((HttpServletRequest) request);
MyRequestWrapper req = new MyRequestWrapper(wrapper);
String userId = req.getId();
chain.doFilter(wrapper, new HttpServletResponseWrapper(res) {
#Override
public ServletOutputStream getOutputStream() throws IOException {
return new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps)
);
}
#Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(new DelegatingServletOutputStream(new TeeOutputStream(super.getOutputStream(), ps))
);
}
});
String responseBody = baos.toString();
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(responseBody);
String name = node.get("name").astext();
((ObjectNode) node1).put("name", name + userId);
chain.doFilter(wrapper, res);
}
MyRequestWrapper:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ServletInputStream input;
public MyRequestWrapper(ServletRequest request) {
super((HttpServletRequest)request);
}
public String getId() throws IOException {
if (input == null) {
try {
JSONObject jsonObject = new JSONObject(IOUtils.toString(super.getInputStream()));
String userId = jsonObject.getString("id");
userId = userId.replaceAll("\\D+","");
return userId;
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
}
MultiReadHttpServletRequest.java
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private byte[] body;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
try {
body = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
body = new byte[0];
}
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
ByteArrayInputStream wrapperStream = new ByteArrayInputStream(body);
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setReadListener(ReadListener readListener) {
}
#Override
public int read() throws IOException {
return wrapperStream.read();
}
};
}
}
Any suggestions are appreciated. TIA.
Nte: After update i am not able to see the updated response as output. I am still seeing just the name but not id appended to it.
The one issue I see with your own implementation of ServletRequest is that you call super.getInputStream() instead of request.getInputStream(). Your own request is empty by default, that's why you're getting time out exception. You have to delegate a call to the actual request:
public class MyRequestWrapper extends HttpServletRequestWrapper {
private ServletInputStream input;
public MyRequestWrapper(ServletRequest request) {
super((HttpServletRequest)request);
}
public String getId() throws IOException {
if (input == null) {
try {
JSONObject jsonObject = new JSONObject(IOUtils.toString(/*DELETEGATE TO ACTUAL REQUEST*/request.getInputStream()));
String userId = jsonObject.getString("id");
userId = userId.replaceAll("\\D+","");
return userId;
} catch (JSONException e) {
e.printStackTrace();
}
}
return null;
}
}

Reading response body from ServerHttpResponse Spring cloud gateway

I am trying to read response body from ServerHttpResponse in a FilterFactory class that extents AbstractGatewayFilterFactory. The method executes, but I never see the log line printed. Is this the correct approach to read response ? If yes, what am I missing here ?
#Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest.Builder reqBuilder = exchange.getRequest().mutate();
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
#Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
log.info("Response : {}", new String(content, StandardCharsets.UTF_8));
return bufferFactory.wrap(content);
}));
}
return super.writeWith(body);
}
};
long start = System.currentTimeMillis();
return chain.filter(exchange.mutate()
.request(reqBuilder.build())
.response(decoratedResponse)
.build());
};
}

How I get the HandlerMethod matchs a HttpServletRequest in a Filter

I'm writing a simple proxy app, and want mapped url will be handled by my controller, but other url (includes error) can be forwarded to another different address. So I use Filter rather than HandlerInterceptorAdapter that cannot be invoked if the resourece is not found because certain "resourece path handler" deals it.
Expectation
http://localhost:8090/upload.html > Filter > http://localhost:8092/upload.html
http://localhost:8090/files/upload > Controller > http://localhost:8092/files/upload
Not
http://localhost:8090/upload.html > Filter > http://localhost:8092/upload.html
http://localhost:8090/files/upload > Controller > http://localhost:8092/files/upload
Or
http://localhost:8090/upload.html > Interceptor > http://localhost:8090/error Not found
http://localhost:8090/files/upload > Filter > http://localhost:8092/files/upload
Demo
I set up a Filter in my subclass of WebMvcConfigurerAdapter.
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
private javax.servlet.Filter proxyFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("[doFilterInternal]isCommitted=" + response.isCommitted() + ", URI = " + request.getRequestURI());
// if(!isRequestMappedInController(request, "my.pakcage"))
httpProxyForward(request, response);
}
};
}
// #Bean
// private FilterRegistrationBean loggingFilterRegistration() {
// FilterRegistrationBean registration = new FilterRegistrationBean(proxyFilter());
// registration.addUrlPatterns("/**");
// return registration;
// }
Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptorAdapter() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// How I determine a controller has handled the request in my interceptor?
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = ((HandlerMethod) handler);
if (handlerMethod.getMethod().getDeclaringClass().getName().startsWith("nxtcasb.casbproxy")) {
System.out.println("[preHandle]dealt: request uri = " + request.getRequestURI() + ", HandlerMethod = " + ((HandlerMethod) handler).getMethod());
return true;
} else {
System.out.println("[preHandle]isCommitted=" + response.isCommitted() + ", HandlerMethod = " + ((HandlerMethod) handler).getMethod());
}
}
// act as an api-gateway
System.out.println("[preHandle]undealt: request uri = " + request.getRequestURI() + ", handler = " + handler);
//ModelAndView modelView = new ModelAndView("redirect: http://www.bing.com");
//throw new ModelAndViewDefiningException(modelView);
httpProxyForward(request, response);
return false;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (handler instanceof HandlerMethod) {
System.out.println("[postHandle]dealt: uri = " + request.getRequestURI() + ", handler = " + ((HandlerMethod) handler).getMethod());
} else {
System.out.println("[postHandle]undealt uri = " + request.getRequestURI() + ", handler = " + handler);
}
}
}).addPathPatterns("/**", "/error");
}
/**
* this is the same as <mvc:default-servlet-handler/> <!-- This tag allows for mapping the DispatcherServlet to "/" -->
*
* #param configurer
*/
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//registry.addResourceHandler("/**").addResourceLocations("classpath:/public");
}
protected void httpProxyForward(HttpServletRequest request, HttpServletResponse response) {
HttpClient httpClient = CreateHttpClient();
HttpUriRequest targetRequest = null;
HttpResponse targetResponse = null;
try {
targetRequest = createHttpUriRequest(request);
targetResponse = httpClient.execute(targetRequest);
} catch (IOException e) {
e.printStackTrace();
} finally {
// make sure the entire entity was consumed, so the connection is released
if (targetResponse != null) {
EntityUtils.consumeQuietly(targetResponse.getEntity()); // #since 4.2
//Note: Don't need to close servlet outputStream:
// http://stackoverflow.com/questions/1159168/should-one-call-close-on-httpservletresponse-getoutputstream-getwriter
}
}
}
}
The api url /files/upload:
#RestController
#RequestMapping(value = "/files")
public class FileUploadProxyController {
private static final Logger logger = LoggerFactory.getLogger(FileUploadProxyController.class);
#RequestMapping(value = "/upload", method = RequestMethod.POST)
public ResponseEntity upload(HttpServletResponse response, HttpServletRequest request) {
try {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Iterator<String> it = multipartRequest.getFileNames();
MultipartFile multipart = multipartRequest.getFile(it.next());
String fileName = multipart.getOriginalFilename();
File dir = new File("files", "proxy-uploaded");
dir.mkdirs();
logger.debug("current dir = {}, uploaded dir = {}", System.getProperty("user.dir"), dir.getAbsolutePath());
File file = new File(dir, fileName);
Files.copy(multipart.getInputStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
//FileCopyUtils.copy(multipart.getInputStream())
// byte[] bytes = multipart.getBytes();
// BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream("upload" + fileName));
// stream.write(bytes);
// stream.close();
RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
//// if Spring version < 3.1, see https://jira.springsource.org/browse/SPR-7909
// requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
String url = "http://localhost:8092/files/upload";
// [resttemplate multipart post](https://jira.spring.io/browse/SPR-13571)
// [Spring RestTemplate - how to enable full debugging/logging of requests/responses?](https://stackoverflow.com/questions/7952154/spring-resttemplate-how-to-enable-full-debugging-logging-of-requests-responses?rq=1)
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
param.add("file", new FileSystemResource(file));
param.add("param1", fileName);
param.add("param2", "Leo");
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String,Object>>(param);
ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
//String string = restTemplate.postForObject(url, param, String.class);
//ResponseEntity e = restTemplate.exchange(url, HttpMethod.POST,
// new HttpEntity<Resource>(new FileSystemResource(file)), String.class);
return responseEntity;
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity("Upload failed", HttpStatus.BAD_REQUEST);
}
}
#RequestMapping("/hello")
public String hello() {
return "hello word";
}
}
Afer reading Spring mvc autowire RequestMappingHandlerMapping or Get destination controller from a HttpServletRequest
The following code works:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// https://stackoverflow.com/questions/129207/getting-spring-application-context
#Autowired
private org.springframework.context.ApplicationContext appContext;
private static final String MY_CONTROLLER_PACKAGE_NAME = "nxtcasb.casbproxy";
#Bean
protected javax.servlet.Filter proxyFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HandlerMethod handlerMethod = null;
try {
RequestMappingHandlerMapping req2HandlerMapping = (RequestMappingHandlerMapping) appContext.getBean("requestMappingHandlerMapping");
// Map<RequestMappingInfo, HandlerMethod> handlerMethods = req2HandlerMapping.getHandlerMethods();
HandlerExecutionChain handlerExeChain = req2HandlerMapping.getHandler(request);
if (Objects.nonNull(handlerExeChain)) {
handlerMethod = (HandlerMethod) handlerExeChain.getHandler();
if (handlerMethod.getBeanType().getName().startsWith(MY_CONTROLLER_PACKAGE_NAME)) {
filterChain.doFilter(request, response);
return;
}
}
} catch (Exception e) {
logger.warn("Lookup the handler method", e);
} finally {
logger.debug("URI = " + request.getRequestURI() + ", handlerMethod = " + handlerMethod);
}
httpProxyForward(request, response);
}
};
}
// #Bean
// private FilterRegistrationBean loggingFilterRegistration() {
// FilterRegistrationBean registration = new FilterRegistrationBean(proxyFilter());
// registration.addUrlPatterns("/**");
// return registration;
// }
/**
* this is the same as <mvc:default-servlet-handler/> <!-- This tag allows for mapping the DispatcherServlet to "/" -->
*
* #param configurer
*/
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//registry.addResourceHandler("/**").addResourceLocations("classpath:/public");
}
protected void httpProxyForward(HttpServletRequest request, HttpServletResponse response) {
HttpClient httpClient = CreateHttpClient();
HttpUriRequest targetRequest = null;
HttpResponse targetResponse = null;
try {
targetRequest = createHttpUriRequest(request);
targetResponse = httpClient.execute(targetRequest);
} catch (IOException e) {
e.printStackTrace();
} finally {
// make sure the entire entity was consumed, so the connection is released
if (targetResponse != null) {
EntityUtils.consumeQuietly(targetResponse.getEntity()); // #since 4.2
//Note: Don't need to close servlet outputStream:
// http://stackoverflow.com/questions/1159168/should-one-call-close-on-httpservletresponse-getoutputstream-getwriter
}
}
}
}

Servlet Filter modify header value with servlet request wrapper not working

I am attempting to change the Content-Type header in a request and change it to "application/json" before it reaches my spring rest controller. I have created a servlet request wrapper to change the values, but when the request reaches the controller it is still "text/plain". The logging shows that the header value has been changed before hitting doFilter();
Here is my class extending HttpServletRequestWrapper
class HttpServletRequestWritableWrapper extends HttpServletRequestWrapper {
private final Logger logger = org.slf4j.LoggerFactory.getLogger(HttpServletRequestWritableWrapper.class);
private final ByteArrayInputStream decryptedBody;
HttpServletRequestWritableWrapper(HttpServletRequest request, byte[] decryptedData) {
super(request);
decryptedBody = new ByteArrayInputStream(decryptedData);
}
#Override
public String getHeader(String name) {
String headerValue = super.getHeader(name);
if("Accept".equalsIgnoreCase(name))
{
logger.debug("Accept header changing :");
return headerValue.replaceAll(
MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE
);
}
else if ("Content-Type".equalsIgnoreCase(name))
{
logger.debug("Content type change: ");
return headerValue.replaceAll(MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE);
}
return headerValue;
}
#Override
public Enumeration<String> getHeaderNames() {
return super.getHeaderNames();
}
#Override
public String getContentType() {
String contentTypeValue = super.getContentType();
if (MediaType.TEXT_PLAIN_VALUE.equalsIgnoreCase(contentTypeValue)) {
logger.debug("Changing on getContentType():");
return MediaType.APPLICATION_JSON_VALUE;
}
return contentTypeValue;
}
#Override
public BufferedReader getReader() throws UnsupportedEncodingException {
return new BufferedReader(new InputStreamReader(decryptedBody, UTF_8));
}
#Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
#Override
public int read() {
return decryptedBody.read();
}
};
}
And here is my filter:
#WebFilter(displayName = "EncryptionFilter", urlPatterns = "/*")
public class EncryptionFilter implements Filter {
private final Logger logger = org.slf4j.LoggerFactory.getLogger(EncryptionFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
byte[] data = "{\"currentClientVersion\":{\"majorElement\":\"1\",\"minorElement\":\"2\"}}".getBytes();
logger.debug("data string " + data.toString());
logger.debug("Content-type before: " + servletRequest.getContentType());
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletRequestWritableWrapper requestWrapper = new HttpServletRequestWritableWrapper(request, data);
//logger.debug("Accept Header: " + requestWrapper.getHeader("Accept"));
//logger.debug("Content-Type: " + requestWrapper.getHeader("Content-Type"));
//logger.debug("Contenttype" + requestWrapper.getContentType());
filterChain.doFilter(requestWrapper, servletResponse);
}
#Override
public void destroy() {
}
}
It appears that the getHeaders method was being called somewhere else after my filter and not returning the headers with my updated values.
I added this override in my HttpServletRequestWrapper and it is now working:
#Override
public Enumeration<String> getHeaders(String name) {
List<String> headerVals = Collections.list(super.getHeaders(name));
int index = 0;
for (String value : headerVals) {
if ("Content-Type".equalsIgnoreCase(name)) {
logger.debug("Content type change: ");
headerVals.set(index, MediaType.APPLICATION_JSON_VALUE);
}
index++;
}
return Collections.enumeration(headerVals);
}

Resources