Implement CustomFeignLogger for feign interface - java

I have this interface:
#FeignClient(name = "${test.feign.name}", url = "${test.feign.url}", configuration = TestConfiguration.class)
public interface TestFeignClient {
#GetMapping(value = "/users", produces = "application/json")
ResponseEntity<?> getAll();
}
And I want to track the log by creating this class CustomFeignLogger.
This CustomFeignLogger object should extends Slf4jLogger Object and override this method:
protected Response logAndRebufferResponse(String configKey,
Level logLevel,
Response response,
long elapsedTime)
throws IOException {
if (logger.isDebugEnabled()) {
return super.logAndRebufferResponse(configKey, logLevel, response, elapsedTime);
}
return response;
}

In the TestConfiguration class you need to inject new bean CustomFeignLogger
#AllArgsConstructor
public class TestConfiguration {
#Bean
public CustomFeignLogger feignLogger() {
return new CustomFeignLogger();
}
}
Then you need to override logAndRebufferResponse method tike this:
protected Response logAndRebufferResponse(String configKey, Logger.Level logLevel, Response response, long elapsedTime) throws IOException {
String responseBody = null;
String reason = response.reason() != null && logLevel.compareTo(Logger.Level.NONE) > 0 ? " " + response.reason() : "";
int status = response.status();
this.log(configKey, "<--- HTTP/1.1 %s%s (%sms)", status, reason, elapsedTime);
// ---------------- Logging headers ----------------
Iterator headersIterator = response.headers().keySet().iterator();
while(headersIterator.hasNext()) {
String field = (String) headersIterator.next();
Iterator valuesIterator = Util.valuesOrEmpty(response.headers(), field).iterator();
while(valuesIterator.hasNext()) {
String value = (String)valuesIterator.next();
this.log(configKey, "%s: %s", field, value);
}
}
// -------------------------------------------------
//----------------- Logging response body ---------------
int bodyLength = 0;
if (response.body() != null
&& status != HttpStatus.SC_NO_CONTENT
&& status != HttpStatus.SC_RESET_CONTENT) {
this.log(configKey, "");
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
bodyLength = bodyData.length;
if (bodyLength > 0) {
String body = Util.decodeOrDefault(bodyData, Util.UTF_8, "Binary data");
responseBody = body.toString();
this.log(configKey, "%s", body);
}
this.log(configKey, "<--- END HTTP ttttt (%s-byte body)", bodyLength);
// trackingService.track
return response.toBuilder().body(bodyData).build();
}
this.log(configKey, "<--- END HTTP tttt (%s-byte body)", Integer.valueOf(bodyLength));
// -------------------------------------------------
return response;
}

Related

How to get request payload class type and response body class type in RestTemplateInterceptor to mask sensitive information using custom annotation?

