First stab at X509 authentication provider
This commit is contained in:
parent
da3801b914
commit
ae91b58685
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue