SOAP Proxy with Spring Integration - spring

I'm trying to wrap my head around spring integration and would preferably like to stay away from XML-based configuration.
What I would like to do is the following:
Accept a SOAP request on a certain endpoint
Forward this request to a downstream SOAP service, without any modification (for now I just want the basics to work)
Return the result to the client
This seems like it should be pretty simple, but I don't have the faintest idea of where to start. Is this even possible with SI? As I understand it, and please correct me if I'm wrong, SI is mainly used for async data flows.
I did check out the integration sample repository, which includes example inbound WS requests, but these are all configured in XML, which, as I said, I'd preferably like to stay far away from.
Any pointers would be much appreciated; I've been reading through documentation for the past two days and I'm none the wiser!

Here is an example using the SimpleWebServiceInboundGateway. In this example we also set the "ExtractPayload" to false so that it sends the RAW soap message. But agree with above, possibly the HTTPInboundRequest is better for your use case. I also didn't find many examples using DSL for the SoapInboundGateway so wanted to share and hope it helps someone else.
#Configuration
#EnableIntegration
public class SoapGatewayConfiguration {
/**
* URL mappings used by WS endpoints
*/
public static final String[] WS_URL_MAPPINGS = {"/services/*", "*.wsdl", "*.xsd"};
public static final String GATEWAY_INBOUND_CHANNEL_NAME = "wsGatewayInboundChannel";
public static final String GATEWAY_OUTBOUND_CHANNEL_NAME = "wsGatewayOutboundChannel";
/**
* Register the servlet mapper, note that it uses MessageDispatcher
*/
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
servlet.setTransformSchemaLocations(true);
servlet.setPublishEvents(true);
ServletRegistrationBean servletDef = new ServletRegistrationBean(servlet, WS_URL_MAPPINGS);
servletDef.setLoadOnStartup(1);
return servletDef;
}
/**
* Create a new Direct channels to handle the messages
*/
#Bean
public MessageChannel wsGatewayInboundChannel() {
return MessageChannels.direct(GATEWAY_INBOUND_CHANNEL_NAME).get();
}
#Bean
public MessageChannel wsGatewayOutboundChannel() {
return MessageChannels.direct(GATEWAY_OUTBOUND_CHANNEL_NAME).get();
}
/**
* Startup the WebServiceInboundGateway Endpoint, this will handle the incoming SOAP requests
* and place them onto the request channel
*/
#Bean
public SimpleWebServiceInboundGateway webServiceInboundGateway(
#Value("${spring.ws.request.timeout:1000}") long requestTimeout,
#Value("${spring.ws.reply.timeout:1000}") long replyTimeout,
#Value("${spring.ws.should.track:true}") boolean shouldTrack
) {
SimpleWebServiceInboundGateway wsg = new SimpleWebServiceInboundGateway();
wsg.setRequestChannel(wsGatewayInboundChannel());
wsg.setReplyChannel(wsGatewayOutboundChannel());
wsg.setExtractPayload(false); // Send the full RAW SOAPMessage and not just payload
wsg.setLoggingEnabled(true);
wsg.setShouldTrack(shouldTrack);
wsg.setReplyTimeout(replyTimeout); // Do not believe this prop supported currently
wsg.setRequestTimeout(requestTimeout); // Do not believe this prop is supported currently
wsg.setCountsEnabled(true);
return wsg;
}
/**
* You must enable debug logging on org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor
* to see the logs from this interceptor
*/
#Bean
public EndpointInterceptor soapMessageLoggingInterceptor() {
SoapEnvelopeLoggingInterceptor li = new SoapEnvelopeLoggingInterceptor();
li.setLogRequest(true);
li.setLogResponse(true);
li.setLogFault(true);
return li;
}
/**
* Validate the incoming web service against the schema
*/
#Bean
public EndpointInterceptor payloadValidatingInterceptor(XsdSchema xsdSchema
, #Value("${spring.ws.soap.validate.request:true}") boolean soapValidateRequest
, #Value("${spring.ws.soap.validate.reply:true}") boolean soapValidateResponse
, #Value("${spring.ws.soap.validate.addErrorDetail:true}") boolean soapAddValidationErrorDetail
) {
PayloadValidatingInterceptor interceptor = new PayloadValidatingInterceptor();
interceptor.setXsdSchema(xsdSchema);
interceptor.setValidateRequest(soapValidateRequest);
interceptor.setValidateResponse(soapValidateResponse);
interceptor.setAddValidationErrorDetail(soapAddValidationErrorDetail);
return interceptor;
}
/**
* Map the allowable service Uri's.
*/
#Bean
public EndpointMapping uriEndpointMapping(
PayloadValidatingInterceptor payloadValidatingInterceptor
, SimpleWebServiceInboundGateway webServiceInboundGateway
, SoapEnvelopeLoggingInterceptor loggingInterceptor) {
UriEndpointMapping mapping = new UriEndpointMapping();
mapping.setUsePath(true);
mapping.setDefaultEndpoint(webServiceInboundGateway);
mapping.setInterceptors(new EndpointInterceptor[]{loggingInterceptor, payloadValidatingInterceptor});
return mapping;
}
/**
* Expose the wsdl at http://localhost:8080/services/myService.wsdl
**/
#Bean
public Wsdl11Definition myService() {
SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition();
wsdl11Definition.setWsdl(new ClassPathResource("META-INF/myService.wsdl"));
return wsdl11Definition;
}
/**
* Expose the xsd at http://localhost:8080/services/mySchema.xsd
**/
#Bean
public XsdSchema mySchema() {
return new SimpleXsdSchema(new ClassPathResource("META-INF/mySchema.xsd"));
}
#Bean
public IntegrationFlow itemLookupFlow() {
return IntegrationFlows.from("wsGatewayInboundChannel")
.log(LoggingHandler.Level.INFO)
.handle(myBeanName, "execute")
.log(LoggingHandler.Level.TRACE, "afterExecute")
.get();
}
}

If your application is just a proxy over other SOAP service, you should consider to use just plain HTTP Inbound Gateway and HTTP Outbound Gateway.
You receive an XML from client and send it into the downstream service. Receive from there an XML again and just push it back to the response for the client.
For this purpose I can suggest HTTP proxy solution via Java DSL:
#Bean
public IntegrationFlow httpProxyFlow() {
return IntegrationFlows
.from(Http.inboundGateway("/service"))
.handle(Http.outboundGateway("/service/internal")
.expectedResponseType(String.class))
.get();
}
The problem with the SimpleWebServiceInboundGateway and SimpleWebServiceOutboundGateway pair that they extract a request and parse a respose to (un)wrap to/from the SOAP envelop. This looks like an overhead for your plain proxy use-case.

I got it working thanks to Artem's answer, with a small tweak. Not sure as to why the channels are required, but at least it's now working.
package com.example.integration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.http.Http;
import org.springframework.integration.http.config.EnableIntegrationGraphController;
import org.springframework.messaging.MessageChannel;
#SpringBootApplication
#EnableIntegration
#EnableIntegrationGraphController(allowedOrigins = "*")
public class IntegrationApplication {
public static void main(String[] args) {
SpringApplication.run(IntegrationApplication.class, args);
}
#Bean
public DirectChannel directChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow httpProxyFlow(MessageChannel directChannel) {
return IntegrationFlows
.from(Http.inboundGateway("/thing").requestChannel(directChannel).replyChannel(directChannel))
.enrichHeaders(h -> h.header("Content-Type", "application/soap+xml; charset=utf-8"))
.handle(Http.outboundGateway("http://www.webservicex.net/geoipservice.asmx").expectedResponseType(String.class))
.channel(directChannel)
.get();
}
}

Related

Control Azure Service Bus Message Listener to start or stop listening from the Topic or queue in spring boot

What I want to Achieve - Azure Service Bus Message Listener to start / stop receiving messages from queue/topic.
Below is a detailed explanation.
Currently I have integrated Azure Service Bus in my application and we listen message as soon as spring boot application starts. Now I want to modify this logic. By default Azure Service Bus Message Listener will be disable. On ApplicationReadyEvent I want to perform some task and after that again I want to enable Azure Service Bus Message Listener to start listening from topic or queue.
So how can I achieve that ?
application.yml
spring:
cloud:
azure:
servicebus:
namespace: **********
xxx:
azure:
servicebus:
connection: ***********
queue: **********
AzureConfiguration.java
import com.azure.spring.integration.servicebus.inbound.ServiceBusInboundChannelAdapter;
import com.azure.spring.messaging.servicebus.core.ServiceBusProcessorFactory;
import com.azure.spring.messaging.servicebus.core.listener.ServiceBusMessageListenerContainer;
import com.azure.spring.messaging.servicebus.core.properties.ServiceBusContainerProperties;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.messaging.MessageChannel;
#Configuration
public class AzureConfiguration{
#Value("${xxx.azure.servicebus.connection}")
private String serviceBusConnection;
#Value("${xxx.azure.servicebus.queue}")
private String serviceBusQueue;
private static final String SERVICE_BUS_INPUT_CHANNEL = "yyyyy";
private static final String SENSOR_DATA_CHANNEL = "zzzzz";
private static final String SERVICE_BUS_LISTENER_CONTAINER = "aaaaa";
#Bean(name = SERVICE_BUS_LISTENER_CONTAINER)
public ServiceBusMessageListenerContainer serviceBusMessageListenerContainer(ServiceBusProcessorFactory processorFactory) {
ServiceBusContainerProperties containerProperties = new ServiceBusContainerProperties();
containerProperties.setConnectionString(serviceBusConnection);
containerProperties.setEntityName(serviceBusQueue);
containerProperties.setAutoComplete(true);
return new ServiceBusMessageListenerContainer(processorFactory, containerProperties);
}
#Bean
public ServiceBusInboundChannelAdapter serviceBusInboundChannelAdapter(
#Qualifier(SERVICE_BUS_INPUT_CHANNEL) MessageChannel inputChannel,
#Qualifier(SERVICE_BUS_LISTENER_CONTAINER) ServiceBusMessageListenerContainer listenerContainer) {
ServiceBusInboundChannelAdapter adapter = new ServiceBusInboundChannelAdapter(listenerContainer);
adapter.setOutputChannel(inputChannel);
return adapter;
}
#Bean(name = SERVICE_BUS_INPUT_CHANNEL)
public MessageChannel serviceBusInputChannel() {
return new DirectChannel();
}
#Bean(name = SENSOR_DATA_CHANNEL)
public MessageChannel sensorDataChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow serviceBusMessageFlow() {
return IntegrationFlows.from(SERVICE_BUS_INPUT_CHANNEL)
.<byte[], String>transform(String::new)
.channel(SENSOR_DATA_CHANNEL)
.get();
}
}
AppEventListenerService.java
import com.azure.spring.integration.servicebus.inbound.ServiceBusInboundChannelAdapter;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import java.util.List;
#Slf4j
#Service
#AllArgsConstructor
public class AppEventListenerService{
#EventListener(ApplicationReadyEvent.class)
public void OnApplicationStarted() {
log.debug("Enter OnApplicationStarted");
// By Default Azure Service Bus Message Listener will be disable
// do some task
// Enable Azure Bus Message Listener
log.debug("Exit OnApplicationStarted");
}
}
In above code in AppEventListenerService.java ,
// Enable Azure Bus Message Listener - Here I want to start ServiceBusConsumer to receive message from topic/queue.
Literally, if you just want to stop the listener and then start it on ApplicationReadyEvent, then you can autowire the ServiceBusInboundChannelAdapter(or ServiceBusMessageListenerContainer) in your AppEventListenerService.java and then simply call the its stop() and start() API in the AppEventListenerService#OnApplicationStarted method.
However, both the ServiceBusMessageListenerContainer and ServiceBusInboundChannelAdapter implements SmartLifecycle interface and is enabled auto-start-up by default. So if you use the above solution, the listener (as well as adapter) has been triggered to start before ApplicationReadyEvent, which means there will still be a period that the listener is consuming messages.
So I assume you may want to turn off the listener till your own business logic has been done. If so, then currently ServiceBusMessageListenerContainer doesn't provide the function to disable auto-start-up, and we will put your feature request to our backlog.
But you could still use the below workarounds to meet your request.
Workaround-1
You can extend the ServiceBusMessageListenerContainer to override the auto-start-up behavior,
public class CustomServiceBusMessageListenerContainer extends ServiceBusMessageListenerContainer {
private boolean autoStartUp = true;
/**
* Create an instance using the supplied processor factory and container properties.
* #param processorFactory the processor factory.
* #param containerProperties the container properties.
*/
public CustomServiceBusMessageListenerContainer(ServiceBusProcessorFactory processorFactory, ServiceBusContainerProperties containerProperties) {
super(processorFactory, containerProperties);
}
public void setAutoStartUp(boolean autoStartUp) {
this.autoStartUp = autoStartUp;
}
#Override
public final boolean isAutoStartup() {
return this.autoStartUp;
}
}
When declaring the ServiceBusMessageListenerContainer and ServiceBusInboundChannelAdapter bean, disable their auto-start-up function.
#Bean(SERVICE_BUS_LISTENER_CONTAINER)
public ServiceBusMessageListenerContainer messageListenerContainer(ServiceBusProcessorFactory processorFactory) {
ServiceBusContainerProperties containerProperties = new ServiceBusContainerProperties();
containerProperties.setEntityName(QUEUE_NAME);
...
CustomServiceBusMessageListenerContainer listenerContainer = new CustomServiceBusMessageListenerContainer(processorFactory, containerProperties);
listenerContainer.setAutoStartUp(false);
return listenerContainer;
}
#Bean
public ServiceBusInboundChannelAdapter queueMessageChannelAdapter(
#Qualifier(SERVICE_BUS_INPUT_CHANNEL) MessageChannel inputChannel,
#Qualifier(SERVICE_BUS_LISTENER_CONTAINER) ServiceBusMessageListenerContainer listenerContainer) {
ServiceBusInboundChannelAdapter adapter = new ServiceBusInboundChannelAdapter(listenerContainer);
adapter.setOutputChannel(inputChannel);
adapter.setAutoStartup(false);
return adapter;
}
Start the ServiceBusInboundChannelAdapter after your business logic in AppEventListenerService#OnApplicationStarted.
Workaround-2
This might be a bit hack, since we don't expose the api to disable auto-start-up in ServiceBusMessageListenerContainer, but it can be done in ServiceBusInboundChannelAdapter. So you can choose to not declare a bean of ServiceBusMessageListenerContainer but change it as a local variable for the adapter,
#Bean
public ServiceBusInboundChannelAdapter queueMessageChannelAdapter(
#Qualifier(SERVICE_BUS_INPUT_CHANNEL) MessageChannel inputChannel, ServiceBusProcessorFactory processorFactory) {
ServiceBusContainerProperties containerProperties = new ServiceBusContainerProperties();
containerProperties.setEntityName(QUEUE_NAME);
...
ServiceBusMessageListenerContainer listenerContainer = new ServiceBusMessageListenerContainer(processorFactory, containerProperties);
ServiceBusInboundChannelAdapter adapter = new ServiceBusInboundChannelAdapter(listenerContainer);
adapter.setOutputChannel(inputChannel);
adapter.setAutoStartup(false);
return adapter;
}
then start the ServiceBusInboundChannelAdapter after your business logic in AppEventListenerService#OnApplicationStarted.
Here I have a work around where we use the JMS to consume the service bus message
The reason to use JMS is that when we use the #JMSListener we can start stop it.
Now to Implement JMS with ServiceBus refer this MSDOC
Now you have to Autowired this JmsListenerEndpointRegistry object and stop the listener.
#Autowired
JmsListenerEndpointRegistry registry;
To stop the JMS you will have to use the stop function:
registry.stop();
Here I have create two Api which will start/Stop the JMS and receiver of messages:
#Component
#RestController
public class Reciever {
#Autowired
JmsListenerEndpointRegistry registry;
#GetMapping("/stop")
public String readBlobFile ()
{
registry.stop();
return "Stopped" ;
}
#GetMapping("/start")
public String readBlobFile1 ()
{
registry.start();
return "StARTED" ;
}
private static final String QUEUE_NAME = "test";
private final Logger logger = LoggerFactory.getLogger(Reciever.class);
#JmsListener(destination = QUEUE_NAME, containerFactory = "jmsListenerContainerFactory")
public void receiveMessage(String s) {
logger.info("Received message: {}", s);
}
}
Now First I call the /stop Api which will stop the JMS and the message will only start coming once the /startapi is called .
output:
As you're using an integration flow registered as a bean the simplest way to start/stop it is to autowire it as StandardIntegrationFlow and call the corresponding method like so:
#Slf4j
#Service
#DependsOn({"serviceBusMessageFlow"})
#RequiredArgsConstructor
public class AppEventListenerService {
private final StandardIntegrationFlow serviceBusMessageFlow;
#EventListener(ApplicationReadyEvent.class)
public void OnApplicationStarted() {
log.debug("Enter OnApplicationStarted");
// Disable Azure Bus Message Listener
serviceBusMessageFlow.stop();
// do some task
// Enable Azure Bus Message Listener
serviceBusMessageFlow.start();
log.debug("Exit OnApplicationStarted");
}
}
Note the #DependsOn annotation might be needed to force the flow bean to be initialized before the event listener bean.
Also, it should be noted that some messages might happen to go through after the flow is initialized and before the listener is triggered.