In my spring boot application I've used a RestTemplateInterceptor to log request and response details in debug mode. To mask the sensitive information in request payload and response body, I've created a custom annotation #LogMaskedStringValue and annotated some fields in request DTO and response DTO. I've created a Serializer MaskStringSerializer to mask the annotated fields with the help of object mapper.
I tried to set the request payload type and expected response body type in request headers and I'm retrieving it in interceptor. But it is not the legitimate way to do, cause the header dependency prevents to use this interceptor in other applications, I tried using RestTemplateRequestCustomizer , Unfortunately it didn't work. Is there any way to get the request payload type and response body type in RestTemplateInterceptor ?
```
#Slf4j
public class RestTemplateLoggingInterceptor implements ClientHttpRequestInterceptor {
private final LogDetailsStorage logDetailsStorage;
private final static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public RestTemplateLoggingInterceptor(LogDetailsStorage logDetailsStorage, String message) {
this.logDetailsStorage = logDetailsStorage;
this.message = message;
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
logDetailsStorage.setOutboundStartTime(System.currentTimeMillis());
String requestType = request.getHeaders().getFirst("requestType");
String responseType = request.getHeaders().getFirst("responseType");
request.getHeaders().remove("requestType");
request.getHeaders().remove("responseType");
logRequest(request, body, requestType);
ClientHttpResponse response = execution.execute(request, body);
logResponse(response, responseType);
return response;
}
private void logRequest(HttpRequest request, byte[] body, String requestType) {
if (log.isDebugEnabled()) {
logDetailsStorage.setOutboundRequestUrl(request.getURI().toString());
logDetailsStorage.setOutboundRequestMethod(request.getMethodValue());
MDC.put(MdcKey.OUTBOUND_REQUEST_METHOD.getMdcKey(), logDetailsStorage.getOutboundRequestMethod());
MDC.put(MdcKey.OUTBOUND_REQUEST_URL.getMdcKey(), logDetailsStorage.getOutboundRequestUrl());
if (body != null && body.length > 0) {
String requestPayload = new String(body, StandardCharsets.UTF_8);
logDetailsStorage.setOutboundRequestPayload(getMaskedPayload(requestType, requestPayload));
MDC.put(MdcKey.OUTBOUND_REQUEST_PAYLOAD.getMdcKey(), logDetailsStorage.getOutboundRequestPayload());
}
log.debug("Making request for " + logDetailsStorage.getOutboundRequestUrl());
MDC.remove(MdcKey.OUTBOUND_REQUEST_METHOD.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_REQUEST_URL.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_REQUEST_PAYLOAD.getMdcKey());
}
}
private void logResponse(ClientHttpResponse response, String responseType) throws IOException {
if (log.isDebugEnabled()) {
String responsePayload = StreamUtils.copyToString(response.getBody(), Charset.defaultCharset());
logDetailsStorage.setOutboundResponsePayload(getMaskedPayload(responseType, responsePayload));
logDetailsStorage.setOutboundStatusCode(String.valueOf(response.getRawStatusCode()));
logDetailsStorage.setOutboundExecutionTime((System.currentTimeMillis() - logDetailsStorage.getOutboundStartTime()) / 1000d + " seconds");
MDC.put(MdcKey.OUTBOUND_REQUEST_METHOD.getMdcKey(), logDetailsStorage.getOutboundRequestMethod());
MDC.put(MdcKey.OUTBOUND_REQUEST_URL.getMdcKey(), logDetailsStorage.getOutboundRequestUrl());
MDC.put(MdcKey.OUTBOUND_RESPONSE_PAYLOAD.getMdcKey(), logDetailsStorage.getOutboundResponsePayload());
MDC.put(MdcKey.OUTBOUND_STATUS_CODE.getMdcKey(), logDetailsStorage.getOutboundStatusCode());
if (logDetailsStorage.getOutboundRequestPayload() != null) {
MDC.put(MdcKey.OUTBOUND_REQUEST_PAYLOAD.getMdcKey(), logDetailsStorage.getOutboundRequestPayload());
}
MDC.put(MdcKey.OUTBOUND_EXECUTION_TIME.getMdcKey(), logDetailsStorage.getOutboundExecutionTime());
log.debug("Got Response for "+ logDetailsStorage.getOutboundRequestUrl());
MDC.remove(MdcKey.OUTBOUND_REQUEST_METHOD.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_REQUEST_URL.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_REQUEST_PAYLOAD.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_EXECUTION_TIME.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_STATUS_CODE.getMdcKey());
MDC.remove(MdcKey.OUTBOUND_RESPONSE_PAYLOAD.getMdcKey());
}
}
private String getMaskedPayload(String classType, String payload) {
if (!StringUtils.isEmpty(classType)) {
try {
Object obj = objectMapper.readValue(payload, Class.forName(classType));
payload = LogUtil.getObjectAsMaskedJsonString(obj);
} catch (JsonProcessingException e) {
log.error("'Failed to parse the payload : {}", payload, e);
} catch (ClassNotFoundException e) {
log.error("Class not found exception occurred : {}", classType, e);
}
}
else {
log.warn("ClassType is empty during getMaskedPayload : {}", classType);
}
return payload;
}
}
```
```
public class MaskStringSerializer extends StdSerializer<String> implements ContextualSerializer {
private String mask;
public MaskStringSerializer() {
super(String.class);
}
public MaskStringSerializer(String mask) {
super(String.class);
this.mask = mask;
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Optional<String> maskValue = Optional.ofNullable(property)
.map(p -> p.getAnnotation(LogMaskStringValue.class))
.map(LogMaskStringValue::value);
return maskValue.map(MaskStringSerializer::new).orElseGet(MaskStringSerializer::new);
}
#Override
public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (mask != null) {
gen.writeString(mask);
} else {
gen.writeString(Optional.ofNullable(value).orElse("null"));
}
}
}
```
```
#UtilityClass
#Slf4j
public class LogUtil {
private final static ObjectMapper sensitiveMapper = new ObjectMapper();
static {
SimpleModule module = new SimpleModule();
module.addSerializer(new MaskStringSerializer());
sensitiveMapper.registerModule(module);
}
public static String getObjectAsMaskedJsonString(Object object) {
String requestBody;
try {
requestBody = sensitiveMapper.writeValueAsString(object);
} catch (JsonProcessingException jsonProcessingException) {
log.error("Error while parsing object: {}", object, jsonProcessingException);
requestBody = object.toString();
}
return requestBody;
}
}
```
```
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Card {
#LogMaskStringValue
private String id;
private String type;
private String last4;
private Integer expirationMonth;
private Integer expirationYear;
}
```
```
`

