SEC-645: Reimplementation of X509 authentication.
This commit is contained in:
parent
718eddadd7
commit
c7792458b4
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue