Skip to content

Commit

Permalink
Merge pull request #16 from choonchernlim/feature/ssl-verification-op…
Browse files Browse the repository at this point in the history
…tion

Feature/ssl verification option
  • Loading branch information
choonchernlim authored Jan 10, 2019
2 parents 98672c0 + 97c7407 commit f774c55
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 140 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## 0.9.0 - 2019-01-09

* FEATURE - Added `SAMLConfigBean.useJdkCacertsForSslVerification` flag to allow SSL verifications to be performed by using JDK's cacerts instead of app's keystore file.

## 0.8.0 - 2018-07-11

* Moved from Java 7 to Java 8.
Expand Down
110 changes: 55 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Tested against IdP's environments:-
<dependency>
<groupId>com.github.choonchernlim</groupId>
<artifactId>spring-security-adfs-saml2</artifactId>
<version>0.8.0</version>
<version>0.9.0</version>
</dependency>
```

Expand All @@ -35,9 +35,9 @@ Tested against IdP's environments:-
* Java 8.
* Both Sp and IdP must use HTTPS protocol.
* Java’s default keysize is limited to 128-bit key due to US export laws and a few countries’ import laws. So, Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files must be installed to allow larger key size, such as 256-bit key.
* Keystore must contain both Sp's public/private keys and imported IdP's public certificate.
* Sp's public/private keys - to generate digital signature before sending SAML messages to IdP.
* IdP's public certificate - to verify IdP's SAML messages to prevent man-in-the-middle attack.
* Keystore contains the following:-
* (REQUIRED) Sp's public/private keys - to generate digital signature before sending SAML messages to IdP.
* (OPTIONAL) IdP's public certificate - to verify IdP's SAML messages to prevent man-in-the-middle attack. This certificate can also be stored under JDK's cacerts.
* To generate Sp's public/private keys:-

```
Expand Down Expand Up @@ -120,6 +120,40 @@ class AppSecurityConfig extends SAMLWebSecurityConfigurerAdapter {
}
```

### Customizing SSL Verification

By default, the keystore file serves 2 purposes:-

* Acts as a keystore, containing app's public/private key.
* Acts as a truststore, containing IdP's certificate with public key.

If the keystore does not contain IdP's certificate, the SSL verification will fail with the following error when attempting to retrieve IdP's metadata:-

```
PKIX path construction failed for untrusted credential: [subjectName='CN=idp.server.com,OU=IDP,C=US']: unable to find valid certification path to requested target
I/O exception (javax.net.ssl.SSLPeerUnverifiedException) caught when processing request: SSL peer failed hostname validation for name: null
Error retrieving metadata from https://idp.server.com/federationmetadata/2007-06/federationmetadata.xml
```

If you store the IdP's certificate under JDK's truststore (ie: cacerts) and you want the SSL verification to rely on that file, do this:-

```java
@Configuration
@EnableWebSecurity
class AppSecurityConfig extends SAMLWebSecurityConfigurerAdapter {

@Override
protected SAMLConfigBean samlConfigBean() {
return new SAMLConfigBeanBuilder()
// ... other configurations
.withUseJdkCacertsForSslVerification(true)
.build();
}

...
}
```

### Environment Properties Driven Configuration

If you don't want to use `@Profile` to configure environment-specific security, you may pass the configuration values through environment properties.
Expand Down Expand Up @@ -213,22 +247,23 @@ protected void configure(final HttpSecurity http) throws Exception {

`SAMLConfigBean` stores app-specific security configuration.

|Property |Required? |Description |
|---------------------------|----------|----------------------------------------------------------------------------------------------------------|
|idpServerName |Yes |IdP server name. Used for retrieving IdP metadata using HTTPS. If IdP link is `https://idp-server/adfs/ls`, value should be `idp-server`. |
|spServerName |Yes |Sp server name. Used for generating correct SAML endpoints in Sp metadata to handle servers doing SSL termination. If Sp link is `https://sp-server:8443/myapp`, value should be `sp-server`. |
|spHttpsPort |No |Sp HTTPS port. Used for generating correct SAML endpoints in Sp metadata to handle servers doing SSL termination. If Sp link is `https://sp-server:8443/myapp`, value should be `8443`. <br/><br/>Default is `443`. |
|spContextPath |No |Sp context path. Used for generating correct SAML endpoints in Sp metadata to handle servers doing SSL termination. If Sp link is `https://sp-server:8443/myapp`, value should be `/myapp`. <br/><br/>Default is `''`. |
|keystoreResource |Yes |App's keystore containing its public/private key and ADFS' certificate with public key. |
|keystorePassword |Yes |Password to access app's keystore. |
|keystoreAlias |Yes |Alias of app's public/private key pair. |
|keystorePrivateKeyPassword |Yes |Password to access app's private key. |
|successLoginDefaultUrl |Yes |Where to redirect user on successful login if no saved request is found in the session. |
|successLogoutUrl |Yes |Where to redirect user on successful logout. |
|failedLoginDefaultUrl |No |Where to redirect user on failed login. This value is set to null, which returns 401 error code on failed login. But, in theory, this will never be used because IdP will handled the failed login on IdP login page.<br/><br/>Default is `''`, which return 401 error code.|
|storeCsrfTokenInCookie |No |Whether to store CSRF token in cookie named `XSRF-TOKEN` and expecting CSRF token to be set using header named `X-XSRF-TOKEN` to cater single-page app using frameworks like React and AngularJS. <br/><br/>Default is `false`. |
|samlUserDetailsService |No |For configuring user details and authorities. When set, `userDetails` will be set as `principal`.<br/><br/>Default is `null`. |
|authnContexts |No |Determine what authentication methods to use. To use the order of authentication methods defined by IdP, set as empty set. To enable Windows Integrated Auth (WIA), use `CustomAuthnContext.WINDOWS_INTEGRATED_AUTHN_CTX`.<br/><br/>Default is `AuthnContext.PASSWORD_AUTHN_CTX` where IdP login page is displayed to obtain user/password.|
|Property |Required? |Description |
|--------------------------------|----------|----------------------------------------------------------------------------------------------------------|
|idpServerName |Yes |IdP server name. Used for retrieving IdP metadata using HTTPS. If IdP link is `https://idp-server/adfs/ls`, value should be `idp-server`. |
|spServerName |Yes |Sp server name. Used for generating correct SAML endpoints in Sp metadata to handle servers doing SSL termination. If Sp link is `https://sp-server:8443/myapp`, value should be `sp-server`. |
|spHttpsPort |No |Sp HTTPS port. Used for generating correct SAML endpoints in Sp metadata to handle servers doing SSL termination. If Sp link is `https://sp-server:8443/myapp`, value should be `8443`. <br/><br/>Default is `443`. |
|spContextPath |No |Sp context path. Used for generating correct SAML endpoints in Sp metadata to handle servers doing SSL termination. If Sp link is `https://sp-server:8443/myapp`, value should be `/myapp`. <br/><br/>Default is `''`. |
|keystoreResource |Yes |App's keystore containing its public/private key and ADFS' certificate with public key. |
|keystorePassword |Yes |Password to access app's keystore. |
|keystoreAlias |Yes |Alias of app's public/private key pair. |
|keystorePrivateKeyPassword |Yes |Password to access app's private key. |
|successLoginDefaultUrl |Yes |Where to redirect user on successful login if no saved request is found in the session. |
|successLogoutUrl |Yes |Where to redirect user on successful logout. |
|failedLoginDefaultUrl |No |Where to redirect user on failed login. This value is set to null, which returns 401 error code on failed login. But, in theory, this will never be used because IdP will handled the failed login on IdP login page.<br/><br/>Default is `''`, which return 401 error code.|
|storeCsrfTokenInCookie |No |Whether to store CSRF token in cookie named `XSRF-TOKEN` and expecting CSRF token to be set using header named `X-XSRF-TOKEN` to cater single-page app using frameworks like React and AngularJS. <br/><br/>Default is `false`. |
|samlUserDetailsService |No |For configuring user details and authorities. When set, `userDetails` will be set as `principal`.<br/><br/>Default is `null`. |
|authnContexts |No |Determine what authentication methods to use. To use the order of authentication methods defined by IdP, set as empty set. To enable Windows Integrated Auth (WIA), use `CustomAuthnContext.WINDOWS_INTEGRATED_AUTHN_CTX`.<br/><br/>Default is `AuthnContext.PASSWORD_AUTHN_CTX` where IdP login page is displayed to obtain user/password.|
|useJdkCacertsForSslVerification |No |When performing IdP's SSL verification, find IdP's certs under JDK's cacerts instead of app's keystore file.<br/><br/>Default is `false`.|


