Best Way to encode Fragment in URL - SpringBoot - spring

I have a spring boot application where an endpoint responds with a url for the client to redirect to. This correct url looks something like:
https://checkout.stripe.com/c/pay/stuff#morestuff which is being properly logged below
Here is my spring boot code:
#PostMapping(path = "/create-checkout-session", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> createSubscription(#RequestParam String priceId) throws StripeException {
...
Session session = Session.create(params);
log.info("Redirecting with session {}", session.getUrl());
return ResponseEntity.status(HttpStatus.FOUND).location(URI.create(session.getUrl())).build();
}
However, when my client receives a response from my endpoint, the URL is truncated up to the # to something like:https://checkout.stripe.com/c/pay/stuff (removing the #morestuff).
I found this post about needing to encode the # and I was wondering what the best way to do this is?

Related

Spring Security 6.0 CsrfToken behavior change

I tested Spring Security as part of my Spring Boot Setup in version 6.0-M5, 6.0-RC1 and 6.0-RC2. I recognized a behavior change and wanted to ask whether this may be a bug. I return the CSRF token as a serialized JSON, but since RC1 the content of the token in the JSON is garbage.
My working code in Spring Boot 6 Milestone 5 still working as expected.
#RestController
public class CsrfController {
#GetMapping("/rest/user/csrf")
public CsrfToken csrf(CsrfToken token) {
return token;
}
}
In my use case I query the controller using a unit test.
#LocalServerPort
int serverPort;
#Autowired
private TestRestTemplate webclient;
#Test
public void getCsrf() {
ResponseEntity<String> entity = webclient.getForEntity("http://localhost:" + serverPort +
"/rest/user/csrf", String.class);
// ... here some code to get the token from the JSON body ...
assertTrue(result.matches("^[a-f0-9\\-]+$"));
This is the first query of the server. A session object between client and server is not established in past queries. This worked in M5 but stopped working in Spring Boot 6 RC1 and RC2
The following controller code made it work again in RC2:
#GetMapping("/rest/user/csrf")
public CsrfToken csrf(HttpServletRequest request, HttpServletResponse response) {
CsrfToken repoToken = tokenRepo.loadToken(request);
if (repoToken != null) {
return repoToken;
}
// required because it is required but ay not be initialized by the tokenRepo
request.getSession();
repoToken = tokenRepo.generateToken(request);
tokenRepo.saveToken(repoToken, request, response);
return repoToken;
}
If I tried the old code in RC2, I received on client side a malformed string. I did not receive a UUID styled token in my JSON serialized response body. I think it is related to the uninitialized session object.
Is this a bug or is an uninitialized session and a resulting not working CrsfToken specified behavior?
I think the issue is in the way I try to get and use the XSFR token.
Because I want to use an Angular frontend, I configured my token repository to provide the tokens via Cookie.
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
This produces cookies the old UUID style. However the authentication expects the new tokens as generated by https://github.com/spring-projects/spring-security/issues/11960 . Probably the cookie mechanism still needs to be migrated until final Spring Boot 3.0.

How to enable Spring Reactive Web MVC to handle Multipart-file?

I'm trying to use the new reactive web-mvc implementation in a spring boot 2.0 application. I'm trying to define a method which consume multipart file but do not succeed at making it working :( - I always get a 415 error.
On one hand I have a controller containing the following request mapping :
#RequestMapping(method = RequestMethod.POST, path = "/myPath/{param}/{param2}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseBody
public Mono<Void> postFile(
#RequestBody MultipartFile data,
#PathVariable("param") String param,
#PathVariable("param2") String param2,
#RequestHeader(name = HEADER_DATE, required = false) #DateTimeFormat(pattern = DATE_FORMAT) Instant instant
){
return fileService.handleData(Mono.just(data), param, param2, instant);
}
On the other hand I had to add a server on the top of the basic dependencies as it seems netty do not handle multipart files. I so added the spring-boot-starter-tomcatdependency which enabled the MultipartAutoConfiguration to be matched and satisfied on application auto configuration.
When posting something using a curl call :
curl 'Meta-Date: 20170101104532' --form "file=#file.bin" http://localhost:8082/myPath/foo/bar
while debug logs are activated (logging.level.org.springframework.web=DEBUG) I got this exception :
org.springframework.web.server.UnsupportedMediaTypeStatusException: Request failure [status: 415, reason: "Content type 'multipart/form-data;boundary=------------------------58fa43b8f1a26de4' not supported"]
This error is thrown by the RequestBodyArgumentResolver which has the the following supported media types : [*/*, text/xml, application/*+json;charset=UTF-8, application/xml, text/plain;charset=UTF-8, application/x-www-form-urlencoded, application/json;charset=UTF-8] provided by 9 DecoderHttpMessageReader.
Before posting I also took a look at :
Spring MultiPart MediaType Unsupported which seems to not be relevant here as my autoconf report contains the following entry : MultipartAutoConfiguration#multipartResolver matched
set content-type to utf-8 with angularjs $http Adding a header setting Content-Transfer-Encoding: binary didn't changed anything.
My understanding is that Spring web 5.0 uses a new request decoder system as I don't find these classes on a spring 4 spring boot application, and there is not yet any DecoderHttpMessageReader dealing with multipart file
Did I miss something ? Or should I wait one to be implemented ?
Okay, It seems this is just not implemented for now as it currently exists a pull request for this feature : Add reactive multipart request support #1201
Should have check this earlier...
[EDIT] : The issue has been solved and merged into Spring master branch. Should no longer be an issue.
#PutMapping(value="/{..}",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<Void> save(#RequestPart("file") FilePart multipartFormData,#RequestParam("fileName") String fileName,#PathVariable("..") String ..) throws IOException {
List<ByteBuffer> bytesList = new LinkedList<>();
multipartFormData.content().
subscribe(item->bytesList.add(item.asByteBuffer()));
int totalBytes = bytesList.stream().mapToInt(item->item.capacity()).sum();
ByteBuffer buffer = ByteBuffer.allocate(totalBytes);
bytesList.stream().forEach(byteBuff->buffer.put(byteBuff));
baseImageHandler.saveImage(buffer, fileName, baseItemId);
return Mono.empty();
}
Please note that it is a dev verison, but this is how I have managed to do it.

Redirect after a POST vs redirect after a GET

I'm working on a Spring project. Here's my basic controller:
#Controller
public class Editor {
private static final String EDITOR_URL = "/editor";
#RequestMapping(value = EDITOR_URL, method = {POST, GET})
public ModelAndView edit(HttpServletResponse response,
HttpServletRequest request,
RedirectAttributes redirectAttributes,
#RequestParam Map<String, String> allRequestParams) {
// The code is trimmed to keep it short
// It doesn't really matter where it gets the URL, it works fine
String redirectURL = getRedirectUrl();
// redirectURL is going to be /editor/pad.html
return new ModelAndView("redirect:" + redirectUrl);
}
From web.xml:
<servlet-mapping>
<servlet-name>edm</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
I have jetty embedded and I'm trying an integration test:
#Test
public void redirectToEditPadSuccess() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(END_POINT + "/edm/editor")
.queryParam("param1", "val1")
.queryParam("param2", "val2");
HttpEntity<?> entity = new HttpEntity<>(requestHeaders);
HttpEntity<String> response = restTemplate.exchange(
builder.build().encode().toUri(),
HttpMethod.POST,
entity,
String.class);
HttpHeaders httpResponseHeaders = response.getHeaders();
List<String> httpReponseLocationHeader = httpResponseHeaders.get("Location");
assertTrue(httpReponseLocationHeader.size() == 1);
String redirectLocation = httpReponseLocationHeader.get(0);
URL redirectURL = new URL(redirectLocation);
assertEquals("/edm/editor/pad.html", redirectURL.getPath());
}
So when I execute the above it works fine and I get a green OK sign.
Now, the controller accepts both POST and GET methods. If I execute the test using GET method (replacing HttpMethod.POST with HttpMethod.GET), the result is going to be a 404.
The logs reveal:
WARN org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/edm/editor/pad.html] in DispatcherServlet with name 'edm'
I tried to debug the application up to the DispatcherServlet and weird thing is that with GET, after the 302/redirect response the Dispatcher is being called again and turns this to a 200 - no idea how and why.
I'm going to try and explain what is going on, and then provide a solution.
First let's forget that you're running a rest case, and assume that the request is coming from a browser.
Scenario 1 : Browser issues a GET request, and the server responds with a redirect.
In this case, the browser reads the response status code as 302 and makes another request using the Location response header. The user sees a quick reload but doesn't notice anything wrong.
Scenario 2 : Browser issues a POST request, and the server responds with a redirect.
In this case, the browser does follow the response code and does issue a redirect, but, the second request is a GET request, and the original request body is lost in the second request. This is because strictly by HTTP standards, the browser cannot "re-post" data to the server, without an explicit request by the user. (Some browsers will prompt the user and ask them if they want to re-post)
Now in your code, RestTemplate is using what I presume to be a default HttpClientFactory, most likely this one: https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java.
This is how RestTemplate is handling the above two scenarios:
Scenario 1 : Rest Template issues a GET request, and the server responds with a redirect.
Here the Rest Template instance will work exactly as a browser would. That's the reason why two requests are being made, and the second one is looking for /edm/editor/pad.html
Scenario 2 : Rest Template issues a POST request, and the server responds with a redirect.
In this case, Rest Template will stop after the first call, because it cannot automatically override your request method and change it to GET, and it cannot prompt you for permission, like a browser would.
Solution: When creating an instance of RestTemplate, pass it an overridden version of the client factory, something like
new RestTemplate(new SimpleClientHttpRequestFactory() {
protected void prepareConnection(HttpURLConnection conn, String httpMethod) throws IOException {
super.prepareConnection(conn, httpMethod);
conn.setInstanceFollowRedirects(false);
}
});
This will instruct rest template to stop after the first request.
Sorry for the lengthy answer, but I hope this clarifies things.

Spring-ws SoapHeader fields access in endpoint method

We are developing a contract-first WebService using spring-ws 2.2.0. We are trying to manage the authentication using a custom tag, named AuthToken, located in the SoapHeader.
The AuthToken has the following structure:
<authToken>
<username>USERNAME</xa:username>
<password>PASSWORD</xa:password>
</authToken>
We are able to generate a WSDL schema containing the specified custom authentication tag inside the SoapHeader.
The problem is that when the client performs the call towards our server we are not able to unmarshal the AuthToken tag (located in the SoapHeader) in our Ws Endpoint implementation.
Using the #RequestPayload annotation in the binding method signature (handleMethodRequest as specified in the example below), we are able to access the unmarshalled payload content (located in the SoapBody).
We tried to make the same thing with the SoapHeader content without success.
In the following code examples we show you what we would like to obtain:
1
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, #SoapHeader(value = "authToken") AuthToken authToken) { }
2
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, AuthToken authToken) { }
3
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, org.springframework.ws.soap.SoapHeader header) { }
4
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, MessageContext messageContext) { }
In case 1, 2 we obtain the following error:
No adapter for endpoint [MethodResponse EndpointImplementation.handleMethodRequest(MethodRequest, AuthToken) throws java.lang.Exception]: Is your endpoint annotated with #Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
In case 3, 4 we have no errors but we are not able to handle SoapHeader or MessageContext (respectively in case 3 and 4) to reach our purposes, accessing the AuthToken to retrieve username and password sub element.
Looking for a solution in the web we found that many people having the same problem uses Spring Interceptors to handle the authentication.
Following the "Interceptors-way" we should access the AuthToken inside the interceptor. Unfortunately we need to use AuthToken field inside handleMethodRequest method for other purposes, for example loading user specific data, not accessible outside handleMethodRequest.
Therefore we cannot follow this way because we need to refer user specific data inside the handleMethodRequest method.
Does anyone know how can we solve the problem? Thanks in advance.
For that use case, the only supported combination of annotation and parameter type is #SoapHeader and SoapHeaderElement. Spring-WS currently doesn't support unmarshalling headers.
A hacky way of getting the value from interceptor to the handleMethodRequest is using a static ThreadLocal instance. Since the same thread that invokes the interceptor also invokes the handleMethodRequest you can use
ThreadLocal.set(AuthToken); // in interceptor.
ThreadLocal.get();// in handler and then clear it after use.
Also, I noticed that #SoapHeader(value = "{authToken") in your example does not have } is that a typo here or in your code?

POST request to Spring REST web service fails with HTTP status 415

I have set up a spring RESTful web service for taking in the username and password from a user. Been following the tutorial on Spring IO
My service is set up to accept user name and password as shown below:
#Controller
#RequestMapping("/users")
public class UserCommandController {
private static Logger log = LoggerFactory.getLogger(UserCommandController.class);
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity createUser(#RequestBody UserDetail userDetail, UriComponentsBuilder builder) {
User newUser = new User();
newUser.setEmail(userDetail.getEmail());
newUser.setPassword(userDetail.getPassword());
newUser.setUserName(userDetail.getUsername());
try {
UserFactory.getInstance().saveNewUser(newUser);
} catch(UserException ue) {
log.error("Saving user failed. Exception: "+ue.getMessage());
}
return new ResponseEntity(HttpStatus.OK);
}
}
I am sending POST parameters to the service as a test through Google chrome plugin POSTMAN but I get "HTTP: 415..The server refused this request because the request entity is in a format not supported by the requested resource for the requested method."
Does anyone have an idea what I am doing wrong ?
Set the header:
Content-Type=application/json
This solved my problem!
The HTTP 415 response code means that the server expected data posted with a different content type. It appears you simply posted a form with username, password and email as parameters. That would result in a content-type of application/x-www-form-urlencoded.
Try posting with a content-type of application/xml or application/json. In your post body, you will need to put your data in the corresponding format. For example, if you use application.xml, the XML body should look something like:
<userDetail>
<userName>xxx</userName>
<password>xxx</password>
<email>xxx</email>
</userDatail>
Of course the exact format (i.e. element names) depends on the XML bindings. In fact, whether or not the expected format is XML or JSON or something else is also likely a server configuration.
Posting a request of this type cannot easily be done with a browser. You will need some other HTTP client. A tool like SOAP-UI might be a good bet.

Resources