Skip to content

Commit

Permalink
Merge pull request #2 from choonchernlim/development
Browse files Browse the repository at this point in the history
0.4.0
  • Loading branch information
choonchernlim committed Jun 6, 2016
2 parents e70618c + 015dc04 commit 86a68e8
Show file tree
Hide file tree
Showing 11 changed files with 352 additions and 21 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Change Log

## 0.4.0 - 2016-06-01

* If `samlConfigBean.samlUserDetailsService` is provided, then set `samlAuthenticationProvider.forcePrincipalAsString` to `false` so that `principal` represents the `userDetails` object.
* Ability to mock security to bypass authentication against ADFS during rapid app development. To use this, `samlConfigBean.samlUserDetailsService` must be set.
* Dependency, parent and plugins updates.

```
com.github.choonchernlim:build-reports ................ 0.2.4 -> 0.3.2
com.google.guava:guava-testlib .......................... 18.0 -> 19.0
junit:junit ............................................. 4.11 -> 4.12
org.codehaus.groovy:groovy-all .............. 2.4.3 -> 2.4.6
org.springframework.security:spring-security-config ...
4.0.3.RELEASE -> 4.1.0.RELEASE
org.springframework.security:spring-security-core ...
4.0.3.RELEASE -> 4.1.0.RELEASE
org.springframework.security:spring-security-web ...
4.0.3.RELEASE -> 4.1.0.RELEASE
org.springframework.security.extensions:spring-security-saml2-core ...
1.0.1.RELEASE -> 1.0.2.RELEASE
maven-compiler-plugin ................................... 3.3 -> 3.5.1
```

