Spring Boot WS-Server - Custom Http Status - spring

I published endpoints using Spring Boot WS-Server
When I use SoapUI I see:
HTTP/1.1 200
Accept: text/xml, text/html, image/gif, image/jpeg, *; q=.2, /; q=.2
SOAPAction: ""
Content-Type: text/xml;charset=utf-8
Content-Length: 828
Date: Thu, 29 Apr 2021 14:04:54 GMT
Keep-Alive: timeout=60
Connection: keep-alive
I would like to set custom HTTP Status in response (I know that it may be against the standard but it is an external requirement). I also read following topic:
Spring WS (DefaultWsdl11Definition) HTTP status code with void
But this solution failed
Spring Boot version: 2.2.7

Problem was solved
As I said I wanted to set custom HTTP status in SOAP response.
I found this post:
Spring WS (DefaultWsdl11Definition) HTTP status code with void
Author used EndpointInterceptor with TransportContext to get HttpServletResponse, then he changed status. The difference between my and his case is the fact, that he returned void from WebService method whereas I wanted to return some response.
In my situation following code in Spring WebServiceMessageReceiverObjectSupport class (method handleConnection) overrode servlet status previously set in interceptor:
if (response instanceof FaultAwareWebServiceMessage && connection instanceof FaultAwareWebServiceConnection) {
FaultAwareWebServiceMessage faultResponse = (FaultAwareWebServiceMessage)response;
FaultAwareWebServiceConnection faultConnection = (FaultAwareWebServiceConnection)connection;
faultConnection.setFaultCode(faultResponse.getFaultCode());
}
In order to bypass this fragment of code I needed to define class with my own implementation of handleConnection method, which extended class WebServiceMessageReceiverHandlerAdapter
In my implementation I excluded change of status. Important thing is to pass WebMessageFactory bean in autowired constructor of this class, otherwise exception is raised during app's startup.
This class has to be marked with Spring stereotype (eg. #Component) and name of this bean has to be configured in Configuration class when configuring ServletRegistrationBean:
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext){
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
servlet.setMessageFactoryBeanName("webServiceMessageFactory");
servlet.setMessageReceiverHandlerAdapterBeanName("myOwnMessageReceiverHandlerAdapter");
return new ServletRegistrationBean<>(servlet,"/ws/*");
}

Related

Spring Boot Admin client POST to admin/instances with configured user and password raise 403 forbidden for all clients