Read messages from different AWS account using #SqsListener

I have an SQS standard queue that is provided by a third party vendor who has given access to our IAM user to read messages from there. So the AWS account ID for the queue is different than the one of my user.
I'm trying to use spring's #SqsListener annotation to consume these messages but I'm having trouble specifying the accountId that should be consumed from.
My bean configuration for the client looks like this:
#Bean
fun amazonSQSAsyncClient(): AmazonSQSAsync = AmazonSQSAsyncClientBuilder.standard()
.withCredentials(AWSStaticCredentialsProvider(BasicAWSCredentials(awsProperties.accessKey, awsProperties.secretKey)))
.withEndpointConfiguration(AwsClientBuilder.EndpointConfiguration(awsProperties.url, awsProperties.region))
.build()
I see no way of specifying the account Id in the credentials, and I also could not find any properties that can be used to define an accountId.
I tried setting the awsProperties.url shown above to something like https://sqs.us-east-1.amazonaws.com/<accountId> but this does not seem to be working. It is still trying to look for the queue in my own account Id and throwing a queue not found error.
Any ideas how to fix this and force the Spring AWS bean to consume from a specific AwsAccount?
You have a user that can access the queu in another account. That means you can run code with that user in your account and that can access the queue on another account.
Initializing a sqsclient will always use the account it is running on
You don't have to adjust this.
#Bean
fun amazonSQSAsyncClient(): AmazonSQSAsync = AmazonSQSAsyncClientBuilder.standard()
.withCredentials(AWSStaticCredentialsProvider(BasicAWSCredentials(awsProperties.accessKey, awsProperties.secretKey)))
.build()
You need to make sure the code can access the queue.
In the code you should set your queue URL like this:
https://sqs.<region>.amazonaws.com/<account>/<queuename>
, I quickly tried to access a queue from another account. If the permissions on the queue are correctly set, you have two possibilities. The first one is using the queue URL instead of the name (I checked, it works). The second one is creating you own DestinationResolver and providing it to the SimpleMessageListenerContainer. I created a small app with Spring Boot and it worked well. I pasted you the code below.
In a next feature release I'll figure out a better way to support this use case.
package demo;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.model.GetQueueUrlRequest;
import com.amazonaws.services.sqs.model.GetQueueUrlResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.aws.core.env.ResourceIdResolver;
import org.springframework.cloud.aws.messaging.config.SimpleMessageListenerContainerFactory;
import org.springframework.cloud.aws.messaging.support.destination.DynamicQueueUrlDestinationResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.core.DestinationResolutionException;
import org.springframework.messaging.core.DestinationResolver;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.util.Assert;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public MessageListener messageListener() {
return new MessageListener();
}
#Bean
public SimpleMessageListenerContainerFactory simpleMessageListenerFactory(AmazonSQS amazonSqs, ResourceIdResolver resourceIdResolver) {
SimpleMessageListenerContainerFactory factory = new SimpleMessageListenerContainerFactory();
factory.setDestinationResolver(new DynamicAccountAwareQueueUrlDestinationResolver(amazonSqs, resourceIdResolver));
return factory;
}
public static class DynamicAccountAwareQueueUrlDestinationResolver implements DestinationResolver<String> {
public static final String ACCOUNT_QUEUE_SEPARATOR = ":";
private final AmazonSQS amazonSqs;
private final DynamicQueueUrlDestinationResolver dynamicQueueUrlDestinationResolverDelegate;
public DynamicAccountAwareQueueUrlDestinationResolver(AmazonSQS amazonSqs, ResourceIdResolver resourceIdResolver) {
Assert.notNull(amazonSqs, "amazonSqs must not be null");
this.amazonSqs = amazonSqs;
this.dynamicQueueUrlDestinationResolverDelegate = new DynamicQueueUrlDestinationResolver(amazonSqs, resourceIdResolver);
}
#Override
public String resolveDestination(String queue) throws DestinationResolutionException {
if (queue.contains(ACCOUNT_QUEUE_SEPARATOR)) {
String account = queue.substring(0, queue.indexOf(ACCOUNT_QUEUE_SEPARATOR));
String queueName = queue.substring(queue.indexOf(ACCOUNT_QUEUE_SEPARATOR) + 1);
GetQueueUrlResult queueUrlResult = this.amazonSqs.getQueueUrl(new GetQueueUrlRequest()
.withQueueName(queueName)
.withQueueOwnerAWSAccountId(account));
return queueUrlResult.getQueueUrl();
} else {
return this.dynamicQueueUrlDestinationResolverDelegate.resolveDestination(queue);
}
}
}
public static class MessageListener {
private static Logger LOG = LoggerFactory.getLogger(MessageListener.class);
#MessageMapping("633332177961:queue-name")
public void listen(String message) {
LOG.info("Received message: {}", message);
}
}
}