## Important SAML Endpoints
Expand All @@ -253,38 +288,3 @@ Learn about my pains and lessons learned while building this module.
* [Configuring Binding for Sending SAML Messages to IdP](http://myshittycode.com/2016/02/18/spring-security-saml-configuring-binding-for-sending-saml-messages-to-idp/)
* [Java + SAML: Illegal Key Size](http://myshittycode.com/2016/02/18/java-saml-illegal-key-size/)

## Troubleshooting

### SSL peer failed hostname validation for name: null

By default, this dependency requires a keystore file that serves 2 purposes:-

* Acts as a keystore, containing app's public/private key.
* Acts as a truststore, containing IdP's certificate with public key.

If the keystore does not contain IdP's certificate, the SSL verification will fail with the following error when attempting to retrieve IdP's metadata:-

```
PKIX path construction failed for untrusted credential: [subjectName='CN=idp.server.com,OU=IDP,C=US']: unable to find valid certification path to requested target
I/O exception (javax.net.ssl.SSLPeerUnverifiedException) caught when processing request: SSL peer failed hostname validation for name: null
Error retrieving metadata from https://idp.server.com/federationmetadata/2007-06/federationmetadata.xml
```

That said, sometimes you may want to rely on the installed JDK's truststore (ie: cacerts) to manage IdP's certificate.

To pull this off, don't create `TLSProtocolConfigurer` object by doing this:-

```java
@Configuration
@EnableWebSecurity
class AppSecurityConfig extends SAMLWebSecurityConfigurerAdapter {

@Bean
@Override
TLSProtocolConfigurer tlsProtocolConfigurer() {
return null;
}

...
}
```
90 changes: 17 additions & 73 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

<parent>
<groupId>com.github.choonchernlim</groupId>
<artifactId>build-reports</artifactId>
<version>0.3.5</version>
<artifactId>spring-boot-ci</artifactId>
<version>0.4.0</version>
<relativePath/>
</parent>

<artifactId>spring-security-adfs-saml2</artifactId>
<version>0.8.0</version>
<version>0.9.0</version>
<packaging>jar</packaging>

<name>Spring Security ADFS SAML2</name>
Expand Down Expand Up @@ -45,17 +45,12 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jdk.version>1.8</jdk.version>

<spring-security-saml2.version>1.0.3.RELEASE</spring-security-saml2.version>
<spring.version>5.0.7.RELEASE</spring.version>
<spring-security.version>5.0.6.RELEASE</spring-security.version>
<opensaml.version>2.6.4</opensaml.version>
<javaee-api.version>7.0</javaee-api.version>
<pojobuilder.version>3.4.0</pojobuilder.version>
<better-preconditions.version>0.1.1</better-preconditions.version>
<javax.servlet-api.version>3.1.0</javax.servlet-api.version>
<spock-core.version>1.1-groovy-2.4-rc-3</spock-core.version>
<cglib-nodep.version>3.2.4</cglib-nodep.version>
<objenesis.version>2.5.1</objenesis.version>
<opensaml.version>2.6.4</opensaml.version>
<javaee-api.version>8.0</javaee-api.version>
<pojobuilder.version>4.2.2</pojobuilder.version>
<objenesis.version>3.0.1</objenesis.version>
<spring-security-saml2.version>1.0.3.RELEASE</spring-security-saml2.version>
</properties>

<dependencyManagement>
Expand All @@ -81,21 +76,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.opensaml</groupId>
<artifactId>opensaml</artifactId>
Expand All @@ -106,26 +86,6 @@
<artifactId>better-preconditions</artifactId>
<version>${better-preconditions.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet-api.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>${spock-core.version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>${cglib-nodep.version}</version>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
Expand Down Expand Up @@ -174,7 +134,7 @@
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<artifactId>spock-spring</artifactId>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -191,29 +151,6 @@

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<compilerId>groovy-eclipse-compiler</compilerId>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>2.9.2-01</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-batch</artifactId>
<version>2.4.3-01</version>
</dependency>
</dependencies>
</plugin>

<!--
When using `mkarneim/pojobuilder` (https://github.com/mkarneim/pojobuilder), the builder source files
are generated at `target/generated-sources/annotations`. If these source files exist and `mvn compile`
Expand All @@ -224,7 +161,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<phase>initialize</phase>
Expand All @@ -242,6 +178,14 @@
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
Loading

0 comments on commit f774c55

Please sign in to comment.