mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-27 14:22:47 +00:00
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…
x
Reference in New Issue
Block a user