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

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");

Related

Communication between Keycloak 16.1.1 in https and 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

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

Junit runs fine individually, but fails during maven build giving sun.security.provider.certpath.SunCertPathBuilderException

A junit is testing JMS response from a mockserver. The Junit is unable to find the certificate path during maven build, but finds it and runs with no exception when run individually.
certificate related application.yml:
bridge:
conduit:
keystore:
type: pkcs12
password: ENC(l4/yeZUp8n4v7iArP1H9OWeVHUO5JCM2)
location: src/main/resources/config/certificatessl.p12
jasypt:
encryptor:
password: G;SobWzm9Z;zWOMQuUF
junit in test class uses following mockserver:
mockServer.when(HttpRequest.request("/gateway/spec").withSecure(true).withBody(Mockito.anyString()))
.respond(HttpResponse.response(myJsonObject));
The context loads the mockserver:
#Bean(destroyMethod = "stop")
#Lazy
public ClientAndServer mockServer() {
ConfigurationProperties.deleteGeneratedKeyStoreOnExit(true);
ClientAndServer mockServer = ClientAndServer.startClientAndServer(7071);
HttpsURLConnection
.setDefaultSSLSocketFactory(KeyStoreFactory.keyStoreFactory().sslContext().getSocketFactory());
return mockServer;
}
application-cxfconduit.xml:
<http:conduit name="{/b2b/Services}PreparationPort.http-conduit">
<http:tlsClientParameters disableCNCheck="true" secureSocketProtocol="TLSv1.2">
<sec:keyManagers keyPassword="#{environment['bridge.conduit.keystore.password']}">
<sec:keyStore type="${bridge.conduit.keystore.type}"
password="#{environment['bridge.conduit.keystore.password']}"
file="${bridge.conduit.keystore.location}" />
</sec:keyManagers>
</http:tlsClientParameters>
<http:client Connection="close" ConnectionTimeout="${bridge.conduit.connectionTimeout:30000}"
ReceiveTimeout="${bridge.conduit.receiveTimeout:120000}" ProxyServer="${bridge.conduit.proxy.address:}"
ProxyServerPort="${bridge.conduit.proxy.port:}" ProxyServerType="HTTP" AutoRedirect="true" AllowChunking="false" />
<http:proxyAuthorization>
<sec:UserName>"${bridge.conduit.proxy.username:}"</sec:UserName>
<sec:Password>"${bridge.conduit.proxy.password:}"</sec:Password>
</http:proxyAuthorization>
</http:conduit>
Why is it giving sun.security.validator.ValidatorException only during maven build, whereas junit runs successfully individually?
The exception thrown during maven build is:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target
Any leads will be appreciated!

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

Keycloak and Spring SAML: SigAlg was null

