فهرست منبع

First stab at X509 authentication provider

Luke Taylor 20 سال پیش
والد
کامیت
ae91b58685

+ 66 - 0
core/src/main/java/org/acegisecurity/providers/x509/X509AuthenticationProvider.java

@@ -0,0 +1,66 @@
+package net.sf.acegisecurity.providers.x509;
+
+import net.sf.acegisecurity.providers.AuthenticationProvider;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthenticationException;
+import net.sf.acegisecurity.UserDetails;
+import org.springframework.beans.factory.InitializingBean;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * @author Luke Taylor
+ */
+public class X509AuthenticationProvider implements AuthenticationProvider,
+    InitializingBean {
+    //~ Static fields/initializers =============================================
+    
+    private static final Log logger = LogFactory.getLog(X509AuthenticationProvider.class);
+
+    //~ Instance fields ========================================================
+    private X509AuthoritiesPopulator x509AuthoritiesPopulator;
+
+    //~ Methods ================================================================
+
+    public void setX509AuthoritiesPopulator(X509AuthoritiesPopulator x509AuthoritiesPopulator) {
+        this.x509AuthoritiesPopulator = x509AuthoritiesPopulator;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if(x509AuthoritiesPopulator == null) {
+            throw new IllegalArgumentException("An X509AuthoritiesPopulator must be set");
+        }
+    }
+
+    /**
+     *
+     * @param authentication
+     * @return
+     * @throws AuthenticationException if the {@link X509AuthoritiesPopulator} rejects the certficate
+     */
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        if (!supports(authentication.getClass())) {
+            return null;
+        }
+
+        if(logger.isDebugEnabled())
+            logger.debug("X509 authentication request: " + authentication);
+
+        X509Certificate clientCertificate = (X509Certificate)authentication.getCredentials();
+
+        // TODO: Cache
+
+
+        // Lookup user details for the given certificate
+        UserDetails userDetails = x509AuthoritiesPopulator.getUserDetails(clientCertificate);
+
+        return new X509AuthenticationToken(userDetails, clientCertificate, userDetails.getAuthorities());
+    }
+
+    public boolean supports(Class authentication) {
+        return X509AuthenticationToken.class.isAssignableFrom(authentication);
+    }
+
+}

+ 57 - 0
core/src/main/java/org/acegisecurity/providers/x509/X509AuthenticationToken.java

@@ -0,0 +1,57 @@
+package net.sf.acegisecurity.providers.x509;
+
+import net.sf.acegisecurity.providers.AbstractAuthenticationToken;
+import net.sf.acegisecurity.GrantedAuthority;
+
+import javax.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+/**
+ * <code>Authentication</code> implementation for X.509 client-certificate authentication.
+ *
+ * @author Luke Taylor
+ * @version $Id$
+ */
+public class X509AuthenticationToken extends AbstractAuthenticationToken {
+    //~ Instance fields ========================================================
+
+    private X509Certificate credentials;
+    private Object principal;
+    private GrantedAuthority[] authorities;
+    private boolean authenticated = false;
+
+    //~ Constructors ===========================================================
+
+    /** Used for an authentication request */
+    public X509AuthenticationToken(X509Certificate credentials) {
+        this.credentials = credentials;
+    }
+
+    public X509AuthenticationToken(Object principal, X509Certificate credentials, GrantedAuthority[] authorities) {
+        this.credentials = credentials;
+        this.principal = principal;
+        this.authorities = authorities;
+    }
+
+    //~ Methods ================================================================
+
+    public void setAuthenticated(boolean isAuthenticated) {
+        this.authenticated = isAuthenticated;
+    }
+
+    public boolean isAuthenticated() {
+        return authenticated;
+    }
+
+    public GrantedAuthority[] getAuthorities() {
+        return authorities;
+    }
+
+    public Object getCredentials() {
+        return credentials;
+    }
+
+    public Object getPrincipal() {
+        return principal;
+    }
+}

+ 76 - 0
core/src/main/java/org/acegisecurity/ui/x509/X509ProcessingFilter.java

