How to write ResponseEntity to HttpServletResponse? - java

How to write ResponseEntity to HttpServletResponse (as it makes #ResponseBody)?
For example I have authentication success handler:
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map responseMap = new HashMap();
responseMap.put("user", "my_user_name");
ResponseEntity responseEntity = new ResponseEntity(response, HttpStatus.OK);
}
If use MappingJackson2HttpMessageConverter I have error: "Could not write content: not in non blocking mode."
Code:
HttpOutputMessage outputMessage = new ServletServerHttpResponse(response);
messageConverter.write(responseEntity, null, outputMessage);
What are the best practices of implementation handlers with HttpServletResponse?

You can use a custom response object, convert it to a JSON string using the Jackson's ObjectMapper and write the result into the request.
Example
MyResponseObject.java
private String user;
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
MyAuthenticationSuccessHandler.java
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
MyResponseObject responseObj = new MyResponseObject();
responseObj.setUser("my_user_name");
String json = new ObjectMapper().writeValueAsString(responseObj);
httpServletResponse.setStatus(HttpStatus.OK.value());
httpServletResponse.getWriter().write(json);
httpServletResponse.flushBuffer();
}

Based on andrearro88's answer, I have made this generic function to copy a ResponseEntity to a HttpServletResponse:
public static void populateResponse(ResponseEntity<String> responseEntity, HttpServletResponse servletResponse)
throws IOException {
for (Map.Entry<String, List<String>> header : responseEntity.getHeaders().entrySet()) {
String chave = header.getKey();
for (String valor : header.getValue()) {
servletResponse.addHeader(chave, valor);
}
}
servletResponse.setStatus(responseEntity.getStatusCodeValue());
servletResponse.getWriter().write(responseEntity.getBody());
}

Related

How to get response from HttpServletResponse and save to database in Java via interceptor

I want to save request and response in String format to database when endpoint /v2 is called.
This implementation workes fine and save is correct but if i call to v1 endpoint, is muted, response is null, just 200 OK in Postman. Normally it response with json {id="123456789"} Whats wrong have i in this implementation that endpoint v1 doesnt work ? If i delete CachingRequestBodyFilter class, v1 works fine but when v2 is called, nothing saved to database.
v1 endpoint is for xml and v2 endpoint is for json format.
Meybe is better way to save request and response to db via interceptors?
#AllArgsConstructor
#Component
#Data
public class RequestInterceptor implements HandlerInterceptor {
private final RequestService requestService;
#Override
public void afterCompletion(#NonNull HttpServletRequest request, #NonNull HttpServletResponse response, #NonNull Object handler, Exception ex){
requestService.saveResponse(request, response, ex);
}
}
public void saveResponse(HttpServletRequest request, HttpServletResponse response, Exception ex) {
try {
String requestBody = getRequestAsString(request);
String responseBody = getResponseAsString(response);
buildMessages(requestBody, responseBody, ex);
} catch (IOException e) {
e.printStackTrace();
}
}
private String getRequestAsString(HttpServletRequest request) throws IOException {
ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;
return new String(requestWrapper.getContentAsByteArray(), requestWrapper.getCharacterEncoding());
}
private String getResponseAsString(HttpServletResponse response) throws IOException {
ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response;
byte[] responseArray = responseWrapper.getContentAsByteArray();
String characterEncoding = responseWrapper.getCharacterEncoding();
responseWrapper.copyBodyToResponse();
return new String(responseArray, characterEncoding);
}
method buildMessages() is just a builder object which i want to save to db.
filter class:
#Component
#WebFilter(filterName = "CachingRequestBodyFilter", urlPatterns = {"/v2/*"})
public class CachingRequestBodyFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
HttpServletResponse response = (HttpServletResponse) res;
ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
filterChain.doFilter(wrappedRequest, wrappedResponse);
}
}
i want to save my request and response to db when endpoint v2 is called and when v1 is called response is not null;

How to disable JWT Authorization on specific API endpoints (Java)?

