keystore.jks and truststore.jks can't be found in Spring Boot App - spring

I wrote a 2-way authetication restful service cleint to consume a secured restful web service on port 8443 over https. Here are parts of application.properties:
trust.store=classpath:truststore.jks
trust.store.password=xyz123
key.store=classpath:keystore.jks
key.store.password=xyz123
Below are two ways to configure RestTemplate
#Configuration
public class SSLTemplate {
#Value("${key.store}")
private String keyStore;
#Value("${key.store.password}")
private String keyStorePassword;
#Value("${trust.store}")
private String trustStore;
#Value("${trust.store.password}")
private String trustStorePassword;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(ResourceUtils.getFile(keyStore), keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile(trustStoreF), trustStorePassword.toCharArray())
.build();
...
}
#Configuration
public class SSLTemplate {
#Value("${key.store}")
private Resource keyStoreR;
#Value("${key.store.password}")
private String keyStorePassword;
#Value("${trust.store}")
private Resource trustStoreR;
#Value("${trust.store.password}")
private String trustStorePassword;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(keyStoreR.getURL(), keyStorePassword.toCharArray(), keyStorePassword.toCharArray())
.loadTrustMaterial(trustStoreR.getURL(), trustStorePassword.toCharArray())
.build();
...
}
When I run the app via bootRun or within Eclipse, both of them work.
But when I use jar launcer
java -jar app.jar
I got below exception.
Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiation
Exception: Failed to instantiate [org.springframework.web.client.RestTemplate]: Factory method 'restTemplate' threw exception;
nested exception is java.io.FileNotFoundException: URL cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/C:/build/libs/app.jar!/BOOT-INF/classes!/truststore.jks
I have also tried
java -Djavax.net.ssl.trustStore=truststore.jks -Djavax.net.ssl.trustStorePassword=xyz123 -Djavax.net.ssl.keyStore=keystore.jks -Djavax.net.ssl.keyStorePassword=xyz123 -jar app.jar
and got the same exception. Any help will be really appreciated.

Try using resource.getInputStream() instead of resource.getFile() as, resource.getFile() in Spring tries to access a file system path but it can not access a path in your JAR
This link has a rich content and do take a look at Andy's answer here
Sample example:
Resource resource = resourceLoader.getResource("classpath:GeoLite2-Country.mmdb");
InputStream dbAsStream = resource.getInputStream();
and do you have use full path
-Djavax.net.ssl.trustStore=something/like/this/truststore.jks

Related

Java AWS SDK v2 S3Client creation fail

I'm try to use S3Client from AWS Java SDK v2, on my JHipster Spring boot project.
In addition to S3CLient i'm using S3Presigner for pre-signed url generation.
This is the configuration class, that i'm using to create beans:
#Configuration
public class AWSConfiguration {
private final Logger log = LoggerFactory.getLogger(AWSConfiguration.class);
#Bean(name = "credentialsProvider")
public AwsCredentialsProvider getCredentialsProvider(#Value("${aws.accessKey}") String accessKey, #Value("${aws.secretKey}") String secretKey) {
return StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey));
}
#Bean(name = "presigner")
#Primary
public S3Presigner getPresigner(#Value("${aws.region}") String awsRegion, #Qualifier("credentialsProvider") final AwsCredentialsProvider credentialsProvider) {
return S3Presigner.builder().credentialsProvider(credentialsProvider).region(Region.of(awsRegion)).build();
}
#Bean(name = "s3Client")
#Primary
public S3Client getS3Client(#Value("${aws.region}") String awsRegion, #Qualifier("credentialsProvider") final AwsCredentialsProvider credentialsProvider) {
return S3Client.builder().credentialsProvider(credentialsProvider).region(Region.of(awsRegion)).build();
}
}
At the boot, i've this error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 's3Client' defined in class path resource [com/cyd/bb/config/AWSConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [software.amazon.awssdk.services.s3.S3Client]: Factory method 'getS3Client' threw exception; nested exception is java.lang.IllegalArgumentException: Expected a profile or property definition on line 1
If i remove "s3Client" bean the project start correctly and i can use presigner without problems.
So s3Client produce the error, someone can help me for this issue?
Thanks in advance.

