SEC-645: Reimplementation of X509 authentication.

This commit is contained in:
Luke Taylor 2008-01-27 11:12:50 +00:00
parent 718eddadd7
commit c7792458b4
4 changed files with 200 additions and 0 deletions

View File

@ -0,0 +1,84 @@
package org.springframework.security.ui.preauth.x509;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.util.Assert;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.MessageSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.security.cert.X509Certificate;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/**
* Obtains the principal from a certificate using a regular expression match against the Subject (as returned by a call
* to {@link X509Certificate#getSubjectDN()}).
* <p>
* The regular expression should contain a single group; for example the default expression "CN=(.?)," matches the
* common name field. So "CN=Jimi Hendrix, OU=..." will give a user name of "Jimi Hendrix".
* <p>
* The matches are case insensitive. So "emailAddress=(.?)," will match "EMAILADDRESS=jimi@hendrix.org, CN=..." giving a
* user name "jimi@hendrix.org"
*
* @author Luke Taylor
* @version $Id$
*/
public class SubjectDnX509PrincipalExtractor implements X509PrincipalExtractor {
//~ Instance fields ================================================================================================
protected final Log logger = LogFactory.getLog(getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private Pattern subjectDnPattern;
public SubjectDnX509PrincipalExtractor() {
setSubjectDnRegex("CN=(.*?),");
}
public Object extractPrincipal(X509Certificate clientCert) {
//String subjectDN = clientCert.getSubjectX500Principal().getName();
String subjectDN = clientCert.getSubjectDN().getName();
logger.debug("Subject DN is '" + subjectDN + "'");
Matcher matcher = subjectDnPattern.matcher(subjectDN);
if (!matcher.find()) {
throw new BadCredentialsException(messages.getMessage("DaoX509AuthoritiesPopulator.noMatching",
new Object[] {subjectDN}, "No matching pattern was found in subject DN: {0}"));
}
if (matcher.groupCount() != 1) {
throw new IllegalArgumentException("Regular expression must contain a single group ");
}
String username = matcher.group(1);
logger.debug("Extracted Principal name is '" + username + "'");
return username;
}
/**
* Sets the regular expression which will by used to extract the user name from the certificate's Subject
* DN.
* <p>
* It should contain a single group; for example the default expression "CN=(.?)," matches the common
* name field. So "CN=Jimi Hendrix, OU=..." will give a user name of "Jimi Hendrix".
* <p>
* The matches are case insensitive. So "emailAddress=(.?)," will match "EMAILADDRESS=jimi@hendrix.org,
* CN=..." giving a user name "jimi@hendrix.org"
*
* @param subjectDnRegex the regular expression to find in the subject
*/
public void setSubjectDnRegex(String subjectDnRegex) {
Assert.hasText(subjectDnRegex, "Regular expression may not be null or empty");
subjectDnPattern = Pattern.compile(subjectDnRegex, Pattern.CASE_INSENSITIVE);
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
}

View File

@ -0,0 +1,51 @@
package org.springframework.security.ui.preauth.x509;
import org.springframework.security.ui.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.ui.FilterChainOrder;
import javax.servlet.http.HttpServletRequest;
import java.security.cert.X509Certificate;
/**
* @author Luke Taylor
* @version $Id$
*/
public class X509PreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
X509Certificate cert = extractClientCertificate(request);
if (cert == null) {
return null;
}
return principalExtractor.extractPrincipal(cert);
}
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return extractClientCertificate(request);
}
private X509Certificate extractClientCertificate(HttpServletRequest request) {
X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
if (certs != null && certs.length > 0) {
return certs[0];
}
if (logger.isDebugEnabled()) {
logger.debug("No client certificate found in request.");
}
return null;
}
public void setPrincipalExtractor(X509PrincipalExtractor principalExtractor) {
this.principalExtractor = principalExtractor;
}
public int getOrder() {
return FilterChainOrder.X509_FILTER;
}
}

View File

@ -0,0 +1,17 @@
package org.springframework.security.ui.preauth.x509;
import java.security.cert.X509Certificate;
/**
* Obtains the principal from an X509Certificate for use within the framework.
*
* @author Luke Taylor
* @version $Id$
*/
public interface X509PrincipalExtractor {
/**
* Returns the principal (usually a String) for the given certificate.
*/
Object extractPrincipal(X509Certificate cert);
}

View File

@ -0,0 +1,48 @@
package org.springframework.security.ui.preauth.x509;
import org.springframework.security.providers.x509.X509TestUtils;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.BadCredentialsException;
import org.junit.Test;
import org.junit.Before;
import static junit.framework.Assert.*;
/**
* @author Luke Taylor
* @version $Id$
*/
public class SubjectDnX509PrincipalExtractorTests {
SubjectDnX509PrincipalExtractor extractor;
@Before
public void setUp() {
extractor = new SubjectDnX509PrincipalExtractor();
extractor.setMessageSource(new SpringSecurityMessageSource());
}
@Test(expected = IllegalArgumentException.class)
public void invalidRegexFails() throws Exception {
extractor.setSubjectDnRegex("CN=(.*?,"); // missing closing bracket on group
}
@Test
public void defaultCNPatternReturnsExcpectedPrincipal() throws Exception {
Object principal = extractor.extractPrincipal(X509TestUtils.buildTestCertificate());
assertEquals("Luke Taylor", principal);
}
@Test
public void matchOnEmailReturnsExpectedPrincipal() throws Exception {
extractor.setSubjectDnRegex("emailAddress=(.*?),");
Object principal = extractor.extractPrincipal(X509TestUtils.buildTestCertificate());
assertEquals("luke@monkeymachine", principal);
}
@Test(expected = BadCredentialsException.class)
public void matchOnShoeSizeThrowsBadCredentials() throws Exception {
extractor.setSubjectDnRegex("shoeSize=(.*?),");
extractor.extractPrincipal(X509TestUtils.buildTestCertificate());
}
}