Skip to content

Commit

Permalink
0.3.2
Browse files Browse the repository at this point in the history
  • Loading branch information
Choon-Chern Lim committed Mar 15, 2016
1 parent fafc06b commit 0e46e1b
Show file tree
Hide file tree
Showing 9 changed files with 242 additions and 47 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Change Log

## 0.3.2 - 2016-03-14

* Used `SAMLContextProviderLB` instead of `SAMLContextProviderImpl` to handle servers doing SSL termination.
* Dropped `SAMLConfigBean.spMetadataBaseUrl`.
* Renamed `SAMLConfigBean.adfsHostName` to `SAMLConfigBean.idpHostName`.
* Added `SAMLConfigBean.spServerName`.
* Added `SAMLConfigBean.spHttpsPort`.
* Added `SAMLConfigBean.spContextPath`.

## 0.3.1 - 2016-03-10

Expand Down
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,34 @@ Spring Security module for service provider (Sp) to authenticate against identit
How this module is configured:-

* `HTTP-Redirect` binding for sending SAML messages to IdP.
* Handles Sp servers doing SSL termination.
* Default authentication method is user/password using IdP's form login page.
* Default signature algorithm is SHA256withRSA.
* Default digest algorithm is SHA-256.
* Default signature algorithm is `SHA256withRSA`.
* Default digest algorithm is `SHA-256`.

Tested against:-
Tested against Sp's environments:-

* ADFS 2.0 - Windows Server 2008 R2
* ADFS 2.1 - Windows Server 2012
* Local Tomcat server without SSL termination.
* Azure Tomcat server with SSL termination.

Tested against IdP's environments:-

* ADFS 2.0 - Windows Server 2008 R2.
* ADFS 2.1 - Windows Server 2012.

## Maven Dependency

```xml
<dependency>
<groupId>com.github.choonchernlim</groupId>
<artifactId>spring-security-adfs-saml2</artifactId>
<version>0.3.1</version>
<version>0.3.2</version>
</dependency>
```

## Prerequisites

