Keycloak and Spring SAML: SigAlg was null - spring

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.

Related

Spring boot resource server with JWT base64 encoded Failed to authenticate since the JWT was invalid

I'm trying to use spring resource server starter with fusionauth.io. the fusion auth token is working just fine with postman and when I want to decode it in jwt.io I should check the secret base64 option to get the valid JWT.
application.yml:
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://localhost:9011/oauth2/token
SecurityConfig
#Configuration
public class SecurityConfig extends ResourceServerConfigurerAdapter {
#Bean
public JwtDecoder jwtDecoder(){
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(
"http://localhost:9011/oauth2/token").build();
return jwtDecoder;
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authz -> authz
.antMatchers(HttpMethod.GET, "/user/**").permitAll()
.antMatchers(HttpMethod.POST, "/user/**").permitAll()
.anyRequest().authenticated()).csrf().disable()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
}
}
sample jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjE1MDE1YWJiYyJ9.eyJhdWQiOiI1OTM4M2ViZS0zYjEzLTQ0YjktODM2MS0xZGQ0MWIxYzdlNDkiLCJleHAiOjE2MDgwODgwMjksImlhdCI6MTYwODA4NDQyOSwiaXNzIjoiYWNtZS5jb20iLCJzdWIiOiJiZGVhZDg5Yi1iNTQ3LTRlNDEtODJlMi1iMWIzNjkxZjA0Y2YiLCJqdGkiOiI3ZjZlYTgwMC1hZTgwLTQ0NzgtOWNmOC1mNzQ5ZTM3YjRlNzIiLCJhdXRoZW50aWNhdGlvblR5cGUiOiJQQVNTV09SRCIsImVtYWlsIjoidGVzdEBlbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImFwcGxpY2F0aW9uSWQiOiI1OTM4M2ViZS0zYjEzLTQ0YjktODM2MS0xZGQ0MWIxYzdlNDkiLCJyb2xlcyI6WyJ1c2VyIl19.o9Qtj7tbqo_imkpNn0eKsg-Fhbn91yu5no1oVaXogNY
the error im getting:
2020-12-16 05:37:56.934 DEBUG 26116 --- [nio-8500-exec-3] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2020-12-16 05:37:56.934 DEBUG 26116 --- [nio-8500-exec-3] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
2020-12-16 05:38:00.012 DEBUG 26116 --- [nio-8500-exec-2] o.s.security.web.FilterChainProxy : Securing GET /user/me
2020-12-16 05:38:00.012 DEBUG 26116 --- [nio-8500-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2020-12-16 05:38:00.020 DEBUG 26116 --- [nio-8500-exec-2] o.s.s.o.s.r.a.JwtAuthenticationProvider : Failed to authenticate since the JWT was invalid
2020-12-16 05:38:00.022 DEBUG 26116 --- [nio-8500-exec-2] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2020-12-16 05:38:00.022 DEBUG 26116 --- [nio-8500-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
The JwtDecoders.fromIssuerLocation will attempt to resolve the jwks_uri from the OpenID Connect discovery document found using the issuer URI.
https://github.com/spring-projects/spring-security/blob/848bd448374156020210c329b886fca010a5f710/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java#L119
The FusionAuth JSON Web Key Set (JWKS) only publishes the public key from asymmetric key pairs. This means there are no public keys published and the Spring boot library cannot verify the token signature.
For example, if your issuerUri is https://example.com then the OpenID Discovery URL is https://example.com/.well-known/openid-configuration and the value for jwks_uri found in the JSON response from that URL will be https://example.com/.well-known/jwks.json. If you hit that URL you will see no public keys are being returned, this is the JSON that the library is consuming in an attempt to build the public key necessary to validate the JWT signature.
To use this strategy then you'll need to configure FusionAuth to sign the JWT using an RSA or ECDSA key pair instead of the default HMAC key which is symmetric.
Generate a new RSA or ECDA key pair in Key Master (Settings > Key Master) and then ensure you have your JWT signing configuration use that key. The primary JWT signing configuration will be found in the tenant, with optional application level overrides.
https://fusionauth.io/docs/v1/tech/core-concepts/tenants/#jwt
https://fusionauth.io/docs/v1/tech/core-concepts/applications/#jwt
Hope that helps. Once you modify your configuration so that public keys are returned in the JWKS response, and the library is still not validating the token, please re-open and we can go from there.
The reason may be that the JwtDecoder is not being referenced by the oauth2ResourceServer. Check this resource here to see the way they are setting up the ouath2ResourceSever: https://curity.io/resources/tutorials/howtos/writing-apis/spring-boot-api/
In general the token is probably failing the signature validation and so you need to make sure your trusted issuer is configured properly.

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

Spring - Service registration with Eureka ignored by Ribbon

I have a Spring Boot app trying to fetch an instance of a service through Eureka/Ribbon:
#LoadBalanced
#Autowired
RestTemplate restTemplate;
#RequestMapping("/hi")
public String hi(#RequestParam(value="name", defaultValue="superuser") String name) {
AccountRest account = this.restTemplate.getForObject("http://AUTHENTICATION-SERVICE/authservice/v1/accounts/userId/"+name,
AccountRest.class);
return "hi, " + account.getId();
}
I am manually registering the "AUTHENTICATION-SERVICE", note that this service only needs to register itself, it does not need to query eureka:
public class ServiceDiscoveryManager {
private ApplicationInfoManager applicationInfoManager;
private EurekaClient eurekaClient;
public void start() {
MyDataCenterInstanceConfig instanceConfig = new MyDataCenterInstanceConfig();
DefaultEurekaClientConfig clientConfig = new DefaultEurekaClientConfig();
InstanceInfo instanceInfo = new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get();
applicationInfoManager = new ApplicationInfoManager(instanceConfig, instanceInfo);
eurekaClient = new DiscoveryClient(applicationInfoManager, clientConfig);
applicationInfoManager.getInfo();
eurekaClient.getApplications();
instanceInfo.setStatus(InstanceStatus.UP);
}
public void stop() {
eurekaClient.shutdown();
}
}
Spring wiring for the ServiceDiscoveryManager:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="serviceDiscoveryManager" class="com.embotics.authervice.serviceDiscovery.ServiceDiscoveryManager" init-method="start"/>
</beans>
the authentication-service eureka-client.properties file:
eureka.name=AUTHENTICATION-SERVICE
eureka.appGroup=AUTHENTICATION-SERVICE
eureka.vipAddress=http://localhost:9080
eureka.port.enabled=true
eureka.port=9080
eureka.traffic.enabled=true
eureka.preferSameZone=true
eureka.serviceUrl.default=http://localhost:8000/eureka/
eureka.decoderName=JacksonJson
eureka.healthCheckUrl=http://localhost:9080/authservice/health
eureka.healthCheckPath=/authservice/health
The Eureka Dashboard shows my authentication service as registered:
When I try to get an instance of the authentication-service with the load balancer I receive the following exception:
java.lang.IllegalStateException: No instances available for AUTHENTICATION-SERVICE
at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:90) ~[spring-cloud-netflix-core-1.2.5.RELEASE.jar:1.2.5.RELEASE]
I've confirmed that the load balancer is aware of the authentication-service, but the DynamicServerListLoadBalancer has the allServerList property as empty.
I've been playing with this for a few hours, wondering if some configuration is invalid. Thanks for any help.

Apache CXF Async Conduit and NTLM using Spring?

I am trying to figure out the best way to move the following bit of code into a spring .xml configuration file: (force async and disable chunkng so NTLM works)
final WSSoap port = new WS().getWSSoap();
final Client client = ClientProxy.getClient(port);
final HTTPConduit httpConduit = (HTTPConduit) client.getConduit();
final HTTPClientPolicy httpClientPolicy = httpConduit.getClient();
httpClientPolicy.setAllowChunking(false);
httpClientPolicy.setAutoRedirect(true);
final BindingProvider bindingProvider = (BindingProvider) port;
final Map<String, Object> requestContext = bindingProvider.getRequestContext();
final Credentials credentials = new NTCredentials("username", "password", "workstation", "domain");
requestContext.put(Credentials.class.getName(), credentials);
requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://www.example.com/");
requestContext.put(AsyncHTTPConduit.USE_ASYNC, Boolean.TRUE);
I've looked over the CXF configuration page (here: http://cxf.apache.org/docs/configuration.html), but I don't see any way to do what I need.
Is there a clean way to handle NTLM authentication for CXF using spring?
Update: I've figured out how to force an async conduit, using the following:
<cxf:bus name="asyncBus">
<cxf:properties>
<entry key="use.async.http.conduit" value="true"/>
</cxf:properties>
</cxf:bus>
<http-conf:conduit name="{http://www.webserviceX.NET}GlobalWeatherSoapPort.http-conduit">
<http-conf:client AllowChunking="false" Connection="Keep-Alive"/>
</http-conf:conduit>
<jaxws:client id="weatherClient" bus="asyncBus"
address="http://www.webservicex.com/globalweather.asmx"
serviceClass="net.webservicex.GlobalWeatherSoap"/>
However I am still having trouble accessing the request context so I can add my NTLM credentials.
I wanted to answer my own question. After many, many hours debugging and stepping through Apache CXF code, I have found a solution. The following spring configuration will enable NTLM authentication over an async conduit:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:cxf="http://cxf.apache.org/core"
xmlns:http-conf="http://cxf.apache.org/transports/http/configuration"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd">
<!--
~
~ create an asynchronous-only bus for NTLM requests
~
-->
<cxf:bus name="asyncBus">
<cxf:properties>
<entry key="use.async.http.conduit" value="true"/>
</cxf:properties>
</cxf:bus>
<!--
~
~ configure conduit for NTLM request
~
-->
<http-conf:conduit name="{http://www.webserviceX.NET}GlobalWeatherSoapPort.http-conduit">
<http-conf:client AllowChunking="false" AutoRedirect="true" Connection="Keep-Alive"/>
</http-conf:conduit>
<!--
~
~ create service stub
~
-->
<jaxws:client id="weatherClient" bus="asyncBus"
address="http://www.webservicex.com/globalweather.asmx"
serviceClass="net.webservicex.GlobalWeatherSoap">
<jaxws:properties>
<entry key="org.apache.http.auth.Credentials">
<bean class="org.apache.http.auth.NTCredentials">
<constructor-arg value="DOMAIN/USER:PASSWORD"/>
</bean>
</entry>
</jaxws:properties>
</jaxws:client>
</beans>
It is also possible to specify the username, password, domain, and workstation in the NTCredentials constructor, if necessary.

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