Code Repo: https://github.com/kenuiuc/springboot-demo
I have an endpoint comsuming data in application/x-www-form-urlencoded format
If I send a PUT request like this:
curl --request PUT \
--url http://localhost:18080/user \
--header 'Content-Type: application/x-www-form-urlencoded;charset=UTF-8' \
--data name=ken%
As you can see the data name=ken% is in illegal URL format so I get 500 response and the server-side error log:
{
"timestamp": "2021-05-27T02:25:36.849+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/user"
}
java.lang.IllegalArgumentException: URLDecoder: Incomplete trailing escape (%) pattern
at java.net.URLDecoder.decode(URLDecoder.java:187) ~[na:1.8.0_291]
at org.springframework.http.converter.FormHttpMessageConverter.read(FormHttpMessageConverter.java:356) ~[spring-web-5.3.7.jar:5.3.7]
at org.springframework.web.filter.FormContentFilter.parseIfNecessary(FormContentFilter.java:109) ~[spring-web-5.3.7.jar:5.3.7]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:88) ~[spring-web-5.3.7.jar:5.3.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.7.jar:5.3.7]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) ~[spring-boot-actuator-2.5.0-20210520.224402-561.jar:2.5.0-SNAPSHOT]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.7.jar:5.3.7]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.7.jar:5.3.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.7.jar:5.3.7]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) [tomcat-embed-core-9.0.46.jar:9.0.46]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.46.jar:9.0.46]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_291]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_291]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.46.jar:9.0.46]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_291]
My goal is to let it return 400 status instead of 500 since this error is actually the client's fault not my server's fault
So I extended the org.springframework.http.converter.FormHttpMessageConverter and overrided the read() method
public class CustomizedFormHttpMsgConverter extends FormHttpMessageConverter {
#Override
public MultiValueMap<String, String> read(#Nullable Class<? extends MultiValueMap<String, ?>> clazz,
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
MediaType contentType = inputMessage.getHeaders().getContentType();
Charset charset = (contentType != null && contentType.getCharset() != null ?
contentType.getCharset() : StandardCharsets.UTF_8);
String body = StreamUtils.copyToString(inputMessage.getBody(), charset);
String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(pairs.length);
for (String pair : pairs) {
int idx = pair.indexOf('=');
if (idx == -1) {
result.add(URLDecoder.decode(pair, charset.name()), null);
}
else {
try {
String name = URLDecoder.decode(pair.substring(0, idx), charset.name());
String value = URLDecoder.decode(pair.substring(idx + 1), charset.name());
result.add(name, value);
} catch (Throwable th) {
System.out.println("HI KEN I GOT HERE!");
throw new HttpMessageNotReadableException(th.getMessage());
}
}
}
return result;
}
}
Then I registered this customized converter in my configuration class:
#Configuration
public class CoreConfig {
#Bean("customizedFormHttpMsgConverter")
public FormHttpMessageConverter createMyConverter() {
return new CustomizedFormHttpMsgConverter();
}
}
Now I can see my converter is successfully registered through my debug endpoint (/myConfig):
{
"requestMappingHandlerAdapter-converters": [
"com.ken.demo.CustomizedFormHttpMsgConverter#576c5536",
"org.springframework.http.converter.ByteArrayHttpMessageConverter#45d20f3d",
"org.springframework.http.converter.StringHttpMessageConverter#55ecbafe",
"org.springframework.http.converter.StringHttpMessageConverter#3b567dad",
"org.springframework.http.converter.ResourceHttpMessageConverter#60ecdde8",
"org.springframework.http.converter.ResourceRegionHttpMessageConverter#54fac191",
"org.springframework.http.converter.xml.SourceHttpMessageConverter#10497d4",
"org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter#9b7294c",
"org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#5a8ba37c",
"org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#f32a60f",
"org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter#dc61831"
],
"configurers": [
"org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter#257cc1fc",
"org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration$MetricsWebMvcConfigurer#42e22a53"
],
"converters": [
"com.ken.demo.CustomizedFormHttpMsgConverter#576c5536",
"org.springframework.http.converter.StringHttpMessageConverter#55ecbafe",
"org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#5a8ba37c"
]
}
However, it looks like the CustomizedFormHttpMsgConverter is never used because the illegal request will still result in the same error:
java.lang.IllegalArgumentException: URLDecoder: Incomplete trailing escape (%) pattern
at java.net.URLDecoder.decode(URLDecoder.java:187) ~[na:1.8.0_291]
at org.springframework.http.converter.FormHttpMessageConverter.read(FormHttpMessageConverter.java:356) ~[spring-web-5.3.7.jar:5.3.7]
Of course, my debug msg "HI KEN I GOT THERE" is never printed out
My questions are:
Why my registered CustomizedFormHttpMsgConverter never get used?
Is my approach (to extend FormHttpMessageConverter) the right way of doing things?
Again my goal is just to change the 500 response to 400 upon URL decoding failures
It looks like the FormHttpMessageConverter is called by filter chain. I have not tested yet but the suggestion is that you should write your own filter and calling your custom converter for this case.
How to define a filter and register it: https://www.baeldung.com/spring-boot-add-filter
Rather than doing so much for just a invalidArgumentException that also for a specific use-case (Param validation).
Put a single Handler for any/all invalidArgumentException thrown from controller.
Now, even if spring or your code throw this exception, you will return custom message and status as you needed.
Sample code :
#ExceptionHandler (value = {IllegalArgumentException.class})
public final ResponseEntity<ServiceErrorResponse> invalidInputs(Exception e) {
// preparing the response body as per the template
// assigns exception message thrown by the failed method or class
// to the service response and sets respective HTTP status, code
return new ResponseEntity<ServiceErrorResponse>(e.getMessage(), new HttpHeaders(), HttpStatus.BAD_REQUEST);
}
Related
#code of servlet
public class EmployeeServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username=request.getParameter("t1");
int pass=Integer.parseInt(request.getParameter("t2"));
Connection con=null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
con=DriverManager.getConnection("jdbc:oracle:thin:HR/hr#localhost:1521:orcl\r\n"+ "","system","system");
Statement stmt =con.createStatement();
String query="select * from employee where emp_id="+pass;
ResultSet rs=stmt.executeQuery(query);
while(rs.next()) {
System.out.println(rs.getInt(1));
System.out.println(rs.getString(2));
}
stmt.close();
con.close();
// Redirect the response to success page
} catch (Exception e) {
e.printStackTrace();
}
}
output::
java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver
Stacktrace::
java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1407)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1215)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:377)
at com.dynamic.employee.controller.EmployeeServlet.doGet(EmployeeServlet.java:88)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:655)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:359)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1735)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:832)
getConections() may have silently returned null - in that case you will have to share the code or analyze yourself. getConections() may also have thrown an exception - you will have to check stdout where you printed the error.
Anyway you need to check stdout closely as you cannot expect any result in the browser. If you want that, ensure to write something into the HttpServletResponse object - I can not see that in your current code.
Edit:
The ClassNotFoundException indicates the JDBC driver is not on your classpath. Not a code issue itself, but check how you run your code.
Could you also check why your JDBC url contains a linefeed?
I am trying to publish a message to Google Cloud PubSub using the library:
implementation "com.google.cloud:spring-cloud-gcp-starter-pubsub:2.0.0".
But, I am getting the below exception:
com.google.cloud.spring.pubsub.support.converter.PubSubMessageConversionException: Unable to convert payload of type com.logistics.domain.events.items.FOItemsReturnedTrackingUpdate to byte[] for sending to Pub/Sub.
Here is the full stack trace:
com.google.cloud.spring.pubsub.support.converter.PubSubMessageConversionException: Unable to convert payload of type com.logistics.domain.events.items.FOItemsReturnedTrackingUpdate to byte[] for sending to Pub/Sub.
at com.google.cloud.spring.pubsub.support.converter.SimplePubSubMessageConverter.toPubSubMessage(SimplePubSubMessageConverter.java:63)
at com.google.cloud.spring.pubsub.core.publisher.PubSubPublisherTemplate.publish(PubSubPublisherTemplate.java:86)
at com.google.cloud.spring.pubsub.core.PubSubTemplate.publish(PubSubTemplate.java:128)
at com.logistics.adapters.publishers.FOItemsReturnedTrackingUpdateEventPublisherPubSubAdapter.publishItemsReturnedTrackingUpdate(FOItemsReturnedTrackingUpdateEventPublisherPubSubAdapter.java:46)
at com.logistics.application.services.FOPackagesService.lambda$processFoPackageAnnulled$2(FOPackagesService.java:100)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at com.logistics.application.services.FOPackagesService.processFoPackageAnnulled(FOPackagesService.java:71)
at com.logistics.adapters.controllers.DummyController.foPackagesAnnulled(DummyController.java:20)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.AbstractRequestLoggingFilter.doFilterInternal(AbstractRequestLoggingFilter.java:289)
at com.logistic.common.model.CustomRequestLoggingFilter.doFilterInternal(CustomRequestLoggingFilter.java:48)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:769)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:359)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1735)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:832)
Below is the code for publishing the event:
#Slf4j
#Component
public class ItemsReturnedTrackingUpdateEventPublisherPubSubAdapter implements
ItemsReturnedTrackingUpdateEventPublisher {
private final String topicName;
private final PubSubTemplate pubSubTemplate;
private final ObjectWriter objectWriter;
public ItemsReturnedTrackingUpdateEventPublisherPubSubAdapter(String topicName,
PubSubTemplate pubSubTemplate,
ObjectWriter objectWriter) {
this.topicName = topicName;
this.pubSubTemplate = pubSubTemplate;
this.objectWriter = objectWriter;
}
#Override
public void publishItemsReturnedTrackingUpdate(ItemsReturnedTrackingUpdate data) {
try {
Map<String, String> attributes = MDC.getCopyOfContextMap();
attributes.put("eventType", EventType.ITEMS_RETURNED_TRACKING_UPDATE.name());
var message = objectWriter.writeValueAsString(data);
log.debug("ItemsReturnedTrackingUpdate event is being published on topic : {} with attributes : {} and data : {}",
topicName, attributes, message);
ListenableFuture<String> result = pubSubTemplate.publish(topicName, data);
log.debug("ItemsReturnedTrackingUpdate published: {}, Message id: {}", data.getOrderId(), result.get());
} catch (Exception e) {
log.error("Error publishing ItemsReturnedTrackingUpdate with id: {}", data.getOrderId());
e.printStackTrace();
throw new PublisherException("Error publishing ItemsReturnedTrackingUpdate with id: " + data.getOrderId(), e);
}
}
}
You need to have a pubsub Message converter like below
#Bean
#Primary
public PubSubMessageConverter pubSubMessageConverter(ObjectMapper objectMapper) {
return new JacksonPubSubMessageConverter(objectMapper);
}
Also attaching sample example for object mapper bean
#Bean
#Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return mapper;
}
Looking at the stack trace, SimplePubSubMessageConverter is choking on the Object being published. Looking at the doc for that class:
... maps payloads of type byte[], ByteString, ByteBuffer, and String to Pub/Sub messages
Change:
pubSubTemplate.publish(topicName, data);
To:
pubSubTemplate.publish(topicName, message);
So I'm working on a Resource Server (a Spring Boot app), and I would like to leverage the goodies of Spring Security OAuth2 Resource Server library.
The problem I'm facing right now is that the Authorization Server (another Spring Boot app) signs JWTs with a symmetric key, that was set to a pretty short string a long time ago, and I that cannot change.
I tried this, following the Spring Security documentation:
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.anyRequest().permitAll())
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
}
#Bean
public JwtDecoder jwtDecoder(#Value("${jwt.secret-key}") String secretKey) {
return NimbusJwtDecoder
.withSecretKey(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HS512"))
.macAlgorithm(MacAlgorithm.HS512)
.build();
}
}
But I get the following error:
Caused by: com.nimbusds.jose.KeyLengthException: The secret length must be at least 256 bits
at com.nimbusds.jose.crypto.impl.MACProvider.<init>(MACProvider.java:118) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jose.crypto.MACVerifier.<init>(MACVerifier.java:168) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jose.crypto.MACVerifier.<init>(MACVerifier.java:81) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jose.crypto.MACVerifier.<init>(MACVerifier.java:113) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory.createJWSVerifier(DefaultJWSVerifierFactory.java:100) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:364) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:303) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.createJwt(NimbusJwtDecoder.java:154) ~[spring-security-oauth2-jose-5.5.3.jar:5.5.3]
... 61 common frames omitted
From what I can see the MACProvider that generates this exception is not configurable and the key length required cannot be relaxed. Is there any way around this?
Thanks
EDIT:
Tried the suggestion of padding the key with 0s like so:
#Bean
public JwtDecoder jwtDecoder(#Value("${jwt.secret-key}") String secretKey) {
var key = secretKey.getBytes(StandardCharsets.UTF_8);
var paddedKey = Arrays.copyOf(key, 128);
return NimbusJwtDecoder
.withSecretKey(new SecretKeySpec(paddedKey, "HS512"))
.macAlgorithm(MacAlgorithm.HS512)
.build();
}
but now I'm getting the following exception:
com.nimbusds.jose.proc.BadJWSException: Signed JWT rejected: Invalid signature
at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:378) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at com.nimbusds.jwt.proc.DefaultJWTProcessor.process(DefaultJWTProcessor.java:303) ~[nimbus-jose-jwt-9.10.1.jar:9.10.1]
at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.createJwt(NimbusJwtDecoder.java:154) ~[spring-security-oauth2-jose-5.5.3.jar:5.5.3]
at org.springframework.security.oauth2.jwt.NimbusJwtDecoder.decode(NimbusJwtDecoder.java:137) ~[spring-security-oauth2-jose-5.5.3.jar:5.5.3]
at org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider.getJwt(JwtAuthenticationProvider.java:97) ~[spring-security-oauth2-resource-server-5.5.3.jar:5.5.3]
at org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider.authenticate(JwtAuthenticationProvider.java:88) ~[spring-security-oauth2-resource-server-5.5.3.jar:5.5.3]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182) ~[spring-security-core-5.5.3.jar:5.5.3]
at org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter.doFilterInternal(BearerTokenAuthenticationFilter.java:130) ~[spring-security-oauth2-resource-server-5.5.3.jar:5.5.3]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:178) ~[spring-security-oauth2-client-5.5.3.jar:5.5.3]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:117) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-5.5.3.jar:5.5.3]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.3.12.jar:5.3.12]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) ~[spring-boot-actuator-2.5.6.jar:2.5.6]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.12.jar:5.3.12]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1722) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
EDIT2:
The source generates the JWT like so (using io.jsonwebtoken 0.9.1 library):
private String generateToken(Map<String, Object> claims, String username) {
Header header = Jwts.header();
header.setType("JWT");
String jti = UUID.randomUUID().toString();
Date now = new Date(System.currentTimeMillis());
return Jwts.builder()
.setClaims(claims)
.setHeader((Map<String, Object>) header)
.setSubject(username)
.setIssuedAt(now)
.setIssuer("issuer")
.setId(jti)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
EDIT3:
Solution:
#Bean
public JwtDecoder jwtDecoder(#Value("${jwt.secret-key}") String secretKey) {
var key = TextCodec.BASE64.decode(secretKey);
var paddedKey = key.length < 128 ? Arrays.copyOf(key, 128) : key;
return NimbusJwtDecoder
.withSecretKey(new SecretKeySpec(paddedKey, "HS512"))
.macAlgorithm(MacAlgorithm.HS512)
.build();
}
it looks like that ,HMAC, if the secret length is shorter than the block size of hash algorithm it would pad the secret with zeros.
and according to this
Block size: the size of the data block the underlying hash algorithm
operates upon. For SHA-256, this is 512 bits, for SHA-384 and SHA-512,
this is 1024 bits.
Output length: the size of the hash value produced by the underlying
hash algorithm. For SHA-256, this is 256 bits, for SHA-384 this is 384
bits, and for SHA-512, this is 512 bits.
the block size of SHA-512 is 128 bytes.
I suggest that if the source used HS512 algorithm try to pad the secret with zeros to see if it works or not. if you have Guava library in your class path:
Bytes.ensureCapacity(secretKey.getBytes(StandardCharsets.UTF_8), 128, 0);
ensureCapacity method source code:
public static byte[] ensureCapacity(byte[] array, int minLength, int padding) {
Preconditions.checkArgument(minLength >= 0, "Invalid minLength: %s", minLength);
Preconditions.checkArgument(padding >= 0, "Invalid padding: %s", padding);
return array.length < minLength ? Arrays.copyOf(array, minLength + padding) : array;
}
Edit 2:
first try to decode the secret to base 64
byte[] decodedBytes = Base64.decodeBase64(secret)
then add padding and use it in your decoder(if the size of the decodedBytes is less than 128):
Arrays.copyOf(decodedBytes, 128);
I did the following test and everything was ok:
private String generateToken(Map<String, Object> claims, String username) {
Header header = Jwts.header();
header.setType("JWT");
String jti = UUID.randomUUID().toString();
Date now = new Date(System.currentTimeMillis());
return Jwts.builder()
.setClaims(claims)
.setHeader((Map<String, Object>) header)
.setSubject(username)
.setIssuedAt(now)
.setIssuer("issuer")
.setId(jti)
.signWith(SignatureAlgorithm.HS512, "asdf")
.compact();
}
public JwtDecoder jwtDecoder() {
// base64 decoder from org.apache.tomcat.util.codec.binary.Base64;
byte[] key = Base64.decodeBase64("asdf");
// var key = "asdf".getBytes(StandardCharsets.UTF_8);
var paddedKey = Arrays.copyOf(key, 128);
return NimbusJwtDecoder
.withSecretKey(new SecretKeySpec(paddedKey, "HS512"))
.macAlgorithm(MacAlgorithm.HS512)
.build();
}
and used it like:
Main s = new Main();
String token = s.generateToken(new HashMap<>(), "hatef");
JwtDecoder decoder = s.jwtDecoder();
System.out.println(decoder.decode(token));
Edit 3:
OP reported in comments:
it worked with
io.jsonwebtoken.impl.TextCodec.BASE64.decode(secretKey) instead of
org.apache.tomcat.util.codec.binary.Base64.decodeBase64(secretKey),
with the latter i still had a com.nimbusds.jose.proc.BadJWSException
When I get some claims from a JWT Token to validate user authentication I get the following error:
Illegal base64url character: ' '
Creating a JWT goes completely fine but "decoding" seems to have some issues...
I also tried a base64url decoder to decode the token before getting the claims but then the token is unvalid.
My JWToken class where I encode and "decode" the token:
#Component
public class JWToken {
private static final String JWT_USERNAME_CLAIM = "sub";
private static final String JWT_ADMIN_CLAIM = "admin";
#Value("${jwt.issuer}")
private String issuer;
#Value("${jwt.passPhrase}")
private String passPhrase;
#Value("${jwt.duration-of-validity}")
private int expiration;
public String encode(String name, boolean admin) {
String token = Jwts.builder()
.claim(JWT_USERNAME_CLAIM, name)
.claim(JWT_ADMIN_CLAIM, admin)
.setSubject(name)
.setIssuer(issuer)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.signWith(SignatureAlgorithm.HS512, passPhrase).compact();
return token;
}
//for retrieving any information from token we will need the secret key
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(passPhrase).parseClaimsJws(token).getBody();
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
//retrieve username from jwt token
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
//retrieve expiration date from jwt token
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
//check if the token has expired
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
//validate token
public Boolean validateToken(String token, String name) {
final String username = getUsernameFromToken(token);
return (username.equals(name) && !isTokenExpired(token));
}
}
My request filter:
#Component
public class JWTRequestFilter extends OncePerRequestFilter {
private static final Set<String> SECURED_PATHS =
Set.of("/api/offers", "/api/bids");
private final JWToken jwToken;
#Autowired
public JWTRequestFilter(JWToken jwToken) {
this.jwToken = jwToken;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
String servletPath = request.getServletPath();
if (HttpMethod.OPTIONS.matches(request.getMethod()) || SECURED_PATHS.stream().noneMatch(servletPath::startsWith)) {
filterChain.doFilter(request, response);
return;
}
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
System.out.println(jwtToken);
username = jwToken.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
throw new AuthenticationException("authentication problem");
} catch (ExpiredJwtException e) {
throw new AuthenticationException("authentication problem");
}
} else {
System.out.println(requestTokenHeader);
logger.warn("JWT Token does not begin with Bearer String");
//throw new AuthenticationException("authentication problem");
}
if(jwToken.validateToken(jwtToken, username)){
filterChain.doFilter(request, response);
}
// Once we get the token validate it.
}
}
The error in my console when I do a get request for /api/offers with the generated JWT token in the header:
io.jsonwebtoken.io.DecodingException: Illegal base64url character: ' '
at io.jsonwebtoken.io.Base64.ctoi(Base64.java:221) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.Base64.decodeFast(Base64.java:270) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.Base64Decoder.decode(Base64Decoder.java:36) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.Base64Decoder.decode(Base64Decoder.java:23) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.io.ExceptionPropagatingDecoder.decode(ExceptionPropagatingDecoder.java:36) ~[jjwt-api-0.11.2.jar:0.11.2]
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:309) ~[jjwt-impl-0.11.2.jar:0.11.2]
at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:550) ~[jjwt-impl-0.11.2.jar:0.11.2]
at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:610) ~[jjwt-impl-0.11.2.jar:0.11.2]
at team10.server.aucserver.security.JWToken.getAllClaimsFromToken(JWToken.java:47) ~[classes/:na]
at team10.server.aucserver.security.JWToken.getClaimFromToken(JWToken.java:51) ~[classes/:na]
at team10.server.aucserver.security.JWToken.getUsernameFromToken(JWToken.java:57) ~[classes/:na]
at team10.server.aucserver.security.JWTRequestFilter.doFilterInternal(JWTRequestFilter.java:52) ~[classes/:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.1.jar:5.3.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.1.jar:5.3.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.1.jar:5.3.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.1.jar:5.3.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.39.jar:9.0.39]
at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]
And line 47 is the getAllClaimsFromToken method in the JWToken class.
For an extra example this is one of the tokens the encode genrated:
Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJyb25ueSIsImFkbWluIjpmYWxzZSwiaXNzIjoicHJpdmF0ZSBjb21wYW55IiwiaWF0IjoxNjExMDA1NTc4LCJleHAiOjE2MTEwMDY3Nzh9.dQwEVfSNa6EIx-U-bgHN50hmrN0wYkj-8jXRoFLTx6JB53ERBWuGUeiXLqtiJ_jTGxEISB-Lv7E9KAyPk8nV3g
What you are decoding isn't the token, you're trying to decode the entire header value. Bearer isn't part of the token, it's the authentication scheme.
More generally, you're writing your own security infrastructure, which is almost always a very bad idea. Spring Security JWT handles all of this for you automatically; use it instead.
For some reason the substring function kept some white space before the token. I changed that line in my JWTRequestFilter.
Old:
jwtToken = requestTokenHeader.substring(7);
New:
jwtToken = requestTokenHeader.split(" ")[1].trim();
The added .trim() will delete any white space before or after the string, so that was the solution for me
U can also use this :
jwtToken = requestTokenHeader.substring("Bearer ".length());
It solved the some problem for me.
I am developing a file repository to store the pdf files. I am using eureka + spring boot which is in producer and consumer architecture. The client is having
public boolean uploadBooks(MultipartFile file, String fileLocation, String fileName) {
boolean callResponse = false;
try {
if(!file.isEmpty()) {
ByteArrayResource fileResource = new ByteArrayResource(file.getBytes()) {
public String getFileName() {
return file.getOriginalFilename();
}
};
LinkedMultiValueMap<String, Object> requestMap = new LinkedMultiValueMap<>();
requestMap.add("file", fileResource);
requestMap.add("repoLocation", fileLocation);
requestMap.add("fileId", fileName);
HttpHeaders requestHeader = new HttpHeaders();
requestHeader.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<LinkedMultiValueMap<String, Object>>(
requestMap, requestHeader);
System.err.println(requestEntity.getBody());
System.err.println(uploadBooks);
ResponseEntity<Boolean> response = restTemplate.exchange(uploadBooks,HttpMethod.POST, requestEntity,
Boolean.class);
callResponse = response.getBody();
}
} catch (Exception ex) {
ex.printStackTrace();
}
return callResponse;
}
and the rest end point is having
#PostMapping("/uploadFile")
public boolean uploadFile(#RequestParam("file") MultipartFile file,
#RequestParam("repoLocation") String repoLocation, #RequestParam("fileId") String fileId) {
boolean process = false;
try {
System.out.println("---->"+fileId);
process = fileServices.saveFile(file, repoLocation, fileId);
} catch (RepoStorageException e) {
e.printStackTrace();
}
if (process) {
return true;
}
return false;
}
when I am uploading file I am getting
org.springframework.web.client.HttpClientErrorException$BadRequest: 400 null
exception. Please help me on writing the end point to handle the client request.
Error Stack trace
org.springframework.web.client.HttpClientErrorException$BadRequest: 400 null
at org.springframework.web.client.HttpClientErrorException.create(HttpClientErrorException.java:79)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:97)
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:79)
at org.springframework.web.client.ResponseErrorHandler.handleError(ResponseErrorHandler.java:63)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:777)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:735)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:669)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:578)
at com.library.remoteservices.repository.LibraryFileRepository.uploadBooks(LibraryFileRepository.java:76)
at com.library.LibraryServiceDiscoveryClient.FileServiceController.uploadBooks(FileServiceController.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:215)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:142)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:998)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:901)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:875)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:770)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Now-a-days there are projects like Spring Content provide a very similar programming model for unstructured data (i.e. files, images, videos, etc) as Spring Data does for structured data. Plus it allows you to associate those files with your Entities.
So (assuming you are using Spring Boot) you would add the following dependencies:
pom.xml
<!-- Java API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-fs-boot-starter</artifactId>
<version>0.4.0</version>
</dependency>
<!-- REST API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest-boot-starter</artifactId>
<version>0.4.0</version>
</dependency>
Add the following attributes to your File entity so that content can be associated with it.
File.java
#Entity
public class File {
...existing fields...
#ContentId
private UUID contentId;
#ContentLength
private long contentLength = 0L;
// if you have rest endpoints
#MimeType
private String mimeType;
...
}
Create a ContentStore (the equivalent of your FileRepository but for your files):
FileContentStore.java
#StoreRestResource(path="fileContents")
public interface FileContentStore extends ContentStore<File, UUID> {
}
When you run your application Spring Content will see the FileContentStore interface and the spring-content-fs dependency and inject a Filesystem implementation of this interface for you. It will also see the spring-content-rest dependency and add an #Controller implementation that forwards GET, PUT, POST and DELETE REST requests onto FileContentStore as well. This saves you from having to write any of the controller code you have above. REST endpoints will be available at /fileContents so...
curl -X POST -F "image=#/path/to/local/file.pdf" /fileContents/{fileId}
will upload file.pdf and associate it with your file entity. And:
curl /fileContents/{fileId} will fetch it again.
This programming model also creates an abstraction over the file storage implementation allowing you to choose any type of storage that Spring Content supports (currently, this is Filesystem, S3, JPA BLOBs, Mongo's Gridfs and Google Storage). Your application code will stay the same regardless.
HTH