* Sp must use HTTPS protocol.
* 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.
Expand All @@ -46,8 +52,9 @@ class AppSecurityConfig extends SAMLWebSecurityConfigurerAdapter {
@Override
protected SAMLConfigBean samlConfigBean() {
return new SAMLConfigBeanBuilder()
.setSpMetadataBaseUrl("https://localhost:8443/my-app")
.setAdfsHostName("idp-adfs-server")
.setIdpServerName("idp-server")
.setSpServerName("sp-server")
.setSpContextPath("/app")
.setKeystoreResource(new DefaultResourceLoader().getResource("classpath:keystore.jks"))
.setKeystorePassword("storepass")
.setKeystoreAlias("alias")
Expand Down Expand Up @@ -91,8 +98,10 @@ class AppSecurityConfig extends SAMLWebSecurityConfigurerAdapter {

|Property |Required? |Description |
|---------------------------|----------|----------------------------------------------------------------------------------------------------------|
|spMetadataBaseUrl |Yes |Sp's metadata base URL with format `https://server(:port)/contextPath` for constructing SAML endpoints (ex: `/saml/**`). This configuration is important to prevent servers doing SSL termination from generating wrong endpoints.|
|adfsHostName |Yes |ADFS host name without HTTPS protocol.<p>If ADFS link is `https://idp-adfs-server/adfs/ls`, the value should be `idp-adfs-server`.|
|idpServerName |Yes |IdP server name.<br/><br/>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. If Sp link is `https://sp-server:8443/myapp`, value should be `sp-server`. <br/><br/>Used for generating correct SAML endpoints in Sp metadata to handle servers doing SSL termination. |
|spHttpsPort |No |Sp HTTPS port. If Sp link is `https://sp-server:8443/myapp`, value should be `8443`.<br/><br/>Default is `443`. <br/><br/>Used for generating correct SAML endpoints in Sp metadata to handle servers doing SSL termination. |
|spContextPath |No |Sp context path. If Sp link is `https://sp-server:8443/myapp`, value should be `/myapp`.<br/><br/>Default is `''`. <br/><br/>Used for generating correct SAML endpoints in Sp metadata to handle servers doing SSL termination. |
|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. |
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>

<artifactId>spring-security-adfs-saml2</artifactId>
<version>0.3.1</version>
<version>0.3.2</version>
<packaging>jar</packaging>

<name>Spring Security ADFS SAML2</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,28 @@
public final class SAMLConfigBean {

/**
* Sp's metadata base URL for constructing SAML endpoints in SAML payload to IdP.
* (REQUIRED) IdP's server name.
*/
private final String spMetadataBaseUrl;
private final String idpServerName;

/**
* (REQUIRED) ADFS host name without HTTPS protocol.
* (REQUIRED) Sp's server name.
*/
private final String adfsHostName;
private final String spServerName;

/**
* (OPTIONAL) Sp's HTTPS port.
* <p/>
* Default is 443.
*/
private final Integer spHttpsPort;

/**
* (OPTIONAL) Sp's context path.
* <p/>
* Default is "".
*/
private final String spContextPath;

/**
* (REQUIRED) Keystore containing app's public/private key and ADFS' certificate with public key.
Expand Down Expand Up @@ -81,8 +95,10 @@ public final class SAMLConfigBean {
*/
private final Set<String> authnContexts;

SAMLConfigBean(final String spMetadataBaseUrl,
final String adfsHostName,
SAMLConfigBean(final String idpServerName,
final String spServerName,
final Integer spHttpsPort,
final String spContextPath,
final Resource keystoreResource,
final String keystoreAlias,
final String keystorePassword,
Expand All @@ -94,8 +110,11 @@ public final class SAMLConfigBean {
final Set<String> authnContexts) {

//@formatter:off
this.spMetadataBaseUrl = expect(spMetadataBaseUrl, "Sp's metadata base URL").not().toBeBlank().check();
this.adfsHostName = expect(adfsHostName, "ADFS host name").not().toBeBlank().check();
this.idpServerName = expect(idpServerName, "IdP server name").not().toBeBlank().check();

this.spServerName = expect(spServerName, "Sp server name").not().toBeBlank().check();
this.spHttpsPort = Optional.fromNullable(spHttpsPort).or(443);
this.spContextPath = Optional.fromNullable(spContextPath).or("");

this.keystoreResource = (Resource) expect(keystoreResource, "Key store").not().toBeNull().check();
this.keystoreAlias = expect(keystoreAlias, "Keystore alias").not().toBeBlank().check();
Expand All @@ -113,12 +132,20 @@ public final class SAMLConfigBean {
//@formatter:on
}

public String getSpMetadataBaseUrl() {
return spMetadataBaseUrl;
public String getIdpServerName() {
return idpServerName;
}

public String getSpServerName() {
return spServerName;
}

public Integer getSpHttpsPort() {
return spHttpsPort;
}

public String getAdfsHostName() {
return adfsHostName;
public String getSpContextPath() {
return spContextPath;
}

public Resource getKeystoreResource() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
* Builder class for constructing SAMLConfigBean.
*/
public final class SAMLConfigBeanBuilder {
private String spMetadataBaseUrl;
private String adfsHostName;
private String idpServerName;
private String spServerName;
private Integer spHttpsPort;
private String spContextPath;
private Resource keystoreResource;
private String keystoreAlias;
private String keystorePassword;
Expand All @@ -21,13 +23,23 @@ public final class SAMLConfigBeanBuilder {
private SAMLUserDetailsService samlUserDetailsService;
private Set<String> authnContexts;

public SAMLConfigBeanBuilder setSpMetadataBaseUrl(final String spMetadataBaseUrl) {
this.spMetadataBaseUrl = spMetadataBaseUrl;
public SAMLConfigBeanBuilder setIdpServerName(final String idpServerName) {
this.idpServerName = idpServerName;
return this;
}

public SAMLConfigBeanBuilder setAdfsHostName(final String adfsHostName) {
this.adfsHostName = adfsHostName;
public SAMLConfigBeanBuilder setSpServerName(final String spServerName) {
this.spServerName = spServerName;
return this;
}

public SAMLConfigBeanBuilder setSpHttpsPort(final Integer spHttpsPort) {
this.spHttpsPort = spHttpsPort;
return this;
}

public SAMLConfigBeanBuilder setSpContextPath(final String spContextPath) {
this.spContextPath = spContextPath;
return this;
}

Expand Down Expand Up @@ -77,8 +89,10 @@ public SAMLConfigBeanBuilder setAuthnContexts(final Set<String> authnContexts) {
}

public SAMLConfigBean createSAMLConfigBean() {
return new SAMLConfigBean(spMetadataBaseUrl,
adfsHostName,
return new SAMLConfigBean(idpServerName,
spServerName,
spHttpsPort,
spContextPath,
keystoreResource,
keystoreAlias,
keystorePassword,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import org.springframework.security.saml.SAMLLogoutProcessingFilter;
import org.springframework.security.saml.SAMLProcessingFilter;
import org.springframework.security.saml.SAMLWebSSOHoKProcessingFilter;
import org.springframework.security.saml.context.SAMLContextProviderImpl;
import org.springframework.security.saml.context.SAMLContextProviderLB;
import org.springframework.security.saml.key.JKSKeyManager;
import org.springframework.security.saml.key.KeyManager;
import org.springframework.security.saml.log.SAMLDefaultLogger;
Expand Down Expand Up @@ -133,7 +133,7 @@ protected final WebSecurity samlizedConfig(final WebSecurity web) throws Excepti
// IDP metadata URL
private String getMetdataUrl() {
return String.format("https://%s/federationmetadata/2007-06/federationmetadata.xml",
samlConfigBean().getAdfsHostName());
samlConfigBean().getIdpServerName());
}

// Entry point to initialize authentication
Expand Down Expand Up @@ -169,9 +169,19 @@ public SAMLEntryPoint samlEntryPoint() {
// Filter automatically generates default SP metadata
@Bean
public MetadataGeneratorFilter metadataGeneratorFilter() {
// generates base URL that matches `SAMLContextProviderLB` configuration
// to ensure SAML endpoints work for server doing SSL termination
StringBuilder sb = new StringBuilder();
sb.append("https://").append(samlConfigBean().getSpServerName());
if (samlConfigBean().getSpHttpsPort() != 443) {
sb.append(":").append(samlConfigBean().getSpHttpsPort());
}
sb.append(samlConfigBean().getSpContextPath());
String entityBaseUrl = sb.toString();

MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setKeyManager(keyManager());
metadataGenerator.setEntityBaseURL(samlConfigBean().getSpMetadataBaseUrl());
metadataGenerator.setEntityBaseURL(entityBaseUrl);
return new MetadataGeneratorFilter(metadataGenerator);
}

Expand Down Expand Up @@ -321,10 +331,22 @@ public SAMLAuthenticationProvider samlAuthenticationProvider() {
return samlAuthenticationProvider;
}

// Provider of default SAML Context
@Bean
public SAMLContextProviderImpl contextProvider() {
return new SAMLContextProviderImpl();
// In order to get SAML to work for Sp servers doing SSL termination, `SAMLContextProviderLB` has
// to be used instead of `SAMLContextProviderImpl` to prevent the following exception:-
//
// "SAML message intended destination endpoint 'https://server/app/saml/SSO' did not match the
// recipient endpoint 'http://server/app/saml/SSO'"
//
// This configuration will work for Sp servers (not) doing SSL termination.
@Bean
public SAMLContextProviderLB contextProvider() {
SAMLContextProviderLB contextProviderLB = new SAMLContextProviderLB();
contextProviderLB.setScheme("https");
contextProviderLB.setServerName(samlConfigBean().getSpServerName());
contextProviderLB.setServerPort(samlConfigBean().getSpHttpsPort());
contextProviderLB.setIncludeServerPortInRequestURL(samlConfigBean().getSpHttpsPort() != 443);
contextProviderLB.setContextPath(samlConfigBean().getSpContextPath());
return contextProviderLB;
}

// Processing filter for WebSSO profile messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ class SAMLConfigBeanSpec extends Specification {
}
}
def allFieldsBeanBuilder = new SAMLConfigBeanBuilder().
setSpMetadataBaseUrl('spMetadataBaseUrl').
setAdfsHostName('adfsHostName').
setIdpServerName('idpServerName').
setSpServerName('spServerName').
setSpHttpsPort(8443).
setSpContextPath('spContextPath').
setKeystoreResource(keystoreResource).
setKeystoreAlias('keystoreAlias').
setKeystorePassword('keystorePassword').
Expand All @@ -40,8 +42,10 @@ class SAMLConfigBeanSpec extends Specification {
def bean = allFieldsBeanBuilder.createSAMLConfigBean()

then:
bean.spMetadataBaseUrl == 'spMetadataBaseUrl'
bean.adfsHostName == 'adfsHostName'
bean.idpServerName == 'idpServerName'
bean.spServerName == 'spServerName'
bean.spHttpsPort == 8443
bean.spContextPath == 'spContextPath'
bean.keystoreResource == keystoreResource
bean.keystoreAlias == 'keystoreAlias'
bean.keystorePassword == 'keystorePassword'
Expand All @@ -56,14 +60,18 @@ class SAMLConfigBeanSpec extends Specification {
def "only required fields"() {
when:
def bean = allFieldsBeanBuilder.
setSpHttpsPort(null).
setSpContextPath(null).
setFailedLoginDefaultUrl(null).
setSamlUserDetailsService(null).
setAuthnContexts(null).
createSAMLConfigBean()

then:
bean.spMetadataBaseUrl == 'spMetadataBaseUrl'
bean.adfsHostName == 'adfsHostName'
bean.idpServerName == 'idpServerName'
bean.spServerName == 'spServerName'
bean.spHttpsPort == 443
bean.spContextPath == ''
bean.keystoreResource == keystoreResource
bean.keystoreAlias == 'keystoreAlias'
bean.keystorePassword == 'keystorePassword'
Expand Down Expand Up @@ -95,8 +103,8 @@ class SAMLConfigBeanSpec extends Specification {

where:
field | expectedException
'SpMetadataBaseUrl' | StringBlankPreconditionException
'AdfsHostName' | StringBlankPreconditionException
'IdpServerName' | StringBlankPreconditionException
'SpServerName' | StringBlankPreconditionException
'KeystoreResource' | ObjectNullPreconditionException
'KeystoreAlias' | StringBlankPreconditionException
'KeystorePassword' | StringBlankPreconditionException
Expand Down
Loading

0 comments on commit 0e46e1b

Please sign in to comment.