Communication between Keycloak 16.1.1 in https and Spring Boot - spring-boot

I'm using a docker-compose file to start a keycloak container in https. In order to do that, I've added these services to my compose:
keycloak-db:
image: postgres
volumes:
- keycloak-db-pgdata:/var/lib/postgresql/data
- ./docker/keycloak-db/init.sql:/docker-entrypoint-initdb.d/init.sql
env_file:
- docker/keycloak-db/keycloak-db.env
ports:
- 10005:5432
keycloak:
image: jboss/keycloak
env_file:
- docker/keycloak/keycloak.env
volumes:
- ./docker/keycloak/https/:/etc/x509/https
ports:
- 10006:8443
volumes:
keycloak-db-pgdata:
driver: local
This is the content of keycloak.env and keycloak-db.env:
keycloak.env
KEYCLOAK_USER=admin
KEYCLOAK_PASSWORD=admin
KEYCLOAK_LOGLEVEL=INFO
DB_VENDOR=POSTGRES
DB_ADDR=keycloak-db
DB_DATABASE=keycloak
DB_USER=keycloak-admin
DB_PASSWORD=password
KEYCLOAK_FRONTEND_URL=https://localhost:10006/auth
keycloak-db.env
POSTGRES_DB=keycloak
POSTGRES_USER=keycloak-admin
POSTGRES_PASSWORD=password
The file init.sql is simply a dump file to restore the database in case of necessity.
The /docker/keycloak/https folder contains two files that I've generated to start keycloak instance in https. These files are:
A self-signed certificate: tls.crt
A private key: tls.key
I used the "keytool" and "openssl" commands from the command prompt to create these files, as follow:
keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950
keytool -importkeystore -srckeystore keycloak.jks -destkeystore keycloak.p12 -deststoretype PKCS12
openssl pkcs12 -in keycloak.p12 -nokeys -out tls.crt
openssl pkcs12 -in keycloak.p12 -nocerts -nodes -out tls.key
keytool -import -trustcacerts -keystore trust.keystore -storepass password -alias localhost -file tls.crt
The keycloak instance runs correctly in https.
The problem is a spring boot application which wants to communicate with it.
This application is managed with maven and uses the keycloak-spring-boot-starter dependency to secure its endpoints with spring security:
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
<version>16.1.1</version>
</dependency>
This is the configuration bean:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true)
#Profile("!test")
public class KeycloakSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/tests").hasAnyRole("USER")
.anyRequest()
.authenticated();
http.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
and this is the content of application.properties file:
logging.level.root=INFO
server.port=8080
keycloak.realm=${The name of realm in keycloak}
keycloak.auth-server-url=https://localhost:10006/auth
keycloak.ssl-required=external
keycloak.resource=${The name of client in keycloak}
keycloak.credentials.secret=${The client secret in keycloak}
keycloak.use-resource-role-mappings=true
keycloak.bearer-only=true
#this is the file I've previously generated using keytool (I also tried to use keycloak.jks here #but I obtained the same error)
keycloak.truststore=classpath:trust.keystore
keycloak.truststore-password=password
keycloak.confidential-port=10006
When I try to call /tests endpoint by postman (with a valid token), I obtain a 500 internal server error with this stacktrace:
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:439) ~[na:na]
at java.base/sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:306) ~[na:na]
at java.base/sun.security.validator.Validator.validate(Validator.java:264) ~[na:na]
at java.base/sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:313) ~[na:na]
at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:222) ~[na:na]
at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:129) ~[na:na]
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:630) ~[na:na]
... 84 common frames omitted
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141) ~[na:na]
at java.base/sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126) ~[na:na]
at java.base/java.security.cert.CertPathBuilder.build(CertPathBuilder.java:297) ~[na:na]
at java.base/sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:434) ~[na:na]
... 90 common frames omitted
Can anyone help me?
Thank you

Related

Spring boot keycloak integration OAuth 2.0 Access Token Response: 401 Unauthorized: [no body]

Update: disabling client auth and authorization will not show the 401 message anymore but secured endpoints will return 403.
I'm trying to integrate keycloak core-version: 20.0.2 running from a docker container.
I've started out following https://www.baeldung.com/spring-boot-keycloak
but after multiple suggestions from different threads I cannot get the integration to work.
application properties:
keycloak.auth-server-url=localhost:1337
keycloak.realm=SARServices
keycloak.resource=sar-login
keycloak.public-client=false
keycloak.principal-attribute=preferred_username
keycloak.credentials.secret=NvkAdEPqbN39ubjqtrjn7dKElgLlUNLj
spring.security.oauth2.client.registration.keycloak.client-id=sar-login
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid
spring.security.oauth2.client.provider.keycloak.issuer-uri=https://localhost:1337/realms/SARServices
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username
WebConfig:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true)
class SecurityConfig(
private val keycloakLogoutHandler: KeycloakLogoutHandler
) {
#Bean
fun keycloakConfigResolver(): KeycloakConfigResolver? {
return KeycloakSpringBootConfigResolver()
}
#Bean
protected fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy {
return RegisterSessionAuthenticationStrategy(SessionRegistryImpl())
}
#Bean
#Throws(Exception::class)
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/*")
.hasRole("adventurer")
.anyRequest()
.permitAll()
http.oauth2Login()
.and()
.logout()
.addLogoutHandler(keycloakLogoutHandler)
.logoutSuccessUrl("/")
return http.build()
}
}
keycloak server log:
2023-01-07 18:46:55 2023-01-07 17:46:55,045 WARN [org.keycloak.events] (executor-thread-65) type=CODE_TO_TOKEN_ERROR, realmId=7eec0241-e69f-4d5c-8b7f-7a96926e8315, clientId=sar-login, userId=null, ipAddress=172.18.0.1, error=invalid_client_credentials, grant_type=authorization_code
docker compose:
version: '1'
services:
postgresql:
image: docker.io/bitnami/postgresql:latest
environment:
- ALLOW_EMPTY_PASSWORD=yes
- POSTGRESQL_USERNAME=bn_keycloak
- POSTGRESQL_DATABASE=bitnami_keycloak
volumes:
- 'postgresql_data:/bitnami/postgresql'
- './certs:/etc/codelance/cert'
keycloak:
image: quay.io/keycloak/keycloak:latest
command: start --hostname-port=1337
ports:
- "1337:8443"
environment:
- KC_HOSTNAME=localhost
- KC_HTTPS_CERTIFICATE_FILE=/etc/codelance/cert/keycloakcert.pem
- KC_HTTPS_CERTIFICATE_KEY_FILE=/etc/codelance/cert/keycloak.pem
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=password
depends_on:
- postgresql
volumes:
- './certs:/etc/codelance/cert'
volumes:
postgresql_data:
driver: local
Web response login after signing in:
client config:
First, Keycloak adapters for Spring are deprecated. Don't use it.
Are you sure you want a client and not a resource-server? In other words, are your controller methods returning template names or JSON payloads?
My answer to this other question contains the solution for both client and resource-server: Use Keycloak Spring Adapter with Spring Boot 3

Server not reachable for HTTPS

i'm trying for the first time to set up an own web site from front to end.
My problem is: I cannot connect to my server to http://mysite.de nor to https://mysite.de.
Goal: Enable the user to reach my server via HTTPS at all. All three input strings should lead to a https connection: my-site.de, http://my-site.de and https://my-site.de.
What have I done already?
I made a spring boot web application and rented a Strato VPS (Cent OS 8, but I also tried it on Ubuntu 18.04).
Used the Digicert CSR maker, the command it gave me was
keytool -genkey -alias pvpfeedback -keyalg RSA -keysize 2048 -keystore pvpfeedback_de.jks -dname "CN=pvpfeedback.de,OU=PvPFeedback, O=PvPFeedback, L=PvPFeedback, ST=PvPFeedback, C=DE" && keytool -certreq -alias pvpfeedback -file pvpfeedback_de.csr -keystore pvpfeedback_de.jks
I uploaded the csr to Strato (for a free SSL certificate from them)
Strato in return lets me download a .crt, a root .crt and an intermediate .crt file. I added the normal .crt and the root .crt to my .jks keystore
keytool -import -alias pvpfeedback -file root_pvpfeedback.de.crt -keystore pvpfeedback_de.jks
keytool -trustcacerts -importcert -alias pvpfeedback -file cert_pvpfeedback.de.crt -keystore pvpfeedback_de.jks
I placed all my stuff to etc/pki/ca-trust/source/anchors and update-ca-trust extract
I'm running my program with java -jar -Djdk.tls.client.protocols=TLSv1.2 pvpfeedback-0.0.1-SNAPSHOT.jar
Some additional stuff:
I set up an Apache to verify that my site is registered, and indeed the default apache landing page was shown.
Chrome give back a connection timeout error on http://my-site.de and https://my-site.de:
Website not reachable
This is my very first post on this site, if I did something wrong please tell me.
When I start the spring boot app on the server, this is the feedback:
2021-02-20 18:00:09.341 INFO 19868 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8443 (https) 8080 (http) with context path ''
2021-02-20 18:00:09.360 INFO 19868 --- [ main] c.e.pvpfeedback.PvpfeedbackApplication : Started PvpfeedbackApplication in 6.042 seconds (JVM running for 7.72)
2021-02-20 18:00:09.559 INFO 19868 --- [ scheduling-1] org.mongodb.driver.connection : Opened connection [connectionId{localValue:7, serverValue:75478}] to pvpfeedbackcluster-shard-00-02.xjveo.mongodb.net:27017
My Spring Boot Https configuration:
#EnableWebSecurity
public class HTTPSSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.requiresChannel()
.anyRequest()
.requiresSecure();
}
}
#Configuration
public class HttpToHttpsServerConfig {
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(getHttpConnector());
return tomcat;
}
private Connector getHttpConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}
}
server.address=<ServerIP>
server.port=8443
server.ssl.key-alias=pvpfeedback
server.ssl.key-store-password=<Password>
server.ssl.key-store=classpath:pvpfeedback_de.jks
server.ssl.key-store-provider=SUN
server.ssl.key-store-type=JKS
I managed to make it work.
I had to forward some ports in the firewall.
The following code snippet did the trick for me:
iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 8443 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 8443 -j ACCEPT

Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException:

I'm new to docker and I'm trying to run redis-server and my springboot app both on a container.
I was able to hit redis(present in a docker container) when I start the springboot app locally just fine, but when i put this springboot app also in the docker container then I'm unable to connect to redis and getting
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 0.0.0.0:6397] with root cause
urlshortner |
urlshortner | java.net.ConnectException: Connection refused
urlshortner | at java.base/sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:na]
urlshortner | at java.base/sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:779) ~[na:na]
urlshortner | at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:330) ~[netty-transport-4.1.45.Final.jar!/:4.1.45.Final]
urlshortner | at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:334) ~[netty-transport-4.1.45.Final.jar!/:4.1.45.Final]
urlshortner | at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:702) ~[netty-transport-4.1.45.Final.jar!/:4.1.45.Final]
urlshortner | at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650) ~[netty-transport-4.1.45.Final.jar!/:4.1.45.Final]
urlshortner | at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576) ~[netty-transport-4.1.45.Final.jar!/:4.1.45.Final]
urlshortner | at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.45.Final.jar!/:4.1.45.Final]
urlshortner | at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.45.Final.jar!/:4.1.45.Final]
urlshortner | at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.45.Final.jar!/:4.1.45.Final]
urlshortner | at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.45.Final.jar!/:4.1.45.Final]
urlshortner | at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
I have tried following:
used docker-compose to get them onto same network
my docker-compose
version: '3'
services:
app:
container_name: urlshortner
image: docker-urlshortner:v1
build: .
links:
- redis
ports:
- "10095:10095"
volumes:
- ~/docker/redis:/urlshortner/logs
redis:
container_name: myredis
image: redis:v1
build: ./redis
hostname: localhost
ports:
- "6379:6379"
dockerfile to start springboot app
FROM adoptopenjdk/openjdk11
VOLUME /urlshortner
ARG JAR_FILE=target/Urlshortning-0.0.1-SNAPSHOT.jar
ADD ${JAR_FILE} urlshortning.jar
EXPOSE 10095
ENTRYPOINT ["java", "-jar", "/urlshortning.jar"]
dockerfile for running redis
FROM redis
COPY redis.conf /redis/redis.conf
CMD [ "redis-server", "/redis/redis.conf" ]
commented out bind 127.0.0.1 and added bind 0.0.0.0 in redis.conf
but still getting same error
my redis config in java app
#Configuration
public class RedisConfig {
private final String url;
private final int port;
private final String password;
#Autowired
private ObjectMapper objectMapper;
public RedisConfig(#Value("${spring.redis.host}") String url, #Value("${spring.redis.port}") int port,
#Value("${spring.redis.password}") String password) {
this.url = url;
this.port = port;
this.password = password;
}
/**
* Redis configuration
*
* #return redisStandaloneConfiguration
*/
#Bean
public RedisStandaloneConfiguration redisStandaloneConfiguration() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(url, port);
redisStandaloneConfiguration.setPassword(password);
return redisStandaloneConfiguration;
}
/**
* Client Options Reject requests when redis is in disconnected state and Redis
* will retry to connect automatically when redis server is down
*
* #return client options
*/
#Bean
public ClientOptions clientOptions() {
return ClientOptions.builder().disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
.autoReconnect(true).build();
}
/**
* Create a LettuceConnection with redis configurations and client options
*
* #param redisStandaloneConfiguration redisStandaloneConfiguration
* #return RedisConnectionFactory
*/
#Bean
public RedisConnectionFactory connectionFactory(RedisStandaloneConfiguration redisStandaloneConfiguration) {
LettuceClientConfiguration configuration = LettuceClientConfiguration.builder().clientOptions(clientOptions())
.build();
return new LettuceConnectionFactory(redisStandaloneConfiguration, configuration);
}
// Setting up the redis template object.
#SuppressWarnings({ "rawtypes", "unchecked" })
#Bean
#ConditionalOnMissingBean(name = "redisTemplate")
#Primary
public RedisTemplate<String, Url> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Url.class);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
RedisTemplate<String, Url> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
}
and my application.properties
server.port=10095
redis.ttl=86400
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=redisdb
i have also tried changing spring.redis.host from localhost to 0.0.0.0 and 127.0.0.1
so when i hit it from postman I get the error
both containers running redis and springboot app
How do i resolve this problem, any help is appreciated, thanks
I changed following which made it work
spring.redis.host=localhost
to
spring.redis.host=redis
in application.properties
and
hostname: localhost
to
hostname: redis
in docker-compose for redis

Https setup for spring boot and swagger2

I am trying to integrate my Sprint Boot applications with Keycloak, starting with secure swagger page.
keytool helped me to generate a selfsigned keystore
keytool -genkey -alias abcdef -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
I use the above to setup ssl for the app
server:
port: "15700"
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: password
key-alias: abcdef
keyStoreType: PKCS12
Without keycloak, the https for swagger works as expected.
I started keycloak from their docker image as below, export http and https
services:
keycloak:
image: jboss/keycloak
environment:
DB_VENDOR: POSTGRES
DB_ADDR: my.ip.address
DB_PORT: 5432
DB_DATABASE: keycloak
DB_USER: username
DB_PASSWORD: password
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: password
ports:
- 8443:8443
- 8080:8080
I ask user to login first when they want to access the swagger docs, so I configure keycloak as below:
keycloak:
auth-server-url: "https://192.168.1.15:8443/auth"
realm: "DemoRealm"
public-client: true
resource: demo-app
security-constraints[0]:
authRoles[0]: "user"
securityCollections[0]:
name: "Demo App"
patterns[0]: "/swagger-ui.html"
Now, not logged in user will be direct to keycloak login page, it works perfect. But after the successful login, when redirect back to the app's swagger page, I go the following error:
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
If I configure the keycloak auth uri to http
keycloak:
auth-server-url: "http://192.168.1.15:8080/auth"
realm: "DemoRealm"
public-client: true
resource: demo-app
security-constraints[0]:
authRoles[0]: "user"
securityCollections[0]:
name: "Demo App"
patterns[0]: "/swagger-ui.html"
everything works perfectly.
Is this a configuration issue for keycloak or for the spring boot app? Any required steps I missed?
You can try to set up your Rest Template bean:
Add dependency:
implementation 'org.apache.httpcomponents:httpclient:4.5'
Provide RestTemplate bean:
#Bean
private RestTemplate restTemplate() {
SSLContext sslContext = buildSslContext();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
private SSLContext buildSslContext() {
try {
char[] keyStorePassword = sslProperties.getKeyStorePassword();
return new SSLContextBuilder()
.loadKeyMaterial(
KeyStore.getInstance(new File(sslProperties.getKeyStore()), keyStorePassword),
keyStorePassword
).build();
} catch (Exception ex) {
throw new IllegalStateException("Unable to instantiate SSL context", ex);
} finally {
sslProperties.setKeyStorePassword(null);
sslProperties.setTrustStorePassword(null);
}
}
Provide required SSL properties in your application.properties or application.yaml file:
server:
ssl:
enabled: true
key-store: /path/to/key.keystore
key-store-password: password
key-alias: alias
trust-store: /path/to/truststore
trust-store-password: password
Alternatively, you can use my spring boot starter

How to configure Spring RestTemplate with SSL (in Spring #MVC)

I want to configure my Spring #MVC stub application's Spring RestTemplate with SSL for communicate to REST base https application, that deployed on Tomcat server (Spring 3, Tomcat 7). I have done up to now my works by refer this link. Now I have not any idea how to use these generated certificates with Spring RestTemplate, Can anyone have some idea please help me. Thanks. Up to now things I have done,
//Spring Security xml Configurations
<http>
<intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
<http-basic/></http>
//Configurations for enable SSL with Tomcat
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="200"
scheme="https" secure="true" SSLEnabled="true"
keystoreFile="C:\Users\Channa\.keystore" keystorePass="changeit"
clientAuth="false" sslProtocol="TLS"/>
For generating Keys, certificates etc,
//Generate client and server keys:
F:\jdk1.6.0_23\bin>keytool -genkey -keystore keystore_client -alias clientKey -dname "CN=localhost, OU=Dev, O=MyBusiness, L=Colombo, S=Westen, C=SL"
F:\jdk1.6.0_23\bin>keytool -genkey -keystore keystore_server -alias serverKey -dname "CN=localhost, OU=Dev, O=MyBusiness, L=Colombo, S=Westen, C=SL"
//Generate client and server certificates:
F:\jdk1.6.0_23\bin>keytool -export -alias clientKey -rfc -keystore keystore_client > client.cert
F:\jdk1.6.0_23\bin>keytool -export -alias serverKey -rfc -keystore keystore_server > server.cert
//Import certificates to corresponding truststores:
F:\jdk1.6.0_23\bin>keytool -import -alias clientCert -file client.cert -keystore truststore_server
F:\jdk1.6.0_23\bin>keytool -import -alias serverCert -file server.cert -keystore truststore_client
//Spring RestTemplate configurations
<!--Http client-->
<bean id="httpClient" class="org.apache.commons.httpclient.HttpClient">
<constructor-arg ref="httpClientParams"/>
<property name="state" ref="httpState"/>
</bean>
<!--Http state-->
<bean id="httpState" class="com.org.imc.test.stub.http.CustomHttpState">
<property name="credentials" ref="usernamePasswordCredentials"/>
</bean>
<!--User name password credentials-->
<bean id="usernamePasswordCredentials" class="org.apache.commons.httpclient.UsernamePasswordCredentials"/>
<!--Http client-->
<bean id="httpClientFactory" class="org.springframework.http.client.CommonsClientHttpRequestFactory">
<constructor-arg ref="httpClient"/>
</bean>
<!--RestTemplate-->
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<constructor-arg ref="httpClientFactory"/>
</bean>
//Https URL going to access
ResponseEntity<User> rECreateUser = restTemplate.postForEntity("https://127.0.0.1:8443/skeleton-1.0/login", user, User.class);
//Exception currently I got:
org.springframework.web.client.ResourceAccessException: I/O error: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
This is because SSL certificate of the service you are calling is not signed by a trusted certificate authority. The workaround is to import the certificate into the certificate trust store (cacerts) of your JRE.
download the cert by opening the URL in a browser, click the lock
icon in the browser's address bar.
Once you have a .cer file execute the below command
keytool -import -keystore jdk1.8.0_77/jre/lib/security/cacerts -file ~/test.cer -alias test
Variant for Spring Boot:
Add dependency:
implementation 'org.apache.httpcomponents:httpclient:4.5'
Provide RestTemplate bean:
#Bean
private RestTemplate restTemplate() {
SSLContext sslContext = buildSslContext();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
private SSLContext buildSslContext() {
try {
char[] keyStorePassword = sslProperties.getKeyStorePassword();
return new SSLContextBuilder()
.loadKeyMaterial(
KeyStore.getInstance(new File(sslProperties.getKeyStore()), keyStorePassword),
keyStorePassword
).build();
} catch (Exception ex) {
throw new IllegalStateException("Unable to instantiate SSL context", ex);
} finally {
sslProperties.setKeyStorePassword(null);
sslProperties.setTrustStorePassword(null);
}
}
Provide required SSL properties in your application.properties or application.yaml file:
server:
ssl:
enabled: true
key-store: /path/to/key.keystore
key-store-password: password
key-alias: alias
trust-store: /path/to/truststore
trust-store-password: password
That's it. Now you can see your Tomcat is starting on 8080 (or another port) (https).
Alternatively, you can use my spring boot starter
You can configure the RestTemplate with the HttpComponentsClientHttpRequestFactory from Apache HttpComponents HttpClient, which definitely supports SSL.
ref: Does REST (RestTemplate) in Spring Library support HTTPS protocol?
You can set a couple of system properties to select the truststore used in clients
System.setProperty("javax.net.ssl.trustStorePassword", "mypassword");
System.setProperty("javax.net.ssl.keyStoreType", "jks");
System.setProperty("javax.net.ssl.trustStore", "truststore_client");

Resources