Request body change when I Use servletRequest.getReader().lines().collect(Collectors.joining())

I'm doing a private api in java, jwt, spring security and the first time come in the request a json object.
user: xxx
password: yyy
The api return a jwt token in the body.
The others call the token come in the body json and to validate it I use this:
sbody = servletRequest.getReader().lines().collect(Collectors.joining());
To get the field token and it get ok, but then of the filter it show the message:
"Required request body is missing: public org.springframework.http.ResponseEntity"
This is my api:
#SpringBootApplication
public class JwtApplication {
public static void main(String[] args) {
SpringApplication.run(JwtApplication.class, args);
}
#EnableWebSecurity
#Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterAfter(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().antMatchers(HttpMethod.POST, "/user").permitAll()
.antMatchers(HttpMethod.POST, "/Autenticacion").permitAll().anyRequest().authenticated();
}
}
}
This is the filter:
public class JWTAuthorizationFilter extends OncePerRequestFilter {
private final String HEADER = "Authorization";
private final String SESSION = "sesion";
private final String PREFIX = "Bearer ";
private final String SECRET = "mySecretKey";
public static final long EXPIRATION_TIME = 900_000; // 15 mins
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
boolean resultado_checktoken = checkJWTToken(httpRequest, httpResponse);
if (resultado_checktoken) {
Claims claims = validateToken(request);
if (claims.get("authorities") != null) {
setUpSpringAuthentication(claims);
} else {
SecurityContextHolder.clearContext();
}
} else {
SecurityContextHolder.clearContext();
}
chain.doFilter(request, response);
} catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
return;
}
System.out.println("supuestamente no hubo problemas");
}
private Claims validateToken(HttpServletRequest request) {
//String jwtToken = request.getHeader(HEADER).replace(PREFIX, "");
String jwtToken="";
try {
jwtToken = this.getBodySession(request);
} catch (IOException e) {
e.printStackTrace();
};
return Jwts.parser().setSigningKey(SECRET.getBytes()).parseClaimsJws(jwtToken).getBody();
}
/**
* Authentication method in Spring flow
*
* #param claims
*/
private void setUpSpringAuthentication(Claims claims) {
#SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get("authorities");
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(claims.getSubject(), null,
authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(auth);
}
private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) throws IOException {
String authenticationHeader = "";
authenticationHeader = this.getBodySession(request);
if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX))
return false;
return true;
}
public String getBodySession(HttpServletRequest request) throws IOException {
String sbody = "";
HttpServletRequest servletRequest = new ContentCachingRequestWrapper(request);
//servletRequest.getParameterMap();
sbody = servletRequest.getReader().lines().collect(Collectors.joining());
String Field = SESSION;
String scampo = "";
if (sbody.contains(Field)) {
scampo = sbody.substring(sbody.indexOf(Field), sbody.indexOf("\n", sbody.indexOf(Field)))
.replace(Field + "\": \"", "").replace("\"", "").replace(",", "");
}
System.out.println("sbody: " + sbody + " sesion: " + scampo);
return scampo;
}
}
This needs to return a boolean explicitly you cannot have two return statements.
private boolean checkJWTToken(HttpServletRequest request, HttpServletResponse res) throws IOException {
String authenticationHeader = "";
authenticationHeader = this.getBodySession(request);
if (authenticationHeader == null || !authenticationHeader.startsWith(PREFIX))
**return false;**
**return true;**
}