FeignClient is passing on headers

I have about 10 microservices all built with Spring boot 2 using Eureka and FeignClients. My microservices use certain header values to keep track of data so when a FeignClient is used it needs to pass on certain values that are in the incoming request. So if Microservice 1 does a call to Microservice 2 it must pass on the headers from the incoming request onto microservice 2. I haven't been able to find out how I can do that. I understand their is #Header however if you have 20 FeignClients then you don't want to have to manually add the #header to all the FeignClients. Can you indicate that FeignClients must read a certain header from the incoming request and pass it on in the FeignClient?
You can use request interceptor in Feign.
Example Implementation:
Request Interceptor:
#Component
public class MyRequestInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String authorization = requestAttributes.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
if(null != authorization) {
template.header(HttpHeaders.AUTHORIZATION, authorization);
}
}
}
Bean Configuration:
#Configuration
public class CustomFeignConfig {
#Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
#Bean
public MyRequestInterceptor basicAuthRequestInterceptor() {
return new MyRequestInterceptor();
}
#Bean
public OkHttpClient client() {
return new OkHttpClient();
}
}

What is the expected behavior when setting `security.oauth2.resource.jwk.key-set-uri` in spring boot

In spring boot, upon configuring a Resource server we have the option to set the security.oauth2.resource.jwk.key-set-uri property if the access tokens will be JWTs and the issuer provides an endpoint for clients to acquire the public RSA key for verification in JWK format.
What is the expected behavior to initiate a keystore from this JWK? The property is being loaded in the ResourceServerProperties.JWK but then what. Should spring boot call this URI and fetch the jwks then create a store for me to use in verification?
I am following this tutorial to setup the configuration of the keystore http://www.baeldung.com/spring-security-oauth-jwt
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource("public.txt");
String publicKey = null;
try {
publicKey = IOUtils.toString(resource.getInputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
return converter;
}
But instead of loading a .pem public key I think I want to load it from a jwk.
If you want to use JWKS, use JwkTokenStore in place of JwtTokenStore.
spring-security-oauth2/jwk internally implements key loading and management according to the auth0 spec
You can also see docs on auto-configuration of the same, however i feel configuring it in quite straight-forward (see below).
We don't have to do any verification as JwkTokenStore sets up the verification with JwkDefinitionSource JwkVerifyingJwtAccessTokenConverter using JWKS exposed at #Value("{jsecurity.oauth2.resource.jwk.key-set-uri}")
However, the spring-security-oauth2/jwk classes from spring don't have any public constructors, we often need and can perform any custom steps in AccessTokenConversion, like a common need is to extract jwt content to auth context, we can always inject a custom converter to JwkTokenStore
import org.springframework.security.oauth2.provider.token.store.jwk.*;
import org.springframework.security.oauth2.provider.token.store.*
import org.springframework.security.oauth2.provider.token.*;
import java.utl.*;
#Configuration
class JwtConfiguration {
#Bean
public DefaultTokenServices tokenServices(final TokenStore tokenStore) {
final DefaultTokenServices dts = new DefaultTokenServices();
dts.setTokenStore(tokenStore);
dts.setSupportRefreshToken(true);
return dts;
}
#Bean
public TokenStore tokenStore(
#Value("{jsecurity.oauth2.resource.jwk.key-set-uri}") final String jwksUrl,
final JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwkTokenStore(jwksUrl, jwtAccessTokenConverter, null);
}
#Bean
public JwtAccessTokenConverter createJwtAccessTokenConverter() {
final JwtAccessTokenConverter converter;
converter.setAccessTokenConverter(new DefaultAccessTokenConverter() {
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
final OAuth2Authentication auth = super.extractAuthentication(map);
auth.setDetails(map); //this will get spring to copy JWT content into
return auth;
}
}
return conveter;
}
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
#Configuration
#EnableResourceServer
class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private String resourceId;
private TokenStore tokenStore;
public ResourceServerConfig(
#Value("\${jwt.reourceId}") private String resourceId,
private TokenStore tokenStore) {
this.resourceId = resourceId;
this.tokenStore = tokenStore;
}
/**
* Ensures request to all endpoints ore a
#Override
public void configure(final HttpSecurity http) {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/**").authenticated();
}
/**
* Configure resources
* Spring OAuth expects "aud" claim in JWT token. That claim's value should match to the resourceId value
* (if not specified it defaults to "oauth2-resource").
*/
#Override
public void configure(final ResourceServerSecurityConfigurer resources) {
resources.resourceId(resourceId).tokenStore(tokenStore);
}
}
The main goal of this implementation would be to verify a JWT locally using the corresponding JWK(JSON WEB TOKEN KEY SET). The JWK used for verification is matched using the kid header parameter of the JWT and the kid attribute of the JWK.
The server can validate this token locally without making any network requests, talking to a database, etc. This can potentially make session management faster because instead of needing to load the user from a database (or cache) on every request, you just need to run a small bit of local code. This is probably the single biggest reason people like using JWTs: they are stateless.

How to set up a Spring Integration 4.3 Email Sending flow with a java class instead of XML?

I am trying to add Spring Integration to a REST MVC Spring app I have been writing. I am using the latest Spring 4.2.x for core, integration and mvc. The idea is to create separate application contexts as on the Dynamic FTP example. The reason why is because I can send emails from 2 separated accounts as well as listen from 2 separated accounts hence having separate application contexts as well as environment variables to aid on bean creation for each context helps a bunch.
I apologize for the newbie questions, but I am having a hard time with the manual as well as trying to figure out how to setup SMTP email configuration class without XML.
I want to have both receive and send integration channels. All email settings will be configured from enviroment variables so I have injected the enviroment: #Autowired Environment env;
I can define:
A MailSender bean
A MailSendingMessageHandler bean
A MessageChannel for the SMTP (outbound)
Now, on XML configurations you have an outbound-channel-adapter where you wire the mail-sender bean as well as the MessageChannel
My goal is to have configurations for:
Send emails.
Listen to IMAP emails and process them.
For sending emails, the idea is to get from a rest endpoint, calling a service and that service is what will put a message to Integration SMTP outbound channel to send an email. Looks like, by using the MailSendingMessageHandler it will get the Integration Message and convert to a Mail Message for the MailSender. I have no idea on how to wire the MailSendingMessageHandler to the outbound channel so that an email can be send. Also I do not know how to, from my #Service class that is called by the rest endpoint how to create the messages and send them through the outbound SMTP channel so emails can be send. On one rest call I send all email recipients I want to reach. Before, each email message body is properly formatted so that I can create each Integration Message (as an email) that will be handled and converted by MailSendingMessageHandler. I have tried to find examples online without success on how to accomplish this.
Any examples you could redirect me? Thanks in advance!
So far I have for the configuration:
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.integration.annotation.InboundChannelAdapter;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.annotation.Poller;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.mail.MailReceiver;
import org.springframework.integration.mail.MailReceivingMessageSource;
import org.springframework.integration.mail.MailSendingMessageHandler;
import org.springframework.mail.MailMessage;
import org.springframework.mail.MailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
import org.springframework.core.env.Environment;
#Configuration
#EnableIntegration
public class IntegrationEmailConfig {
#Autowired
Environment env;
#Bean
public static PropertySourcesPlaceholderConfigurer pspc() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
#InboundChannelAdapter(value = "emailInboundChannel", poller = #Poller(fixedDelay = "5000") )
public MailReceivingMessageSource mailMessageSource(MailReceiver imapMailReceiver) {
return new MailReceivingMessageSource(imapMailReceiver);
}
private Properties additionalMailProperties() {
Properties properties = new Properties();
if (env.containsProperty("mail.smtp.auth")) {
properties.setProperty("mail.smtp.auth",env.getProperty("mail.smtp.auth"));
}
if (env.containsProperty("mail.smtp.starttls.enable")) {
properties.setProperty("mail.smtp.starttls.enable",env.getProperty("mail.smtp.starttls.enable"));
}
return properties;
}
#Bean
public MailSender mailSender() throws Exception {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
if (env.containsProperty("mail.server.host")) {
mailSender.setHost(env.getProperty("mail.server.host"));
} else {
throw new Exception("Missing mail.server.host property");
}
if (env.containsProperty("mail.server.port")) {
mailSender.setPort(Integer.parseInt(env.getProperty("mail.server.port")));
} else {
throw new Exception("Missing mail.server.port property");
}
if (env.containsProperty("mail.server.username")) {
mailSender.setUsername(env.getProperty("mail.server.username"));
} else {
throw new Exception("Missing mail.server.username property");
}
if (env.containsProperty("mail.server.password")) {
mailSender.setPassword(env.getProperty("mail.server.password"));
} else {
throw new Exception("Missing mail.server.password property");
}
mailSender.setJavaMailProperties(additionalMailProperties());
return mailSender;
}
#Bean
public MailSendingMessageHandler mailSendingMessageHandler() throws Exception {
MailSendingMessageHandler mailSendingMessageHandler = new MailSendingMessageHandler(mailSender());
//mailSendingMessageHandler.setChannelResolver(channelResolver);
return mailSendingMessageHandler;
}
/* #Bean
public DirectChannel outboundMail() {
DirectChannel outboundChannel = new DirectChannel();
return outboundChannel;
}
*/
#Bean
public MessageChannel smtpChannel() {
return new DirectChannel();
}
/* #Bean
#Value("${imap.url}")
public MailReceiver imapMailReceiver(String imapUrl) {
// ImapMailReceiver imapMailReceiver = new ImapMailReceiver(imapUrl);
// imapMailReceiver.setShouldMarkMessagesAsRead(true);
// imapMailReceiver.setShouldDeleteMessages(false);
// // other setters here
// return imapMailReceiver;
MailReceiver receiver = mock(MailReceiver.class);
MailMessage message = mock(Message.class);
when(message.toString()).thenReturn("Message from " + imapUrl);
Message[] messages = new Message[] {message};
try {
when(receiver.receive()).thenReturn(messages);
}
catch (MessagingException e) {
e.printStackTrace();
}
return receiver;
}*/
}
Simply annotate the MailSendingMessageHandler bean with #ServiceActivator, the framework will register a ConsumerEndpointFactoryBean to wrap the handler. See the documentation about "Annotations on #Beans".

Resources