I'm trying to setup POC using Spring Security, Spring Security SAML and Keycloak. For this, I'm using the simple-service-provider sample which is provided by the Spring SAML core project.
I managed to get the SAML setup working as long as Keycloak does not require the client signature (Client Signature Required disabled). When I enable this option, I get the following error and stacktrace in Keycloak
07:07:40,385 WARN [org.keycloak.events] (default task-8) type=LOGIN_ERROR, realmId=ea-localhost, clientId=null, userId=null, ipAddress=172.17.0.1, error=invalid_signature
07:07:44,961 ERROR [org.keycloak.protocol.saml.SamlService] (default task-7) request validation failed: org.keycloak.common.VerificationException: SigAlg was null
at org.keycloak.protocol.saml.SamlProtocolUtils.verifyRedirectSignature(SamlProtocolUtils.java:135)
at org.keycloak.protocol.saml.SamlService$RedirectBindingProtocol.verifySignature(SamlService.java:518)
at org.keycloak.protocol.saml.SamlService$BindingProtocol.handleSamlRequest(SamlService.java:233)
at org.keycloak.protocol.saml.SamlService$BindingProtocol.execute(SamlService.java:478)
at org.keycloak.protocol.saml.SamlService.redirectBinding(SamlService.java:553)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
The Spring configuration is as follows
spring:
thymeleaf:
cache: false
security:
saml2:
service-provider:
entity-id: spring-saml-test
sign-metadata: false
sign-requests: true
want-assertions-signed: true
keys:
active:
- name: key
type: SIGNING
private-key: |
-----BEGIN RSA PRIVATE KEY-----
SNIP
-----END RSA PRIVATE KEY-----
passphrase: SNIP
certificate: |
-----BEGIN CERTIFICATE-----
SNIP
-----END CERTIFICATE-----
- name: key
type: ENCRYPTION
private-key: |
-----BEGIN RSA PRIVATE KEY-----
SNIP
-----END RSA PRIVATE KEY-----
passphrase: SNIP
certificate: |
-----BEGIN CERTIFICATE-----
SNIP
-----END CERTIFICATE-----
providers:
- name: keycloak
metadata: |
<?xml version="1.0" encoding="UTF-8"?>
<EntityDescriptor entityID="http://localhost:8090/auth/realms/ea-localhost"
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<IDPSSODescriptor WantAuthnRequestsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<SingleLogoutService
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8090/auth/realms/ea-localhost/protocol/saml" />
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="http://localhost:8090/auth/realms/ea-localhost/protocol/saml" />
<KeyDescriptor use="signing">
<dsig:KeyInfo>
<dsig:KeyName>tURdSWRSOIQXpkOdAZzAevrtov8QU5ea0dqJpng2hSY</dsig:KeyName>
<dsig:X509Data>
<dsig:X509Certificate>SNIP</dsig:X509Certificate>
</dsig:X509Data>
</dsig:KeyInfo>
</KeyDescriptor>
</IDPSSODescriptor>
</EntityDescriptor>
link-text: KEYCLOAK LOCAL IDP
name-id: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
This is a sample request which is sent to keycloak
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest AssertionConsumerServiceIndex="0" AssertionConsumerServiceURL="http://localhost:8080/sample-sp/saml/sp/SSO" Destination="http://localhost:8090/auth/realms/ea-localhost/protocol/saml" ForceAuthn="false" ID="1a072bcf-2822-424d-98ea-5e1f0c3b83b7" IsPassive="false" IssueInstant="2018-05-31T08:00:42.939Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">spring-saml-test</saml2:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#1a072bcf-2822-424d-98ea-5e1f0c3b83b7">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>R/D3Qk8KmcZdTwBZypDDq+D8lcMpvYcElmiwg01dYK0=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
gldzIzX2Ti+nHhz99jQgLwLQ1IZnNJBGM39MpBo7pcFmWQ83Y4R4Bv+OfGbdmqO8GTbZo86zjRM0
+c1w+/QFvbZv4hEudIFuuDbzgCcTG2tyFau525+T7IZcBuPXexYEE+JX/y9cZifo7ws7EolfUC/V
e3qHlYGzOx/cPx6qPem6QawDaU8X46WkYDIOjAJGxrbqGY8fR3YC+PGndD4/+47Zrcp58REBUDPH
X680RZJP+06nnOIS5seKuIOyzEYmz8FLrsN2RLy0QnR3Qws+aWoP0ut04CFgmpcV5JmmNpMXASIT
86Xy53N6q1XvXqAhZuwG1WUriJBZD0mCPDmDhA==
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>SNIP</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<saml2p:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
<saml2p:RequestedAuthnContext Comparison="exact"/>
</saml2p:AuthnRequest>
I've tried different combinations but as soon as Keycloak verifies the signature, the exception is thrown.
The code in Keycloak which causes the exception is this:
public static void verifyRedirectSignature(SAMLDocumentHolder documentHolder, KeyLocator locator, UriInfo uriInformation, String paramKey) throws VerificationException {
MultivaluedMap<String, String> encodedParams = uriInformation.getQueryParameters(false);
String request = encodedParams.getFirst(paramKey);
String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
String signature = encodedParams.getFirst(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
String relayState = encodedParams.getFirst(GeneralConstants.RELAY_STATE);
String decodedAlgorithm = uriInformation.getQueryParameters(true).getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
if (request == null) throw new VerificationException("SAM was null");
if (algorithm == null) throw new VerificationException("SigAlg was null");
if (signature == null) throw new VerificationException("Signature was null");
What am I doing wrong here ? Or is it a bug in Keycloak of Spring Saml ?
I'm using Keycloak 4.0.0.Beta2 but the same issue occurs in the latest v3.
I've pulled the latest spring-security-saml code from github (2.0.0.BUILD-SNAPSHOT, 48ccd77ebabb5465af81a53b7095cdfb466a62b5).
The AuthNRequest is sent as REDIRECT but formatted as a POST.
This is a bug.
https://github.com/spring-projects/spring-security/issues/7711
The workaround is to turn off signature requirements for the incoming AuthNRequest as ensure that the IDP uses the whitelisted/configured URLs instead of the ones in the AuthNRequest message itself.

Resources