@@ -0,0 +1,76 @@
+package net.sf.acegisecurity.ui.x509;
+
+import net.sf.acegisecurity.ui.AbstractProcessingFilter;
+import net.sf.acegisecurity.ui.WebAuthenticationDetails;
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthenticationException;
+import net.sf.acegisecurity.context.ContextHolder;
+import net.sf.acegisecurity.context.security.SecureContext;
+import net.sf.acegisecurity.providers.x509.X509AuthenticationToken;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.security.cert.X509Certificate;
+
+/**
+ * Processes the X.509 certificate submitted by a client - typically
+ * when HTTPS is used with client-authentiction enabled.
+ * <p>
+ * An {@link X509AuthenticationToken} is created with the certificate
+ * as the credentials.
+ * </p>
+ * <p>
+ * The configured authentication manager is expected to supply a
+ * provider which can handle this token (usually an instance of
+ * {@link net.sf.acegisecurity.providers.x509.X509AuthenticationProvider}).
+ * </p>
+ *
+ * <p>
+ * <b>Do not use this class directly.</b> Instead configure
+ * <code>web.xml</code> to use the {@link
+ * net.sf.acegisecurity.util.FilterToBeanProxy}.
+ * </p>
+ *
+ * @author Luke Taylor
+ */
+public class X509ProcessingFilter extends AbstractProcessingFilter {
+
+    public String getDefaultFilterProcessesUrl() {
+        return "/*";
+    }
+
+    /**
+     * X.509 authentication doesn't have a specific login URL, so the default implementation
+     * using <code>endsWith</code> isn't adequate.
+     *
+     */
+    protected boolean requiresAuthentication(HttpServletRequest request,
+        HttpServletResponse response) {
+        return true; // for the time being. Should probably do a pattern match on the URL
+    }
+
+    /**
+     *
+     * @param request the request containing the client certificate
+     * @return
+     * @throws AuthenticationException if the authentication manager rejects the certificate for some reason.
+     */
+    public Authentication attemptAuthentication(HttpServletRequest request) throws AuthenticationException {
+        X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+
+        X509Certificate clientCertificate = null;
+
+        if(certs != null && certs.length > 0) {
+            clientCertificate = certs[0];
+        } else {
+            logger.warn("No client certificate found in Request.");
+        }
+        // TODO: warning is probably superfluous, as it may get called when a non-protected URL is used and no certificate is present.
+
+        X509AuthenticationToken authRequest = new X509AuthenticationToken(clientCertificate);
+
+        // authRequest.setDetails(new WebAuthenticationDetails(request));
+
+        return this.getAuthenticationManager().authenticate(authRequest);
+    }
+}

+ 63 - 0
core/src/test/java/org/acegisecurity/providers/x509/X509AuthenticationProviderTests.java

@@ -0,0 +1,63 @@
+package net.sf.acegisecurity.providers.x509;
+
+import junit.framework.TestCase;
+import net.sf.acegisecurity.*;
+import net.sf.acegisecurity.providers.dao.User;
+
+import java.security.cert.X509Certificate;
+
+/**
+ * @author Luke Taylor
+ */
+public class X509AuthenticationProviderTests extends TestCase {
+    //~ Constructors ===========================================================
+
+    public X509AuthenticationProviderTests() {
+        super();
+    }
+
+    public X509AuthenticationProviderTests(String arg0) {
+        super(arg0);
+    }
+
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testAuthenticationInvalidCertificate() throws Exception {
+        X509AuthenticationProvider provider = new X509AuthenticationProvider();
+        provider.setX509AuthoritiesPopulator(new MockAuthoritiesPopulator(true));
+        try {
+            provider.authenticate(X509TestUtils.createToken());
+            fail("Should have thrown BadCredentialsException");
+        } catch(BadCredentialsException e) {
+            //ignore
+        }
+    }
+
+
+    //~ Inner Classes ==========================================================
+
+    public static class MockAuthoritiesPopulator implements X509AuthoritiesPopulator {
+        private boolean rejectCertificate;
+
+        public MockAuthoritiesPopulator(boolean rejectCertificate) {
+            this.rejectCertificate = rejectCertificate;
+        }
+
+        public UserDetails getUserDetails(X509Certificate userCertificate) throws AuthenticationException {
+            if(rejectCertificate) {
+                throw new BadCredentialsException("Invalid Certificate");
+            }
+
+            return new User ("user", "password", true, true, true,
+                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl(
+                        "ROLE_B")});
+        }
+    }
+
+
+
+}

