Spring Security: deserialize request body twice (oauth2 processing) - java

This question is a result of some work I'm doing with the Spring Security Oauth2 library. I've set up an oauth2 authorization server and an oauth2 resource server, the latter of which is meant to authorize based on access tokens.
The problem is that normally access tokens are passed in a header, but the big client we're setting this up for wants to pass the access token in a JSON request body. There's an interface you can use to set up custom access token extraction, but it looks like this:
public interface TokenExtractor {
/**
* Extract a token value from an incoming request without authentication.
*
* #param request the current ServletRequest
* #return an authentication token whose principal is an access token (or null if there is none)
*/
Authentication extract(HttpServletRequest request);
}
So, as best I can tell, all I have access to is the raw HTTPServletRequest, from which I need to deserialize the request and extract the access token.
Further complicating things, though, is the fact that the request body also contains other parameters needed for processing, so I want to deserialize it to a DTO class that I pass into my controller, something like so:
#RequestMapping("/oauth/someresource")
#Transactional
public Map<String, String> resource(#AuthenticationPrincipal UserDetails userDetails,
#RequestBody ClientRequestDto clientRequestDto) {
// Do some processing based on the request dto
}
I tried manually deserializing the request in the token extractor, but then I get an error "java.lang.IllegalStateException: getReader() has already been called for this request".
I was brainstorming a few possible solutions that I could research, and so far I've come up with:
find a way to reset the input stream
deserialize the object in the Token Extractor, attach it to the raw request object, and just access the raw request object in my controller instead of using #RequestBody
like 2, but find a way to add a custom deserializer that fetches the object attached to the raw request instead of processing the request's input stream.
Anyways, those are just some thoughts, if anyone has any ideas in terms of an elegant way of solving this, I'd greatly appreciate it.
EDIT: I did find this question which is similar: Spring reading request body twice, and the last answer did have one possible solution (creating a decorator request class that allows multiple input stream reads and creating a filter early on in the filter chain that wraps the HttpServletRequest). It seems workable, but a little heavy duty, so I'll leave this up to see if anyone has any other ideas as well.

So I ended up finding yet another question that addressed this issue that I didn't see before posting (How can I read request body multiple times in Spring 'HandlerMethodArgumentResolver'?). That one also suggested creating a decorator around the HttpServletRequest, so I adapted the info from http://www.myjavarecipes.com/how-to-read-post-request-data-twice-in-spring/, adding a protection against large requests.
Here's what I came up with, in case anyone has any feedback:
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
// We include a max byte size to protect against malicious requests, since this all has to be read into memory
public static final Integer MAX_BYTE_SIZE = 1_048_576; // 1 MB
private String _body;
public MultiReadHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
_body = "";
InputStream bounded = new BoundedInputStream(request.getInputStream(), MAX_BYTE_SIZE);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bounded));
String line;
while ((line = bufferedReader.readLine()) != null){
_body += line;
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(_body.getBytes());
return new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener readListener) {
}
};
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
I used the following configuration:
#Bean
FilterRegistrationBean multiReadFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
MultiReadRequestFilter multiReadRequestFilter = new MultiReadRequestFilter();
registrationBean.setFilter(multiReadRequestFilter);
registrationBean.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER - 2);
registrationBean.setUrlPatterns(Sets.newHashSet("/path/here"));
return registrationBean;
}

Related

Passing parameters between JAX-RS WriterInterceptor and ReaderInterceptor

