From 3fa6b153fed887e13d7f80b3b531892696e7bb36 Mon Sep 17 00:00:00 2001 From: "Jeremy.Stone" Date: Mon, 7 Apr 2014 11:28:13 +0100 Subject: [PATCH 1/3] Add support for Kerberos (as well as Negotiate) authentication. Include support for WWW-Authenticate response. Include support for decrypt. (Dispose gss context and encrypt still to do) --- build.gradle | 4 +- ...KerberosServiceAuthenticationProvider.java | 14 +- .../kerberos/KerberosServiceRequestToken.java | 96 +++++++++++- .../kerberos/KerberosTicketValidator.java | 61 +++++++- .../SunJaasKerberosTicketValidator.java | 32 ++-- ...gKerberosAuthenticationSuccessHandler.java | 66 +++++++++ .../SpnegoAuthenticationProcessingFilter.java | 8 +- ...erosServiceAuthenticationProviderTest.java | 9 +- ...egoAuthenticationProcessingFilterTest.java | 137 +++++++++++++----- 9 files changed, 352 insertions(+), 75 deletions(-) create mode 100644 spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/ResponseHeaderSettingKerberosAuthenticationSuccessHandler.java diff --git a/build.gradle b/build.gradle index 69f41ec..8333394 100644 --- a/build.gradle +++ b/build.gradle @@ -17,9 +17,9 @@ configure(allprojects) { sourceCompatibility=1.6 targetCompatibility=1.6 - ext.junitVersion = '4.10' + ext.junitVersion = '4.11' ext.springSecurityVersion = '3.1.4.RELEASE' - ext.springVersion = '3.2.2.RELEASE' + ext.springVersion = '3.2.3.RELEASE' [compileJava, compileTestJava]*.options*.compilerArgs = ['-Xlint:none'] diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceAuthenticationProvider.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceAuthenticationProvider.java index 575919e..f157080 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceAuthenticationProvider.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceAuthenticationProvider.java @@ -26,6 +26,7 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsChecker; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.extensions.kerberos.KerberosTicketValidator.KerberosTicketValidation; import org.springframework.security.extensions.kerberos.web.SpnegoAuthenticationProcessingFilter; import org.springframework.util.Assert; @@ -82,15 +83,16 @@ public class KerberosServiceAuthenticationProvider implements KerberosServiceRequestToken auth = (KerberosServiceRequestToken) authentication; byte[] token = auth.getToken(); LOG.debug("Try to validate Kerberos Token"); - String username = this.ticketValidator.validateTicket(token); - LOG.debug("Succesfully validated " + username); - UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); + KerberosTicketValidation ticketValidation = this.ticketValidator.validateTicket(token); + LOG.debug("Succesfully validated " + ticketValidation.username()); + UserDetails userDetails = this.userDetailsService.loadUserByUsername(ticketValidation.username()); userDetailsChecker.check(userDetails); additionalAuthenticationChecks(userDetails, auth); - KerberosServiceRequestToken responseAuth = new KerberosServiceRequestToken(userDetails, userDetails.getAuthorities(), token); + KerberosServiceRequestToken responseAuth = new KerberosServiceRequestToken( + userDetails, ticketValidation, + userDetails.getAuthorities(), token); responseAuth.setDetails(authentication.getDetails()); - return responseAuth; - + return responseAuth; } diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java index 907ea0e..88744bb 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java @@ -16,22 +16,32 @@ package org.springframework.security.extensions.kerberos; +import java.io.UnsupportedEncodingException; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.Arrays; import java.util.Collection; +import javax.security.auth.Subject; + +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.MessageProp; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.extensions.kerberos.KerberosTicketValidator.KerberosTicketValidation; import org.springframework.security.extensions.kerberos.web.SpnegoAuthenticationProcessingFilter; /** - * Holds the Kerberos/SPNEGO token for requesting a kerberized service - * and is also the output of KerberosServiceAuthenticationProvider.
+ * Holds the Kerberos/SPNEGO token for requesting a kerberized service and is + * also the output of KerberosServiceAuthenticationProvider.
* Will mostly be created in SpnegoAuthenticationProcessingFilter * and authenticated in KerberosServiceAuthenticationProvider. - * - * This token cannot be re-authenticated, as you will get a Kerberos Reply error. - * + * + * This token cannot be re-authenticated, as you will get a Kerberos Reply + * error. + * * @author Mike Wiesner * @since 1.0 * @version $Id$ @@ -42,19 +52,27 @@ import org.springframework.security.extensions.kerberos.web.SpnegoAuthentication public class KerberosServiceRequestToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 395488921064775014L; + private final byte[] token; + private final Object principal; + private final KerberosTicketValidation ticketValidation; + /** Creates an authenticated token, normally used as an output of an authentication provider. * @param principal the user principal (mostly of instance UserDetails + * @param ticketValidation result of ticket validation * @param authorities the authorities which are granted to the user * @param token the Kerberos/SPNEGO token * @see UserDetails */ - public KerberosServiceRequestToken(Object principal, Collection authorities, byte[] token) { + public KerberosServiceRequestToken(Object principal, + KerberosTicketValidation ticketValidation, + Collection authorities, byte[] token) { super(authorities); this.token = token; this.principal = principal; + this.ticketValidation = ticketValidation; super.setAuthenticated(true); } @@ -68,6 +86,7 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { public KerberosServiceRequestToken(byte[] token) { super(null); this.token = token; + this.ticketValidation = null; this.principal = null; } @@ -113,10 +132,73 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { return this.principal; } - /** Returns the Kerberos token + /** + * Returns the Kerberos token */ public byte[] getToken() { return this.token; } + /** + * Gets the ticket validation + * + * @return the ticket validation (which will be null if the token is + * unauthenticated) + */ + public KerberosTicketValidation getTicketValidation() { + return ticketValidation; + } + + /** + * Determines whether an authenticated token has a response token + * + * @return whether a response token is available + */ + public boolean hasResponseToken() { + return ticketValidation != null + && ticketValidation.responseToken() != null; + } + + /** + * Gets the (Base64) encoded response token assuming one is available. + * + * @return encoded response token + */ + public String getEncodedResponseToken() { + if (!hasResponseToken()) + throw new IllegalStateException( + "Unauthenticated or no response token"); + + try { + return new String(Base64.encode(ticketValidation.responseToken()), + "UTF-8"); + } + catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Unable to encode response token", + e); + } + } + + /** + * Unwraps an encrypted message using the gss context + * + * @param data + * @param offset + * @param length + * @return the decrypted message + * @throws PrivilegedActionException + */ + public byte[] decrypt(final byte[] data, final int offset, final int length) + throws PrivilegedActionException { + + return Subject.doAs(getTicketValidation().subject(), + new PrivilegedExceptionAction() { + public byte[] run() throws Exception { + final GSSContext context = getTicketValidation() + .getGssContext(); + return context.unwrap(data, offset, length, + new MessageProp(true)); + } + }); + } } diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosTicketValidator.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosTicketValidator.java index 9d72d7a..b98a433 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosTicketValidator.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosTicketValidator.java @@ -16,12 +16,19 @@ package org.springframework.security.extensions.kerberos; +import java.util.HashSet; + +import javax.security.auth.Subject; +import javax.security.auth.kerberos.KerberosPrincipal; + +import org.ietf.jgss.GSSContext; import org.springframework.security.authentication.BadCredentialsException; /** * Implementations of this interface are used in - * {@link KerberosServiceAuthenticationProvider} to validate a Kerberos/SPNEGO Ticket. - * + * {@link KerberosServiceAuthenticationProvider} to validate a Kerberos/SPNEGO + * Ticket. + * * @author Mike Wiesner * @since 1.0 * @version $Id$ @@ -29,11 +36,55 @@ import org.springframework.security.authentication.BadCredentialsException; */ public interface KerberosTicketValidator { - /** Validates a Kerberos/SPNEGO ticket. + /** + * Validates a Kerberos/SPNEGO ticket. * @param token Kerbeos/SPNEGO ticket * @return authenticated kerberos principal * @throws BadCredentialsException if the ticket is not valid */ - public String validateTicket(byte[] token) throws BadCredentialsException; + public KerberosTicketValidation validateTicket(byte[] token) + throws BadCredentialsException; -} \ No newline at end of file + /** + * Result of ticket validation + * + * @author Jeremy.Stone + */ + public static class KerberosTicketValidation { + private final String username; + + private final byte[] responseToken; + + private final GSSContext gssContext; + + private final String servicePrincipal; + + KerberosTicketValidation(String username, String servicePrincipal, + byte[] responseToken, GSSContext gssContext) { + this.username = username; + this.servicePrincipal = servicePrincipal; + this.responseToken = responseToken; + this.gssContext = gssContext; + } + + public String username() { + return username; + } + + public byte[] responseToken() { + return responseToken; + } + + public GSSContext getGssContext() { + return gssContext; + } + + public Subject subject() { + final HashSet princs = new HashSet(); + princs.add(new KerberosPrincipal(servicePrincipal)); + + return new Subject(false, princs, new HashSet(), + new HashSet()); + } + } +} diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/SunJaasKerberosTicketValidator.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/SunJaasKerberosTicketValidator.java index a955a8d..125ba2b 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/SunJaasKerberosTicketValidator.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/SunJaasKerberosTicketValidator.java @@ -61,14 +61,15 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, /* (non-Javadoc) * @see org.springframework.security.extensions.kerberos.KerberosTicketValidator#validateTicket(byte[]) */ - public String validateTicket(byte[] token) { - String username = null; + public KerberosTicketValidation validateTicket(byte[] token) { try { - username = Subject.doAs(this.serviceSubject, new KerberosValidateAction(token)); - } catch (PrivilegedActionException e) { - throw new BadCredentialsException("Kerberos validation not succesfull", e); + return Subject.doAs(this.serviceSubject, + new KerberosValidateAction(token)); + } + catch (PrivilegedActionException e) { + throw new BadCredentialsException( + "Kerberos validation not succesful", e); } - return username; } /** The service principal of the application. @@ -139,21 +140,26 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, * @author Mike Wiesner * @since 1.0 */ - private static class KerberosValidateAction implements PrivilegedExceptionAction { + private class KerberosValidateAction implements PrivilegedExceptionAction { byte[] kerberosTicket; public KerberosValidateAction(byte[] kerberosTicket) { this.kerberosTicket = kerberosTicket; } - public String run() throws Exception { - GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null); - context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length); + @Override + public KerberosTicketValidation run() throws Exception { + GSSContext context = GSSManager.getInstance().createContext( + (GSSCredential) null); + byte[] responseToken = context.acceptSecContext(kerberosTicket, 0, + kerberosTicket.length); + String user = context.getSrcName().toString(); - context.dispose(); - return user; - } + // context.dispose(); + return new KerberosTicketValidation(user, servicePrincipal, + responseToken, context); + } } /** diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/ResponseHeaderSettingKerberosAuthenticationSuccessHandler.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/ResponseHeaderSettingKerberosAuthenticationSuccessHandler.java new file mode 100644 index 0000000..23eb16f --- /dev/null +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/ResponseHeaderSettingKerberosAuthenticationSuccessHandler.java @@ -0,0 +1,66 @@ +package org.springframework.security.extensions.kerberos.web; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.extensions.kerberos.KerberosServiceRequestToken; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; + +/** + * Adds a WWW-Authenticate (or other) header to the response following + * successful authentication. + * + * @author Jeremy.Stone + */ +public class ResponseHeaderSettingKerberosAuthenticationSuccessHandler + implements AuthenticationSuccessHandler { + + private static final String NEGOTIATE_PREFIX = "Negotiate "; + + private static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + + private String headerName = WWW_AUTHENTICATE; + + private String headerPrefix = NEGOTIATE_PREFIX; + + /** + * Sets the name of the header to set. By default this is + * {@value #WWW_AUTHENTICATE}. + * + * @param a_headerName + */ + public void setHeaderName(String a_headerName) { + headerName = a_headerName; + } + + /** + * Sets the value of the prefix for the encoded response token value. By + * default this is {@value #NEGOTIATE_PREFIX}. + * + * @param a_headerPrefix + */ + public void setHeaderPrefix(String a_headerPrefix) { + headerPrefix = a_headerPrefix; + } + + /** + * @see org.springframework.security.web.authentication.AuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse, + * org.springframework.security.core.Authentication) + */ + @Override + public void onAuthenticationSuccess(HttpServletRequest request, + HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + KerberosServiceRequestToken auth = (KerberosServiceRequestToken) authentication; + + if (auth.hasResponseToken()) { + response.addHeader(headerName, + headerPrefix + auth.getEncodedResponseToken()); + } + } +} diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/SpnegoAuthenticationProcessingFilter.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/SpnegoAuthenticationProcessingFilter.java index 8a2fcff..2e599c7 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/SpnegoAuthenticationProcessingFilter.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/web/SpnegoAuthenticationProcessingFilter.java @@ -36,7 +36,6 @@ import org.springframework.security.extensions.kerberos.KerberosServiceAuthentic import org.springframework.security.extensions.kerberos.KerberosServiceRequestToken; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; @@ -142,11 +141,14 @@ public class SpnegoAuthenticationProcessingFilter extends GenericFilterBean { String header = request.getHeader("Authorization"); - if ((header != null) && header.startsWith("Negotiate ")) { + if (header != null + && (header.startsWith("Negotiate ") || header + .startsWith("Kerberos "))) { if (logger.isDebugEnabled()) { logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header); } - byte[] base64Token = header.substring(10).getBytes("UTF-8"); + byte[] base64Token = header.substring(header.indexOf(" ") + 1) + .getBytes("UTF-8"); byte[] kerberosTicket = Base64.decode(base64Token); KerberosServiceRequestToken authenticationRequest = new KerberosServiceRequestToken(kerberosTicket); authenticationRequest.setDetails(authenticationDetailsSource.buildDetails(request)); diff --git a/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/KerberosServiceAuthenticationProviderTest.java b/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/KerberosServiceAuthenticationProviderTest.java index 978ca29..716f6b0 100644 --- a/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/KerberosServiceAuthenticationProviderTest.java +++ b/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/KerberosServiceAuthenticationProviderTest.java @@ -35,6 +35,7 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.extensions.kerberos.KerberosTicketValidator.KerberosTicketValidation; /** * Test class for {@link KerberosServiceAuthenticationProvider} @@ -51,7 +52,11 @@ public class KerberosServiceAuthenticationProviderTest { // data private static final byte[] TEST_TOKEN = "TestToken".getBytes(); + private static final byte[] RESPONSE_TOKEN = "ResponseToken".getBytes(); private static final String TEST_USER = "Testuser@SPRINGSOURCE.ORG"; + + private static final KerberosTicketValidation TICKET_VALIDATION = new KerberosTicketValidation(TEST_USER, "XXX", RESPONSE_TOKEN, null); + private static final List AUTHORITY_LIST = AuthorityUtils.createAuthorityList("ROLE_ADMIN"); private static final UserDetails USER_DETAILS = new User(TEST_USER, "empty", true, true, true,true, AUTHORITY_LIST); private static final KerberosServiceRequestToken INPUT_TOKEN = new KerberosServiceRequestToken(TEST_TOKEN); @@ -111,7 +116,7 @@ public class KerberosServiceAuthenticationProviderTest { @Test(expected=UsernameNotFoundException.class) public void testUsernameNotFound() throws Exception { // stubbing - when(ticketValidator.validateTicket(TEST_TOKEN)).thenReturn(TEST_USER); + when(ticketValidator.validateTicket(TEST_TOKEN)).thenReturn(TICKET_VALIDATION); when(userDetailsService.loadUserByUsername(TEST_USER)).thenThrow(new UsernameNotFoundException("")); // testing @@ -130,7 +135,7 @@ public class KerberosServiceAuthenticationProviderTest { private Authentication callProviderAndReturnUser(UserDetails userDetails, Authentication inputToken) { // stubbing - when(ticketValidator.validateTicket(TEST_TOKEN)).thenReturn(TEST_USER); + when(ticketValidator.validateTicket(TEST_TOKEN)).thenReturn(TICKET_VALIDATION); when(userDetailsService.loadUserByUsername(TEST_USER)).thenReturn(userDetails); // testing diff --git a/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/web/SpnegoAuthenticationProcessingFilterTest.java b/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/web/SpnegoAuthenticationProcessingFilterTest.java index 5c0dfc2..3320ba1 100644 --- a/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/web/SpnegoAuthenticationProcessingFilterTest.java +++ b/spring-security-kerberos-core/src/test/java/org/springframework/security/extensions/kerberos/web/SpnegoAuthenticationProcessingFilterTest.java @@ -15,8 +15,13 @@ */ package org.springframework.security.extensions.kerberos.web; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.io.IOException; @@ -39,6 +44,7 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.extensions.kerberos.KerberosServiceRequestToken; +import org.springframework.security.extensions.kerberos.KerberosTicketValidator.KerberosTicketValidation; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; @@ -53,22 +59,40 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS public class SpnegoAuthenticationProcessingFilterTest { private SpnegoAuthenticationProcessingFilter filter; + private AuthenticationManager authenticationManager; + private HttpServletRequest request; + private HttpServletResponse response; + private FilterChain chain; + private AuthenticationSuccessHandler successHandler; + private AuthenticationFailureHandler failureHandler; + private WebAuthenticationDetailsSource detailsSource; // data private static final byte[] TEST_TOKEN = "TestToken".getBytes(); + private static final String TEST_TOKEN_BASE64 = "VGVzdFRva2Vu"; - private static final Authentication AUTHENTICATION = new KerberosServiceRequestToken("test", - AuthorityUtils.createAuthorityList("ROLE_ADMIN"), TEST_TOKEN); + + private static KerberosTicketValidation UNUSED_TICKET_VALIDATION = mock(KerberosTicketValidation.class); + + private static final Authentication AUTHENTICATION = new KerberosServiceRequestToken( + "test", UNUSED_TICKET_VALIDATION, AuthorityUtils.createAuthorityList("ROLE_ADMIN"), + TEST_TOKEN); + private static final String HEADER = "Authorization"; - private static final String TOKEN_PREFIX = "Negotiate "; - private static final BadCredentialsException BCE = new BadCredentialsException(""); + + private static final String TOKEN_PREFIX_NEG = "Negotiate "; + + private static final String TOKEN_PREFIX_KERB = "Kerberos "; + + private static final BadCredentialsException BCE = new BadCredentialsException( + ""); @Before public void before() throws Exception { @@ -85,29 +109,50 @@ public class SpnegoAuthenticationProcessingFilterTest { @Test public void testEverythingWorks() throws Exception { - everythingWorks(); + everythingWorks(TOKEN_PREFIX_NEG); + } + + @Test + public void testEverythingWorks_Kerberos() throws Exception { + everythingWorks(TOKEN_PREFIX_KERB); } @Test public void testEverythingWorksWithHandlers() throws Exception { + everythingWorksWithHandlers(TOKEN_PREFIX_NEG); + } + + @Test + public void testEverythingWorksWithHandlers_Kerberos() throws Exception { + everythingWorksWithHandlers(TOKEN_PREFIX_KERB); + } + + private void everythingWorksWithHandlers(String tokenPrefix) throws Exception { createHandler(); - everythingWorks(); - verify(successHandler).onAuthenticationSuccess(request, response, AUTHENTICATION); - verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class), + everythingWorks(tokenPrefix); + verify(successHandler).onAuthenticationSuccess(request, response, + AUTHENTICATION); + verify(failureHandler, never()).onAuthenticationFailure( + any(HttpServletRequest.class), any(HttpServletResponse.class), any(AuthenticationException.class)); } - private void everythingWorks() throws IOException, ServletException { + private void everythingWorks(String tokenPrefix) throws IOException, + ServletException { // stubbing - when(request.getHeader(HEADER)).thenReturn(TOKEN_PREFIX + TEST_TOKEN_BASE64); - KerberosServiceRequestToken requestToken = new KerberosServiceRequestToken(TEST_TOKEN); + when(request.getHeader(HEADER)).thenReturn( + tokenPrefix + TEST_TOKEN_BASE64); + KerberosServiceRequestToken requestToken = new KerberosServiceRequestToken( + TEST_TOKEN); requestToken.setDetails(detailsSource.buildDetails(request)); - when(authenticationManager.authenticate(requestToken)).thenReturn(AUTHENTICATION); + when(authenticationManager.authenticate(requestToken)).thenReturn( + AUTHENTICATION); // testing filter.doFilter(request, response, chain); verify(chain).doFilter(request, response); - assertEquals(AUTHENTICATION, SecurityContextHolder.getContext().getAuthentication()); + assertEquals(AUTHENTICATION, SecurityContextHolder.getContext() + .getAuthentication()); } @Test @@ -115,16 +160,19 @@ public class SpnegoAuthenticationProcessingFilterTest { filter.doFilter(request, response, chain); // If the header is not present, the filter is not allowed to call // authenticate() - verify(authenticationManager, never()).authenticate(any(Authentication.class)); + verify(authenticationManager, never()).authenticate( + any(Authentication.class)); // chain should go on verify(chain).doFilter(request, response); - assertEquals(null, SecurityContextHolder.getContext().getAuthentication()); + assertEquals(null, SecurityContextHolder.getContext() + .getAuthentication()); } @Test public void testAuthenticationFails() throws Exception { authenticationFails(); - verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + verify(response) + .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } @Test @@ -132,7 +180,8 @@ public class SpnegoAuthenticationProcessingFilterTest { createHandler(); authenticationFails(); verify(failureHandler).onAuthenticationFailure(request, response, BCE); - verify(successHandler, never()).onAuthenticationSuccess(any(HttpServletRequest.class), any(HttpServletResponse.class), + verify(successHandler, never()).onAuthenticationSuccess( + any(HttpServletRequest.class), any(HttpServletResponse.class), any(Authentication.class)); verify(response, never()).setStatus(anyInt()); } @@ -140,25 +189,32 @@ public class SpnegoAuthenticationProcessingFilterTest { @Test public void testAlreadyAuthenticated() throws Exception { try { - Authentication existingAuth = new UsernamePasswordAuthenticationToken("mike", "mike", + Authentication existingAuth = new UsernamePasswordAuthenticationToken( + "mike", "mike", AuthorityUtils.createAuthorityList("ROLE_TEST")); SecurityContextHolder.getContext().setAuthentication(existingAuth); - when(request.getHeader(HEADER)).thenReturn(TOKEN_PREFIX + TEST_TOKEN_BASE64); + when(request.getHeader(HEADER)).thenReturn( + TOKEN_PREFIX_NEG + TEST_TOKEN_BASE64); filter.doFilter(request, response, chain); - verify(authenticationManager, never()).authenticate(any(Authentication.class)); - } finally { + verify(authenticationManager, never()).authenticate( + any(Authentication.class)); + } + finally { SecurityContextHolder.clearContext(); } } @Test - public void testAlreadyAuthenticatedWithNotAuthenticatedToken() throws Exception { + public void testAlreadyAuthenticatedWithNotAuthenticatedToken() + throws Exception { try { // this token is not authenticated yet! - Authentication existingAuth = new UsernamePasswordAuthenticationToken("mike", "mike"); + Authentication existingAuth = new UsernamePasswordAuthenticationToken( + "mike", "mike"); SecurityContextHolder.getContext().setAuthentication(existingAuth); - everythingWorks(); - } finally { + everythingWorks(TOKEN_PREFIX_NEG); + } + finally { SecurityContextHolder.clearContext(); } } @@ -166,38 +222,45 @@ public class SpnegoAuthenticationProcessingFilterTest { @Test public void testAlreadyAuthenticatedWithAnonymousToken() throws Exception { try { - Authentication existingAuth = new AnonymousAuthenticationToken("test", "mike", + Authentication existingAuth = new AnonymousAuthenticationToken( + "test", "mike", AuthorityUtils.createAuthorityList("ROLE_TEST")); SecurityContextHolder.getContext().setAuthentication(existingAuth); - everythingWorks(); - } finally { + everythingWorks(TOKEN_PREFIX_NEG); + } + finally { SecurityContextHolder.clearContext(); } } - + @Test public void testAlreadyAuthenticatedNotActive() throws Exception { try { - Authentication existingAuth = new UsernamePasswordAuthenticationToken("mike", "mike", + Authentication existingAuth = new UsernamePasswordAuthenticationToken( + "mike", "mike", AuthorityUtils.createAuthorityList("ROLE_TEST")); SecurityContextHolder.getContext().setAuthentication(existingAuth); filter.setSkipIfAlreadyAuthenticated(false); - everythingWorks(); - } finally { + everythingWorks(TOKEN_PREFIX_NEG); + } + finally { SecurityContextHolder.clearContext(); } } private void authenticationFails() throws IOException, ServletException { // stubbing - when(request.getHeader(HEADER)).thenReturn(TOKEN_PREFIX + TEST_TOKEN_BASE64); - when(authenticationManager.authenticate(any(Authentication.class))).thenThrow(BCE); + when(request.getHeader(HEADER)).thenReturn( + TOKEN_PREFIX_NEG + TEST_TOKEN_BASE64); + when(authenticationManager.authenticate(any(Authentication.class))) + .thenThrow(BCE); // testing filter.doFilter(request, response, chain); // chain should stop here and it should send back a 500 // future version should call some error handler - verify(chain, never()).doFilter(any(ServletRequest.class), any(ServletResponse.class)); + verify(chain, never()).doFilter(any(ServletRequest.class), + any(ServletResponse.class)); } private void createHandler() { From 81c7b0b2ad5cca1a9eea74f52cfc5e099b68b775 Mon Sep 17 00:00:00 2001 From: "Jeremy.Stone" Date: Mon, 7 Apr 2014 15:39:20 +0100 Subject: [PATCH 2/3] Add encrypt capability and make holding on to gss context optional. --- .../kerberos/KerberosServiceRequestToken.java | 82 +++++++++++++++++-- .../SunJaasKerberosTicketValidator.java | 18 +++- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java index 88744bb..44c2d37 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java @@ -49,6 +49,26 @@ import org.springframework.security.extensions.kerberos.web.SpnegoAuthentication * @see SpnegoAuthenticationProcessingFilter */ +/** + * @author Jeremy.Stone + * + */ +/** + * @author Jeremy.Stone + * + */ +/** + * @author Jeremy.Stone + * + */ +/** + * @author Jeremy.Stone + * + */ +/** + * @author Jeremy.Stone + * + */ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 395488921064775014L; @@ -59,8 +79,11 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { private final KerberosTicketValidation ticketValidation; - /** Creates an authenticated token, normally used as an output of an authentication provider. - * @param principal the user principal (mostly of instance UserDetails + /** + * Creates an authenticated token, normally used as an output of an + * authentication provider. + * @param principal the user principal (mostly of instance + * UserDetails * @param ticketValidation result of ticket validation * @param authorities the authorities which are granted to the user * @param token the Kerberos/SPNEGO token @@ -79,7 +102,7 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { /** * Creates an unauthenticated instance which should then be authenticated by * KerberosServiceAuthenticationProvider/code> - * + * * @param token Kerberos/SPNEGO token * @see KerberosServiceAuthenticationProvider */ @@ -118,14 +141,18 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { return true; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.springframework.security.core.Authentication#getCredentials() */ public Object getCredentials() { return null; } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.springframework.security.core.Authentication#getPrincipal() */ public Object getPrincipal() { @@ -201,4 +228,49 @@ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { } }); } + + /** + * Unwraps an encrypted message using the gss context + * + * @param data + * @return the decrypted message + * @throws PrivilegedActionException + */ + public byte[] decrypt(final byte[] data) throws PrivilegedActionException { + return decrypt(data, 0, data.length); + } + + /** + * Wraps an message using the gss context + * + * @param data + * @param offset + * @param length + * @return the encrypted message + * @throws PrivilegedActionException + */ + public byte[] encrypt(final byte[] data, final int offset, final int length) + throws PrivilegedActionException { + + return Subject.doAs(getTicketValidation().subject(), + new PrivilegedExceptionAction() { + public byte[] run() throws Exception { + final GSSContext context = getTicketValidation() + .getGssContext(); + return context.wrap(data, offset, length, + new MessageProp(true)); + } + }); + } + + /** + * Wraps an message using the gss context + * + * @param data + * @return the encrypted message + * @throws PrivilegedActionException + */ + public byte[] encrypt(final byte[] data) throws PrivilegedActionException { + return encrypt(data, 0, data.length); + } } diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/SunJaasKerberosTicketValidator.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/SunJaasKerberosTicketValidator.java index 125ba2b..42dc076 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/SunJaasKerberosTicketValidator.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/SunJaasKerberosTicketValidator.java @@ -55,6 +55,7 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, private String servicePrincipal; private Resource keyTabLocation; private Subject serviceSubject; + private boolean holdOnToGSSContext; private boolean debug = false; private static final Log LOG = LogFactory.getLog(SunJaasKerberosTicketValidator.class); @@ -106,6 +107,17 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, public void setDebug(boolean debug) { this.debug = debug; } + + /** + * Determines whether to hold on to the {@link GSSContext GSS security context} or + * otherwise {@link GSSContext#dispose() dispose} of it immediately (the default behaviour). + *

Holding on to the GSS context allows decrypt and encrypt operations for subsequent + * interactions with the principal. + * @param holdOnToGSSContext + */ + public void setHoldOnToGSSContext(boolean holdOnToGSSContext) { + this.holdOnToGSSContext = holdOnToGSSContext; + } /* (non-Javadoc) * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() @@ -156,9 +168,11 @@ public class SunJaasKerberosTicketValidator implements KerberosTicketValidator, String user = context.getSrcName().toString(); - // context.dispose(); + if (!holdOnToGSSContext) { + context.dispose(); + } return new KerberosTicketValidation(user, servicePrincipal, - responseToken, context); + responseToken, context); } } From d8646b57626a08325d8cbfbb831658f48909a319 Mon Sep 17 00:00:00 2001 From: Jeremy Stone Date: Tue, 8 Apr 2014 18:27:45 +0100 Subject: [PATCH 3/3] Remove extraneous comments --- .../kerberos/KerberosServiceRequestToken.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java index 44c2d37..6cba876 100644 --- a/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java +++ b/spring-security-kerberos-core/src/main/java/org/springframework/security/extensions/kerberos/KerberosServiceRequestToken.java @@ -48,27 +48,6 @@ import org.springframework.security.extensions.kerberos.web.SpnegoAuthentication * @see KerberosServiceAuthenticationProvider * @see SpnegoAuthenticationProcessingFilter */ - -/** - * @author Jeremy.Stone - * - */ -/** - * @author Jeremy.Stone - * - */ -/** - * @author Jeremy.Stone - * - */ -/** - * @author Jeremy.Stone - * - */ -/** - * @author Jeremy.Stone - * - */ public class KerberosServiceRequestToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 395488921064775014L;