+ 32 - 0
core/src/test/java/org/acegisecurity/providers/x509/X509AuthenticationTokenTests.java

@@ -0,0 +1,32 @@
+package net.sf.acegisecurity.providers.x509;
+
+import junit.framework.TestCase;
+
+import java.security.cert.X509Certificate;
+import java.security.cert.CertificateFactory;
+import java.io.ByteArrayInputStream;
+
+/**
+ * @author Luke Taylor
+ */
+public class X509AuthenticationTokenTests extends TestCase {
+
+    public X509AuthenticationTokenTests() {
+    }
+
+    public X509AuthenticationTokenTests(String s) {
+        super(s);
+    }
+
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testAuthenticated() throws Exception {
+        X509AuthenticationToken token = X509TestUtils.createToken();
+        assertTrue(!token.isAuthenticated());
+        token.setAuthenticated(true);
+        assertTrue(token.isAuthenticated());
+    }
+}
+

+ 87 - 0
core/src/test/java/org/acegisecurity/providers/x509/X509TestUtils.java

@@ -0,0 +1,87 @@
+package net.sf.acegisecurity.providers.x509;
+
+import java.security.cert.X509Certificate;
+import java.security.cert.CertificateFactory;
+import java.io.ByteArrayInputStream;
+
+/**
+ * @author Luke Taylor
+ */
+public class X509TestUtils {
+
+    public static X509AuthenticationToken createToken() throws Exception {
+        return new X509AuthenticationToken(buildTestCertificate());
+    }
+    /**
+      * Builds an X.509 certificate. In human-readable form it is:
+      * <pre>
+      * Certificate:
+      *   Data:
+      *      Version: 3 (0x2)
+      *      Serial Number: 1 (0x1)
+      *      Signature Algorithm: sha1WithRSAEncryption
+      *      Issuer: CN=Monkey Machine CA, C=UK, ST=Scotland, L=Glasgow,
+      *          O=monkeymachine.co.uk/emailAddress=ca@monkeymachine.co.uk
+      *      Validity
+      *          Not Before: Mar  6 23:28:22 2005 GMT
+      *          Not After : Mar  6 23:28:22 2006 GMT
+      *      Subject: C=UK, ST=Scotland, L=Glasgow, O=Monkey Machine Ltd,
+      *          OU=Open Source Development Lab., CN=Luke Taylor/emailAddress=luke@monkeymachine
+      *      Subject Public Key Info:
+      *          Public Key Algorithm: rsaEncryption
+      *          RSA Public Key: (512 bit)
+      *              [omitted]
+      *      X509v3 extensions:
+      *          X509v3 Basic Constraints:
+      *          CA:FALSE
+      *          Netscape Cert Type:
+      *          SSL Client
+      *          X509v3 Key Usage:
+      *          Digital Signature, Non Repudiation, Key Encipherment
+      *          X509v3 Subject Key Identifier:
+      *          6E:E6:5B:57:33:CF:0E:2F:15:C2:F4:DF:EC:14:BE:FB:CF:54:56:3C
+      *          X509v3 Authority Key Identifier:
+      *          keyid:AB:78:EC:AF:10:1B:8A:9B:1F:C7:B1:25:8F:16:28:F2:17:9A:AD:36
+      *          DirName:/CN=Monkey Machine CA/C=UK/ST=Scotland/L=Glasgow/O=monkeymachine.co.uk/emailAddress=ca@monkeymachine.co.uk
+      *          serial:00
+      *          Netscape CA Revocation Url:
+      *          https://monkeymachine.co.uk/ca-crl.pem
+      *   Signature Algorithm: sha1WithRSAEncryption
+      *             [signature omitted]
+      * </pre>
+      */
+     public static X509Certificate buildTestCertificate() throws Exception
+     {
+         String cert = "-----BEGIN CERTIFICATE-----\n" +
+                 "MIIEQTCCAymgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBkzEaMBgGA1UEAxMRTW9u\n" +
+                 "a2V5IE1hY2hpbmUgQ0ExCzAJBgNVBAYTAlVLMREwDwYDVQQIEwhTY290bGFuZDEQ\n" +
+                 "MA4GA1UEBxMHR2xhc2dvdzEcMBoGA1UEChMTbW9ua2V5bWFjaGluZS5jby51azEl\n" +
+                 "MCMGCSqGSIb3DQEJARYWY2FAbW9ua2V5bWFjaGluZS5jby51azAeFw0wNTAzMDYy\n" +
+                 "MzI4MjJaFw0wNjAzMDYyMzI4MjJaMIGvMQswCQYDVQQGEwJVSzERMA8GA1UECBMI\n" +
+                 "U2NvdGxhbmQxEDAOBgNVBAcTB0dsYXNnb3cxGzAZBgNVBAoTEk1vbmtleSBNYWNo\n" +
+                 "aW5lIEx0ZDElMCMGA1UECxMcT3BlbiBTb3VyY2UgRGV2ZWxvcG1lbnQgTGFiLjEU\n" +
+                 "MBIGA1UEAxMLTHVrZSBUYXlsb3IxITAfBgkqhkiG9w0BCQEWEmx1a2VAbW9ua2V5\n" +
+                 "bWFjaGluZTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDItxZr07mm65ttYH7RMaVo\n" +
+                 "VeMCq4ptfn+GFFEk4+54OkDuh1CHlk87gEc1jx3ZpQPJRTJx31z3YkiAcP+RDzxr\n" +
+                 "AgMBAAGjggFIMIIBRDAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIHgDALBgNV\n" +
+                 "HQ8EBAMCBeAwHQYDVR0OBBYEFG7mW1czzw4vFcL03+wUvvvPVFY8MIHABgNVHSME\n" +
+                 "gbgwgbWAFKt47K8QG4qbH8exJY8WKPIXmq02oYGZpIGWMIGTMRowGAYDVQQDExFN\n" +
+                 "b25rZXkgTWFjaGluZSBDQTELMAkGA1UEBhMCVUsxETAPBgNVBAgTCFNjb3RsYW5k\n" +
+                 "MRAwDgYDVQQHEwdHbGFzZ293MRwwGgYDVQQKExNtb25rZXltYWNoaW5lLmNvLnVr\n" +
+                 "MSUwIwYJKoZIhvcNAQkBFhZjYUBtb25rZXltYWNoaW5lLmNvLnVrggEAMDUGCWCG\n" +
+                 "SAGG+EIBBAQoFiZodHRwczovL21vbmtleW1hY2hpbmUuY28udWsvY2EtY3JsLnBl\n" +
+                 "bTANBgkqhkiG9w0BAQUFAAOCAQEAZ961bEgm2rOq6QajRLeoljwXDnt0S9BGEWL4\n" +
+                 "PMU2FXDog9aaPwfmZ5fwKaSebwH4HckTp11xwe/D9uBZJQ74Uf80UL9z2eo0GaSR\n" +
+                 "nRB3QPZfRvop0I4oPvwViKt3puLsi9XSSJ1w9yswnIf89iONT7ZyssPg48Bojo8q\n" +
+                 "lcKwXuDRBWciODK/xWhvQbaegGJ1BtXcEHtvNjrUJLwSMDSr+U5oUYdMohG0h1iJ\n" +
+                 "R+JQc49I33o2cTc77wfEWLtVdXAyYY4GSJR6VfgvV40x85ItaNS3HHfT/aXU1x4m\n" +
+                 "W9YQkWlA6t0blGlC+ghTOY1JbgWnEfXMmVgg9a9cWaYQ+NQwqA==\n" +
+                 "-----END CERTIFICATE-----";
+
+         ByteArrayInputStream in = new ByteArrayInputStream(cert.getBytes());
+         CertificateFactory cf = CertificateFactory.getInstance("X.509");
+         return (X509Certificate)cf.generateCertificate(in);
+
+     }
+
+}