2
0
Эх сурвалжийг харах

SEC-645: Reimplementation of X509 authentication.

Luke Taylor 17 жил өмнө
parent
commit
c7792458b4

+ 84 - 0
core/src/main/java/org/springframework/security/ui/preauth/x509/SubjectDnX509PrincipalExtractor.java

@@ -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);
+    }
+}

+ 51 - 0
core/src/main/java/org/springframework/security/ui/preauth/x509/X509PreAuthenticatedProcessingFilter.java

@@ -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;    
+    }
+}

+ 17 - 0
core/src/main/java/org/springframework/security/ui/preauth/x509/X509PrincipalExtractor.java

@@ -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);
+}

+ 48 - 0
core/src/test/java/org/springframework/security/ui/preauth/x509/SubjectDnX509PrincipalExtractorTests.java

@@ -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());
+    }
+}