How to log any outgoing responses from #RestController?

I'm using CommonsRequestLoggingFilter to log any incoming requests on my #RestController.
#RestController
public class MyController {
#PostMapping("/")
public MyRsp ping() {
...
return myRsp;
}
}
The users will send POST JSON requests, which are already logged using:
#Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeClientInfo(true);
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
return filter;
}
Question: how can I achieve the same for the JSON Response that I sent back to the user?
I don't know why spring offers a rest logger that uses ContentCachingRequestWrapper, but does not offer response logging. Because it can be implemented quite similar to the req logging, as follows:
public class MyFilter extends CommonsRequestLoggingFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
//same mechanism as for request caching in superclass
HttpServletResponse responseToUse = response;
if (isIncludePayload() && !isAsyncDispatch(request) && !(response instanceof ContentCachingResponseWrapper)) {
responseToUse = new ContentCachingResponseWrapper(response);
}
//outgoing request is logged in superclass
super.doFilterInternal(request, responseToUse, filterChain);
//log incoming response
String rsp = getResponseMessage(responseToUse);
LOGGER.info(rsp);
}
//equivalent to super.createMessage() for request logging
private String getResponseMessage(HttpServletResponse rsp) {
StringBuilder msg = new StringBuilder();
ContentCachingResponseWrapper wrapper =
WebUtils.getNativeResponse(request, ContentCachingResponseWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
int length = Math.min(buf.length, getMaxPayloadLength());
String payload;
try {
payload = new String(buf, 0, length, wrapper.getCharacterEncoding());
}
catch (UnsupportedEncodingException ex) {
payload = "[unknown]";
}
msg.append(";payload=").append(payload);
}
}
}
}
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {}
#Pointcut("within(path.to your.controller.package.*)")
public void myController() {}
#Around("requestMapping() || myController()")
public MyRsp logAround(ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.getArgs()// This will give you arguments if any being passed to your controller.
...............
MyRsp myRsp = (MyRsp) joinPoint.proceed();
...............
return myRsp;
}

Java IDE Idea log loop

