[[ActAs_WS-Trust_Scenario]] = ActAs WS-Trust Scenario The ActAs feature is used in scenarios that require composite delegation. It is commonly used in multi-tiered systems where an application calls a service on behalf of a logged in user or a service calls another service on behalf of the original caller. ActAs is nothing more than a new sub-element in the RequestSecurityToken (RST). It provides additional information about the original caller when a token is negotiated with the STS. The ActAs element usually takes the form of a token with identity claims such as name, role, and authorization code, for the client to access the service. The ActAs scenario is an extension of <>. In this example the ActAs service calls the ws-service on behalf of a user. There are only a couple of additions to the basic scenario's code. An ActAs web service provider and callback handler have been added. The ActAs web services' WSDL imposes the same security policies as the ws-provider. UsernameTokenCallbackHandler is new. It is a utility that generates the content for the ActAs element. And lastly there are a couple of code additions in the STS to support the ActAs request. == Web service provider This section examines the web service elements from the basic WS-Trust scenario that have been changed to address the needs of the ActAs example. The components are * ActAs web service provider's WSDL * ActAs web service provider's Interface and Implementation classes. * ActAsCallbackHandler class * UsernameTokenCallbackHandler * Crypto properties and keystore files * MANIFEST.MF === Web service provider WSDL The ActAs web service provider's WSDL is a clone of the ws-provider's WSDL. The wsp:Policy section is the same. There are changes to the service endpoint, targetNamespace, portType, binding name, and service. [source,xml] ----   ---- [[web-service-interface]] === Web Service Interface The web service provider interface class, ActAsServiceIface, is a simple web service definition. [source, java] ---- package org.jboss.test.ws.jaxws.samples.wsse.policy.trust.actas;   import javax.jws.WebMethod; import javax.jws.WebService;   @WebService ( targetNamespace = "http://www.jboss.org/jbossws/ws-extensions/actaswssecuritypolicy" ) public interface ActAsServiceIface { @WebMethod String sayHello(); } ---- [[web-service-implementation]] === Web Service Implementation The web service provider implementation class, ActAsServiceImpl, is a simple POJO. It uses the standard WebService annotation to define the service endpoint and two Apache WSS4J annotations, EndpointProperties and EndpointProperty used for configuring the endpoint for the CXF runtime. The WSS4J configuration information provided is for WSS4J's Crypto Merlin implementation. ActAsServiceImpl is calling ServiceImpl acting on behalf of the user. Method setupService performs the requisite configuration setup. [source, java] ---- package org.jboss.test.ws.jaxws.samples.wsse.policy.trust.actas;   import org.apache.cxf.Bus; import org.apache.cxf.BusFactory; import org.apache.cxf.annotations.EndpointProperties; import org.apache.cxf.annotations.EndpointProperty; import org.apache.cxf.ws.security.SecurityConstants; import org.apache.cxf.ws.security.trust.STSClient; import org.jboss.test.ws.jaxws.samples.wsse.policy.trust.service.ServiceIface; import org.jboss.test.ws.jaxws.samples.wsse.policy.trust.shared.WSTrustAppUtils;   import javax.jws.WebService; import javax.xml.namespace.QName; import javax.xml.ws.BindingProvider; import javax.xml.ws.Service; import java.net.MalformedURLException; import java.net.URL; import java.util.Map;   @WebService ( portName = "ActAsServicePort", serviceName = "ActAsService", wsdlLocation = "WEB-INF/wsdl/ActAsService.wsdl", targetNamespace = "http://www.jboss.org/jbossws/ws-extensions/actaswssecuritypolicy", endpointInterface = "org.jboss.test.ws.jaxws.samples.wsse.policy.trust.actas.ActAsServiceIface" )   @EndpointProperties(value = { @EndpointProperty(key = "ws-security.signature.username", value = "myactaskey"), @EndpointProperty(key = "ws-security.signature.properties", value = "actasKeystore.properties"), @EndpointProperty(key = "ws-security.encryption.properties", value = "actasKeystore.properties"), @EndpointProperty(key = "ws-security.callback-handler", value = "org.jboss.test.ws.jaxws.samples.wsse.policy.trust.actas.ActAsCallbackHandler") })   public class ActAsServiceImpl implements ActAsServiceIface { public String sayHello() { try { ServiceIface proxy = setupService(); return "ActAs " + proxy.sayHello(); } catch (MalformedURLException e) { e.printStackTrace(); } return null; }   private ServiceIface setupService()throws MalformedURLException { ServiceIface proxy = null; Bus bus = BusFactory.newInstance().createBus();   try { BusFactory.setThreadDefaultBus(bus);   final String serviceURL = "http://" + WSTrustAppUtils.getServerHost() + ":8080/jaxws-samples-wsse-policy-trust/SecurityService"; final QName serviceName = new QName("http://www.jboss.org/jbossws/ws-extensions/wssecuritypolicy", "SecurityService"); final URL wsdlURL = new URL(serviceURL + "?wsdl"); Service service = Service.create(wsdlURL, serviceName); proxy = (ServiceIface) service.getPort(ServiceIface.class);   Map ctx = ((BindingProvider) proxy).getRequestContext(); ctx.put(SecurityConstants.CALLBACK_HANDLER, new ActAsCallbackHandler());   ctx.put(SecurityConstants.SIGNATURE_PROPERTIES, Thread.currentThread().getContextClassLoader().getResource("actasKeystore.properties" )); ctx.put(SecurityConstants.SIGNATURE_USERNAME, "myactaskey" ); ctx.put(SecurityConstants.ENCRYPT_PROPERTIES, Thread.currentThread().getContextClassLoader().getResource("../../META-INF/clientKeystore.properties" )); ctx.put(SecurityConstants.ENCRYPT_USERNAME, "myservicekey");   STSClient stsClient = new STSClient(bus); Map props = stsClient.getProperties(); props.put(SecurityConstants.USERNAME, "alice"); props.put(SecurityConstants.ENCRYPT_USERNAME, "mystskey"); props.put(SecurityConstants.STS_TOKEN_USERNAME, "myactaskey" ); props.put(SecurityConstants.STS_TOKEN_PROPERTIES, Thread.currentThread().getContextClassLoader().getResource("actasKeystore.properties" )); props.put(SecurityConstants.STS_TOKEN_USE_CERT_FOR_KEYINFO, "true");   ctx.put(SecurityConstants.STS_CLIENT, stsClient);   } finally { bus.shutdown(true); }   return proxy; }   } ---- [[actascallbackhandler]] === ActAsCallbackHandler ActAsCallbackHandler is a callback handler for the WSS4J Crypto API. It is used to obtain the password for the private key in the keystore. This class enables CXF to retrieve the password of the user name to use for the message signature. This class has been revised to return the passwords for this service, myactaskey and the "actas" user, alice. [source, java] ---- package org.jboss.test.ws.jaxws.samples.wsse.policy.trust.actas;   import org.jboss.wsf.stack.cxf.extensions.security.PasswordCallbackHandler; import java.util.HashMap; import java.util.Map;   public class ActAsCallbackHandler extends PasswordCallbackHandler {   public ActAsCallbackHandler() { super(getInitMap()); }   private static Map getInitMap() { Map passwords = new HashMap(); passwords.put("myactaskey", "aspass"); passwords.put("alice", "clarinet"); return passwords; } } ---- [[usernametokencallbackhandler]] === UsernameTokenCallbackHandler The ActAs and OnBeholdOf sub-elements of the RequestSecurityToken are required to be defined as WSSE Username Tokens. This utility generates the properly formated element. [source, java] ---- package org.jboss.test.ws.jaxws.samples.wsse.policy.trust.shared;   import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.message.Message; import org.apache.cxf.ws.security.SecurityConstants; import org.apache.cxf.ws.security.trust.delegation.DelegationCallback; import org.apache.ws.security.WSConstants; import org.apache.ws.security.message.token.UsernameToken; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.Element; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSSerializer;   import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import java.io.IOException; import java.util.Map;   /** * A utility to provide the 3 different input parameter types for jaxws property * "ws-security.sts.token.act-as" and "ws-security.sts.token.on-behalf-of". * This implementation obtains a username and password via the jaxws property * "ws-security.username" and "ws-security.password" respectively, as defined * in SecurityConstants. It creates a wss UsernameToken to be used as the * delegation token. */   public class UsernameTokenCallbackHandler implements CallbackHandler {   public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof DelegationCallback) { DelegationCallback callback = (DelegationCallback) callbacks[i]; Message message = callback.getCurrentMessage();   String username = (String)message.getContextualProperty(SecurityConstants.USERNAME); String password = (String)message.getContextualProperty(SecurityConstants.PASSWORD); if (username != null) { Node contentNode = message.getContent(Node.class); Document doc = null; if (contentNode != null) { doc = contentNode.getOwnerDocument(); } else { doc = DOMUtils.createDocument(); } UsernameToken usernameToken = createWSSEUsernameToken(username,password, doc); callback.setToken(usernameToken.getElement()); } } else { throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback"); } } }   /** * Provide UsernameToken as a string. * @param ctx * @return */ public String getUsernameTokenString(Map ctx){ Document doc = DOMUtils.createDocument(); String result = null; String username = (String)ctx.get(SecurityConstants.USERNAME); String password = (String)ctx.get(SecurityConstants.PASSWORD); if (username != null) { UsernameToken usernameToken = createWSSEUsernameToken(username,password, doc); result = toString(usernameToken.getElement().getFirstChild().getParentNode()); } return result; }   /** * * @param username * @param password * @return */ public String getUsernameTokenString(String username, String password){ Document doc = DOMUtils.createDocument(); String result = null; if (username != null) { UsernameToken usernameToken = createWSSEUsernameToken(username,password, doc); result = toString(usernameToken.getElement().getFirstChild().getParentNode()); } return result; }   /** * Provide UsernameToken as a DOM Element. * @param ctx * @return */ public Element getUsernameTokenElement(Map ctx){ Document doc = DOMUtils.createDocument(); Element result = null; UsernameToken usernameToken = null; String username = (String)ctx.get(SecurityConstants.USERNAME); String password = (String)ctx.get(SecurityConstants.PASSWORD); if (username != null) { usernameToken = createWSSEUsernameToken(username,password, doc); result = usernameToken.getElement(); } return result; }   /** * * @param username * @param password * @return */ public Element getUsernameTokenElement(String username, String password){ Document doc = DOMUtils.createDocument(); Element result = null; UsernameToken usernameToken = null; if (username != null) { usernameToken = createWSSEUsernameToken(username,password, doc); result = usernameToken.getElement(); } return result; }   private UsernameToken createWSSEUsernameToken(String username, String password, Document doc) {   UsernameToken usernameToken = new UsernameToken(true, doc, (password == null)? null: WSConstants.PASSWORD_TEXT); usernameToken.setName(username); usernameToken.addWSUNamespace(); usernameToken.addWSSENamespace(); usernameToken.setID("id-" + username);   if (password != null){ usernameToken.setPassword(password); }   return usernameToken; }     private String toString(Node node) { String str = null;   if (node != null) { DOMImplementationLS lsImpl = (DOMImplementationLS) node.getOwnerDocument().getImplementation().getFeature("LS", "3.0"); LSSerializer serializer = lsImpl.createLSSerializer(); serializer.getDomConfig().setParameter("xml-declaration", false); //by default its true, so set it to false to get String without xml-declaration str = serializer.writeToString(node); } return str; }   } ---- [[crypto-properties-and-keystore-files]] === Crypto properties and keystore files The ActAs service must provide its own credentials. The requisite properties file, actasKeystore.properties, and keystore, actasstore.jks, were created. .... org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin org.apache.ws.security.crypto.merlin.keystore.type=jks org.apache.ws.security.crypto.merlin.keystore.password=aapass org.apache.ws.security.crypto.merlin.keystore.alias=myactaskey org.apache.ws.security.crypto.merlin.keystore.file=actasstore.jks .... [[manifest.mf]] === MANIFEST.MF When deployed on WildFly this application requires access to the JBossWs and CXF APIs provided in modules org.jboss.ws.cxf.jbossws-cxf-client and org.apache.cxf. The Apache CXF internals, org.apache.cxf.impl, are needed in handling the ActAs and OnBehalfOf extensions. The dependency statement directs the server to provide them at deployment. .... Manifest-Version: 1.0 Ant-Version: Apache Ant 1.8.2 Created-By: 1.7.0_25-b15 (Oracle Corporation) Dependencies: org.jboss.ws.cxf.jbossws-cxf-client, org.apache.cxf.impl .... [[security-token-service]] == Security Token Service This section examines the STS elements from the basic WS-Trust scenario that have been changed to address the needs of the ActAs example. The components are. * STS's implementation class. * STSCallbackHandler class [[sts-implementation-class]] === STS Implementation class The initial description of SampleSTS can be found <>. The declaration of the set of allowed token recipients by address has been extended to accept ActAs addresses and OnBehalfOf addresses. The addresses are specified as reg-ex patterns. The TokenIssueOperation requires class, UsernameTokenValidator be provided in order to validate the contents of the OnBehalfOf claims and class, UsernameTokenDelegationHandler to be provided in order to process the token delegation request of the ActAs on OnBehalfOf user. [source, java] ---- package org.jboss.test.ws.jaxws.samples.wsse.policy.trust.sts;   import java.util.Arrays; import java.util.LinkedList; import java.util.List;   import javax.xml.ws.WebServiceProvider;   import org.apache.cxf.annotations.EndpointProperties; import org.apache.cxf.annotations.EndpointProperty; import org.apache.cxf.interceptor.InInterceptors; import org.apache.cxf.sts.StaticSTSProperties; import org.apache.cxf.sts.operation.TokenIssueOperation; import org.apache.cxf.sts.operation.TokenValidateOperation; import org.apache.cxf.sts.service.ServiceMBean; import org.apache.cxf.sts.service.StaticService; import org.apache.cxf.sts.token.delegation.UsernameTokenDelegationHandler; import org.apache.cxf.sts.token.provider.SAMLTokenProvider; import org.apache.cxf.sts.token.validator.SAMLTokenValidator; import org.apache.cxf.sts.token.validator.UsernameTokenValidator; import org.apache.cxf.ws.security.sts.provider.SecurityTokenServiceProvider;   @WebServiceProvider(serviceName = "SecurityTokenService", portName = "UT_Port", targetNamespace = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/", wsdlLocation = "WEB-INF/wsdl/ws-trust-1.4-service.wsdl") //be sure to have dependency on org.apache.cxf module when on AS7, otherwise Apache CXF annotations are ignored @EndpointProperties(value = { @EndpointProperty(key = "ws-security.signature.username", value = "mystskey"), @EndpointProperty(key = "ws-security.signature.properties", value = "stsKeystore.properties"), @EndpointProperty(key = "ws-security.callback-handler", value = "org.jboss.test.ws.jaxws.samples.wsse.policy.trust.sts.STSCallbackHandler"), @EndpointProperty(key = "ws-security.validate.token", value = "false") //to let the JAAS integration deal with validation through the interceptor below }) @InInterceptors(interceptors = {"org.jboss.wsf.stack.cxf.security.authentication.SubjectCreatingPolicyInterceptor"}) public class SampleSTS extends SecurityTokenServiceProvider { public SampleSTS() throws Exception { super();   StaticSTSProperties props = new StaticSTSProperties(); props.setSignatureCryptoProperties("stsKeystore.properties"); props.setSignatureUsername("mystskey"); props.setCallbackHandlerClass(STSCallbackHandler.class.getName()); props.setIssuer("DoubleItSTSIssuer");   List services = new LinkedList(); StaticService service = new StaticService(); service.setEndpoints(Arrays.asList( "http://localhost:(\\d)*/jaxws-samples-wsse-policy-trust/SecurityService", "http://\\[::1\\]:(\\d)*/jaxws-samples-wsse-policy-trust/SecurityService", "http://\\[0:0:0:0:0:0:0:1\\]:(\\d)*/jaxws-samples-wsse-policy-trust/SecurityService",   "http://localhost:(\\d)*/jaxws-samples-wsse-policy-trust-actas/ActAsService", "http://\\[::1\\]:(\\d)*/jaxws-samples-wsse-policy-trust-actas/ActAsService", "http://\\[0:0:0:0:0:0:0:1\\]:(\\d)*/jaxws-samples-wsse-policy-trust-actas/ActAsService",   "http://localhost:(\\d)*/jaxws-samples-wsse-policy-trust-onbehalfof/OnBehalfOfService", "http://\\[::1\\]:(\\d)*/jaxws-samples-wsse-policy-trust-onbehalfof/OnBehalfOfService", "http://\\[0:0:0:0:0:0:0:1\\]:(\\d)*/jaxws-samples-wsse-policy-trust-onbehalfof/OnBehalfOfService" )); services.add(service);   TokenIssueOperation issueOperation = new TokenIssueOperation(); issueOperation.setServices(services); issueOperation.getTokenProviders().add(new SAMLTokenProvider()); // required for OnBehalfOf issueOperation.getTokenValidators().add(new UsernameTokenValidator()); // added for OnBehalfOf and ActAs issueOperation.getDelegationHandlers().add(new UsernameTokenDelegationHandler()); issueOperation.setStsProperties(props);   TokenValidateOperation validateOperation = new TokenValidateOperation(); validateOperation.getTokenValidators().add(new SAMLTokenValidator()); validateOperation.setStsProperties(props);   this.setIssueOperation(issueOperation); this.setValidateOperation(validateOperation); } } ---- [[stscallbackhandler]] === STSCallbackHandler The user, alice, and corresponding password was required to be added for the ActAs example. [source, java] ---- package org.jboss.test.ws.jaxws.samples.wsse.policy.trust.sts;   import java.util.HashMap; import java.util.Map;   import org.jboss.wsf.stack.cxf.extensions.security.PasswordCallbackHandler;   public class STSCallbackHandler extends PasswordCallbackHandler { public STSCallbackHandler() { super(getInitMap()); }   private static Map getInitMap() { Map passwords = new HashMap(); passwords.put("mystskey", "stskpass"); passwords.put("alice", "clarinet"); return passwords; } } ---- [[web-service-requester]] == Web service requester This section examines the ws-requester elements from the basic WS-Trust scenario that have been changed to address the needs of the ActAs example. The component is * ActAs web service requester implementation class [[web-service-requester-implementation]] === Web service requester Implementation The ActAs ws-requester, the client, uses standard procedures for creating a reference to the web service in the first four lines. To address the endpoint security requirements, the web service's "Request Context" is configured via the BindingProvider. Information needed in the message generation is provided through it. The ActAs user, myactaskey, is declared in this section and UsernameTokenCallbackHandler is used to provide the contents of the ActAs element to the STSClient. In this example a STSClient object is created and provided to the proxy's request context. The alternative is to provide keys tagged with the ".it" suffix as was done in https://docs.jboss.org/author/display/JBWS/WS-Trust+and+STS#WS-TrustandSTS-WebservicerequesterImplementation[the Basic Scenario client]. The use of ActAs is configured through the props map using the SecurityConstants.STS_TOKEN_ACT_AS key. The alternative is to use the STSClient.setActAs method. [source, java] ---- final QName serviceName = new QName("http://www.jboss.org/jbossws/ws-extensions/actaswssecuritypolicy", "ActAsService"); final URL wsdlURL = new URL(serviceURL + "?wsdl"); Service service = Service.create(wsdlURL, serviceName); ActAsServiceIface proxy = (ActAsServiceIface) service.getPort(ActAsServiceIface.class);   Bus bus = BusFactory.newInstance().createBus(); try { BusFactory.setThreadDefaultBus(bus);   Map ctx = proxy.getRequestContext();   ctx.put(SecurityConstants.CALLBACK_HANDLER, new ClientCallbackHandler()); ctx.put(SecurityConstants.ENCRYPT_PROPERTIES, Thread.currentThread().getContextClassLoader().getResource( "META-INF/clientKeystore.properties")); ctx.put(SecurityConstants.ENCRYPT_USERNAME, "myactaskey"); ctx.put(SecurityConstants.SIGNATURE_PROPERTIES, Thread.currentThread().getContextClassLoader().getResource( "META-INF/clientKeystore.properties")); ctx.put(SecurityConstants.SIGNATURE_USERNAME, "myclientkey");   // Generate the ActAs element contents and pass to the STSClient as a string UsernameTokenCallbackHandler ch = new UsernameTokenCallbackHandler(); String str = ch.getUsernameTokenString("alice","clarinet"); ctx.put(SecurityConstants.STS_TOKEN_ACT_AS, str);   STSClient stsClient = new STSClient(bus); Map props = stsClient.getProperties(); props.put(SecurityConstants.USERNAME, "bob"); props.put(SecurityConstants.CALLBACK_HANDLER, new ClientCallbackHandler()); props.put(SecurityConstants.ENCRYPT_PROPERTIES, Thread.currentThread().getContextClassLoader().getResource( "META-INF/clientKeystore.properties")); props.put(SecurityConstants.ENCRYPT_USERNAME, "mystskey"); props.put(SecurityConstants.STS_TOKEN_USERNAME, "myclientkey"); props.put(SecurityConstants.STS_TOKEN_PROPERTIES, Thread.currentThread().getContextClassLoader().getResource( "META-INF/clientKeystore.properties")); props.put(SecurityConstants.STS_TOKEN_USE_CERT_FOR_KEYINFO, "true");   ctx.put(SecurityConstants.STS_CLIENT, stsClient); } finally { bus.shutdown(true); } proxy.sayHello(); ----