I'm using JAX-RS and within the WriterInterceptor, I need to access some information contained in the original request.
As an example, consider the below request body.
{
"ClientId": "MY_CLIENT_ID",
"UserId": "MY_USER_ID",
"AccountId": "MY_ACCOUNT_ID",
"Scope" : "MY_SCOPES",
}
Within my WriteInterceptor, I need to read Client ID and User ID from the request and add those values to the response.
I am currently working on a ReadInterceptor implementation for this. I initially assumed there is a way to put parameters to ReaderInterceptorContext and then read it somehow from the WriterInterceptorContext. But It seems there is no way to do that. ( Please correct me if I'm wrong).
So, now I'm trying to use a concurrent hashmap to store these parameters in the ReaderInterceptor and retrieve it in the WriteInterceptor. I need a unique key to create the correlation between request and response. Is it ok to use the thread ID for this?
Please point me if there is a better approach to resolve this problem
I resolved this problem by adding a container response filter which can add a header to the response. Read interceptor reads required parameters from the request and set those as context properties.
#Override
public Object aroundReadFrom(ReaderInterceptorContext readerInterceptorContext)
throws IOException, WebApplicationException {
InputStream is = readerInterceptorContext.getInputStream();
String requestBody = new Scanner(is, StandardCharsets.UTF_8.name()).useDelimiter("\\A").next();
JSONObject request = new JSONObject(requestBody);
//Adding the stream back to the context object
readerInterceptorContext.setInputStream(new ByteArrayInputStream(requestBody.getBytes()));
//Adding properties to read in filter
readerInterceptorContext.setProperty("ClientId", request.get("ClientId"));
readerInterceptorContext.setProperty("UserId","UserId"));
return readerInterceptorContext.proceed();
}
These properties are then read inside the container response filter and added as a response header.
#Override
public void filter(ContainerRequestContext containerReqContext, ContainerResponseContext containerResponseContext) {
//Adding temporary headers to read in WriterInterceptor
containerResponseContext.getHeaders().add(
"ClientId", containerReqContext.getProperty("ClientId"));
containerResponseContext.getHeaders().add(
"UserId", containerReqContext.getProperty("UserId"));
}
Existing writer interceptor read these headers, add those to JWT and then remove as header values. I did a POC for this and it is working as expected
#Override
public void aroundWriteTo(WriterInterceptorContext writerInterceptorContext) throws IOException {
OutputStream outputStream = writerInterceptorContext.getOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writerInterceptorContext.setOutputStream(baos);
String clientId = writerInterceptorContext.getHeaders().getFirst("ClientId").toString();
String user = writerInterceptorContext.getHeaders().getFirst("UserId").toString();
}

AWS Java Lambda Function with API Gateway - POJO input and OutputStream output

I'm creating a simple AWS Lambda Function in Java that creates and returns a PDF. The function is invoked by an API Gateway. The input is a simple POJO class, but the output should be an OutputStream for the file.
For the input, I've tried creating a POJO class and just using the APIGatewayProxyRequestEvent and either works fine. Below is a simple example I used that takes in a input and prints back the query string parameters.
public class LambdaFunctionHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
#Override
public APIGatewayProxyResponseEvent handleRequest( APIGatewayProxyRequestEvent input, Context context ) {
return new APIGatewayProxyResponseEvent()
.withStatusCode(200)
.withHeaders(Collections.emptyMap())
.withBody("{\"input\":\"" + input.getQueryStringParameters() + "\"}");
}
}
That works fine, but now I need to alter it to use an OutputStream as the the output. How can this be done? I see that I can use the RequestStreamHandler and AWS has some documentation on implementing this. However, that would force my input to be an InputStream, which I'm not sure how that would work with the API Gateway.
How can I serve this PDF back to the client requesting it?
Remember that the POJO method of the Lambda handler is a convenience only. Ultimately, you could do this yourself and use the InputStream/OutputStream Lambda pattern. Something like:
public void handleRequest(InputStream inputStream,
OutputStream outputStream,
Context context) throws IOException {
String inputString = new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.joining("\n"));
ObjectMapper objectMapper = new ObjectMapper();
APIGatewayProxyRequestEvent request = objectMapper.readValue(inputString, APIGatewayProxyRequestEvent.class);
// do your thing, generate a PDF
byte[] thePDF = ...
// create headers
Map<String, String> headers = new HashMap<>();
headers.put("Content-type", "application/pdf");
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent().
.withStatusCode(200)
.withHeaders(headers)
.withBody(Base64.Encoder.encode(thePDF))
.withIsBase64Encoded(Boolean.TRUE);
outputStream.write(objectMapper.writeValueAsString(response)
.getBytes(StandardCharsets.UTF_8));
}
However, I'm not convinced that this is really any better. If you want to return just the PDF without the APIGatewayProxyResponseEvent you can but now you'll have to update API Gateway to correctly send the Content-Type header.

Spring - Retry request if service returns 409 HTTP Code

