Repository: cxf-fediz Updated Branches: refs/heads/master 3df6b1445 -> e06a6a532
Adding support for authenticating to the IdP with a Kerberos ticket Project: http://git-wip-us.apache.org/repos/asf/cxf-fediz/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf-fediz/commit/e06a6a53 Tree: http://git-wip-us.apache.org/repos/asf/cxf-fediz/tree/e06a6a53 Diff: http://git-wip-us.apache.org/repos/asf/cxf-fediz/diff/e06a6a53 Branch: refs/heads/master Commit: e06a6a532adf86e01e1c6cb37c71ee409d5fb659 Parents: 3df6b14 Author: Colm O hEigeartaigh <[email protected]> Authored: Tue Aug 26 16:58:04 2014 +0100 Committer: Colm O hEigeartaigh <[email protected]> Committed: Tue Aug 26 16:58:04 2014 +0100 ---------------------------------------------------------------------- .../service/idp/STSAuthenticationProvider.java | 115 +++++- .../KerberosAuthenticationProcessingFilter.java | 199 ++++++++++ .../idp/kerberos/KerberosEntryPoint.java | 70 ++++ .../kerberos/KerberosServiceRequestToken.java | 139 +++++++ .../idp/kerberos/KerberosTokenValidator.java | 175 +++++++++ .../src/main/resources/restContextKerberos.xml | 167 ++++++++ .../idp/src/main/webapp/WEB-INF/kerberos.jaas | 8 + .../webapp/WEB-INF/security-config-kerberos.xml | 80 ++++ .../fediz/integrationtests/KerberosTest.java | 388 +++++++++++++++++++ .../tomcat7/src/test/resources/kerberos.jaas | 8 + 10 files changed, 1336 insertions(+), 13 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e06a6a53/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/STSAuthenticationProvider.java ---------------------------------------------------------------------- diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/STSAuthenticationProvider.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/STSAuthenticationProvider.java index 3946fd0..d604281 100644 --- a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/STSAuthenticationProvider.java +++ b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/STSAuthenticationProvider.java @@ -19,26 +19,39 @@ package org.apache.cxf.fediz.service.idp; import java.net.URI; +import java.security.Principal; +import java.security.PrivilegedActionException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.security.auth.login.LoginException; import javax.xml.namespace.QName; import org.w3c.dom.Element; + import org.apache.cxf.Bus; import org.apache.cxf.BusFactory; //import org.apache.cxf.endpoint.Client; import org.apache.cxf.fediz.core.Claim; import org.apache.cxf.fediz.core.ClaimTypes; +import org.apache.cxf.fediz.service.idp.kerberos.KerberosServiceRequestToken; +import org.apache.cxf.fediz.service.idp.kerberos.KerberosTokenValidator; //import org.apache.cxf.transport.http.HTTPConduit; //import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; import org.apache.cxf.ws.security.SecurityConstants; import org.apache.cxf.ws.security.tokenstore.SecurityToken; +import org.apache.wss4j.common.kerberos.KerberosServiceContext; import org.apache.wss4j.common.saml.SamlAssertionWrapper; import org.apache.wss4j.dom.WSConstants; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; import org.opensaml.xml.XMLObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,12 +99,13 @@ public class STSAuthenticationProvider implements AuthenticationProvider { protected Map<String, Object> properties = new HashMap<String, Object>(); + private KerberosTokenValidator kerberosTokenValidator; + @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { Bus cxfBus = getBus(); - IdpSTSClient sts = new IdpSTSClient(cxfBus); sts.setAddressingNamespace("http://www.w3.org/2005/08/addressing"); if (tokenType != null && tokenType.length() > 0) { @@ -104,8 +118,43 @@ public class STSAuthenticationProvider implements AuthenticationProvider { sts.setServiceQName(new QName(namespace, wsdlService)); sts.setEndpointQName(new QName(namespace, wsdlEndpoint)); - sts.getProperties().put(SecurityConstants.USERNAME, authentication.getName()); - sts.getProperties().put(SecurityConstants.PASSWORD, (String)authentication.getCredentials()); + Principal kerberosPrincipal = null; + if (authentication instanceof KerberosServiceRequestToken) { + + if (kerberosTokenValidator == null) { + LOG.error("KerberosTokenValidator must be configured to support kerberos"); + return null; + } + KerberosServiceContext kerberosContext; + try { + kerberosContext = + kerberosTokenValidator.validate((KerberosServiceRequestToken)authentication); + if (kerberosContext != null) { + GSSCredential delegatedCredential = kerberosContext.getDelegationCredential(); + if (delegatedCredential != null) { + sts.getProperties().put(SecurityConstants.DELEGATED_CREDENTIAL, + delegatedCredential); + sts.getProperties().put(SecurityConstants.KERBEROS_USE_CREDENTIAL_DELEGATION, "true"); + } + kerberosPrincipal = kerberosContext.getPrincipal(); + } + } catch (LoginException ex) { + LOG.info("Failed to authenticate user '" + authentication.getName() + "'", ex); + return null; + } catch (PrivilegedActionException ex) { + LOG.info("Failed to authenticate user '" + authentication.getName() + "'", ex); + return null; + } + + sts.getProperties().put(SecurityConstants.KERBEROS_JAAS_CONTEXT_NAME, + kerberosTokenValidator.getContextName()); + sts.getProperties().put(SecurityConstants.KERBEROS_SPN, + kerberosTokenValidator.getServiceName()); + } else { + sts.getProperties().put(SecurityConstants.USERNAME, authentication.getName()); + sts.getProperties().put(SecurityConstants.PASSWORD, (String)authentication.getCredentials()); + } + sts.getProperties().putAll(properties); if (use200502Namespace) { sts.setNamespace(HTTP_SCHEMAS_XMLSOAP_ORG_WS_2005_02_TRUST); @@ -148,27 +197,59 @@ public class STSAuthenticationProvider implements AuthenticationProvider { //Add IDP_LOGIN role to be able to access resource Idp, TrustedIdp, etc. authorities.add(new SimpleGrantedAuthority("ROLE_IDP_LOGIN")); - UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken( - authentication.getName(), authentication.getCredentials(), authorities); - STSUserDetails details = new STSUserDetails(authentication.getName(), - (String)authentication.getCredentials(), - authorities, - token); - upat.setDetails(details); + if (authentication instanceof KerberosServiceRequestToken) { + KerberosServiceRequestToken ksrt = + new KerberosServiceRequestToken(kerberosPrincipal, authorities, + ((KerberosServiceRequestToken)authentication).getToken()); + + STSUserDetails details = new STSUserDetails(kerberosPrincipal.getName(), + "", + authorities, + token); + ksrt.setDetails(details); + + LOG.debug("[IDP_TOKEN={}] provided for user '{}'", token.getId(), kerberosPrincipal.getName()); + return ksrt; + } else { + UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken( + authentication.getName(), authentication.getCredentials(), authorities); + + STSUserDetails details = new STSUserDetails(authentication.getName(), + (String)authentication.getCredentials(), + authorities, + token); + upat.setDetails(details); + + LOG.debug("[IDP_TOKEN={}] provided for user '{}'", token.getId(), authentication.getName()); + return upat; + } - LOG.debug("[IDP_TOKEN={}] provided for user '{}'", token.getId(), authentication.getName()); - return upat; } catch (Exception ex) { LOG.info("Failed to authenticate user '" + authentication.getName() + "'", ex); return null; } } + + protected GSSContext createGSSContext() throws GSSException { + Oid oid = new Oid("1.2.840.113554.1.2.2"); + + GSSManager gssManager = GSSManager.getInstance(); + + String spn = "[email protected]"; + GSSName gssService = gssManager.createName(spn, null); + + return gssManager.createContext(gssService.canonicalize(oid), + oid, null, GSSContext.DEFAULT_LIFETIME); + + } + @Override public boolean supports(Class<?> authentication) { - return authentication.equals(UsernamePasswordAuthenticationToken.class); + return authentication.equals(UsernamePasswordAuthenticationToken.class) + || authentication.equals(KerberosServiceRequestToken.class); } public String getWsdlLocation() { @@ -338,6 +419,14 @@ public class STSAuthenticationProvider implements AuthenticationProvider { this.use200502Namespace = use200502Namespace; } + public KerberosTokenValidator getKerberosTokenValidator() { + return kerberosTokenValidator; + } + + public void setKerberosTokenValidator(KerberosTokenValidator kerberosTokenValidator) { + this.kerberosTokenValidator = kerberosTokenValidator; + } + //May be uncommented for debugging // private void setTimeout(Client client, Long timeout) { // HTTPConduit conduit = (HTTPConduit) client.getConduit(); http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e06a6a53/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosAuthenticationProcessingFilter.java ---------------------------------------------------------------------- diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosAuthenticationProcessingFilter.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosAuthenticationProcessingFilter.java new file mode 100644 index 0000000..60173f9 --- /dev/null +++ b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosAuthenticationProcessingFilter.java @@ -0,0 +1,199 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cxf.fediz.service.idp.kerberos; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; +import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; +import org.springframework.util.Assert; +import org.springframework.web.filter.GenericFilterBean; +/** + * Parses the SPNEGO authentication Header, which was generated by the browser + * and creates a {@link KerberosServiceRequestToken} out if it. It will then + * call the {@link AuthenticationManager}. + * + * @author Mike Wiesner + * @since 1.0 + * @version $Id$ + * @see KerberosServiceAuthenticationProvider + * @see KerberosEntryPoint + */ +public class KerberosAuthenticationProcessingFilter extends GenericFilterBean { + private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = + new WebAuthenticationDetailsSource(); + private AuthenticationManager authenticationManager; + private AuthenticationSuccessHandler successHandler; + private AuthenticationFailureHandler failureHandler; + private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy(); + private boolean skipIfAlreadyAuthenticated = true; + /* + * (non-Javadoc) + * + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, + * javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + if (skipIfAlreadyAuthenticated) { + Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication(); + if (existingAuth != null && existingAuth.isAuthenticated() + && !(existingAuth instanceof AnonymousAuthenticationToken)) { + chain.doFilter(request, response); + return; + } + } + String header = request.getHeader("Authorization"); + if ((header != null) && header.startsWith("Negotiate ")) { + if (logger.isDebugEnabled()) { + logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header); + } + byte[] base64Token = header.substring(10).getBytes("UTF-8"); + byte[] kerberosTicket = Base64.decode(base64Token); + KerberosServiceRequestToken authenticationRequest = new KerberosServiceRequestToken(kerberosTicket); + authenticationRequest.setDetails(authenticationDetailsSource.buildDetails(request)); + Authentication authentication; + try { + authentication = authenticationManager.authenticate(authenticationRequest); + } catch (AuthenticationException e) { + //That shouldn't happen, as it is most likely a wrong + //configuration on the server side + logger.warn("Negotiate Header was invalid: " + header, e); + SecurityContextHolder.clearContext(); + if (failureHandler != null) { + failureHandler.onAuthenticationFailure(request, response, e); + } else { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.flushBuffer(); + } + return; + } + sessionStrategy.onAuthentication(authentication, request, response); + SecurityContextHolder.getContext().setAuthentication(authentication); + if (successHandler != null) { + successHandler.onAuthenticationSuccess(request, response, authentication); + } + } + chain.doFilter(request, response); + } + /** + * The authentication manager for validating the ticket. + * + * @param authenticationManager + */ + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + /** + * This handler is called after a successful authentication. One can add + * additional authentication behavior by setting this.<br /> + * Default is null, which means nothing additional happens + * + * @param successHandler + */ + public void setSuccessHandler(AuthenticationSuccessHandler successHandler) { + this.successHandler = successHandler; + } + /** + * This handler is called after a failure authentication. In most cases you + * only get Kerberos/SPNEGO failures with a wrong server or network + * configurations and not during runtime. If the client encounters an error, + * he will just stop the communication with server and therefore this + * handler will not be called in this case.<br /> + * Default is null, which means that the Filter returns the HTTP 500 code + * + * @param failureHandler + */ + public void setFailureHandler(AuthenticationFailureHandler failureHandler) { + this.failureHandler = failureHandler; + } + /** + * Should Kerberos authentication be skipped if a user is already authenticated + * for this request (e.g. in the HTTP session). + * + * @param skipIfAlreadyAuthenticated default is true + */ + public void setSkipIfAlreadyAuthenticated(boolean skipIfAlreadyAuthenticated) { + this.skipIfAlreadyAuthenticated = skipIfAlreadyAuthenticated; + } + /** + * The session handling strategy which will be invoked immediately after an authentication request is + * successfully processed by the <tt>AuthenticationManager</tt>. Used, for example, to handle changing of the + * session identifier to prevent session fixation attacks. + * + * @param sessionStrategy the implementation to use. If not set a null implementation is + * used. + */ + public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthStrategy) { + this.sessionStrategy = sessionAuthStrategy; + } + public void setAuthenticationDetailsSource( + AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) { + Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required"); + this.authenticationDetailsSource = authenticationDetailsSource; + } + /* + * (non-Javadoc) + * + * @see + * org.springframework.web.filter.GenericFilterBean#afterPropertiesSet() + */ + @Override + public void afterPropertiesSet() throws ServletException { + super.afterPropertiesSet(); + Assert.notNull(this.authenticationManager, "authenticationManager must be specified"); + } +} + + + http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e06a6a53/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosEntryPoint.java ---------------------------------------------------------------------- diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosEntryPoint.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosEntryPoint.java new file mode 100644 index 0000000..457a60e --- /dev/null +++ b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosEntryPoint.java @@ -0,0 +1,70 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * Copyright 2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cxf.fediz.service.idp.kerberos; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +/** +* Sends back a request for a Negotiate Authentication to the browser. +* +* @author Mike Wiesner +* @since 1.0 +* @version $Id$ +* @see KerberosAuthenticationProcessingFilter +*/ +public class KerberosEntryPoint implements AuthenticationEntryPoint { + + private static final Log LOG = LogFactory.getLog(KerberosEntryPoint.class); + + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException ex) throws IOException, ServletException { + if (LOG.isDebugEnabled()) { + LOG.debug("Sending back Negotiate Header for request: " + request.getRequestURL()); + } + response.addHeader("WWW-Authenticate", "Negotiate"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.flushBuffer(); + } + +} + http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e06a6a53/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosServiceRequestToken.java ---------------------------------------------------------------------- diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosServiceRequestToken.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosServiceRequestToken.java new file mode 100644 index 0000000..ecee024 --- /dev/null +++ b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosServiceRequestToken.java @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/* + * Copyright 2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cxf.fediz.service.idp.kerberos; + +import java.util.Arrays; +import java.util.Collection; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +/** + * Holds the Kerberos/SPNEGO token for requesting a kerberized service + * and is also the output of <code>KerberosServiceAuthenticationProvider</code>.<br> + * Will mostly be created in <code>SpnegoAuthenticationProcessingFilter</code> + * and authenticated in <code>KerberosServiceAuthenticationProvider</code>. + * + * This token cannot be re-authenticated, as you will get a Kerberos Reply error. + * + * @author Mike Wiesner + * @since 1.0 + * @version $Id$ + * @see KerberosServiceAuthenticationProvider + * @see KerberosAuthenticationProcessingFilter + */ +public class KerberosServiceRequestToken extends AbstractAuthenticationToken { + private static final long serialVersionUID = 395488921064775014L; + private final byte[] token; + private final Object principal; + + /** Creates an authenticated token, normally used as an output of an authentication provider. + * @param principal the user principal (mostly of instance <code>UserDetails</code> + * @param authorities the authorities which are granted to the user + * @param token the Kerberos/SPNEGO token + * @see UserDetails + */ + public KerberosServiceRequestToken(Object principal, + Collection<? extends GrantedAuthority> authorities, + byte[] token) { + super(authorities); + this.token = token; + this.principal = principal; + super.setAuthenticated(true); + } + + /** + * Creates an unauthenticated instance which should then be authenticated by + * <code>KerberosServiceAuthenticationProvider/code> + * + * @param token Kerberos/SPNEGO token + * @see KerberosServiceAuthenticationProvider + */ + public KerberosServiceRequestToken(byte[] token) { + super(null); + this.token = token; + this.principal = null; + } + + /** + * Calculates hashcode based on the Kerberos token + */ + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + Arrays.hashCode(token); + return result; + } + + /** + * equals() is based only on the Kerberos token + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + KerberosServiceRequestToken other = (KerberosServiceRequestToken) obj; + if (!Arrays.equals(token, other.token)) { + return false; + } + return true; + } + + /* (non-Javadoc) + * @see org.springframework.security.core.Authentication#getCredentials() + */ + public Object getCredentials() { + return null; + } + + /* (non-Javadoc) + * @see org.springframework.security.core.Authentication#getPrincipal() + */ + public Object getPrincipal() { + return this.principal; + } + + /** Returns the Kerberos token + */ + public byte[] getToken() { + return this.token; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e06a6a53/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosTokenValidator.java ---------------------------------------------------------------------- diff --git a/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosTokenValidator.java b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosTokenValidator.java new file mode 100644 index 0000000..d3e65fe --- /dev/null +++ b/services/idp/src/main/java/org/apache/cxf/fediz/service/idp/kerberos/KerberosTokenValidator.java @@ -0,0 +1,175 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cxf.fediz.service.idp.kerberos; + +import java.security.Principal; +import java.security.PrivilegedActionException; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.apache.wss4j.common.kerberos.KerberosServiceContext; +import org.apache.wss4j.common.kerberos.KerberosServiceExceptionAction; + +/** + * Validate a Kerberos Token + */ +public class KerberosTokenValidator { + + private static final org.slf4j.Logger LOG = + org.slf4j.LoggerFactory.getLogger(KerberosTokenValidator.class); + + private String serviceName; + private CallbackHandler callbackHandler; + private String contextName; + private boolean usernameServiceNameForm; + + /** + * Get the JAAS Login context name to use. + * @return the JAAS Login context name to use + */ + public String getContextName() { + return contextName; + } + + /** + * Set the JAAS Login context name to use. + * @param contextName the JAAS Login context name to use + */ + public void setContextName(String contextName) { + this.contextName = contextName; + } + + /** + * Get the CallbackHandler to use with the LoginContext + * @return the CallbackHandler to use with the LoginContext + */ + public CallbackHandler getCallbackHandler() { + return callbackHandler; + } + + /** + * Set the CallbackHandler to use with the LoginContext. It can be null. + * @param callbackHandler the CallbackHandler to use with the LoginContext + */ + public void setCallbackHandler(CallbackHandler callbackHandler) { + this.callbackHandler = callbackHandler; + } + + /** + * The name of the service to use when contacting the KDC. This value can be null, in which + * case it defaults to the current principal name. + * @param serviceName the name of the service to use when contacting the KDC + */ + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + /** + * Get the name of the service to use when contacting the KDC. This value can be null, in which + * case it defaults to the current principal name. + * @return the name of the service to use when contacting the KDC + */ + public String getServiceName() { + return serviceName; + } + + public KerberosServiceContext validate(KerberosServiceRequestToken token) + throws LoginException, PrivilegedActionException { + if (LOG.isDebugEnabled()) { + try { + String jaasAuth = System.getProperty("java.security.auth.login.config"); + String krbConf = System.getProperty("java.security.krb5.conf"); + LOG.debug("KerberosTokenValidator - Using JAAS auth login file: " + jaasAuth); + LOG.debug("KerberosTokenValidator - Using KRB conf file: " + krbConf); + } catch (SecurityException ex) { + LOG.debug(ex.getMessage(), ex); + } + } + + // Get a TGT from the KDC using JAAS + LoginContext loginContext = null; + if (callbackHandler != null) { + loginContext = new LoginContext(getContextName(), callbackHandler); + } else { + loginContext = new LoginContext(getContextName()); + } + loginContext.login(); + if (LOG.isDebugEnabled()) { + LOG.debug("Successfully authenticated to the TGT"); + } + + // Get the service name to use - fall back on the principal + Subject subject = loginContext.getSubject(); + String service = serviceName; + if (service == null) { + Set<Principal> principals = subject.getPrincipals(); + if (principals.isEmpty()) { + LOG.debug("No Client principals found after login"); + return null; + } + service = principals.iterator().next().getName(); + } + + // Validate the ticket + KerberosServiceExceptionAction action = + new KerberosServiceExceptionAction(token.getToken(), service, isUsernameServiceNameForm()); + KerberosServiceContext krbServiceCtx = Subject.doAs(subject, action); + + if (LOG.isDebugEnabled()) { + LOG.debug("Successfully validated a ticket"); + } + + return krbServiceCtx; + } + + /** + * SPN can be configured to be in either <b>"hostbased"</b> or <b>"username"</b> form.<br/> + * - <b>"hostbased"</b> - specifies that the service principal name should be interpreted + * as a "host-based" name as specified in GSS API Rfc, section "4.1: Host-Based Service + * Name Form" - The service name, as it is specified in LDAP/AD, as it is listed in the + * KDC.<br/> + * - <b>"username"</b> - specifies that the service principal name should be interpreted + * as a "username" name as specified in GSS API Rfc, section "4.2: User Name Form" + * This is usually the client username in LDAP/AD used for authentication to the KDC. + * + * <br/><br/>Default is <b>"hostbased"</b>. + * + * @return the isUsernameServiceNameForm + */ + public boolean isUsernameServiceNameForm() { + return usernameServiceNameForm; + } + + /** + * If true - sets the SPN form to "username" + * <br/>If false<b>(default)</b> - the SPN form is "hostbased" + * + * @see KerberosSecurity#retrieveServiceTicket(String, CallbackHandler, String, boolean) + * + * @param isUsernameServiceNameForm the isUsernameServiceNameForm to set + */ + public void setUsernameServiceNameForm(boolean isUsernameServiceNameForm) { + this.usernameServiceNameForm = isUsernameServiceNameForm; + } +} http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e06a6a53/services/idp/src/main/resources/restContextKerberos.xml ---------------------------------------------------------------------- diff --git a/services/idp/src/main/resources/restContextKerberos.xml b/services/idp/src/main/resources/restContextKerberos.xml new file mode 100644 index 0000000..95e7907 --- /dev/null +++ b/services/idp/src/main/resources/restContextKerberos.xml @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:context="http://www.springframework.org/schema/context" + xmlns:jaxrs="http://cxf.apache.org/jaxrs" + xmlns:security="http://www.springframework.org/schema/security" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-3.1.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.0.xsd + http://cxf.apache.org/jaxrs + http://cxf.apache.org/schemas/jaxrs.xsd + http://www.springframework.org/schema/security + http://www.springframework.org/schema/security/spring-security-3.1.xsd"> + +<!-- +<context:component-scan base-package="org.apache.cxf.fediz.service.idp.protocols" /> + + + <bean + class="org.apache.cxf.fediz.service.idp.protocols.TrustedIdpProtocolHandlerImpl" /> + --> + <context:property-placeholder location="classpath:realm.properties"/> + + <bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider"> + <property name="depthProperties"> + <bean id="depthProperties" + class="org.apache.cxf.staxutils.DocumentDepthProperties"> + <property name="innerElementCountThreshold" value="500" /> + </bean> + </property> + <property name="marshallerProperties"> + <map> + <entry key="jaxb.formatted.output"> + <value type="java.lang.Boolean">true</value> + </entry> + </map> + </property> + </bean> + + <bean id="exceptionMapper" + class="org.apache.cxf.fediz.service.idp.rest.RestServiceExceptionMapper" /> + + <bean id="jsonProvider" class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" /> + + <jaxrs:server id="idpService" address="/rs"> + <jaxrs:serviceBeans> + <ref bean="idpServiceImpl" /> + <ref bean="claimServiceImpl" /> + <ref bean="applicationServiceImpl" /> + <ref bean="trustedIdpServiceImpl" /> + <ref bean="entitlementServiceImpl" /> + <ref bean="roleServiceImpl" /> + <ref bean="rootServiceImpl" /> + </jaxrs:serviceBeans> + <jaxrs:providers> + <ref bean="jaxbProvider" /> + <ref bean="jsonProvider" /> + <ref bean="exceptionMapper" /> + </jaxrs:providers> + <jaxrs:extensionMappings> + <entry key="json" value="application/json;charset=UTF-8" /> + <entry key="xml" value="application/xml;charset=UTF-8" /> + </jaxrs:extensionMappings> + </jaxrs:server> + + <bean id="rootServiceImpl" + class="org.apache.cxf.fediz.service.idp.rest.RootServiceImpl" /> + + <bean id="idpServiceImpl" class="org.apache.cxf.fediz.service.idp.rest.IdpServiceImpl" /> + + <bean id="claimServiceImpl" + class="org.apache.cxf.fediz.service.idp.rest.ClaimServiceImpl" /> + + <bean id="applicationServiceImpl" + class="org.apache.cxf.fediz.service.idp.rest.ApplicationServiceImpl" /> + + <bean id="trustedIdpServiceImpl" + class="org.apache.cxf.fediz.service.idp.rest.TrustedIdpServiceImpl" /> + + <bean id="entitlementServiceImpl" + class="org.apache.cxf.fediz.service.idp.rest.EntitlementServiceImpl" /> + + <bean id="roleServiceImpl" + class="org.apache.cxf.fediz.service.idp.rest.RoleServiceImpl" /> + + + <!-- If enabled, you can't access the Service layer within the Spring Webflow --> + <!-- The user has no role during the login phase of WS-Federation --> + <security:global-method-security pre-post-annotations="enabled"/> + + <security:http pattern="/services/rs/**" auto-config="false" use-expressions="true" + entry-point-ref="kerberosEntryPoint"> + <security:custom-filter after="CHANNEL_FILTER" ref="stsPortFilter" /> + <security:custom-filter after="SERVLET_API_SUPPORT_FILTER" ref="entitlementsEnricher" /> + <security:intercept-url pattern="/**" access="isAuthenticated()"/> + <!--<security:http-basic />--> + <security:custom-filter ref="kerberosAuthenticationProcessingFilter" position="BASIC_AUTH_FILTER" /> + </security:http> + + <bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /> + + <bean id="defaultPasswordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder" /> + + <bean id="kerberosEntryPoint" + class="org.apache.cxf.fediz.service.idp.kerberos.KerberosEntryPoint" /> + + <bean id="spnegoAuthenticationProcessingFilter" + class="org.apache.cxf.fediz.service.idp.kerberos.KerberosAuthenticationProcessingFilter"> + <property name="authenticationManager" ref="authenticationManager" /> + </bean> + + <security:authentication-manager alias="authenticationManager"> + <security:authentication-provider> + <!-- <security:password-encoder ref="defaultPasswordEncoder"/>--> + <!-- <security:password-encoder hash="sha-256" base64="true" />--> + <!-- + <security:password-encoder hash="sha-256" base64="true"> + <security:salt-source user-property="username"/> + </security:password-encoder> + --> + <security:user-service properties="classpath:/users.properties" /> + </security:authentication-provider> + <security:authentication-provider ref="stsAuthProvider" /> + </security:authentication-manager> + + <bean id="entitlementsEnricher" class="org.apache.cxf.fediz.service.idp.service.security.GrantedAuthorityEntitlements" /> + + <bean id="stsPortFilter" class="org.apache.cxf.fediz.service.idp.STSPortFilter" /> + + <bean id="kerberosTokenValidator" class="org.apache.cxf.fediz.service.idp.kerberos.KerberosTokenValidator"> + <property name="contextName" value="bob"/> + <property name="serviceName" value="[email protected]"/> + </bean> + + <bean id="stsAuthProvider" class="org.apache.cxf.fediz.service.idp.STSAuthenticationProvider"> + <!--<property name="wsdlLocation" value="https://localhost:0/fediz-idp-sts/${realm.STS_URI}/STSServiceTransportUT?wsdl"/> + <property name="wsdlEndpoint" value="TransportUT_Port"/>--> + <property name="wsdlLocation" value="https://localhost:0/fediz-idp-sts/${realm.STS_URI}/STSServiceTransportKerberos?wsdl"/> + <property name="wsdlEndpoint" value="TransportKerberos_Port"/> + <property name="wsdlService" value="SecurityTokenService"/> + <property name="appliesTo" value="urn:fediz:idp"/> + <property name="tokenType" value="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"/> + <property name="kerberosTokenValidator" ref="kerberosTokenValidator"/> + </bean> + +</beans> + http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e06a6a53/services/idp/src/main/webapp/WEB-INF/kerberos.jaas ---------------------------------------------------------------------- diff --git a/services/idp/src/main/webapp/WEB-INF/kerberos.jaas b/services/idp/src/main/webapp/WEB-INF/kerberos.jaas new file mode 100644 index 0000000..b773cf6 --- /dev/null +++ b/services/idp/src/main/webapp/WEB-INF/kerberos.jaas @@ -0,0 +1,8 @@ + +alice { + com.sun.security.auth.module.Krb5LoginModule required refreshKrb5Config=true useKeyTab=true keyTab="/etc/alice.keytab" principal="alice"; +}; + +bob { + com.sun.security.auth.module.Krb5LoginModule required refreshKrb5Config=true useKeyTab=true storeKey=true keyTab="/etc/bob.keytab" principal="bob/service.ws.apache.org"; +}; http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e06a6a53/services/idp/src/main/webapp/WEB-INF/security-config-kerberos.xml ---------------------------------------------------------------------- diff --git a/services/idp/src/main/webapp/WEB-INF/security-config-kerberos.xml b/services/idp/src/main/webapp/WEB-INF/security-config-kerberos.xml new file mode 100644 index 0000000..32242c1 --- /dev/null +++ b/services/idp/src/main/webapp/WEB-INF/security-config-kerberos.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:security="http://www.springframework.org/schema/security" + xmlns:context="http://www.springframework.org/schema/context" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans-2.0.xsd + http://www.springframework.org/schema/context + http://www.springframework.org/schema/context/spring-context-3.0.xsd + http://www.springframework.org/schema/security + http://www.springframework.org/schema/security/spring-security-3.1.xsd"> + + <context:property-placeholder location="classpath:realm.properties"/> + + <!-- DISABLE in production as it might log confidential information about the user --> + <!-- <security:debug /> --> + + <!-- Configure Spring Security --> + <security:http pattern="/federation/**" auto-config="false" use-expressions="true" + entry-point-ref="kerberosEntryPoint"> + <security:custom-filter after="CHANNEL_FILTER" ref="stsPortFilter" /> + <security:custom-filter after="SERVLET_API_SUPPORT_FILTER" ref="entitlementsEnricher" /> + <security:intercept-url pattern="/FederationMetadata/2007-06/FederationMetadata.xml" access="isAnonymous() or isAuthenticated()" /> + + <!-- MUST be http-basic thus systests run fine --> + <!--<security:http-basic />--> + <!--<security:form-login />--> + <security:custom-filter ref="kerberosAuthenticationProcessingFilter" position="BASIC_AUTH_FILTER" /> + </security:http> + + <bean id="kerberosEntryPoint" + class="org.apache.cxf.fediz.service.idp.kerberos.KerberosEntryPoint" /> + + <bean id="kerberosAuthenticationProcessingFilter" + class="org.apache.cxf.fediz.service.idp.kerberos.KerberosAuthenticationProcessingFilter"> + <property name="authenticationManager" ref="authenticationManager" /> + </bean> + + <security:authentication-manager alias="authenticationManager"> + <security:authentication-provider ref="stsAuthProvider" /> + </security:authentication-manager> + + <bean id="stsPortFilter" class="org.apache.cxf.fediz.service.idp.STSPortFilter" /> + + <bean id="kerberosTokenValidator" class="org.apache.cxf.fediz.service.idp.kerberos.KerberosTokenValidator"> + <property name="contextName" value="bob"/> + <property name="serviceName" value="[email protected]"/> + </bean> + + <bean id="stsAuthProvider" class="org.apache.cxf.fediz.service.idp.STSAuthenticationProvider"> + <!--<property name="wsdlLocation" value="https://localhost:0/fediz-idp-sts/${realm.STS_URI}/STSServiceTransportUT?wsdl"/> + <property name="wsdlEndpoint" value="TransportUT_Port"/> --> + <property name="wsdlLocation" value="https://localhost:0/fediz-idp-sts/${realm.STS_URI}/STSServiceTransportKerberos?wsdl"/> + <property name="wsdlEndpoint" value="TransportKerberos_Port"/> + <property name="wsdlService" value="SecurityTokenService"/> + <property name="appliesTo" value="urn:fediz:idp"/> + <property name="tokenType" value="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0"/> + <property name="kerberosTokenValidator" ref="kerberosTokenValidator"/> + </bean> + +</beans> http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e06a6a53/systests/tomcat7/src/test/java/org/apache/cxf/fediz/integrationtests/KerberosTest.java ---------------------------------------------------------------------- diff --git a/systests/tomcat7/src/test/java/org/apache/cxf/fediz/integrationtests/KerberosTest.java b/systests/tomcat7/src/test/java/org/apache/cxf/fediz/integrationtests/KerberosTest.java new file mode 100644 index 0000000..c205ed8 --- /dev/null +++ b/systests/tomcat7/src/test/java/org/apache/cxf/fediz/integrationtests/KerberosTest.java @@ -0,0 +1,388 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cxf.fediz.integrationtests; + + +import java.io.File; +import java.io.FileInputStream; +import java.security.KeyStore; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.List; + +import javax.net.ssl.SSLContext; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; + +import net.htmlparser.jericho.Element; +import net.htmlparser.jericho.FormField; +import net.htmlparser.jericho.FormFields; +import net.htmlparser.jericho.HTMLElementName; +import net.htmlparser.jericho.Source; + +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleState; +import org.apache.catalina.connector.Connector; +import org.apache.catalina.startup.Tomcat; +import org.apache.cxf.fediz.core.ClaimTypes; +import org.apache.cxf.fediz.tomcat.FederationAuthenticator; +import org.apache.http.Consts; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContextBuilder; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.LaxRedirectStrategy; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.apache.xml.security.utils.Base64; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; + +/** + * A test that sends a Kerberos ticket to the IdP for authentication. The IdP must be configured + * to validate the Kerberos ticket, and in turn get a delegation token to authenticate to the + * STS + retrieve claims etc. + */ [email protected] +public class KerberosTest { + + static String idpHttpsPort; + static String rpHttpsPort; + + private static Tomcat idpServer; + private static Tomcat rpServer; + + @BeforeClass + public static void init() { + System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); + System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true"); + System.setProperty("org.apache.commons.logging.simplelog.log.httpclient.wire", "info"); + System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.commons.httpclient", "info"); + System.setProperty("org.apache.commons.logging.simplelog.log.org.springframework.webflow", "info"); + System.setProperty("org.apache.commons.logging.simplelog.log.org.springframework.security.web", "info"); + System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.cxf.fediz", "info"); + System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.cxf", "info"); + + idpHttpsPort = System.getProperty("idp.https.port"); + Assert.assertNotNull("Property 'idp.https.port' null", idpHttpsPort); + rpHttpsPort = System.getProperty("rp.https.port"); + Assert.assertNotNull("Property 'rp.https.port' null", rpHttpsPort); + + initIdp(); + initRp(); + } + + private static void initIdp() { + try { + idpServer = new Tomcat(); + idpServer.setPort(0); + String currentDir = new File(".").getCanonicalPath(); + idpServer.setBaseDir(currentDir + File.separator + "target"); + + idpServer.getHost().setAppBase("tomcat/idp/webapps"); + idpServer.getHost().setAutoDeploy(true); + idpServer.getHost().setDeployOnStartup(true); + + Connector httpsConnector = new Connector(); + httpsConnector.setPort(Integer.parseInt(idpHttpsPort)); + httpsConnector.setSecure(true); + httpsConnector.setScheme("https"); + //httpsConnector.setAttribute("keyAlias", keyAlias); + httpsConnector.setAttribute("keystorePass", "tompass"); + httpsConnector.setAttribute("keystoreFile", "test-classes/server.jks"); + httpsConnector.setAttribute("truststorePass", "tompass"); + httpsConnector.setAttribute("truststoreFile", "test-classes/server.jks"); + httpsConnector.setAttribute("clientAuth", "want"); + // httpsConnector.setAttribute("clientAuth", "false"); + httpsConnector.setAttribute("sslProtocol", "TLS"); + httpsConnector.setAttribute("SSLEnabled", true); + + idpServer.getService().addConnector(httpsConnector); + + idpServer.addWebapp("/fediz-idp-sts", "fediz-idp-sts"); + idpServer.addWebapp("/fediz-idp", "fediz-idp"); + + idpServer.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void initRp() { + try { + rpServer = new Tomcat(); + rpServer.setPort(0); + String currentDir = new File(".").getCanonicalPath(); + rpServer.setBaseDir(currentDir + File.separator + "target"); + + rpServer.getHost().setAppBase("tomcat/rp/webapps"); + rpServer.getHost().setAutoDeploy(true); + rpServer.getHost().setDeployOnStartup(true); + + Connector httpsConnector = new Connector(); + httpsConnector.setPort(Integer.parseInt(rpHttpsPort)); + httpsConnector.setSecure(true); + httpsConnector.setScheme("https"); + //httpsConnector.setAttribute("keyAlias", keyAlias); + httpsConnector.setAttribute("keystorePass", "tompass"); + httpsConnector.setAttribute("keystoreFile", "test-classes/server.jks"); + httpsConnector.setAttribute("truststorePass", "tompass"); + httpsConnector.setAttribute("truststoreFile", "test-classes/server.jks"); + // httpsConnector.setAttribute("clientAuth", "false"); + httpsConnector.setAttribute("clientAuth", "want"); + httpsConnector.setAttribute("sslProtocol", "TLS"); + httpsConnector.setAttribute("SSLEnabled", true); + + rpServer.getService().addConnector(httpsConnector); + + //Context ctx = + Context cxt = rpServer.addWebapp("/fedizhelloworld", "simpleWebapp"); + FederationAuthenticator fa = new FederationAuthenticator(); + fa.setConfigFile(currentDir + File.separator + "target" + File.separator + + "test-classes" + File.separator + "fediz_config.xml"); + cxt.getPipeline().addValve(fa); + + + rpServer.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @AfterClass + public static void cleanup() { + try { + if (idpServer.getServer() != null + && idpServer.getServer().getState() != LifecycleState.DESTROYED) { + if (idpServer.getServer().getState() != LifecycleState.STOPPED) { + idpServer.stop(); + } + idpServer.destroy(); + } + } catch (Exception e) { + e.printStackTrace(); + } + + try { + if (rpServer.getServer() != null + && rpServer.getServer().getState() != LifecycleState.DESTROYED) { + if (rpServer.getServer().getState() != LifecycleState.STOPPED) { + rpServer.stop(); + } + rpServer.destroy(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String getIdpHttpsPort() { + return idpHttpsPort; + } + + public String getRpHttpsPort() { + return rpHttpsPort; + } + + public String getServletContextName() { + return "fedizhelloworld"; + } + + @org.junit.Test + public void testKerberos() throws Exception { + String url = "https://localhost:" + getRpHttpsPort() + "/fedizhelloworld/secure/fedservlet"; + // Get a Kerberos Ticket + Base64 encode it + String ticket = getEncodedKerberosTicket(); + + String response = sendHttpGet(url, ticket, 200, 200, Integer.parseInt(getIdpHttpsPort())); + + String user = "alice"; + Assert.assertTrue("Principal not " + user, response.indexOf("userPrincipal=" + user) > 0); + Assert.assertTrue("User " + user + " does not have role Admin", response.indexOf("role:Admin=false") > 0); + Assert.assertTrue("User " + user + " does not have role Manager", response.indexOf("role:Manager=false") > 0); + Assert.assertTrue("User " + user + " must have role User", response.indexOf("role:User=true") > 0); + + String claim = ClaimTypes.FIRSTNAME.toString(); + Assert.assertTrue("User " + user + " claim " + claim + " is not 'Alice'", + response.indexOf(claim + "=Alice") > 0); + claim = ClaimTypes.LASTNAME.toString(); + Assert.assertTrue("User " + user + " claim " + claim + " is not 'Smith'", + response.indexOf(claim + "=Smith") > 0); + claim = ClaimTypes.EMAILADDRESS.toString(); + Assert.assertTrue("User " + user + " claim " + claim + " is not '[email protected]'", + response.indexOf(claim + "[email protected]") > 0); + + } + + private String getEncodedKerberosTicket() throws Exception { + + System.setProperty("java.security.auth.login.config", "src/test/resources/kerberos.jaas"); + System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true"); + + Oid kerberos5Oid = new Oid("1.2.840.113554.1.2.2"); + + GSSManager manager = GSSManager.getInstance(); + GSSName serverName = manager.createName("[email protected]", + GSSName.NT_HOSTBASED_SERVICE); + + GSSContext context = manager + .createContext(serverName.canonicalize(kerberos5Oid), kerberos5Oid, + null, GSSContext.DEFAULT_LIFETIME); + + context.requestCredDeleg(true); + + final byte[] token = new byte[0]; + + String contextName = "alice"; + LoginContext lc = new LoginContext(contextName); + lc.login(); + + byte[] ticket = (byte[])Subject.doAs(lc.getSubject(), new CreateServiceTicketAction(context, token)); + return Base64.encode(ticket); + } + + private final class CreateServiceTicketAction implements PrivilegedExceptionAction<byte[]> { + private final GSSContext context; + private final byte[] token; + + private CreateServiceTicketAction(GSSContext context, byte[] token) { + this.context = context; + this.token = token; + } + + public byte[] run() throws GSSException { + return context.initSecContext(token, 0, token.length); + } + } + + public static String sendHttpGet(String url, String ticket, + int returnCodeIDP, int returnCodeRP, int idpPort) + throws Exception { + + CloseableHttpClient httpClient = null; + try { + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + FileInputStream instream = new FileInputStream(new File("./target/test-classes/client.jks")); + try { + trustStore.load(instream, "clientpass".toCharArray()); + } finally { + try { + instream.close(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); + sslContextBuilder.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()); + sslContextBuilder.loadKeyMaterial(trustStore, "clientpass".toCharArray()); + + SSLContext sslContext = sslContextBuilder.build(); + SSLConnectionSocketFactory sslSocketFactory = + new SSLConnectionSocketFactory(sslContext); + + HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); + httpClientBuilder.setSSLSocketFactory(sslSocketFactory); + httpClientBuilder.setRedirectStrategy(new LaxRedirectStrategy()); + + httpClient = httpClientBuilder.build(); + + HttpGet httpget = new HttpGet(url); + httpget.addHeader("Authorization", "Negotiate " + ticket); + + HttpResponse response = httpClient.execute(httpget); + HttpEntity entity = response.getEntity(); + + System.out.println(response.getStatusLine()); + if (entity != null) { + System.out.println("Response content length: " + entity.getContentLength()); + } + Assert.assertTrue("IDP HTTP Response code: " + response.getStatusLine().getStatusCode() + + " [Expected: " + returnCodeIDP + "]", + returnCodeIDP == response.getStatusLine().getStatusCode()); + + if (response.getStatusLine().getStatusCode() != 200) { + return null; + } + + // Redirect to a POST is not supported without user interaction + // http://www.ietf.org/rfc/rfc2616.txt + // If the 301 status code is received in response to a request other + // than GET or HEAD, the user agent MUST NOT automatically redirect the + // request unless it can be confirmed by the user, since this might + // change the conditions under which the request was issued. + + Source source = new Source(EntityUtils.toString(entity)); + List <NameValuePair> nvps = new ArrayList <NameValuePair>(); + FormFields formFields = source.getFormFields(); + + List<Element> forms = source.getAllElements(HTMLElementName.FORM); + Assert.assertEquals("Only one form expected but got " + forms.size(), 1, forms.size()); + String postUrl = forms.get(0).getAttributeValue("action"); + + Assert.assertNotNull("Form field 'wa' not found", formFields.get("wa")); + Assert.assertNotNull("Form field 'wresult' not found", formFields.get("wresult")); + + for (FormField formField : formFields) { + if (formField.getUserValueCount() != 0) { + nvps.add(new BasicNameValuePair(formField.getName(), + formField.getValues().get(0))); + } + } + HttpPost httppost = new HttpPost(postUrl); + httppost.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); + + response = httpClient.execute(httppost); + + entity = response.getEntity(); + System.out.println(response.getStatusLine()); + Assert.assertTrue("RP HTTP Response code: " + response.getStatusLine().getStatusCode() + + " [Expected: " + returnCodeRP + "]", + returnCodeRP == response.getStatusLine().getStatusCode()); + + if (entity != null) { + System.out.println("Response content length: " + entity.getContentLength()); + } + + return EntityUtils.toString(entity); + } finally { + // When HttpClient instance is no longer needed, + // shut down the connection manager to ensure + // immediate deallocation of all system resources + if (httpClient != null) { + httpClient.close(); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/e06a6a53/systests/tomcat7/src/test/resources/kerberos.jaas ---------------------------------------------------------------------- diff --git a/systests/tomcat7/src/test/resources/kerberos.jaas b/systests/tomcat7/src/test/resources/kerberos.jaas new file mode 100644 index 0000000..b773cf6 --- /dev/null +++ b/systests/tomcat7/src/test/resources/kerberos.jaas @@ -0,0 +1,8 @@ + +alice { + com.sun.security.auth.module.Krb5LoginModule required refreshKrb5Config=true useKeyTab=true keyTab="/etc/alice.keytab" principal="alice"; +}; + +bob { + com.sun.security.auth.module.Krb5LoginModule required refreshKrb5Config=true useKeyTab=true storeKey=true keyTab="/etc/bob.keytab" principal="bob/service.ws.apache.org"; +};