I have API endpoints configured for JWT Authorization:
#Bean
public FilterRegistrationBean<EsgJwtAuth> esgJwtAuthBean() {
EsgJwtAuth esgJwtAuth;
try {
HashMap<String, String> myConf = new HashMap<>();
myConf.put("header", "Authorization");
myConf.put("jwksUrl", jwksUrl);
myConf.put("jwksCacheSize", jwksCacheSize);
myConf.put("jwksExpiresIn", jwksExpiresIn);
myConf.put("jwksRateLimitSize", jwksRateLimitSize);
myConf.put("jwksRateLimitRefillRate", jwksRateLimitRefillRate);
esgJwtAuth = new EsgJwtAuth(myConf);
} catch (Exception e) {
esgJwtAuth = new EsgJwtAuth();
}
FilterRegistrationBean<EsgJwtAuth> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(esgJwtAuth);
registrationBean.addUrlPatterns("/a/*");
registrationBean.addUrlPatterns("/b/*");
registrationBean.addUrlPatterns("/c/*");
return registrationBean;
}
Now, i wanted to make /a/noauth to not require a JWT authorization. But for other endpoints such as /a/withauth/, /a/* i want it to require JWT authorization (as registered on the FilterRegistrationBean)
So far, i tried extending my class with OncePerRequestFilter and implemented the ff codes:
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
List<String> excludeUrlPatterns = new ArrayList<String>();
excludeUrlPatterns.add("/a/noauth/");
excludeUrlPatterns.add("/b/noauth/");
return excludeUrlPatterns.stream()
.anyMatch(exclude -> request.getRequestURI().contains(exclude));
}
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
chain.doFilter(request, response);
}
But when i run a request for /a/noauth without the Header: <JWT token>, it is Unauthorized. How to allow it? Thank you.
I do think that your excludeUrlPatterns List contains the wrong values (i.e. put /a/noauth instead of /a/noauth/).
I would rather suggest to use the AntPathMatcher class to check your excluding patterns. See below a naive implementation:
private AntPathMatcher pathMatcher = new AntPathMatcher();
#Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
List<String> excludeUrlPatterns = new ArrayList<String>();
excludeUrlPatterns.add("/a/noauth");
excludeUrlPatterns.add("/b/noauth");
return excludeUrlPatterns.stream()
.anyMatch(p -> pathMatcher.match(p, request.getRequestURI()));
}

How can I modify the object of a post call in filter. Spring boot

I have a filter in my application
#Component
#Order(2)
public class RequestResponseLoggingFilter implements Filter {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// SET VALUE OF OBJECT
}
// other methods
}
I have a Restcall which uses a class.
#RequestMapping
Class Test{
#PostMapping("/test")
public void postEntry(#Valid #RequestBody Testing testing){
}
}
Class Testing{
#NotNull(message="ERROR")
String id;
....
}
I get the id in my filter and I would like to set the id of Testing class in my Filter. Is this possible?
You can use MockHttpServletRequest something like this
#Test
public void testAddEventWithWebAuthenticationDetails() {
HttpSession session = new MockHttpSession(null, "test-session-id");
MockHttpServletRequest request = new MockHttpServletRequest();
request.setSession(session);
request.setRemoteAddr("1.2.3.4");
WebAuthenticationDetails details = new WebAuthenticationDetails(request);
Map<String, Object> data = new HashMap<>();
data.put("test-key", details);
AuditEvent event = new AuditEvent("test-user", "test-type", data);
customAuditEventRepository.add(event);
List<PersistentAuditEvent> persistentAuditEvents = persistenceAuditEventRepository.findAll();
assertThat(persistentAuditEvents).hasSize(1);
PersistentAuditEvent persistentAuditEvent = persistentAuditEvents.get(0);
assertThat(persistentAuditEvent.getData().get("remoteAddress")).isEqualTo("1.2.3.4");
assertThat(persistentAuditEvent.getData().get("sessionId")).isEqualTo("test-session-id");
}
More examples here
or
if you want to do it filter way
few Points Before that
Request body can be read only once.
If you read the body in a filter, the target servlet will not be able to re-read it and this will also cause IllegalStateException.
You will need ServletRequestWrapper or its child: HttpServletRequestWrapper so that you can read HTTP request body and then the servlet can still read it later.
Workflow will be
The only way would be for you to consume the entire input stream yourself in the filter.
Take what you want from it, and then create a new InputStream for the content you read.
Put that InputStream in to a ServletRequestWrapper (or HttpServletRequestWrapper).
// Sample Wrapper class where you can read body and modify body content
public class SampleHttpServletRequest
extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public SampleHttpServletRequest(HttpServletRequest request) {
super(request);
}
#Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null)
cacheInputStream();
return new CachedServletInputStream();
}
#Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
#Override
public int read() throws IOException {
return input.read();
}
}
}
Filter class
public class MyFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
/* wrap the request in order to read the inputstream multiple times */
MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);
doMyThing(multiReadRequest.getInputStream());
chain.doFilter(multiReadRequest, response);
}
}
Refer these post for more detail
Http Servlet request lose params from POST body after read it once
HttpServletRequestWrapper, example implementation for setReadListener / isFinished / isReady?

Consuming HttpServletRequest Multiple Times and Chain Between Methods