I've integrated SBA to the project. Without any authentication all clients register for SBA server and I can see all actuator endpoints, which I've opened.
I wish to have login page at SBA admin page and wish open registration only for SBA clients with proper permissions. I've added user/password properties at both sides, according to SBA documentation.
If I just add at SBA server user and password, and the same user and password I also configure for all SBA clients, all clients got responses 403 FORBIDDEN Failed to register application as Application()
SBA server part:
application.properties
spring.security.user.name=admin
spring.security.user.password=admin
spring.boot.admin.client.instance.metadata.user.name=admin
spring.boot.admin.client.instance.metadata.user.password=admin
server.port=8090
management.endpoints.web.exposure.include=
management.endpoints.web.base-path=/_manage
management.endpoints.jmx.exposure.include=
management.endpoint.shutdown.enabled=true
management.server.port=8090
security configuration with opened permissions to /instances
#Configuration
#ConditionalOnProperty(value = "spring.security.enabled", havingValue = "true")
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.permitAll();
http
.logout().logoutUrl("/logout");
http
.csrf().disable();
http
.authorizeRequests()
.antMatchers("/login.html", "/** ** /** .css", "/img/** ** ", "/third-party/** ** ")
.permitAll();
http
.authorizeRequests()
.antMatchers("/instances")
.permitAll();
//.authenticated();
http.httpBasic();
}
}
SBA client has the same user and password, to be able to register at SBA server:
application.properties
instance.name=clientdemo
feed.generation.type=CLIENTDEMO
spring.boot.admin.url=http://localhost:8090
spring.boot.admin.client.url=http://localhost:8090
spring.boot.admin.client.username=admin
spring.boot.admin.client.password=admin
spring.security.user.name=admin
spring.security.user.password=admin
server.port=8091
# ACTUATOR
management.endpoints.web.exposure.include=*
#management.endpoints.web.base-path=/_manage
management.endpoint.shutdown.enabled=true
management.server.port=8091
As result at SBA client side I see from the logs an error :
: Writing [Application(name=demo-worker, managementUrl=http://http://localhost:8091/actuator, healthUrl=http://localhost:8091/actuator/health, serviceUrl=http://localhost:8091/)] as "application/json"
: sun.net.www.MessageHeader#4089d6fd8 pairs: {POST /instances HTTP/1.1: null}{Accept: application/json}{Content-Type: application/json}{Authorization: Basic YWRtaW46YWRtaW4=}{User-Agent: Java/1.8.0_211}{Host: localhost:8090}{Connection: keep-alive}{Content-Length: 267}
: sun.net.www.MessageHeader#50cf05ff11 pairs: {null: HTTP/1.1 403}{Content-Type: text/plain}{Cache-Control: no-cache, no-store, max-age=0, must-revalidate}{Pragma: no-cache}{Expires: 0}{X-Content-Type-Options: nosniff}{X-Frame-Options: DENY}{X-XSS-Protection: 1 ; mode=block}{Referrer-Policy: no-referrer}{Transfer-Encoding: chunked}{Date: Wed, 13 Nov 2019 12:48:32 GMT}
: Response 403 FORBIDDEN
: Failed to register application as Application(name=demo-worker, managementUrl=http://localhost:8091/actuator, healthUrl=http://localhost:8091/actuator/health, serviceUrl=http://localhost:8091/) at spring-boot-admin ([http://localhost:8090/instances]): 403 null
: HTTP POST http://localhost:8090/instances
: Accept=[application/json, application/*+json]
And from SBA server side I see at the logs :
{"name":"demo-worker","managementUrl":"http://localhost:8091/actuator","healthUrl":"http://localhost:8091/actuator/health","serviceUrl":"http://localhost:8091/","metadata":o.a.c.a.AuthenticatorBase: Security checking request POST /instances
My SBA server is registered at port 8090 and SBA client is registered at port 8091.
you should add this:
.antMatchers(HttpMethod.POST, "/actuator/**")
.permitAll()

Is it possible to define an end point in a Controller with a RequestBody and a RequestPart?

I need to create a single end point that accepts a RequestBody OR a RequestPart.
If the request contains the RequestPart it will execute some logic to process the MultipartFile otherwise it will process the object passed in the RequestBody.
I checked How to send #Requestbody and #Requestpart together in spring but it differs from my question because I don't want to send both, RequestBody and RequestPart, at the same time.
I defined my entry point as:
#RequestMapping(value="/xyz/api/{endPoint}", method= RequestMethod.POST)
public void endPointPost(
#PathVariable String endPoint,
HttpServletRequest request,
HttpServletResponse response,
#RequestBody(required=false) Object body,
#RequestPart(required=false) MultipartFile uploadFile) throws Exception {
If the request contains only the RequestBody it works correctly, for instance:
{"body":{"companyCD":"myTest"}}
However, when sending the multipart request it fails with the follwing error:
2019-10-18 00:50:43,440 DEBUG [http-nio-8080-exec-8] org.springframework.web.servlet.handler.AbstractHandlerMapping: Mapped to public void com.monoplus.mcd.rest.GenericController.endPointPost(java.lang.String,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.lang.Object,org.springframework.web.multipart.MultipartFile) throws java.lang.Exception
2019-10-18 00:50:43,440 INFO [http-nio-8080-exec-8] com.monoplus.mcd.rest.ServletControllerInterceptor: ServletControllerInterceptor - preHandle
2019-10-18 00:50:43,442 DEBUG [http-nio-8080-exec-8] org.springframework.web.method.support.InvocableHandlerMethod: Could not resolve parameter [3] in public void com.monoplus.mcd.rest.GenericController.endPointPost(java.lang.String,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.lang.Object,org.springframework.web.multipart.MultipartFile) throws java.lang.Exception: Content type 'multipart/form-data;boundary=----WebKitFormBoundaryG1Xr4xtC2rNYWuCd;charset=UTF-8' not supported
2019-10-18 00:50:43,446 DEBUG [http-nio-8080-exec-8] org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: Using #ExceptionHandler public final org.springframework.http.ResponseEntity<java.lang.Object> org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest) throws java.lang.Exception
2019-10-18 00:50:43,481 DEBUG [http-nio-8080-exec-8] org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor: No match for [text/html, application/xhtml+xml, image/webp, image/apng, application/signed-exchange;v=b3, application/xml;q=0.9, */*;q=0.8], supported: []
2019-10-18 00:50:43,482 DEBUG [http-nio-8080-exec-8] org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver: Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundaryG1Xr4xtC2rNYWuCd;charset=UTF-8' not supported]
2019-10-18 00:50:43,483 INFO [http-nio-8080-exec-8] com.monoplus.mcd.rest.ServletControllerInterceptor: ServletControllerInterceptor - afterCompletion - org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryResponseWrapper#6c9a1e05
2019-10-18 00:50:43,484 DEBUG [http-nio-8080-exec-8] org.springframework.web.servlet.FrameworkServlet: Completed 415 UNSUPPORTED_MEDIA_TYPE
Please note that Could not resolve parameter [3]... refers to the RequestBody parameter.
This is my multipart request:
Header
Host: localhost:88
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------262541039624932
Content-Length: 1401
Connection: keep-alive
Referer: http://localhost:88/appl/html/master/FileImport.html
Cookie: JSESSIONID=3f052417-1702-48b6-b7c2-cac5609ef525; SESSION=M2YwNTI0MTctMTcwMi00OGI2LWI3YzItY2FjNTYwOWVmNTI1
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
Body
-----------------------------262541039624932
Content-Disposition: form-data; name="uploadFile"; filename="testFile.txt"
Content-Type: text/plain
1 - File content
-----------------------------262541039624932
Content-Disposition: form-data; name="_ns"
-----------------------------262541039624932
Content-Disposition: form-data; name="_qt"
false
-----------------------------262541039624932
Content-Disposition: form-data; name="_body"
{"USER_NAME":""}
-----------------------------262541039624932--
Any help is appreciated.
Thank you
I'm thinking about this question from a RESTful point of view and not necessarily spring. If you are 1) trying to create or edit (post or put) a resource or 2) trying to upload a file; shouldn't those be two different URI Paths?
Thanks to Chris suggestion I was able to solve my question, I defined a different entry point for the Multipart content.
#RequestMapping(value="/xyz/api/{endPoint}", method= RequestMethod.POST, consumes = {"multipart/form-data"})
public void multiPartEndPointPost(
#PathVariable String endPoint,
HttpServletRequest request,
HttpServletResponse response
) throws Exception {
this.doSomeStuff(endPoint, request, response);
}
The important part is the consumes = {"multipart/form-data"} then I can use Apache Commons FileUpload to upload the files.
The answer for [Cannot use Apache Commons FileUpload with Spring Boot multipart.resolve-lazily also helped me to solve my question.

resteasy ContainerRequestFilter didn't work in springboot

resteasy 3.1.3.Final and springboot 1.5.7
I want do somthing before the request go ino the restful method,but it never worked.
here is the restful method interface.
#Path("/demo")
#Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
#Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public interface DemoService {
#POST
#Path("/query")
List<EntityDemoInfo> queryByType(QueryRequest requst);
}
Here is the filter.
#Provider
#PreMatching
public class RequestFilter implements HttpRequestPreprocessor,ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
System.out.println("-----------------");
}
#Override
public void preProcess(HttpRequest request) {
System.out.println("================");
}
}
It never go in the filter and print the log,even if i tried the annotations #Provider/#PreMatching/#Configuration in any combination.
Later i think maybe something registry problem,and tried to add #Bean in #SpringBootApplication class.This can print what I register,however when debugging request the registry/factory din't have my RequestFilter, thus it didn't work. What's wrong with it? thanks !
#Bean
public SynchronousDispatcher synchronousDispatcher() {
ResteasyProviderFactory providerFactory = ResteasyProviderFactory.getInstance();
RequestFilter requestFilter = new RequestFilter();
providerFactory.getContainerRequestFilterRegistry().registerSingleton(requestFilter);
SynchronousDispatcher dispatcher = new SynchronousDispatcher(providerFactory);
dispatcher.addHttpPreprocessor(requestFilter);
System.out.println("*****************");
System.out.println(providerFactory.getContainerRequestFilterRegistry().preMatch());
return dispatcher;
}
As 'paypal' codes do in https://github.com/paypal/resteasy-spring-boot , I added RequestFilter like Hantsy mentioned below, it didn't work!
Here is the log.
14:44:01.537 [main] INFO org.apache.tomcat.util.net.NioSelectorPool Using a shared selector for servlet write/read
14:44:01.548 [main] INFO org.jboss.resteasy.resteasy_jaxrs.i18n RESTEASY002225: Deploying javax.ws.rs.core.Application: class com.sample.app.JaxrsApplication
#################
################# ------This is what I add in JaxrsApplication
14:44:01.548 [main] INFO org.jboss.resteasy.resteasy_jaxrs.i18n RESTEASY002215: Adding singleton provider java.lang.Class from Application class com.sample.app.JaxrsApplication
14:44:01.554 [main] INFO org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer Tomcat started on port(s): 8080 (http)
14:44:01.559 [main] INFO com.sample.app.Application Started Application in 2.478 seconds (JVM running for 2.978)
//There is when i post a request as it say what happened,nothing,but got the response.Thus it didn't work!
14:45:58.657 [RMI TCP Connection(2)-127.0.0.1] INFO org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar$SpringApplicationAdmin Application shutdown requested.
14:45:58.657 [RMI TCP Connection(2)-127.0.0.1] INFO org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext#34f22f9d: startup date [Fri Oct 20 14:43:59 CST 2017]; root of context hierarchy
14:45:58.659 [RMI TCP Connection(2)-127.0.0.1] INFO org.springframework.context.support.DefaultLifecycleProcessor Stopping beans in phase 0
14:45:58.660 [RMI TCP Connection(2)-127.0.0.1] INFO org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanExporter Unregistering JMX-exposed beans on shutdown
14:45:58.660 [RMI TCP Connection(2)-127.0.0.1] INFO org.springframework.boot.actuate.endpoint.jmx.EndpointMBeanExporter Unregistering JMX-exposed beans
14:45:58.660 [RMI TCP Connection(2)-127.0.0.1] INFO org.springframework.jmx.export.annotation.AnnotationMBeanExporter Unregistering JMX-exposed beans on shutdown
The resteasy documentation provides simple guide for intgrating resteasy with Spring and Spring Boot. Hope these links are helpful.
Resteasy and Spring Integration
Spring Boot starter, described in the 43.4. Spring Boot starter section of the Resteasy doc.
If you are using Spring Boot as described in the doc, just register you custom Filter in your Application class.
#Component
#ApplicationPath("/sample-app/")
public class JaxrsApplication extends Application {
#Override
public Set<Object> getSingletons() {
Set<Object> singletons = new HashSet<>();
singletons.add(yourFilter);
return singletons;
}
}
Updated: I forked the paypal/resteasy-spring-boot, and modified the sample-app, added a EchoFitler for demo purpose.
Check the source codes from my Github account.
Run the sample-app via mvn spring-boot:run.
Use curl to test the apis.
# curl -v -X POST -H "Content-Type:text/plain" -H "Accept:application/json" http://localhost:8080/sample-app/echo -d "test"
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /sample-app/echo HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.56.0
> Content-Type:text/plain
> Accept:application/json
> Content-Length: 4
>
* upload completely sent off: 4 out of 4 bytes
< HTTP/1.1 200
< X-Application-Context: application
< Content-Type: application/json
< Content-Length: 45
< Date: Fri, 20 Oct 2017 07:19:43 GMT
<
{"timestamp":1508483983603,"echoText":"test"}* Connection #0 to host localhost left intact
And you will see the filtering info in the spring-boot console.
filtering request context:org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext#1ca8d1e4
filtering request/response context:org.jboss.resteasy.core.interception.jaxrs.ResponseContainerRequestContext#1787a18c
org.jboss.resteasy.core.interception.jaxrs.ContainerResponseContextImpl#4aad828e
Hope this is helpful.

Eureka on Cloudfoundry RestTemplate gets 301 Moved Permanently

I’m setting up a Spring Boot microservice infrastructure with a Eureka Service Registry.
I’m using RestTemplate to call another service (resolution done via Eureka) locally it works perfect! But on Cloud Foundry I always get a “301 Moved permanently” errorcode when calling the service.
Anyone knows if there is a specific configuration necessary for RestTemplate to work with Eureka on Cloud Foundry?
#Bean
#LoadBalanced
RestTemplate getRestTemplate() {
return new RestTemplate();
}
public UserMapping getUserMappingFromRemoteServer(String name_id){
UserMapping userMappingResponse = mappingTemplate.getForObject("http://user-mapping/user?id=" + name_id, UserMapping.class);
}
My response is always
Setting request Accept header to [application/json, application/*+json]
Created GET request for "http://user-mapping/user?id=1"
GET request for "http://user-mapping/user?id=1" resulted in 301 (MOVED_PERMANENTLY)
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.user.SmartCharging.UserMapping] and content type [text/html]]
eureka:
instance:
non-secure-port-enabled: false
secure-port-enabled: true
did the job

Spring Cloud AWS SQS AccessDenied

I am currently having a connection issue trying to connect to an AWS SQS Queue using Spring Cloud and Spring Boot. I believe I have everything configured fine but am getting:
2015-07-01 18:12:11,926 [WARN][-]
org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext[487]
- Exception encountered during context initialization - cancelling refresh attempt
org.springframework.context.ApplicationContextException: Failed to
start bean 'simpleMessageListenerContainer'; nested exception is
com.amazonaws.AmazonServiceException: Access to the resource
https://sqs.us-west-2.amazonaws.com/{Number}/{Queue Name} is denied.
(Service: AmazonSQS; Status Code: 403; Error Code: AccessDenied;
Request ID: 87312428-ec0f-5990-9f69-6a269a041b4d)
#Configuration
#EnableSqs
public class CloudConfiguration {
private static final Logger log = Logger.getLogger(CloudConfiguration.class);
#MessageMapping("QUEUE")
public void retrieveProvisionMessages(User user) {
log.warn(user.firstName);
}
}
YML
cloud:
aws:
credentials.accessKey: AccessKey
credentials.secretKey: SecretKey
region.static: us-west-2
credentials.instanceProfile: true
When it attempts to connect I see that a header value of:
AWS4-HMAC-SHA256 Credential=accesskey/20150701/us-west-2/sqs/aws4_request, SignedHeaders=host;user-agent;x-amz-date, Signature=signature
After the request is sent:
HTTP/1.1 403 Forbidden [Server: Server, Date: Wed, 01 Jul 2015 22:51:25 GMT, Content-Type: text/xml, Content-Length: 349, Connection: keep-alive, x-amzn-RequestId: Request Id] org.apache.http.conn.BasicManagedEntity#37e55df6
I have checked all AIM policies and they are correct.
Using:
private AmazonSQS establishQueue(){
AmazonSQS sqs = new AmazonSQSClient(new BasicAWSCredentials(accessKey, secretKey));
sqs.setRegion(RegionUtils.getRegion(region));
return sqs;
}
AmazonSQS sqs = establishQueue();
return sqs.receiveMessage(sqs.getQueueUrl(userProductPurchase).getQueueUrl());
with the same credentials works fine. Any help is greatly appreciated.
Thanks
Do you have GetQueueAttributes calls allowed for your IAM user?
I think it's using also few more operations. Not only ReceiveMessage and GetQueueUrl.
In my case, using Spring Cloud, I had to set the following permissions up:
sqs:DeleteMessage
sqs:GetQueueUrl
sqs:ReceiveMessage
sqs:SendMessage
sqs:GetQueueAttributes

Resources