SpringBootTest, Testcontainers, container start up - Mapped port can only be obtained after the container is started

I am using docker/testcontainers to run a postgresql db for testing. I have effectively done this for unit testing that is just testing the database access. However, I have now brought springboot testing into the mix so I can test with an embedded web service and I am having problems.
The issue seems to be that the dataSource bean is being requested before the container starts.
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [com/myproject/integrationtests/IntegrationDataService.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.sql.DataSource]: Factory method 'dataSource' threw exception; nested exception is java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.sql.DataSource]: Factory method 'dataSource' threw exception; nested exception is java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
Caused by: java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
Here is my SpringBootTest:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = {IntegrationDataService.class, TestApplication.class},
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringBootTestControllerTesterIT
{
#Autowired
private MyController myController;
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void testRestControllerHello()
{
String url = "http://localhost:" + port + "/mycontroller/hello";
ResponseEntity<String> result =
restTemplate.getForEntity(url, String.class);
assertEquals(result.getStatusCode(), HttpStatus.OK);
assertEquals(result.getBody(), "hello");
}
}
Here is my spring boot application referenced from the test:
#SpringBootApplication
public class TestApplication
{
public static void main(String[] args)
{
SpringApplication.run(TestApplication.class, args);
}
}
Here is the IntegrationDataService class which is intended to startup the container and provide the sessionfactory/datasource for everything else
#Testcontainers
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#EnableTransactionManagement
#Configuration
public class IntegrationDataService
{
#Container
public static PostgreSQLContainer postgreSQLContainer = (PostgreSQLContainer) new PostgreSQLContainer("postgres:9.6")
.withDatabaseName("test")
.withUsername("sa")
.withPassword("sa")
.withInitScript("db/postgresql/schema.sql");
#Bean
public Properties hibernateProperties()
{
Properties hibernateProp = new Properties();
hibernateProp.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
hibernateProp.put("hibernate.format_sql", true);
hibernateProp.put("hibernate.use_sql_comments", true);
// hibernateProp.put("hibernate.show_sql", true);
hibernateProp.put("hibernate.max_fetch_depth", 3);
hibernateProp.put("hibernate.jdbc.batch_size", 10);
hibernateProp.put("hibernate.jdbc.fetch_size", 50);
hibernateProp.put("hibernate.id.new_generator_mappings", false);
// hibernateProp.put("hibernate.hbm2ddl.auto", "create-drop");
// hibernateProp.put("hibernate.jdbc.lob.non_contextual_creation", true);
return hibernateProp;
}
#Bean
public SessionFactory sessionFactory() throws IOException
{
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
sessionFactoryBean.setHibernateProperties(hibernateProperties());
sessionFactoryBean.setPackagesToScan("com.myproject.model.entities");
sessionFactoryBean.afterPropertiesSet();
return sessionFactoryBean.getObject();
}
#Bean
public PlatformTransactionManager transactionManager() throws IOException
{
return new HibernateTransactionManager(sessionFactory());
}
#Bean
public DataSource dataSource()
{
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(postgreSQLContainer.getDriverClassName());
dataSource.setUrl(postgreSQLContainer.getJdbcUrl());
dataSource.setUsername(postgreSQLContainer.getUsername());
dataSource.setPassword(postgreSQLContainer.getPassword());
return dataSource;
}
}
The error occurs on requesting the datasource bean from the sessionFactory from one of the Dao classes before the container starts up.
What the heck am I doing wrong?
Thanks!!!
The reason for your java.lang.IllegalStateException: Mapped port can only be obtained after the container is started exception is that when the Spring Context now gets created during your test with #SpringBootTest it tries to connect to the database on application startup.
As you only launch your PostgreSQL inside your IntegrationDataService class, there is a timing issue as you can't obtain the JDBC URL or create a connection on application startup as this bean is not yet properly created.
In general, you should NOT use any test-related code inside your IntegrationDataService class. Starting/stopping the database should be done inside your test setup.
This ensures to first start the database container, wait until it's up- and running, and only then launch the actual test and create the Spring Context.
I've summarized the required setup mechanism for JUnit 4/5 with Testcontainers and Spring Boot, that help you get the setup right.
In the end, this can look like the following
// JUnit 5 example with Spring Boot >= 2.2.6
#Testcontainers
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationIT {
#Container
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
.withPassword("inmemory")
.withUsername("inmemory");
#DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
}
#Test
public void contextLoads() {
}
}

