Spring Boot Localhost https issue - spring

I wanted to make my localhost from hypertext transfer protocol to the secured socket layer hypertext transfer protocol. So I have added a key store to my spring boot project, and configured my application.properties like this:
INFORMATION
But I get an error in chrome that:
localhost uses an unsupported protocol.
ERR__VERSION_OR_CIPHER_MISMATCH
Note: I have done no changes to any of the java files.

When I faced this issue I solved it by a bean like below. hope this helps you
#Configuration
class CustomConfiguration {
#Bean
public EmbeddedServletContainerFactory servletContainer() {
final int port = 8443;
final String keystoreFile = "/path/to/keystore"
final String keystorePass = "keystore-password"
final String keystoreType = "pkcs12"
final String keystoreProvider = "SunJSSE"
final String keystoreAlias = "tomcat"
TomcatEmbeddedServletContainerFactory factory =
new TomcatEmbeddedServletContainerFactory(this.port);
factory.addConnectorCustomizers( new TomcatConnectorCustomizer() {
void customize(Connector con) {
Http11NioProtocol proto = (Http11NioProtocol) con.getProtocolHandler();
proto.setSSLEnabled(true);
con.setScheme("https");
con.setSecure(true);
proto.setKeystoreFile(keystoreFile);
proto.setKeystorePass(keystorePass);
proto.setKeystoreType(keystoreType);
proto.setProperty("keystoreProvider", keystoreProvider);
proto.setKeyAlias(keystoreAlias);
}
});
return factory;
}
}
Also make sure you call keytool with -storetype pkcs12, not -storepass pkcs12

Related

Encrypt server port in springboot application properties using jasypt