I had just changed My IDE from Eclipse To Idea 14 ,and find a log loop problem.
I can't post image,here is the link:
http://img.rehulu.com/idea.png
Just like the picture,keep looping about half a second and there is no income request.
The same code in Eclipse is OK.
web.xml
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.rehulu.coreapi.service.impl.LoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
LoggingFilter.class
public class LoggingFilter extends OncePerRequestFilter {
protected static final Logger logger = Logger.getLogger(LoggingFilter.class);
private AtomicLong id = new AtomicLong(1);
private static final String REQUEST_PREFIX = "Req:%s sId:%s Ip:%s Method:%s Uri:%s Parameter:%s";
private static final String RESPONSE_PREFIX = "Resp:";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
final FilterChain filterChain) throws ServletException, IOException {
long requestId = id.incrementAndGet();
request = new RequestWrapper(requestId, request);
response = new ResponseWrapper(requestId, response);
try {
filterChain.doFilter(request, response);
} finally {
logRequest(request);
logResponse((ResponseWrapper) response);
}
}
private void logRequest(final HttpServletRequest request) {
StringBuilder msg = new StringBuilder();
msg.append(REQUEST_PREFIX);
HttpSession session = request.getSession(false);
String id = "";
if (session != null) {
id = session.getId();
}
String uri = request.getRequestURI();
String method = request.getMethod();
String parameter = "";
if (request instanceof HttpServletRequest && !isMultipart(request)) {
HttpServletRequest requestWrapper = (HttpServletRequest) request;
Map<String, String[]> parameters = requestWrapper.getParameterMap();
for (Entry<String, String[]> entry : parameters.entrySet()) {
String[] value = entry.getValue();
String keyV = "";
if (value == null || value.length == 0) {
continue;
} else {
if (value.length == 1) {
keyV = value[0];
} else {
keyV = Arrays.toString(value);
}
}
parameter += "{" + entry.getKey() + ":" + keyV + "}";
}
}
logger.info(String.format(REQUEST_PREFIX, String.valueOf(((RequestWrapper) request).getId()), id,
IPUtil.getClientIP(request), method, uri, parameter));
}
private boolean isMultipart(final HttpServletRequest request) {
return request.getContentType() != null && request.getContentType().startsWith("multipart/form-data");
}
private void logResponse(final ResponseWrapper response) {
StringBuilder msg = new StringBuilder();
msg.append(RESPONSE_PREFIX).append((response.getId()));
try {
String contentType = response.getContentType();
if (contentType != null && contentType.contains("json")) {
msg.append(" Payload:").append(new String(response.toByteArray(), response.getCharacterEncoding()));
} else {
msg.append(" Content-Type:").append(contentType);
}
} catch (UnsupportedEncodingException e) {
logger.warn("Failed to parse response payload", e);
}
logger.info(msg.toString());
}
}
Thanks very much !!!

The request sent by the client was syntactically incorrect while calling a WebService through rest using AngularJS

I have WebService which is checking whether the email is already present in the database.
#RequestMapping(value = "/checkPersonEmail/{jClientSessionId}/{jClientSessionId}/{email}", method = RequestMethod.GET)
public boolean checkName(#PathVariable String jUserSessionId,
#PathVariable String jClientSessionId, #PathVariable String email)
throws Exception {
String decryptedClientKey = EncryptContetns
.getDecryptedvalue(jClientSessionId);
List<String> emailIds = dimPersonManager
.getEmailIdsByClientKey(Long.valueOf(decryptedClientKey));
List<String> emailIdList = new ArrayList<String>();
for (String ids : emailIds) {
emailIdList.add(ids.toLowerCase());
}
if (emailIdList != null && !emailIdList.isEmpty()) {
if (emailIdList.contains(email.toLowerCase())) {
return false;
} else {
return true;
}
} else {
return true;
}
}
Then I'm calling this service using a http.get method as show below.
AngularJS call
$scope.checkEmail = function() {
if (!appClient.isUndefinedOrNull($scope.person.email)) {
alert( $scope.person.email);
$scope.spinnerClass = "icon-2x icon-spinner icon-spin";
var serverConnect = serverUrl + 'checkPersonEmail' + '/' + jUserSessionId + '/' + jClientSessionId + '/'+ $scope.person.email;
$http.get(serverConnect).success(function(data, status) {
// alert(JSON.stringify(data));
if (data == "true") {
$scope.spinnerClass = "icon-hide";
$scope.msgClass = "text-success icon-ok";
$scope.message = "Available";
} else {
$scope.spinnerClass = "icon-hide";
$scope.msgClass = "text-error icon-remove";
$scope.message = "Not Available";
}
}).error(function(data, status) {
alert("Failure");
});
}
}
Whenever this checkEmail is called it is a HTTP Bad request(400).
Could be because you have two jClientSessionId and no jUserSessionId in the #RequestMapping ({jClientSessionId}/{jClientSessionId})?
You need to put #ResponseBody annotation on your checkName method, if your controller is not annotated as #RestController.

Categories

Resources