What is the JNDI name should I use to lookup for a remote interface deployed in websphere using Spring Boot?

I have a remote interface deployed in websphere 8.5.5 and I want to look up for this in spring boot application. I have made similar interface in my spring boot as common interface RMI I also used SimpleRemoteStatelessSessionProxyFactoryBean but the proxy returned is null and it was throwing null pointer in proxy.invokeMethod()
#Configuration
public class Config {
private static final String INITIAL_CONTEXT_FACTORY = "com.ibm.websphere.naming.WsnInitialContextFactory";
private static final String PROVIDER_URL = "corbaname:iiop:localhost:2809/NameServiceServerRoot";
#Primary
#Bean
public static AdminService adminService() {
Properties jndiProps = new Properties();
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
properties.put(Context.PROVIDER_URL, PROVIDER_URL);
SimpleRemoteStatelessSessionProxyFactoryBean factory = new SimpleRemoteStatelessSessionProxyFactoryBean();
factory.setJndiEnvironment(jndiProps);
factory.setJndiName("java:global/[AppName]/[ModuleName]/ejb/[BeanName]![RemoteInterface]");
factory.setBusinessInterface(AdminService.class);
factory.setResourceRef(true);
AdminService proxy = (AdminService) factory.getObject();
try {
proxy.invokeMethod();
}catch(RemoteException e) {
e.printStackTrace();
}
return proxy;
}
}
Now it's throwing this error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'AdminService' defined in class path resource [...Config.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [...AdminService]: Factory method 'adminEJBService' threw exception; nested exception is javax.naming.NameNotFoundException: Name [global/[AppName]/[ModuleName]/ejb/[BeanName]![RemoteInterface] is not bound in this Context. Unable to find [global].
You must replace everything in [] with the actual name, so not [AppName], but MyApp, etc. If not sure, you can determine the exact lookup string by looking in SystemOut.log file of the WebSphere 8.5.5 server for the message CNTR0167I. For example, the actual message would look like this:
CNTR0167I: The server is binding the javax.management.j2ee.ManagementHome interface of the Management enterprise bean in the mejb.jar module of the ManagementEJB application. The binding location is: java:global/ManagementEJB/mejb/Management!javax.management.j2ee.ManagementHome

Unable to use #LoadBalanced with OAuth2RestTemplate configured on ClientCredentials

I want to use OAuth2 ClientCredentials flow for inter service communication between two Resource Servers. Everything works fine except that i am not able to use service name (Ribbon Load Balancer feature) instead of hostname in my OAuth2RestTemplate calls to remote resource server.
One of my Resource Server (that calls another Resource Server) has below configuration:
Spring Boot 1.5.13
Spring Cloud Edgware.SR3
build.gradle contains entries for eureka and ribbon
compile('org.springframework.cloud:spring-cloud-starter-ribbon')
compile('org.springframework.cloud:spring-cloud-starter-eureka')
#Configuration
class RestTemplateConfig {
#Bean
#ConfigurationProperties("security.oauth2.client")
public ClientCredentialsResourceDetails oauth2ClientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
#LoadBalanced
#Bean(name = "oauthRestTemplate")
public OAuth2RestOperations oAuthRestTemplate(ClientCredentialsResourceDetails oauth2ClientCredentialsResourceDetails) {
return new OAuth2RestTemplate(oauth2ClientCredentialsResourceDetails);
}
}
Service Consuming this OAuth2RestTemplate
#Service
class TestService {
#Autowired
#Qualifier("oauthRestTemplate")
private OAuth2RestOperations oAuth2RestOperations;
public void notifyOrderStatus(long orderId, OrderStatus newStatus) {
oAuth2RestOperations.exchange("http://notification-service/api/order/{id}/status/{status}", HttpMethod.POST, null, Void.class, orderId, newStatus.name());
}
}
Exception appears while invoking remote service using service name i.e. http://notification-service instead of actual hostname and port of remote resource server. If I use actual hostname + port, then everything works fine but I don't want my one resource to know host/post of another resource server.
Exception:
Caused by: java.net.UnknownHostException: notification-service
I have few questions:
If my RestTemplate is annotated with #LoadBalanced, then everything works fine. Does OAuth2RestTemplate support this annotation and can we use service name instead of hostname? If yes, any reference or documentation would be appreciated.
Is it a good idea to use oauth2 client credentials for inter service security between two resource servers? I do not see any samples for the same in documentation?
#LoadBalanced RestTemplate works when we use RestTemplateCustomizer to customize the newly created OAuth2RestTemplate, as shown in the below code:
#Bean(name = "MyOAuthRestTemplate")
#LoadBalanced
public OAuth2RestOperations restTemplate(RestTemplateCustomizer customizer, ClientCredentialsResourceDetails oauth2ClientCredentialsResourceDetails) {
OAuth2RestTemplate restTemplate = new OAuth2RestTemplate(oauth2ClientCredentialsResourceDetails);
customizer.customize(restTemplate);
return restTemplate;
}
Using service name instead of actual host name works fine using this RestTemplate.