I am using jasypt to encrypt some properties in application.properties file of my springboot application.
I have a requirement to encrypt the server port as well.
When I encrypt it and run my application the application does not start.
I get the below error
Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Integer] for value 'ENC(OWRKKTBHQcjWItQG/+V7hw==)'; nested exception is java.lang.NumberFormatException: For input string: "ENC(OWRKKTBHQcjWItQG/+V7hw==)"
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-5.2.2.RELEASE.jar:5.2.2.RELEASE]
looks like its not decrypting the property
below is my jasypt custom configuration
#Configuration
public class JasyptEncryptionConfig {
public final static String ENCRYPTION_KEY = "Ph03nix5parr0w!m#ven!r";
#Bean(name ="jasyptStringEncryptor")
public StringEncryptor passwordEncryptor(){
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(ENCRYPTION_KEY); // encryptor's private key
config.setAlgorithm("PBEWithMD5AndDES");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
}
below are the application properties
server.port: ENC(OWRKKTBHQcjWItQG/+V7hw==)
management.port: ENC(OWRKKTBHQcjWItQG/+V7hw==)
management.address: 127.0.0.1
management.context-path: /actuator
Other properties are working fine, only server port does not work.
Maybe you can use environment as screen.
You cannot change port int to String config.setStringOutputType("base64");
ServerProperties.class public void setPort(Integer port)

Spring-ws security header to load configurations for multiple cert

I am trying to invoke a SOAP webservice in my spring boot application using spring-ws with a keystore which has multiple certs. The configuration always defaults to single cert.
Sample code below:
Wss4jSecurityInterceptor wss4jSecurityInterceptor = new Wss4jSecurityInterceptor();
Merlin merlin = new Merlin();
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream inputStream = new FileInputStream(ResourceUtils.getFile(("keystore.jks")));
keyStore.load(inputStream, "tester".toCharArray());
merlin.setKeyStore(keyStore);
wss4jSecurityInterceptor.setSecurementSignatureCrypto(merlin);
wss4jSecurityInterceptor.setSecurementUsername("test");
wss4jSecurityInterceptor.setSecurementPassword("");
webServiceTemplate.setInterceptors(new org.springframework.ws.client.support.interceptor.ClientInterceptor[]
{wss4jSecurityInterceptor});
When i checked the source code of the apache library class WSSecSignature class. I see there is a configuration for picking up multiple cert. But am not sure how to set the singleCert to be false in the wss4jSecurityInterceptor. It always goes to the else block in the below logic
if (!this.useSingleCert) {
this.secRef.addTokenType("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1");
ref.setValueType("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1");
} else {
ref.setValueType("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3");
}
Is there a config i need to set while setting the keystore to Merin object, to make the useSingleCert as false?
Found a work around to override the Wss4jSecurityInterceptor, set the property to false and use the extended interceptor
class SecurityInterceptor extends Wss4jSecurityInterceptor
{
#Override
protected RequestData initializeRequestData(MessageContext messageContext) {
messageContext.setProperty(WSHandlerConstants.USE_SINGLE_CERTIFICATE, "false");
return super.initializeRequestData(messageContext);
}
}

shiro buji pac4j cas single sign out not work

spring boot 2.2.5
shiro-spring-boot-web-starter 1.5.1
buji-pac4j 4.1.1
pac4j-cas 3.8.3
cas overlay template 5.3.
I start cas server in tomcat with https, and start two clients(pac4j1 and pac4j2) in eclipse.
single sign on works, but single sign out failed.
Following are my configs:
I only added one service file under cas server which looks like:
{
"#class": "org.apereo.cas.services.RegexRegisteredService",
"serviceId": "^(http)://localhost.*",
"name": "local",
"id": 10000003,
"evaluationOrder": 1
}
application.yml of pac4j1:
server:
port: 8444
servlet:
context-path: /pac4j1
cas:
client-name: pac4j1Client
server:
url: https://localhost:8443/cas
project:
url: http://localhost:8444/pac4j1
Pac4jConfig:
#Configuration
public class Pac4jConfig {
#Value("${cas.server.url}")
private String casServerUrl;
#Value("${cas.project.url}")
private String projectUrl;
#Value("${cas.client-name}")
private String clientName;
#Bean("authcConfig")
public Config config(CasClient casClient, ShiroSessionStore shiroSessionStore) {
Config config = new Config(casClient);
config.setSessionStore(shiroSessionStore);
return config;
}
#Bean
public ShiroSessionStore shiroSessionStore(){
return new ShiroSessionStore();
}
#Bean
public CasClient casClient(CasConfiguration casConfig){
CasClient casClient = new CasClient(casConfig);
casClient.setCallbackUrl(projectUrl + "/callback?client_name=" + clientName);
casClient.setName(clientName);
return casClient;
}
#Bean
public CasConfiguration casConfig(){
final CasConfiguration configuration = new CasConfiguration();
configuration.setLoginUrl(casServerUrl + "/login");
configuration.setProtocol(CasProtocol.CAS20);
configuration.setAcceptAnyProxy(true);
configuration.setPrefixUrl(casServerUrl + "/");
return configuration;
}
}
shiro config:
#Configuration
public class ShiroConfig {
#Value("${cas.project.url}")
private String projectUrl;
#Value("${cas.server.url}")
private String casServerUrl;
#Value("${cas.client-name}")
private String clientName;
#Bean("securityManager")
public DefaultWebSecurityManager securityManager(Pac4jSubjectFactory subjectFactory, CasRealm casRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(casRealm);
manager.setSubjectFactory(subjectFactory);
return manager;
}
#Bean
public CasRealm casRealm(){
CasRealm realm = new CasRealm();
realm.setClientName(clientName);
realm.setCachingEnabled(false);
realm.setAuthenticationCachingEnabled(false);
realm.setAuthorizationCachingEnabled(false);
return realm;
}
#Bean
public Pac4jSubjectFactory subjectFactory(){
return new Pac4jSubjectFactory();
}
#Bean
public FilterRegistrationBean<SingleSignOutFilter> singleSignOutFilter() {
FilterRegistrationBean<SingleSignOutFilter> bean = new FilterRegistrationBean<SingleSignOutFilter>();
bean.setName("singleSignOutFilter");
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix(casServerUrl);
singleSignOutFilter.setIgnoreInitConfiguration(true);
bean.setFilter(singleSignOutFilter);
bean.addUrlPatterns("/*");
bean.setEnabled(true);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
#Bean
public FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean() {
FilterRegistrationBean<DelegatingFilterProxy> filterRegistration = new FilterRegistrationBean<DelegatingFilterProxy>();
filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
filterRegistration.addInitParameter("targetFilterLifecycle", "true");
filterRegistration.setEnabled(true);
filterRegistration.addUrlPatterns("/*");
filterRegistration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD);
return filterRegistration;
}
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/", "securityFilter");
filterChainDefinitionMap.put("/index", "securityFilter");
filterChainDefinitionMap.put("/callback", "callbackFilter");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**","anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}
#Bean("shiroFilter")
public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, Config config) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
loadShiroFilterChain(shiroFilterFactoryBean);
Map<String, Filter> filters = new HashMap<>(3);
SecurityFilter securityFilter = new SecurityFilter();
securityFilter.setConfig(config);
securityFilter.setClients(clientName);
filters.put("securityFilter", securityFilter);
MyCallbackFilter callbackFilter = new MyCallbackFilter();
callbackFilter.setConfig(config);
callbackFilter.setDefaultUrl(projectUrl);
filters.put("callbackFilter", callbackFilter);
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setConfig(config);
logoutFilter.setCentralLogout(true);
logoutFilter.setLocalLogout(true);
logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName);
filters.put("logout",logoutFilter);
shiroFilterFactoryBean.setFilters(filters);
return shiroFilterFactoryBean;
}
}
application.properties of cas server is default, and cas server use https(https://localhost:8443/cas) while cas clients are http(http://localhost:8444/pac4j1).
Where am I wrong?
with the help of the link SLO which provided by leopal, i know that cas server need to send log out request back to client.
Hence, i checked the log of cas server and found INFO [org.apereo.cas.logout.DefaultLogoutManager] - <Performing logout operations for.
so i added log for org.apereo.cas.logout and found that there are some classes about logout: DefaultLogoutManager, DefaultSingleLogoutServiceLogoutUrlBuilder, DefaultSingleLogoutServiceMessageHandler and SimpleUrlValidator.
when performing logout, DefaultSingleLogoutServiceLogoutUrlBuilder.determineLogoutUrl will get the logout url from registered service or get the original url from cas client if original url is a valid url.
So my problem is : i didn't define logout url in service json file and the original url from cas client is localhost:8444 which is a invalid ipv4. As a result, cas server will not send logout request back to client.
Solution is : use ip in project url instead of localhost in application.yml of cas client:
cas:
client-name: pac4j1Client
server:
url: https://localhost:8443/cas
project:
url: http://192.168.2.119:8444/pac4j1
another solution is set logoutUrl for each cas client service json file(not tried yet).

How to use p12 client certificate with spring feign client

I have a Spring Boot application that calls a remote service.
This remote web service provided me a p12 file that should authenticate my application.
How do I configure my feign client to use the p12 certificate ?
I've tried settings these properties:
-Djavax.net.ssl.keyStore=path_to_cert.p12 -Djavax.net.ssl.keyStorePassword=xxx -Djavax.net.ssl.keyStoreType=PKCS12
But it doesn't change anything, I still get this error:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
I could finally manage to do it with a lot of blind trial and error.
The problem is, by default, the feign builder builds feign clients with null SSLSocketFactory:
org.springframework.cloud.openfeign.FeignClientsConfiguration#feignBuilder:
#Bean
#Scope("prototype")
#ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder().retryer(retryer);
}
feign.Feign.Builder:
public static class Builder {
// ...
private Client client = new Client.Default(null, null);
So, I had to define this bean in a #Configuration:
#Bean
#Profile({"prod", "docker"})
public Feign.Builder feignBuilder() {
return Feign.builder()
.retryer(Retryer.NEVER_RETRY)
.client(new Client.Default(getSSLSocketFactory(), null));
with this method: (can't remember source)
SSLSocketFactory getSSLSocketFactory() {
char[] allPassword = keyStorePassword.toCharArray();
SSLContext sslContext = null;
try {
sslContext = SSLContextBuilder
.create()
.setKeyStoreType(keyStoreType)
.loadKeyMaterial(ResourceUtils.getFile(keyStore), allPassword, allPassword)
.build();
} catch (Exception e) { /* *** */ }
return sslContext.getSocketFactory();
}
Now, it works for me, I debugged though the feign client calls and the sslSocketFactory is correctly passed to the underlying connection.
In case you wish to achieve the above effect programmatically without using keytool, you can do the following:
class CustomFeignConfiguration {
private val log = Logger.getLogger(this.javaClass.name)
#Value("\${client_p12_base64_encoded_string}")
private val clientP12: String = ""
#Value("\${client_p12_password}")
private val clientP12Pass: String = ""
#Bean
fun feignClient(): Client {
val sslSocketFactory= getSSLSocketFactory()
log.info("CUSTOM FEIGN CLIENT CALLED")
return Client.Default(sslSocketFactory, DefaultHostnameVerifier())
}
private fun getSSLSocketFactory(): SSLSocketFactory {
val decoder = java.util.Base64.getDecoder()
val p12 = decoder.decode(clientP12)
val p12File = File("clientCer.p12")
p12File.writeBytes(p12)
try {
val sslContext = SSLContexts
.custom()
.loadKeyMaterial(p12File, clientP12Pass.toCharArray(), clientP12Pass.toCharArray())
.build()
return sslContext.socketFactory
} catch (exception: Exception) {
throw RuntimeException(exception)
}
}
}
The FeignClient interface that is using the configuration has to load this specifically
#FeignClient(name = "client", configuration = [CustomFeignConfiguration::class], url = "\${url}")
interface Client {
....
....
}
The SSLContexts library can only use p12 certificates and we have to convert the certificates and keys in PEM format to the P12 format.
Create a p12 certificate from your PEM certificate and key using the following SSL command:
openssl pkcs12 -export -inkey domain.key -in domain.crt -out domain.p12
Please record the password that you enter after you run this command.
Convert this p12 certificate to a base64 string using the following command
base64 domain.p12 > domain.p12.base64
Convert this multiline string to a single line string using the following command:
tr -d "\n\r" < domain.p12.base64 > domain.p12.base64.singleline
Use the single line string from this command and the password that you recorded earlier in your application.properties.

Spring Boot and how to configure connection details to MongoDB?

Being new to Spring Boot I am wondering on how I can configure connection details for MongoDB.
I have tried the normal examples but none covers the connection details.
I want to specify the database that is going to be used and the url/port of the host that runs MongoDB.
Any hints or tips?
Just to quote Boot Docs:
You can set spring.data.mongodb.uri property to change the url, or alternatively specify a host/port. For example, you might declare the following in your application.properties:
spring.data.mongodb.host=mongoserver
spring.data.mongodb.port=27017
All available options for spring.data.mongodb prefix are fields of MongoProperties:
private String host;
private int port = DBPort.PORT;
private String uri = "mongodb://localhost/test";
private String database;
private String gridFsDatabase;
private String username;
private char[] password;
It's also important to note that MongoDB has the concept of "authentication database", which can be different than the database you are connecting to. For example, if you use the official Docker image for Mongo and specify the environment variables MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD, a user will be created on 'admin' database, which is probably not the database you want to use. In this case, you should specify parameters accordingly on your application.properties file using:
spring.data.mongodb.host=127.0.0.1
spring.data.mongodb.port=27017
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.username=<username specified on MONGO_INITDB_ROOT_USERNAME>
spring.data.mongodb.password=<password specified on MONGO_INITDB_ROOT_PASSWORD>
spring.data.mongodb.database=<the db you want to use>
spring.data.mongodb.host and spring.data.mongodb.port are not supported if you’re using the Mongo 3.0 Java driver. In such cases, spring.data.mongodb.uri should be used to provide all of the configuration, like this:
spring.data.mongodb.uri=mongodb://user:secret#mongo1.example.com:12345
In a maven project create a file src/main/resources/application.yml with the following content:
spring.profiles: integration
# use local or embedded mongodb at localhost:27017
---
spring.profiles: production
spring.data.mongodb.uri: mongodb://<user>:<passwd>#<host>:<port>/<dbname>
Spring Boot will automatically use this file to configure your application. Then you can start your spring boot application either with the integration profile (and use your local MongoDB)
java -jar -Dspring.profiles.active=integration your-app.jar
or with the production profile (and use your production MongoDB)
java -jar -Dspring.profiles.active=production your-app.jar
You can define more details by extending AbstractMongoConfiguration.
#Configuration
#EnableMongoRepositories("demo.mongo.model")
public class SpringMongoConfig extends AbstractMongoConfiguration {
#Value("${spring.profiles.active}")
private String profileActive;
#Value("${spring.application.name}")
private String proAppName;
#Value("${spring.data.mongodb.host}")
private String mongoHost;
#Value("${spring.data.mongodb.port}")
private String mongoPort;
#Value("${spring.data.mongodb.database}")
private String mongoDB;
#Override
public MongoMappingContext mongoMappingContext()
throws ClassNotFoundException {
// TODO Auto-generated method stub
return super.mongoMappingContext();
}
#Override
#Bean
public Mongo mongo() throws Exception {
return new MongoClient(mongoHost + ":" + mongoPort);
}
#Override
protected String getDatabaseName() {
// TODO Auto-generated method stub
return mongoDB;
}
}
In case that somebody is trying to connect to a Atlas MongoDB Cluster in application.properties has to have a config like:
spring.data.mongodb.uri=mongodb+srv://databaseUsername:usernamePassword#cluster0.j4koa.mongodb.net/databaseUsername?retryWrites=true&w=majority
In my case I needed to set up MongoDB for integration tests using Testcontainers. Using properites file was not an option since Mongo port had to be specified during runtime. I wanted to preseve original MongoDB autoconfiguration provided by SpringBoot but override some of the properties. This can be achieved by defining a bean of type MongoClientSettingsBuilderCustomizer which can be used to customize mongo settings :
#Bean
public MongoClientSettingsBuilderCustomizer clientSettingsBuilderCustomizer(final GenericContainer<?> mongoDBContainer) {
String database = environment.getProperty("spring.data.mongodb.database");
ConnectionString connectionString = new ConnectionString(String.format("mongodb://localhost:%s/%s", mongoDBContainer.getFirstMappedPort(), database));
return settings -> settings.applyConnectionString(connectionString);
}
If you simply want to read a MongoDB connection string from an environment variable, one way is to set the following environment variable:
SPRING_DATA_MONGODB_URI=mongodb://localhost:27017/trying-mongo
This doesn't require any changes in the application.properties as the spring data mongo will read the value from the above environment variable by default.
Here is How you can do in Spring Boot 2.0 by creating custom MongoClient adding Providing more control for Connection ,
Please follow github Link for Full Source Code
#Configuration
#EnableMongoRepositories(basePackages = { "com.frugalis.repository" })
#ComponentScan(basePackages = { "com.frugalis.*" })
#PropertySource("classpath:application.properties")
public class MongoJPAConfig extends AbstractMongoConfiguration {
#Value("${com.frugalis.mongo.database}")
private String database;
#Value("${com.frugalis.mongo.server}")
private String host;
#Value("${com.frugalis.mongo.port}")
private String port;
#Value("${com.frugalis.mongo.username}")
private String username;
#Value("${com.frugalis.mongo.password}")
private String password;
#Override
protected String getDatabaseName() {
return database;
}
#Override
protected String getMappingBasePackage() {
return "com.frugalis.entity.mongo";
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongoClient(), getDatabaseName());
}
#Override
#Bean
public MongoClient mongoClient() {
List<MongoCredential> allCred = new ArrayList<MongoCredential>();
System.out.println("???????????????????"+username+" "+database+" "+password+" "+host+" "+port);
allCred.add(MongoCredential.createCredential(username, database, password.toCharArray()));
MongoClient client = new MongoClient((new ServerAddress(host, Integer.parseInt(port))), allCred);
client.setWriteConcern(WriteConcern.ACKNOWLEDGED);
return client;
}}

Resources