Skip to content

Commit

Permalink
Configurable auth method
Browse files Browse the repository at this point in the history
  • Loading branch information
Choon-Chern Lim committed Mar 2, 2016
1 parent 9072d24 commit c6ce4e8
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 18 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## 0.2.0 - 2016-03-02

* Options to allow different authentication method. Default is user/password using IdP's form login page.
* `CustomAuthnContext.WINDOWS_INTEGRATED_AUTHN_CTX` to allow Windows Integrated Authentication.

## 0.1.0 - 2016-02-28

* Initial.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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.
* SSO is disabled by forcing IdP login page to appear so that users don't automatically get logged in through Windows Integrated Auth (WIA).
* Default authentication method is user/password using IdP's form login page.
* Default signature algorithm is SHA256withRSA.
* Default digest algorithm is SHA-256.

Expand All @@ -20,7 +20,7 @@ Tested against:-
<dependency>
<groupId>com.github.choonchernlim</groupId>
<artifactId>spring-security-adfs-saml2</artifactId>
<version>0.1.0</version>
<version>0.2.0</version>
</dependency>
```

Expand Down Expand Up @@ -63,16 +63,21 @@ class AppSecurityConfig extends SAMLWebSecurityConfigurerAdapter {
// (Optional) Where to redirect user on failed login. This is probably not needed
// because IdP should handle the failed login instead of returning back to Sp.
// So, you probably don't need to set this.
.setFailedLoginDefaultUrl(null)
// Default is empty string.
.setFailedLoginDefaultUrl("/error")
// (Optional) An opportunity to define user authorities or user properties either
// by cherry picking the claim values from IdP's SAML response or from other
// data sources
// data sources.
// Default is null.
.setSamlUserDetailsService(new SAMLUserDetailsService() {
@Override
public Object loadUserBySAML(final SAMLCredential credential) throws UsernameNotFoundException {
return ...;
}
})
// (Optional) Authentication method (WIA, user/password, etc)
// Default is user/password authentication.
.setAuthnContexts(ImmutableSet.of(CustomAuthnContext.WINDOWS_INTEGRATED_AUTHN_CTX))
.createSAMLConfigBean();
}

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.1.0</version>
<version>0.2.0</version>
<packaging>jar</packaging>

<name>Spring Security ADFS SAML2</name>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.choonchernlim.security.adfs.saml2;

/**
* ADFS-specific authentication method.
*/
public final class CustomAuthnContext {
/**
* Windows integration authentication.
*/
public static final String WINDOWS_INTEGRATED_AUTHN_CTX = "urn:federation:authentication:windows";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

import static com.github.choonchernlim.betterPreconditions.preconditions.PreconditionFactory.expect;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import org.opensaml.saml2.core.AuthnContext;
import org.springframework.core.io.Resource;
import org.springframework.security.saml.userdetails.SAMLUserDetailsService;

import java.util.Set;

/**
* This class contains all properties that can be configured by Sp using the provided builder class.
*/
Expand Down Expand Up @@ -56,14 +60,26 @@ public final class SAMLConfigBean {
*/
private final SAMLUserDetailsService samlUserDetailsService;

/**
* Determine what authentication methods to use.
* <p/>
* To use the order of authentication methods defined by IdP, set as empty set.
* <p/>
* To enable Windows Integrated Auth (WIA) cross browsers and OSes, use `CustomAuthnContext.WINDOWS_INTEGRATED_AUTHN_CTX`.
* <p/>
* Default is user/password authentication where IdP login page is displayed.
*/
private final Set<String> authnContexts;

SAMLConfigBean(final String adfsHostName,
final Resource keyStoreResource,
final String keystoreAlias,
final String keystorePassword,
final String successLoginDefaultUrl,
final String successLogoutUrl,
final String failedLoginDefaultUrl,
final SAMLUserDetailsService samlUserDetailsService) {
final SAMLUserDetailsService samlUserDetailsService,
final Set<String> authnContexts) {

this.adfsHostName = expect(adfsHostName, "ADFS host name").not().toBeBlank().check();

Expand All @@ -73,9 +89,12 @@ public final class SAMLConfigBean {

this.successLoginDefaultUrl = expect(successLoginDefaultUrl, "Success login URL").not().toBeBlank().check();
this.successLogoutUrl = expect(successLogoutUrl, "Success logout URL").not().toBeBlank().check();

this.failedLoginDefaultUrl = Optional.fromNullable(failedLoginDefaultUrl).or("");

this.samlUserDetailsService = samlUserDetailsService;

this.authnContexts = Optional.fromNullable(authnContexts).or(ImmutableSet.of(AuthnContext.PASSWORD_AUTHN_CTX));
}

public String getAdfsHostName() {
Expand Down Expand Up @@ -109,4 +128,8 @@ public String getFailedLoginDefaultUrl() {
public SAMLUserDetailsService getSamlUserDetailsService() {
return samlUserDetailsService;
}

public Set<String> getAuthnContexts() {
return authnContexts;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import org.springframework.core.io.Resource;
import org.springframework.security.saml.userdetails.SAMLUserDetailsService;

import java.util.Set;

/**
* Builder class for constructing SAMLConfigBean.
*/
Expand All @@ -15,6 +17,7 @@ public final class SAMLConfigBeanBuilder {
private String successLogoutUrl;
private String failedLoginDefaultUrl;
private SAMLUserDetailsService samlUserDetailsService;
private Set<String> authnContexts;

public SAMLConfigBeanBuilder setAdfsHostName(final String adfsHostName) {
this.adfsHostName = adfsHostName;
Expand Down Expand Up @@ -56,6 +59,11 @@ public SAMLConfigBeanBuilder setSamlUserDetailsService(final SAMLUserDetailsServ
return this;
}

public SAMLConfigBeanBuilder setAuthnContexts(final Set<String> authnContexts) {
this.authnContexts = authnContexts;
return this;
}

public SAMLConfigBean createSAMLConfigBean() {
return new SAMLConfigBean(adfsHostName,
keyStoreResource,
Expand All @@ -64,6 +72,7 @@ public SAMLConfigBean createSAMLConfigBean() {
successLoginDefaultUrl,
successLogoutUrl,
failedLoginDefaultUrl,
samlUserDetailsService);
samlUserDetailsService,
authnContexts);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.velocity.app.VelocityEngine;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.AuthnContext;
import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
Expand Down Expand Up @@ -71,7 +70,6 @@
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.Collections;
import java.util.Timer;

/**
Expand Down Expand Up @@ -156,14 +154,11 @@ public SAMLEntryPoint samlEntryPoint() {
// See: http://stackoverflow.com/questions/30528636/saml-login-errors
webSSOProfileOptions.setForceAuthN(true);

// Disable SSO by forcing login page to appear, even though SSO is enabled on ADFS.
// By default, if SSO is enable on ADFS, certain browsers will automatically log user in through
// Windows Integrated Auth (WIA) and user agent detection, and there's no proper way to log out
// since it will automatically log user back in.
//
// This ensures the app can properly log out when clicking on the log out button and
// ensures the unified IDP login page is served regardless of the types of browser used.
webSSOProfileOptions.setAuthnContexts(Collections.singletonList(AuthnContext.PASSWORD_AUTHN_CTX));
// Determine what authentication method to use (WIA, user/password, etc).
// If not set, it will use authentication method order defined by IdP
if (!samlConfigBean().getAuthnContexts().isEmpty()) {
webSSOProfileOptions.setAuthnContexts(samlConfigBean().getAuthnContexts());
}

SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.choonchernlim.security.adfs.saml2

import com.github.choonchernlim.betterPreconditions.exception.ObjectNullPreconditionException
import com.github.choonchernlim.betterPreconditions.exception.StringBlankPreconditionException
import org.opensaml.saml2.core.AuthnContext
import org.springframework.core.io.DefaultResourceLoader
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.User
Expand Down Expand Up @@ -29,7 +30,8 @@ class SAMLConfigBeanSpec extends Specification {
setSuccessLoginDefaultUrl('successLoginDefaultUrl').
setSuccessLogoutUrl('successLogoutUrl').
setFailedLoginDefaultUrl('failedLoginDefaultUrl').
setSamlUserDetailsService(samlUserDetailsService)
setSamlUserDetailsService(samlUserDetailsService).
setAuthnContexts([CustomAuthnContext.WINDOWS_INTEGRATED_AUTHN_CTX] as Set)

def "required and optional fields"() {
when:
Expand All @@ -44,13 +46,15 @@ class SAMLConfigBeanSpec extends Specification {
bean.successLogoutUrl == 'successLogoutUrl'
bean.failedLoginDefaultUrl == 'failedLoginDefaultUrl'
bean.samlUserDetailsService == samlUserDetailsService
bean.authnContexts == [CustomAuthnContext.WINDOWS_INTEGRATED_AUTHN_CTX] as Set
}

def "only required fields"() {
when:
def bean = allFieldsBeanBuilder.
setFailedLoginDefaultUrl(null).
setSamlUserDetailsService(null).
setAuthnContexts(null).
createSAMLConfigBean()

then:
Expand All @@ -62,6 +66,17 @@ class SAMLConfigBeanSpec extends Specification {
bean.successLogoutUrl == 'successLogoutUrl'
bean.failedLoginDefaultUrl == ''
bean.samlUserDetailsService == null
bean.authnContexts == [AuthnContext.PASSWORD_AUTHN_CTX] as Set
}

def "authnContexts - empty set is fine"() {
when:
def bean = allFieldsBeanBuilder.
setAuthnContexts([] as Set).
createSAMLConfigBean()

then:
bean.authnContexts == [] as Set
}

@Unroll
Expand Down

0 comments on commit c6ce4e8

Please sign in to comment.