SpringBoot embedded tomcat server reads unicode character in query parameter as null - java

I have a rest end point designed in spring boot. Tomcat is being used as embedded server. It takes a query parameter.
When I pass query parameter as param1%uFF07 tomcat internally reads parameter as null
When I pass query parameter as param1%FF07 tomcat reads as some character.
tomcat only reads '%' character when followed by two hexadecimal numbers, if u is placed after '%' character tomcat parse parameter as null with message
Character decoding failed. Parameter [name] with value [param1%uFF07]
has been ignored. Note that the name and value quoted here may be
corrupted due to the failed decoding. Use debug level logging to see
the original, non-corrupted values. Note: further occurrences of
Parameter errors will be logged at DEBUG level.
Here is spring boot controller code
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value = "name", required = false) String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}

You are passing % sign in your url, but % is symbol in url, to pass % as it is... you will have to pass %25 then it will work as you expected.
So, if you pass %25uFF07 then it will show you %uFF07 as value.
No need to change anything in application.properties or any kind of settings. I have tested this in my project.
Please feel free to ask for any clarification. Hope It Helps.

I found out a way using filters. Basics about filters could be found over here. We can intercept request query string there and use Tomcat UDecoder class to parse the query string and if any exception is thrown we can show response of 400
public class SimpleFilter implements Filter {
private final UDecoder urlDecoder = new UDecoder();
private final Logger logger = LoggerFactory.getLogger(SimpleFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String queryString = httpServletRequest.getQueryString();
if (queryString != null) {
ByteChunk byteChunk = new ByteChunk();
byteChunk.setBytes(queryString.getBytes(), 0, queryString.length());
try {
urlDecoder.convert(byteChunk, true);
} catch (IOException ioException) {
logger.error("Hazarduos character found in request parameter.");
httpServletResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return;
}
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}

Related

Response body empty even when caching enabled

Cannot get response body in my filter. Caching enabled.
I have tried many filter implementations - Filter, OncePerRequestFilter and GenericFilterBean. I have also tried writing custom caching mechanism but non of that work. I have already one logging filter working - this filter is executed before the new one and serves for reading request and response. The logging filter works but I want to add another one which reads response and validate it against XSD. Problem is, that logging filter gets the response filled OK but the XML validator filter gets empty string.
My #Controller method just returns Callable. Asynchronous processing may not be problem tho because logger filter works well.
#Component
public class ResposeBodyXmlValidator extends OncePerRequestFilter {
private final XmlUtils xmlUtils;
private final Resource xsdResource;
public ResposeBodyXmlValidator(
XmlUtils xmlUtils,
#Value("classpath:xsd/some.xsd") Resource xsdResource
) {
this.xmlUtils = xmlUtils;
this.xsdResource = xsdResource;
}
#Override
protected void doFilterInternal(
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain
) throws ServletException, IOException {
ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(httpServletResponse);
doFilter(httpServletRequest, response, filterChain);
if (MediaType.APPLICATION_XML.getType().equals(response.getContentType())) {
try {
xmlUtils.validate(new String(response.getContentAsByteArray(), response.getCharacterEncoding()), xsdResource.getInputStream());
} catch (IOException | SAXException e) {
String exceptionString = String.format("Chyba při volání %s\nNevalidní výstupní XML: %s",
httpServletRequest.getRemoteAddr(),
e.getMessage());
response.setContentType(MediaType.TEXT_PLAIN_VALUE + "; charset=UTF-8");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.getWriter().print(exceptionString);
}
}
response.copyBodyToResponse(); // I found this needs to be added at the end of the filter
}
}
I expect all my filters to be able to read response body which is cached.
Update
I was mistaken myself. Even LoggerFilter cannot read seponse. I think this has something to do with asynchronous processing of a request.

How to get CasToken from server?

I want to do a Cas Authentication from Standalone-Application but it fails on getting the Ticket from server. Can anyone provide me example code for a method that returns the ticket as String so i can use it for the Authentication. As you see the only Parameter should be the URL from the server. Thats waht i have yet(i know casToken is initialized on null an it doesnt work).
protected String getCasTicket(String serviceUrl) {
String casToken = null;
if (casToken == null){
logger.error("Failed to get CAS-Token!");
}else{
logger.info("Got CAS-Token successful!");
}
return casToken;
}
public class CasAuthenticationServlet extends HttpServlet {
...
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// NOTE: The CasAuthenticationToken can also be obtained using
// SecurityContextHolder.getContext().getAuthentication()
final CasAuthenticationToken token = (CasAuthenticationToken) request.getUserPrincipal();
// proxyTicket could be reused to make calls to the CAS service even if the
// target url differs
final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl);
// Make a remote call using the proxy ticket
final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8");
String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
...
}
CasAuthenticationProvider constructs a CasAuthenticationToken including the details contained in the TicketResponse and the GrantedAuthoritys.
Control then returns to CasAuthenticationFilter, which places the created CasAuthenticationToken in the security context.
Cas Example: https://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/sample-apps.html#cas-sample
EDIT:
Please refer https://www.javaworld.com/article/3313114/what-is-a-java-servlet-request-handling-for-java-web-applications.html for creating a servlet

Sling Filter for Wrapping a Request Body

Use Case:
We are developing an AEM Closed User Group site where users will need to submit forms which trigger workflows. Since the users are authenticated, part of the workflow payload needs to include the user who initiated the form.
I'm considering using AEM Forms for this, which saves to nodes under /content/usergenerated/content/forms/af/my-site but the user is not mentioned in the payload (only the service user). In this case, there are two service users: workflow-service running the workflow, and fd-service which handled the form processing and initial saving. E.G. the following code called from the workflow step reports 'fd-service'
workItem.getWorkflowData().getMetaDataMap().get("userId", String.class);
To work around this constraint,
Workflow initiated from publish AEM instance: All workflow instances are created using a service user when adaptive forms, interactive communications, or letters are submitted from AEM publish instance. In these cases, the user name of the logged-in user is not captured in the workflow instance data.
I am adding a filter servlet to intercept the initial form submission before the AEM Forms servlet using a request wrapper to modify the request body adding the original userID.
In terms of forms, workflows and launchers.. This is basically the setup I have
https://helpx.adobe.com/aem-forms/6/aem-workflows-submit-process-form.html
I have reviewed the following resources:
How to change servlet request body in java filter?
https://coderanch.com/t/364591/java/read-request-body-filter
https://gitter.im/Adobe-Consulting-Services/acs-aem-commons?at=5b2d59885862c35f47bf3c71
https://helpx.adobe.com/experience-manager/6-4/forms/using/forms-workflow-osgi-handling-user-data.html
Here is the code for my wrapper
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletInputStream;
import java.io.*;
public class FormSubmitRequestWrapper extends SlingHttpServletRequestWrapper {
String requestPayload;
private static final Logger log = LoggerFactory.getLogger(FormSubmitRequestWrapper.class);
public FormSubmitRequestWrapper(SlingHttpServletRequest slingRequest) {
super(slingRequest);
// read the original payload into the requestPayload variable
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
// read the payload into the StringBuilder
InputStream inputStream = slingRequest.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
// make an empty string since there is no payload
stringBuilder.append("");
}
} catch (IOException ex) {
log.error("Error reading the request payload", ex);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException iox) {
log.error("Error closing bufferedReader", iox);
}
}
}
requestPayload = stringBuilder.toString();
}
/**
* Override of the getInputStream() method which returns an InputStream that reads from the
* stored requestPayload string instead of from the request's actual InputStream.
*/
#Override
public ServletInputStream getInputStream ()
throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestPayload.getBytes());
ServletInputStream inputStream = new ServletInputStream() {
public int read ()
throws IOException {
return byteArrayInputStream.read();
}
};
return inputStream;
}
}
Here is my filter
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.engine.EngineConstants;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Session;
import javax.servlet.*;
import java.io.IOException;
#Component(service = Filter.class,
immediate = true,
property = {
Constants.SERVICE_DESCRIPTION + "=Add the CUG userID to any UGC posts",
EngineConstants.SLING_FILTER_SCOPE + "=" + EngineConstants.FILTER_SCOPE_REQUEST,
Constants.SERVICE_RANKING + ":Integer=3000",
EngineConstants.SLING_FILTER_PATTERN + "=/content/forms/af/my-site.*"
})
public class DecorateUserGeneratedFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(DecorateUserGeneratedFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final SlingHttpServletResponse slingResponse = (SlingHttpServletResponse ) response;
final SlingHttpServletRequest slingRequest= (SlingHttpServletRequest) request;
FormSubmitRequestWrapper wrappedRequest = new FormSubmitRequestWrapper(slingRequest);
log.info("starting ConfirmAlumniStatus workflow");
log.info(getCurrentUserId(slingRequest));
chain.doFilter(wrappedRequest, slingResponse);
}
#Override
public void destroy() {
}
public String getCurrentUserId(SlingHttpServletRequest request) {
ResourceResolver resolver = request.getResourceResolver();
Session session = resolver.adaptTo(Session.class);
String userId = session.getUserID();
return userId;
}
}
When POST submissions get processed by this filter, I'm getting the error below stating the request body has already been read. So it seems the filter ranking might not be high enough.
25.06.2018 13:11:13.200 ERROR [0:0:0:0:0:0:0:1 [1529946669719] POST /content/forms/af/my-site/request-access/jcr:content/guideContainer.af.internalsubmit.jsp
HTTP/1.1] org.apache.sling.engine.impl.SlingRequestProcessorImpl
service: Uncaught Throwable java.lang.IllegalStateException: Request
Data has already been read at
org.apache.sling.engine.impl.request.RequestData.getInputStream(RequestData.java:669)
at
org.apache.sling.engine.impl.SlingHttpServletRequestImpl.getInputStream(SlingHttpServletRequestImpl.java:292)
at
javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:136)
at
my.site.servlets.FormSubmitRequestWrapper.(FormSubmitRequestWrapper.java:26)
at
my.site.servlets.DecorateUserGeneratedFilter.doFilter(DecorateUserGeneratedFilter.java:75)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73)
at
com.cognifide.cq.includefilter.DynamicIncludeFilter.doFilter(DynamicIncludeFilter.java:82)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68)
at
org.apache.sling.engine.impl.debug.RequestProgressTrackerLogFilter.doFilter(RequestProgressTrackerLogFilter.java:10
I don't think the service ranking is working. When I view
http://localhost:4502/system/console/status-slingfilter
my filter is listed as shown. Judging from the other filters listed, I think the leftmost number is the filter ranking. For some reason my filter is ranked 0 even though I set is as service.ranking=700
0 : class my.site.servlets.DecorateUserGeneratedFilter (id:
8402, property: service.ranking=700); called: 0; time: 0ms; time/call:
-1µs
Update: I was able to fix the filter rank, making it 700 still gave the IllegalStateException. Making it 3000 made that problem go away. But when request.getInputStream() is called from my wrapper. It returns null.
What you are trying to do might be the easy route, but might not be future-proof for new AEM releases.
You need total control of how your workflow is triggered!:
Your forms should have a field that contains the workflow path (and maybe other information needed for that workflow)
Create a custom servlet that your forms will post to.
In that servlet process all user posted values (from the form). But especially get a hold of the intended workflow path and trigger it using the workflow API.
This way you don't have to mess with launchers and the workflows are triggered by your users using their user id.
Hope this helps.
Right idea, wrong location.
The short answer is that when you implement the SlingHttpServletRequestWrapper it provides a default handling of method calls to the original SlingHttpServletRequest if you're adding a parameter on the fly what you want to do is to make sure that the methods that are interacting with the parameters are overridden so that you can be sure yours is added. So on initialization, call the original parameter map, copy those items in a new map which includes your own values.
Then over ride any methods that would request those values
getParameter(String)
getParameterMap()
getParameterNames()
getParameterValues(String)
Don't touch the InputStream, that's already been processed to obtain any parameters that are being passed in.
Additionally, that is one of two ways you can handle this type of use case, the other option is to use the SlingPOSTProcessors as documented
https://sling.apache.org/documentation/bundles/manipulating-content-the-slingpostservlet-servlets-post.html
which allows you to detect what is being written to the repository and modify the data to include, like your case, an additional field.
if you are looking for code example :
#SlingFilter(order = 1)
public class MyFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
return;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
ServletRequest request = servletRequest;
if (servletRequest instanceof SlingHttpServletRequest) {
final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) servletRequest;
request = new SlingHttpServletRequestWrapper(slingRequest) {
String userId = getCurrentUserId(slingRequest);
};
}
filterChain.doFilter(request, servletResponse);
}
#Override
public void destroy() {
return;
}