I have to read HttpServletRequest multiple times. I have wrapped HttpServletRequest like said in those posts Http Servlet request lose params from POST body after read it once
In my filter class which extends AbstractAuthenticationProcessingFilter, i can consume and chain request in successfulAuthentication method since it has chain parameter. But in addition to those solutions i have to chain request between attempt and succesful authentication steps:
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
// wrapping request and consuming
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
// Since i couldn't chain wrapped httpServletRequest from attemptAuthentication step, this request still gets non-wrapping one and inputstream is empty
}
How can i pass wrapped request from attemptAuthentication to successfulAuthentication?
This is an old question but in case someone gets the same problem:
You can wrap a request like suggested in the previous answer but you need to wrap it before it gets filtered by Authentication filter:
Spring security configuration will look something like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
AuthFilter authFilter = new AuthFilter();
WrapperFilter wrapperFilter = new WrapperFilter();
http .cors()
.and()
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(exceptionHandler)
.and()
.authorizeRequests()
.antMatchers("/v1/*", "/api/*")
.authenticated()
.and()
.addFilterBefore(authFilter, BasicAuthenticationFilter.class)
.addFilterBefore(wrapperFilter, AuthFilter.class);
}
So that wrapper filter goes before your auth filter and wrapper filter's doFilter wraps the request and passes it on:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
MultiReadHttpServletRequest wrapper = new MultiReadHttpServletRequest((HttpServletRequest) request);
chain.doFilter(wrapper, response);
}
And MultiReadHttpServletRequest is the following:
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();
}
};
}
}
There is no such way. The only thing you can do is wrap the request every time you gonna read it in one more filter. Just wrap the request every time before you read it copying body.
See some working code for a wrapper e.g. here

Restful api how to pass info after basic http auth

I am building a mobile app and Restful API, I want the user of the app to be able to do GET what ever resources he want without Authentication. But if he want to do POST he have to enter his username and pass.
I already made a HTTP basic Authentication by putting a filter in web.xml.
<filter>
<filter-name>AuthenticationFilter</filter-name>
<filter-class>org.service.RestAuthenticationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AuthenticationFilter</filter-name>
<url-pattern>/webapi/*</url-pattern>
</filter-mapping>
and there are the classes
public class AuthenticationService {
ClientsService s = new ClientsService();
public boolean authenticate(String authCredentials) {
if (null == authCredentials)
return false;
// header value format will be "Basic encodedstring" for Basic
// authentication. Example "Basic YWRtaW46YWRtaW4="
final String encodedUserPassword = authCredentials.replaceFirst("Basic"
+ " ", "");
String usernameAndPassword = null;
try {
byte[] decodedBytes = Base64.decode(
encodedUserPassword);
usernameAndPassword = new String(decodedBytes, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
final StringTokenizer tokenizer = new StringTokenizer(
usernameAndPassword, ":");
final String username = tokenizer.nextToken();
final String password = tokenizer.nextToken();
boolean authenticationStatus =s.auth(username, password);
return authenticationStatus;
}
}
and the filter
public class RestAuthenticationFilter implements javax.servlet.Filter {
public static final String AUTHENTICATION_HEADER = "Authorization";
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filter) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authCredentials = httpServletRequest
.getHeader(AUTHENTICATION_HEADER);
// better injected
AuthenticationService authenticationService = new AuthenticationService();
boolean authenticationStatus = authenticationService
.authenticate(authCredentials);
if (authenticationStatus) {
filter.doFilter(request, response);
} else {
if (response instanceof HttpServletResponse) {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse
.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
what i need to know is : how to pass the username and password or maybe just the id of the client to the methods of Restful after the Authentication.
A solution different to my comment: you can look for the HTTP request method and then make a decision if you call a authentication method or not:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filter) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String authCredentials = httpServletRequest
.getHeader(AUTHENTICATION_HEADER);
boolean authenticationStatus;
// check request method
if (((HttpServletRequest).request).getMethod().equals("GET")) {
authenticationStatus=true;
} else {
// better injected
AuthenticationService authenticationService =
new AuthenticationService();
authenticationStatus = authenticationService
.authenticate(authCredentials);
}
if (authenticationStatus) {
filter.doFilter(request, response);
} else {
if (response instanceof HttpServletResponse) {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse
.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}
}
Update:
With JAX-RS a possible solution for obtaining more request information may look like this:
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
#GET
#Path("offers")
#Produces("application/xml")
public YourList getOffers(#Context HttpServletRequest request)
{
System.out.println("request to "+request.getRequestURI()+" , Auth: "+request.getHeader(AUTHENTICATION_HEADER));
// more stuff for obtaining data
}

Categories