/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
/**
* Adds X509 based pre authentication to an application. Since validating the
* certificate happens when the client connects, the requesting and validation
* of the client certificate should be performed by the container. Spring Security
* will then use the certificate to look up the {@link Authentication} for the user.
*
* <h2>Security Filters</h2>
*
* The following Filters are populated
*
* <ul>
* <li>{@link X509AuthenticationFilter}</li>
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* The following shared objects are created
*
* <ul>
* <li>
* {@link AuthenticationEntryPoint}
* is populated with an {@link Http403ForbiddenEntryPoint}</li>
* <li>A {@link PreAuthenticatedAuthenticationProvider} is populated into
* {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
* </li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* The following shared objects are used:
*
* <ul>
* <li>A {@link UserDetailsService} shared object is used if no {@link AuthenticationUserDetailsService} is specified</li>
* </ul>
*
* @author Rob Winch
* @since 3.2
*/
public final class X509Configurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<H> {
private X509AuthenticationFilter x509AuthenticationFilter;
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService;
private String subjectPrincipalRegex;
private AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails> authenticationDetailsSource;
/**
* Creates a new instance
* @see HttpSecurity#x509()
*/
public X509Configurer() {
}
/**
* Allows specifying the entire {@link X509AuthenticationFilter}. If this is
* specified, the properties on {@link X509Configurer} will not be
* populated on the {@link X509AuthenticationFilter}.
*
* @param x509AuthenticationFilter the {@link X509AuthenticationFilter} to use
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> x509AuthenticationFilter(
X509AuthenticationFilter x509AuthenticationFilter) {
this.x509AuthenticationFilter = x509AuthenticationFilter;
return this;
}
/**
* Specifies the {@link AuthenticationDetailsSource}
*
* @param authenticationDetailsSource the {@link AuthenticationDetailsSource} to use
* @return the {@link X509Configurer} to use
*/
public X509Configurer<H> authenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails> authenticationDetailsSource) {
this.authenticationDetailsSource = authenticationDetailsSource;
return this;
}
/**
* Shortcut for invoking {@link #authenticationUserDetailsService(AuthenticationUserDetailsService)} with a {@link UserDetailsByNameServiceWrapper}.
*
* @param userDetailsService the {@link UserDetailsService} to use
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> userDetailsService(
UserDetailsService userDetailsService) {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService = new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>();
authenticationUserDetailsService.setUserDetailsService(userDetailsService);
return authenticationUserDetailsService(authenticationUserDetailsService);
}
/**
* Specifies the {@link AuthenticationUserDetailsService} to use. If not
* specified, the shared {@link UserDetailsService} will be used to create a
* {@link UserDetailsByNameServiceWrapper}.
*
* @param authenticationUserDetailsService the {@link AuthenticationUserDetailsService} to use
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> authenticationUserDetailsService(
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService) {
this.authenticationUserDetailsService = authenticationUserDetailsService;
return this;
}
/**
* Specifies the regex to extract the principal from the certificate. If not
* specified, the default expression from
* {@link SubjectDnX509PrincipalExtractor} is used.
*
* @param subjectPrincipalRegex
* the regex to extract the user principal from the certificate
* (i.e. "CN=(.*?)(?:,|$)").
* @return the {@link X509Configurer} for further customizations
*/
public X509Configurer<H> subjectPrincipalRegex(String subjectPrincipalRegex) {
this.subjectPrincipalRegex = subjectPrincipalRegex;
return this;
}
@Override
public void init(H http) throws Exception {
PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
authenticationProvider.setPreAuthenticatedUserDetailsService(getAuthenticationUserDetailsService(http));
http
.authenticationProvider(authenticationProvider)
.setSharedObject(AuthenticationEntryPoint.class,new Http403ForbiddenEntryPoint());
}
@Override
public void configure(H http) throws Exception {
X509AuthenticationFilter filter = getFilter(http.getAuthenticationManager());
http.addFilter(filter);
}
private X509AuthenticationFilter getFilter(
AuthenticationManager authenticationManager) {
if (x509AuthenticationFilter == null) {
x509AuthenticationFilter = new X509AuthenticationFilter();
x509AuthenticationFilter.setAuthenticationManager(authenticationManager);
if(subjectPrincipalRegex != null) {
SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
principalExtractor.setSubjectDnRegex(subjectPrincipalRegex);
x509AuthenticationFilter.setPrincipalExtractor(principalExtractor);
}
if(authenticationDetailsSource != null) {
x509AuthenticationFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
}
x509AuthenticationFilter = postProcess(x509AuthenticationFilter);
}
return x509AuthenticationFilter;
}
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getAuthenticationUserDetailsService(H http) {
if(authenticationUserDetailsService == null) {
userDetailsService(http.getSharedObject(UserDetailsService.class));
}
return authenticationUserDetailsService;
}
}