I have an Spring + CXF application which consumes a Transmission API: Transmission RPC running in another server.
According to Transmission docs, you need to send a token which is generated on the first request. The server then responds with a 409 http code along with a header containing the token. This token should be sent on all subsequent calls:
2.3.1. CSRF Protection Most Transmission RPC servers require a X-Transmission-Session-Id header to be sent with requests, to prevent
CSRF attacks. When your request has the wrong id -- such as when you
send your first request, or when the server expires the CSRF token --
the Transmission RPC server will return an HTTP 409 error with the
right X-Transmission-Session-Id in its own headers. So, the correct
way to handle a 409 response is to update your
X-Transmission-Session-Id and to resend the previous request.
I was looking for solution either using a CXF filter or interceptor, that basically will handle the 409 response and retry the initial request adding the token header. I'm thinking that clients can persist this token and send it in future calls.
I'm not very familiar with cxf so I was wondering if this can be accomplish and how. Any hint would be helpful.
Thanks!
Here spring-retry can be utilized which is now an independent project and no longer part of spring-batch.
As explained here retry callback will help make another call updated with the token header.
Pseudo code / logic in this case would look something like below
RetryTemplate template = new RetryTemplate();
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
/*
* 1. Check if RetryContext contains the token via hasAttribute. If available set the header else proceed
* 2. Call the transmission API
* 3.a. If API responds with 409, read the token
* 3.a.1. Store the token in RetryContext via setAttribute method
* 3.a.2. Throw a custom exception so that retry kicks in
* 3.b. If API response is non 409 handle according to business logic
* 4. Return result
*/
}
});
Make sure to configure the RetryTemplate with reasonable retry & backoff policies so as to avoid any resource contention / surprises.
Let know in comments in case of any queries / roadblock.
N.B.: RetryContext's implementation RetryContextSupport has the hasAttribute & setAttribute method inherited from Spring core AttributeAccessor
Assuming you are using Apache CXF JAX RS Client it is easy to do by just creating a custom Runtime Exception and ResponseExceptionMapper for it. So the idea is to manually convert 409 outcomes to some exception and then handle them correctly (in your case retry the service call).
See following code snipped for fully working example.
#SpringBootApplication
#EnableJaxRsProxyClient
public class SpringBootClientApplication {
// This can e stored somewhere in db or elsewhere
private static String lastToken = "";
public static void main(String[] args) {
SpringApplication.run(SpringBootClientApplication.class, args);
}
#Bean
CommandLineRunner initWebClientRunner(final TransmissionService service) {
return new CommandLineRunner() {
#Override
public void run(String... runArgs) throws Exception {
try {
System.out.println(service.sayHello(1, lastToken));
// catch the TokenExpiredException get the new token and retry
} catch (TokenExpiredException ex) {
lastToken = ex.getNewToken();
System.out.println(service.sayHello(1, lastToken));
}
}
};
}
public static class TokenExpiredException extends RuntimeException {
private String newToken;
public TokenExpiredException(String token) {
newToken = token;
}
public String getNewToken() {
return newToken;
}
}
/**
* This is where the magic is done !!!!
*/
#Provider
public static class TokenExpiredExceptionMapper implements ResponseExceptionMapper<TokenExpiredException> {
#Override
public TokenExpiredException fromResponse(Response r) {
if (r.getStatus() == 409) {
return new TokenExpiredException(r.getHeaderString("X-Transmission-Session-Id"));
}
return null;
}
}
#Path("/post")
public interface TransmissionService {
#GET
#Path("/{a}")
#Produces(MediaType.APPLICATION_JSON_VALUE)
String sayHello(#PathParam("a") Integer a, #HeaderParam("X-Transmission-Session-Id") String sessionId)
throws TokenExpiredException;
}
}

How to parse a URL and run a method with Spring MVC 'reflectively'?

I have a Spring Boot application that uses Spring MVC in the usual manner, with a bunch of #RequestMapping methods, Freemarker definitions, and the like. This is all tied together with a WebMvcConfigurerAdapter class.
I'd like to provide a service where the user submits a list of valid URLs, and the webapp would work out which controller would be called, passes in the parameters, and returns a combined result for every URL — all in one request.
This would save the user from having to make hundreds of HTTP calls, but would still allow them to make one-off requests if need be. Ideally, I'd just inject an auto-configured Spring bean, so I don't have to repeat the URL resolving and adapting and handling that Spring does internally, and the controller's list of other controllers would never go out of sync with the real list of controllers.
I expected to write something like this (simplified to only deal with one URL, which is pointless but easier to understand):
#Autowired BeanThatSolvesAllMyProblems allMappings;
#PostMapping(path = "/encode", consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public String encode(#RequestBody String inputPath) {
if (allMappings.hasMappingForPath(inputPath)) {
return allMappings.getMapping(inputPath).execute();
} else {
return "URL didn't match, sorry";
}
}
Instead, I've had to define Spring beans I don't know what they do and have been repeating some of what Spring is meant to do for me, which I'm worried won't work quite the same as it would if the user just made the call themselves:
// these two are #Beans, with just their default constructor called.
#Autowired RequestMappingHandlerMapping handlers;
#Autowired RequestMappingHandlerAdapter adapter;
#PostMapping(path = "/encode", consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public String encode(#RequestBody String inputText) {
final HttpServletRequest mockRequest = new MockHttpServletRequest(null, inputText);
final StringBuilder result = new StringBuilder();
this.handlers.getHandlerMethods().forEach((requestMappingInfo, handlerMethod) -> {
if (requestMappingInfo.getPatternsCondition().getMatchingCondition(mockRequest) != null) {
try {
final MockHttpServletResponse mockResponse = new MockHttpServletResponse();
result.append("Result: ").append(adapter.handle(mockRequest, mockResponse, handlerMethod));
result.append(", ").append(mockResponse.getContentAsString());
result.append("\n");
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
});
return result.toString();
}
I thought I was doing quite well going down this path, but it's failing with Missing URI template variable errors, and not only do I have no idea how to put the request parameters in (another thing which Spring could be able to handle itself), but I'm not even sure that this is the right way to go about doing this. So how do I simulate a Spring MVC request "reflectively", from within the webapp itself?
JSON API spec. solves this problem by allowing sending multiple operations per request. There even exists a quite mature implementation that supports this feature which is called Elide. But I guess this is might not fully meet your requirements.
Anyway, here's what you can do.
You have to take into consideration that DispatcherServlet holds handlerMappings list that is used to detect appropriate request handler and handlerAdaptors. The selection strategy for both lists is configurable (see DispatcherServlet#initHandlerMappings and #initHandlerAdapters).
You should work out a way you would prefer to retrieve this lists of handlerMappings/initHandlerAdapters and stay in sync with DispatcherServlet.
After that you can implement your own HandlerMapping/HandlerAdaptor (or present a Controller method as in your example) that would handle the request to /encode path.
Btw, HandlerMapping as javadoc says is
Interface to be implemented by objects that define a mapping between
requests and handler objects
or simply saying if we take DefaultAnnotationHandlerMapping that would map our HttpServletRequests to #Controller methods annotated with #RequestMapping. Having this mapping HandlerAdapter prepares incoming request to consuming controller method, f.ex. extracting request params, body and using them to call controller's method.
Having this, you can extract URLs from main request, create a list of stub HttpRequests holding the information needed for further processing and loop through them calling this:
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
having a handlerMapping you call
HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
and then you can finally call
ha.handle(processedRequest, response, mappedHandler.getHandler());
which in turn would execute the controller's method with params.
But having all this, I would not recommend to following this approach, instead, think about usage of JSON API spec or any other.
How about using Springs RestTemplate as client for this? You could call your controllers within the spring controller as if it would be an external resource:
#ResponseBody
public List<String> encode(#RequestBody List inputPaths) {
List<String> response = new ArrayList<>(inputPaths.size());
for (Object inputPathObj : inputPaths) {
String inputPath = (String) inputPathObj;
try {
RequestEntity.BodyBuilder requestBodyBuilder = RequestEntity.method(HttpMethod.GET, new URI(inputPath)); // change to appropriate HttpMethod, maybe some mapping?
// add headers and stuff....
final RequestEntity<Void> requestEntity = requestBodyBuilder.build(); // when you have a request body change Void to e.g. String
ResponseEntity<String> responseEntity = null;
try {
responseEntity = restTemplate.exchange(requestEntity, String.class);
} catch (final HttpClientErrorException ex) {
// add your exception handling here, e.g.
responseEntity = new ResponseEntity<>(ex.getResponseHeaders(), ex.getStatusCode());
throw ex;
} finally {
response.add(responseEntity.getBody());
}
} catch (URISyntaxException e) {
// exception handling here
}
}
return response;
}
Note that generic do not work for the #RequestBody inputPaths.
See alse http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html and https://spring.io/guides/gs/consuming-rest/ .
I agree with the other answers that you should consider this feature outside of your project, instead of having it in the code. It is a question of design and you can choose the approach you want. Based on your comment that these are GET requests, you can achieve what you want with a request dispatcher to trigger your requests within your special Controller service method for each URL and capture the response with a HttpServletResponseWrapper instance.
In the following code sample, the "consolidate" method takes comma separated URLs like this ("http://localhost:8080/index/index1,index2", here "index1,index2" is the URL list), consolidates their text output into a single payload and returns it. For this example URL, the consolidated outputs of http://localhost:8080/index1 and http://localhost:8080/index2 will be returned. You might want to extend/modify this with added parameters, validation, etc for the URLs. I tested this code with Spring Boot 1.2.x.
#Controller
public class MyController {
#RequestMapping("/index/{urls}")
#ResponseBody
String consolidate(#PathVariable String[] urls, HttpServletRequest request, HttpServletResponse response) {
StringBuilder responseBody = new StringBuilder();
//iterate for each URL provided
for (String url : urls) {
RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher("/" + url);
HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper((HttpServletResponse) response) {
private CharArrayWriter output = new CharArrayWriter();
#Override
public PrintWriter getWriter() {
return new PrintWriter(output);
}
#Override
public String toString() {
return output.toString();
}
};
try {
dispatcher.include(request, wrapper);
//append the response text
responseBody.append(wrapper.toString());
} catch (ServletException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//This holds the consolidated output
return responseBody.toString();
}
#RequestMapping("/index1")
String index1() {
return "index1";
}
#RequestMapping("/index2")
String index2() {
return "index2";
}
}

Changing content type in jax-rs REST service

Forgive me, but I may not be familiar with all the lingo necessary to ask this question properly.
I'm working on a fairly simple REST web service in Java using the org.apache.cxf.jaxrs.ext implementation of jax-rs. The method header is like this:
#GET
#Path("json/{fullAlias}")
#Produces({"application/json"})
public String json(#PathParam("fullAlias") String fullAlias, #Context MessageContext req)
where MessageContext is org.apache.cxf.jaxrs.ext.MessageContext.
There are two things I'm trying to accomplish that I can't seem to figure out:
Change the content-type if certain conditions are met (e.g. for an error)
Change the status code of the response
I've tried using changing the response by accessing it through the MessageContext:
HttpServletResponse response = req.getHttpServletResponse();
response.setContentType("text/plain")
response.setStatus("HttpServletResponse.SC_BAD_REQUEST);
But these changes have no bearing on the response sent; with or without the #Produces annotation, setting the content type inside the method doesn't affect the actual content type (With the annotation, it of course returns "application/json", without it defaults to "text/html").
I am returning a simple String as the body. I've entertained trying to return a javax.ws.rs.core.Response object to do what I want, but I don't know much about it.
How would I change the content type and/or the status codes from inside this method?
One approach is to throw a WebApplicationException, as described by Pace, which will work if you are looking to specifically handle an error condition. If you are looking to be able to change your content at any time for any reason, then you will want to take a look at returning a Response as the result of your service method rather than a String. Returning a Response gives you the greatest amount of control over how your service responds to the client request (it does require more code than returning a simple string).
Here is an example of how you would can make use of the Response object:
#GET
#Path("json/{fullAlias}")
public Response json(#PathParam("fullAlias") String fullAlias, #Context MessageContext req) {
...
if (success) {
ResponseBuilder rBuild = Response.ok(responseData, MediaType.APPLICATION_JSON);
return rBuild.build();
}
else {
ResponseBuilder rBuild = Response.status(Response.Status.BAD_REQUEST);
return rBuild.type(MediaType.TEXT_PLAIN)
.entity("error message")
.build();
}
}
I'm not sure if it's the best approach but I've done the following to solve your question #1.
public WebApplicationException createStatusException(String statusMessage) {
ResponseBuilder rb = Response.noContent();
rb = rb.type(MediaType.TEXT_PLAIN);
rb = rb.status(Status.BAD_REQUEST);
rb = rb.entity(statusMessage);
return new WebApplicationException(rb.build());
}
EDIT: I then threw the resulting WebApplicationException.
You can write your own Response Filter to change the content-type header.
#Provider
public class MimeAddingFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("Content-Type", "image/png");
}
}
This filter will add the "image/png" content-type header. You can also change or remove headers in JAX-RS response filters.

Categories

Resources