Camel MQTT Configuration options DSL route URL parameter with bean value not working

I ma trying to test pub/sub from an external broker ( AWs IoT ); started off of the camel-example-spring-boot example project and added thecamel-mqtt-starter. Everything seems to work fine until I try to define the mqtt routes. I am having issues with configuring the sslContext url parameter :
#Configuration
public class AppConfig {
#Bean(name="awsiotsslcontext")
SSLContext awsiotsslcontext(){
SSLContext sslContext = null;
try{
ClassLoader cl = this.getClass().getClassLoader();
InputStream is = cl.getResourceAsStream("/cert/myApp.cert.pem");
// You could get a resource as a stream instead.
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate)cf.generateCertificate(is);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null); // You don't need the KeyStore instance to come from a file.
ks.setCertificateEntry("caCert", caCert);
tmf.init(ks);
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
} catch (Exception e){
}
return sslContext;
}
}
And then in my route builder :
#Component
public class SampleCamelRouter extends RouteBuilder {
#Autowired
SSLContext awsiotsslcontext;
#Override
public void configure() throws Exception {
from("timer://foo?repeatCount=0&delay=5000&fixedRate=true&period=10s")
.setBody(simple("TEST MESSAGE"))
.to("mqtt:awsiot?host=ssl://{{aws.iot.host}}:8883&publishTopicName={{aws.iot.sub.topic}}&sslContext=#awsiotsslcontext").log("Sent :"+body().convertToString().toString());
from("mqtt:awsiot?host=ssl://{{aws.iot.host}}:8883&subscribeTopicName={{aws.iot.sub.topic}}&sslContext=#awsiotsslcontext").log("Recieved : "+body().convertToString().toString());
}
}
getting the following error :
java.lang.IllegalArgumentException: Could not find a suitable setter
for property: sslContext as there isn't a setter method with same
type: java.lang.String nor type conversion possible: No type converter
available to convert from type: java.lang.String to the required type:
javax.net.ssl.SSLContext with value #awsiotsslcontext
I believe this is a simple endpoint configuration issue, but tried various things and nothing seems to work. Having the # with the bean name should have camel to look up in the registry for the bean but here it recognizes it as String ? Any workaround here ?
This was an issue on camel route configuration; when I configured my route under a #Configuration instead of having under #Component as suggested by the documentation, it doesn't complain about bean definition with '#' in the URI; I would expect in a Spring Boot Application the beans would load before the routes by default:
#Bean
RouteBuilder awsIoTRoute() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("timer://foo?repeatCount=0&delay=5000&fixedRate=true&period=17s")
.setBody(simple("TEST MESSAGE"))
.to("mqtt:awsIoTPublisher?host=ssl://{{aws.iot.host}}:8883&publishTopicName={{aws.iot.pub.topic}}&clientId={{aws.iot.pub.clientId}}&sslContext=#sslContext")
.log("Sent :"+body().convertToString().toString());
from("mqtt:awsIoTReciever?host=ssl://{{aws.iot.host}}:8883&subscribeTopicName={{aws.iot.sub.topic}}&clientId={{aws.iot.sub.clientId}}&sslContext=#sslContext").log("Recieved : "+body().convertToString());
}
};
}

Resources