Forward request to another rest service using filter

I am writing a servlet filter to forward Jersy requests based on certain condition. But they does not seem to forwarding.
public class SampleFilter
extends GenericFilterBean
{
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException
{
String generateRedirectUrl=FormURL((HttpServletRequest)req);
RequestDispatcher dispatcher = req.getRequestDispatcher(generateRedirectUrl);
dispatcher.forward(req, resp);
}
private String FormURL(HttpServletRequest req)
{
// get the request check if it contains the customer
String reqUrl = req.getRequestURI();
log.info("Original Url is"+reqUrl);
if(reqUrl.contains("test"))
{
return "/api/abcd/" +"test";
}
return "Someurl";
}
}
I need to forward the url as below.
Original: http://localhost/api/test/1234/true
New URL:http://localhost/api/abcd/1234/true
Am I doing any thing wrong.
"Am I doing any thing wrong."
In general I think this will work - you can forward from a filter, but your logic is wrong.
Using your rule below:
Original: http://localhost/api/test/1234/true
New URL:http://localhost/api/abcd/1234/true
The code:
if(reqUrl.contains("test"))
{
return "/api/abcd/" +"test";
}
will produce /api/abcd/test which is not what you're after.
I would do something like the following:
private String formURL(HttpServletRequest req) {
// get the request check if it contains the customer
String reqUrl = req.getRequestURI();
if (log.isInfoEnabled() {
log.info("Original Url is"+reqUrl);
}
if(reqUrl.contains("test")) {
return reqUrl.replace("test", "abcd");
}
return "Someurl";
Also, the code is very brutal. This will also change the URL
http://test.site.com/abd/def
to
http://abcd.site.com/abd/def
which is probably not what you want. You'll probably need to either do more clever string manipulation or convert to something like the URI class which will allow you to target the path more accurately.
Hope this helps,
Will

How can I associate and inbound and outbound soaphandler with each-other

http://docs.oracle.com/javaee/5/api/javax/xml/ws/handler/soap/SOAPHandler.html
How can I associate an inbound handleMessage(SOAPMessageContext) call with and outbound handleMessage(SOAPMessageContext).
I've tried a few things, Weblogic doesn't reuse the context object so reference checking doesn't work. I can't find any property that indicates the request distinctly and therefor I can't seem to create a link between the request and response.
Alternatively is there a better way to get the request and response on web-logic problematically and associated together such that they can be dumped to the database for future debugging purposes.
Okay, so this may not work on all implementations of JAX-WS but for weblogic this certainly does.
What I've done
public final boolean handleMessage(SOAPMessageContext context) {
// Using `Object` type as we don't have any need to import servlet libraries into a JAX-WS library
Object o = context.get(MessageContext.SERVLET_REQUEST);
// o will hold a reference to the request
// if inbound.o == outbound.o the request objects are identical and therefor we can associate them.
return true;
}
I don't see why this wouldn't work in other containers but please double check before using this method.
This requires a bit of boiler plate code, but it's not dependent on an app server.
The AttachAttributesFilter adds a startTime and UUID to the attributes. These attributes are then read in LogSoapHandler for inbound and outbound messages.
A simple search in the log will show the input and output for a specific UUID.
#WebFilter("/yourwebservice")
public class AttachAttributesFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
request.setAttribute("STARTTIME", System.currentTimeMillis());
request.setAttribute("UUID",java.util.UUID.randomUUID().toString());
chain.doFilter(request, response);
}
}
Then I use the attributes in a LogSoapHander
public class LogSoapHandler implements
javax.xml.ws.handler.soap.SOAPHandler<SOAPMessageContext> {
public boolean handleMessage(SOAPMessageContext messagecontext) {
Boolean outbound = (Boolean) messagecontext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
String messageID = (String) httpSR.getAttribute("UUID");
Object startTime = httpSR.getAttribute("STARTTIME");
try {
final SOAPMessage message = messagecontext.getMessage();
String encoding = getMessageEncoding(message);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
boolean fault = (message.getSOAPBody().hasFault());
message.writeTo(baos);
String body = (baos.toString(encoding));
log.info(outbound+"|"+messageID+"|"+startTime+"|"+System.currentTimeMillis()+"|"+body+"|"+fault));
} catch (SOAPException | IOException ex) {
//handle your error
}
return true;
}
private String getMessageEncoding(SOAPMessage msg) throws SOAPException {
String encoding = "utf-8";
if (msg.getProperty(SOAPMessage.CHARACTER_SET_ENCODING) != null) {
encoding = msg.getProperty(SOAPMessage.CHARACTER_SET_ENCODING)
.toString();
}
return encoding;
}
And to be complete the HandlerChain boilder plate :
#HandlerChain(file="loghandler.xml")
public class MyWSDL {
..
}
loghandler.xml :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<javaee:handler-chains
xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<javaee:handler-chain>
<javaee:handler>
<javaee:handler-class>xxx.LogSoapHandler</javaee:handler-class>
</javaee:handler>
</javaee:handler-chain>
</javaee:handler-chains>

Categories