## 0.3.3 - 2016-04-13
* Inject Spring environment to get access to project properties file. ([#1](https://github.com/choonchernlim/spring-security-adfs-saml2/pull/1))

Expand Down
20 changes: 18 additions & 2 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.3.3</version>
<version>0.4.0</version>
</dependency>
```

Expand All @@ -42,6 +42,8 @@ Tested against IdP's environments:-

## Usage

### Configuration Example

```java
// Create a Java-based Spring configuration that extends SAMLWebSecurityConfigurerAdapter.
@Configuration
Expand Down Expand Up @@ -92,6 +94,20 @@ class AppSecurityConfig extends SAMLWebSecurityConfigurerAdapter {
}
```

### Mocking Security by Harcdoding a Given User for Rapid App Development

```java
@Override
protected void configure(final HttpSecurity http) throws Exception {
// `CurrentUser` must extend `User`
final CurrentUser currentUser = new CurrentUser("First name", "Last Name", "ROLE_ADMIN");

mockSecurity(http, currentUser)
.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated();
}
```

## SAMLConfigBean Properties

`SAMLConfigBean` stores app-specific security configuration.
Expand All @@ -109,7 +125,7 @@ class AppSecurityConfig extends SAMLWebSecurityConfigurerAdapter {
|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.|
|samlUserDetailsService |No |For configuring user authorities (ex: `ROLE_*`) if needed.<br/><br/>Default is `null`. |
|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.|


Expand Down
42 changes: 33 additions & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
<parent>
<groupId>com.github.choonchernlim</groupId>
<artifactId>build-reports</artifactId>
<version>0.2.4</version>
<version>0.3.2</version>
</parent>

<artifactId>spring-security-adfs-saml2</artifactId>
<version>0.3.3</version>
<version>0.4.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Spring Security ADFS SAML2</name>
Expand Down Expand Up @@ -44,9 +44,9 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jdk.version>1.7</jdk.version>

<spring.version>4.2.4.RELEASE</spring.version>
<spring-security.version>4.0.3.RELEASE</spring-security.version>
<spring-security-saml2.version>1.0.1.RELEASE</spring-security-saml2.version>
<spring.version>4.2.6.RELEASE</spring.version>
<spring-security.version>4.1.0.RELEASE</spring-security.version>
<spring-security-saml2.version>1.0.2.RELEASE</spring-security-saml2.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -101,16 +101,28 @@
<version>0.1.1</version>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.3</version>
<version>2.4.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<version>18.0</version>
<version>19.0</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -122,7 +134,19 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
<artifactId>objenesis</artifactId>
<version>2.4</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand All @@ -132,7 +156,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<version>3.5.1</version>
<configuration>
<compilerId>groovy-eclipse-compiler</compilerId>
<source>${jdk.version}</source>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.github.choonchernlim.security.adfs.saml2;

import static com.github.choonchernlim.betterPreconditions.preconditions.PreconditionFactory.expect;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
* This filter class can be placed before Spring Security's FilterSecurityInterceptor to bypass any security
* by setting the given user to the security context and HTTP session. This allows developer to do rapid app
* development without the need to log into the app again and again.
*/
final class MockFilterSecurityInterceptor implements Filter {

private final User user;

MockFilterSecurityInterceptor(final User user) {
expect(user, "user").not().toBeNull().check();
this.user = user;
}

@Override
public void doFilter(final ServletRequest servletRequest,
final ServletResponse servletResponse,
final FilterChain filterChain) throws IOException, ServletException {

// To be consistent with SAML configuration, the `userDetails` is set as `principal` too
final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
user,
null,
user.getAuthorities());

// setting user details
authentication.setDetails(user);

// setting `authentication` to security context
final SecurityContext securityContext = SecurityContextHolder.getContext();
securityContext.setAuthentication(authentication);

// setting `authentication` to HTTP session
((HttpServletRequest) servletRequest)
.getSession(true)
.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext);

filterChain.doFilter(servletRequest, servletResponse);
}

@Override
public void init(final FilterConfig filterConfig) throws ServletException {
}

@Override
public void destroy() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public final class SAMLConfigBean {
private final String failedLoginDefaultUrl;

/**
* For configuring user authorities if needed.
* For configuring user details and authorities.
* <p/>
* Default is null.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.choonchernlim.security.adfs.saml2;

import static com.github.choonchernlim.betterPreconditions.preconditions.PreconditionFactory.expect;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.httpclient.HttpClient;
Expand All @@ -21,6 +22,7 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.saml.SAMLAuthenticationProvider;
import org.springframework.security.saml.SAMLBootstrap;
import org.springframework.security.saml.SAMLDiscovery;
Expand Down Expand Up @@ -48,6 +50,7 @@
import org.springframework.security.saml.processor.SAMLProcessorImpl;
import org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer;
import org.springframework.security.saml.trust.httpclient.TLSProtocolSocketFactory;
import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
import org.springframework.security.saml.util.VelocityFactory;
import org.springframework.security.saml.websso.ArtifactResolutionProfileImpl;
import org.springframework.security.saml.websso.SingleLogoutProfile;
Expand All @@ -63,6 +66,7 @@
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
Expand Down Expand Up @@ -114,16 +118,39 @@ public static SAMLBootstrap samlBootstrap() {
// CSRF must be disabled when processing /saml/** to prevent "Expected CSRF token not found" exception.
// See: http://stackoverflow.com/questions/26508835/spring-saml-extension-and-spring-security-csrf-protection-conflict/26560447
protected final HttpSecurity samlizedConfig(final HttpSecurity http) throws Exception {
http.httpBasic().authenticationEntryPoint(samlEntryPoint())
return http
.httpBasic().authenticationEntryPoint(samlEntryPoint())
.and()
.csrf().ignoringAntMatchers("/saml/**")
.and()
.authorizeRequests().antMatchers("/saml/**").permitAll()
.and()
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(filterChainProxy(), BasicAuthenticationFilter.class);
}

/**
* Mocks security by hardcoding a given user so that it will always appear that user is accessing the protected
* resources. This is useful to allow developer to bypass any web authentication against ADFS during rapid
* app development.
*
* @param http HttpSecurity instance
* @param user User instance
* @return HttpSecurity that will never authenticate against ADFS
*/
protected final HttpSecurity mockSecurity(final HttpSecurity http, final User user) {
expect(user, "user").not().toBeNull().check();

if (samlConfigBean().getSamlUserDetailsService() == null) {
throw new SpringSecurityAdfsSaml2Exception(
"`samlConfigBean.samlUserDetailsService` cannot be null. " +
"When mocking security, the given user details object will be set as principal. " +
"Because setting `samlConfigBean.samlUserDetailsService` will set the user details object as principal, " +
"this property must be configured to ensure the mock security mimics the actual security configuration."
);
}

return http;
return http.addFilterBefore(new MockFilterSecurityInterceptor(user), FilterSecurityInterceptor.class);
}

/**
Expand Down Expand Up @@ -333,9 +360,18 @@ public CachingMetadataManager metadata() throws MetadataProviderException {
@Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
if (samlConfigBean().getSamlUserDetailsService() != null) {
samlAuthenticationProvider.setUserDetails(samlConfigBean().getSamlUserDetailsService());
SAMLUserDetailsService samlUserDetailsService = samlConfigBean().getSamlUserDetailsService();

if (samlUserDetailsService != null) {
samlAuthenticationProvider.setUserDetails(samlUserDetailsService);

// By default, `principal` is always going to be `NameID` even though the `Authentication` object
// contain `userDetails` object. So, if `userDetails` is provided, then don't force `principal` as
// string so that `principal` represents `userDetails` object.
// See: http://stackoverflow.com/questions/33786861/how-to-override-the-nameid-value-in-samlauthenticationprovider
samlAuthenticationProvider.setForcePrincipalAsString(false);
}

return samlAuthenticationProvider;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.choonchernlim.security.adfs.saml2;

/**
* Module specific exception.
*/
public final class SpringSecurityAdfsSaml2Exception extends RuntimeException {

SpringSecurityAdfsSaml2Exception(final String message) {
super(message);
}
}
8 changes: 4 additions & 4 deletions src/site/site.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/DECORATION/1.6.0"
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.6.0
http://maven.apache.org/xsd/decoration-1.6.0.xsd">
xmlns="http://maven.apache.org/DECORATION/1.7.0"
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.7.0
http://maven.apache.org/xsd/decoration-1.7.0.xsd">
<bannerLeft>
<name>${project.name}</name>
<href>${project.url}</href>
Expand All @@ -16,7 +16,7 @@
<skin>
<groupId>org.apache.maven.skins</groupId>
<artifactId>maven-fluido-skin</artifactId>
<version>1.4</version>
<version>1.5</version>
</skin>

<body>
Expand Down
Loading

0 comments on commit 86a68e8

Please sign in to comment.