Security Token Service (STS)
Stable • Since 1.5.3
Issue, renew and validate security tokens in context of WS-Trust.
Maven coordinates
Create a new project using quarkus-cxf-services-sts
on code.quarkus.io
or add these coordinates to your existing project:
<dependency>
<groupId>io.quarkiverse.cxf</groupId>
<artifactId>quarkus-cxf-services-sts</artifactId>
</dependency>
Check the User guide and especially its Dependency management section for more information about writing applications with Quarkus CXF. |
Usage
Here are the key parts of a basic WS-Trust scenario:
-
WS-SecurityPolicy - except for defining security requirements, such as transport protocols, encryption and signing, it can also contain an
<IssuedToken>
assertion. It specifies the requirements and constraints for these security tokens that the client must adhere to when accessing the service. -
Security Token Service (STS) - issues, validates, and renews security tokens upon request. It acts as a trusted authority that authenticates clients and issues tokens that assert the client’s identity and permissions.
-
Client - requests a token from the STS to access a web service. It must authenticate itself to the STS and provide details about the kind of token required.
-
Service - relies on the STS to authenticate clients and validate their tokens.
Runnable example
There is an integration test covering WS-Trust in the Quarkus CXF source tree. Let’s walk through it and see how the individual parts are set to work together.
WS-SecurityPolicy
The policy is located in asymmetric-saml2-policy.xml
file.
Its key part is the <IssuedToken>
assertion requiring a SAML 2.0 token:
<sp:IssuedToken
sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<sp:RequestSecurityTokenTemplate>
<t:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0</t:TokenType>
<t:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey</t:KeyType>
</sp:RequestSecurityTokenTemplate>
<wsp:Policy>
<sp:RequireInternalReference />
</wsp:Policy>
<sp:Issuer>
<wsaws:Address>http://localhost:8081/services/sts</wsaws:Address>
<wsaws:Metadata xmlns:wsdli="http://www.w3.org/2006/01/wsdl-instance"
wsdli:wsdlLocation="http://localhost:8081/services/sts?wsdl">
<wsaw:ServiceName xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
xmlns:stsns="http://docs.oasis-open.org/ws-sx/ws-trust/200512/"
EndpointName="UT_Port">stsns:SecurityTokenService</wsaw:ServiceName>
</wsaws:Metadata>
</sp:Issuer>
</sp:IssuedToken>
Security Token Service (STS)
The STS is implemented in Sts.java
:
@WebServiceProvider(serviceName = "SecurityTokenService", portName = "UT_Port", targetNamespace = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/", wsdlLocation = "ws-trust-1.4-service.wsdl")
public class Sts extends SecurityTokenServiceProvider {
public Sts() throws Exception {
super();
StaticSTSProperties props = new StaticSTSProperties();
props.setSignatureCryptoProperties("stsKeystore.properties");
props.setSignatureUsername("sts");
props.setCallbackHandlerClass(StsCallbackHandler.class.getName());
props.setIssuer("SampleSTSIssuer");
List<ServiceMBean> services = new LinkedList<ServiceMBean>();
StaticService service = new StaticService();
final Config config = ConfigProvider.getConfig();
final int port = LaunchMode.current().equals(LaunchMode.TEST) ? config.getValue("quarkus.http.test-port", Integer.class)
: config.getValue("quarkus.http.port", Integer.class);
service.setEndpoints(Arrays.asList(
"http://localhost:" + port + "/services/hello-ws-trust",
"http://localhost:" + port + "/services/hello-ws-trust-actas",
"http://localhost:" + port + "/services/hello-ws-trust-onbehalfof"));
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);
}
}
and configured in application.properties
:
quarkus.cxf.endpoint."/sts".implementor = io.quarkiverse.cxf.it.ws.trust.sts.Sts
quarkus.cxf.endpoint."/sts".logging.enabled = pretty
quarkus.cxf.endpoint."/sts".security.signature.username = sts
quarkus.cxf.endpoint."/sts".security.signature.password = password
quarkus.cxf.endpoint."/sts".security.validate.token = false
quarkus.cxf.endpoint."/sts".security.signature.properties."org.apache.ws.security.crypto.provider" = org.apache.ws.security.components.crypto.Merlin
quarkus.cxf.endpoint."/sts".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.type" = pkcs12
quarkus.cxf.endpoint."/sts".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.password" = password
quarkus.cxf.endpoint."/sts".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.file" = sts.pkcs12
Service
The service is implemented in TrustHelloServiceImpl.java
:
@WebService(portName = "TrustHelloServicePort", serviceName = "TrustHelloService", targetNamespace = "https://quarkiverse.github.io/quarkiverse-docs/quarkus-cxf/test/ws-trust", endpointInterface = "io.quarkiverse.cxf.it.ws.trust.server.TrustHelloService")
public class TrustHelloServiceImpl implements TrustHelloService {
@WebMethod
@Override
public String hello(String person) {
return "Hello " + person + "!";
}
}
The asymmetric-saml2-policy.xml
mentioned above is set in the Service Endpoint Interface TrustHelloService.java
:
@WebService(targetNamespace = "https://quarkiverse.github.io/quarkiverse-docs/quarkus-cxf/test/ws-trust")
@Policy(placement = Policy.Placement.BINDING, uri = "classpath:/asymmetric-saml2-policy.xml")
public interface TrustHelloService {
@WebMethod
@Policies({
@Policy(placement = Policy.Placement.BINDING_OPERATION_INPUT, uri = "classpath:/io-policy.xml"),
@Policy(placement = Policy.Placement.BINDING_OPERATION_OUTPUT, uri = "classpath:/io-policy.xml")
})
String hello(String person);
}
The service endpoint is configured in application.properties
:
quarkus.cxf.endpoint."/hello-ws-trust".implementor = io.quarkiverse.cxf.it.ws.trust.server.TrustHelloServiceImpl
quarkus.cxf.endpoint."/hello-ws-trust".logging.enabled = pretty
quarkus.cxf.endpoint."/hello-ws-trust".security.signature.username = service
quarkus.cxf.endpoint."/hello-ws-trust".security.signature.password = password
quarkus.cxf.endpoint."/hello-ws-trust".security.signature.properties."org.apache.ws.security.crypto.provider" = org.apache.ws.security.components.crypto.Merlin
quarkus.cxf.endpoint."/hello-ws-trust".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.type" = pkcs12
quarkus.cxf.endpoint."/hello-ws-trust".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.password" = password
quarkus.cxf.endpoint."/hello-ws-trust".security.signature.properties."org.apache.ws.security.crypto.merlin.keystore.alias" = service
quarkus.cxf.endpoint."/hello-ws-trust".security.signature.properties."org.apache.ws.security.crypto.merlin.file" = service.pkcs12
quarkus.cxf.endpoint."/hello-ws-trust".security.encryption.properties."org.apache.ws.security.crypto.provider" = org.apache.ws.security.components.crypto.Merlin
quarkus.cxf.endpoint."/hello-ws-trust".security.encryption.properties."org.apache.ws.security.crypto.merlin.keystore.type" = pkcs12
quarkus.cxf.endpoint."/hello-ws-trust".security.encryption.properties."org.apache.ws.security.crypto.merlin.keystore.password" = password
quarkus.cxf.endpoint."/hello-ws-trust".security.encryption.properties."org.apache.ws.security.crypto.merlin.keystore.alias" = service
quarkus.cxf.endpoint."/hello-ws-trust".security.encryption.properties."org.apache.ws.security.crypto.merlin.file" = service.pkcs12
Client
Finally, for the SOAP client to be able to communicate with the service, its STSClient
needs to be configured.
It can be done in application.properties
:
quarkus.cxf.client.hello-ws-trust.security.sts.client.wsdl = http://localhost:${quarkus.http.test-port}/services/sts?wsdl
quarkus.cxf.client.hello-ws-trust.security.sts.client.service-name = {http://docs.oasis-open.org/ws-sx/ws-trust/200512/}SecurityTokenService
quarkus.cxf.client.hello-ws-trust.security.sts.client.endpoint-name = {http://docs.oasis-open.org/ws-sx/ws-trust/200512/}UT_Port
quarkus.cxf.client.hello-ws-trust.security.sts.client.username = client
quarkus.cxf.client.hello-ws-trust.security.sts.client.password = password
quarkus.cxf.client.hello-ws-trust.security.sts.client.encryption.username = sts
quarkus.cxf.client.hello-ws-trust.security.sts.client.encryption.properties."org.apache.ws.security.crypto.provider" = org.apache.ws.security.components.crypto.Merlin
quarkus.cxf.client.hello-ws-trust.security.sts.client.encryption.properties."org.apache.ws.security.crypto.merlin.keystore.type" = pkcs12
quarkus.cxf.client.hello-ws-trust.security.sts.client.encryption.properties."org.apache.ws.security.crypto.merlin.keystore.password" = password
quarkus.cxf.client.hello-ws-trust.security.sts.client.encryption.properties."org.apache.ws.security.crypto.merlin.keystore.alias" = client
quarkus.cxf.client.hello-ws-trust.security.sts.client.encryption.properties."org.apache.ws.security.crypto.merlin.keystore.file" = client.pkcs12
quarkus.cxf.client.hello-ws-trust.security.sts.client.token.username = client
quarkus.cxf.client.hello-ws-trust.security.sts.client.token.properties."org.apache.ws.security.crypto.provider" = org.apache.ws.security.components.crypto.Merlin
quarkus.cxf.client.hello-ws-trust.security.sts.client.token.properties."org.apache.ws.security.crypto.merlin.keystore.type" = pkcs12
quarkus.cxf.client.hello-ws-trust.security.sts.client.token.properties."org.apache.ws.security.crypto.merlin.keystore.password" = password
quarkus.cxf.client.hello-ws-trust.security.sts.client.token.properties."org.apache.ws.security.crypto.merlin.keystore.alias" = client
quarkus.cxf.client.hello-ws-trust.security.sts.client.token.properties."org.apache.ws.security.crypto.merlin.keystore.file" = client.pkcs12
quarkus.cxf.client.hello-ws-trust.security.sts.client.token.usecert = true
The properties for configuring the STS client are provided by the |
Alternatively, the client can be set as a bean reference:
quarkus.cxf.client.hello-ws-trust-bean.security.sts.client = #stsClientBean
In that case, the @Named
bean needs to be produced programmatically, e.g. using @jakarta.enterprise.inject.Produces
:
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Named;
import org.apache.cxf.ws.security.SecurityConstants;
import io.quarkiverse.cxf.ws.security.sts.client.STSClientBean;
public class BeanProducers {
/**
* Create and configure an STSClient for use by the TrustHelloService client.
*/
@Produces
@ApplicationScoped
@Named("stsClientBean")
STSClientBean createSTSClient() {
/*
* We cannot use org.apache.cxf.ws.security.trust.STSClient as a return type of this bean producer method
* because it does not have a no-args constructor. STSClientBean is a subclass of STSClient having one.
*/
STSClientBean stsClient = STSClientBean.create();
stsClient.setWsdlLocation("http://localhost:8081/services/sts?wsdl");
stsClient.setServiceQName(new QName("http://docs.oasis-open.org/ws-sx/ws-trust/200512/", "SecurityTokenService"));
stsClient.setEndpointQName(new QName("http://docs.oasis-open.org/ws-sx/ws-trust/200512/", "UT_Port"));
Map<String, Object> props = stsClient.getProperties();
props.put(SecurityConstants.USERNAME, "client");
props.put(SecurityConstants.PASSWORD, "password");
props.put(SecurityConstants.ENCRYPT_PROPERTIES,
Thread.currentThread().getContextClassLoader().getResource("clientKeystore.properties"));
props.put(SecurityConstants.ENCRYPT_USERNAME, "sts");
props.put(SecurityConstants.STS_TOKEN_USERNAME, "client");
props.put(SecurityConstants.STS_TOKEN_PROPERTIES,
Thread.currentThread().getContextClassLoader().getResource("clientKeystore.properties"));
props.put(SecurityConstants.STS_TOKEN_USE_CERT_FOR_KEYINFO, "true");
return stsClient;
}
}