如何在Spring Boot中将SAML令牌添加到CXF客户端请求

如何在Spring Boot中将SAML令牌添加到CXF客户端请求,spring,web-services,spring-boot,cxf,Spring,Web Services,Spring Boot,Cxf,我们正在Spring Boot中构建一个CXF客户端。用于对SOAP服务器进行身份验证/授权的SAML令牌在每个请求中都从外部身份验证代理以自定义HTTP头提供给我们的应用程序。因此,我需要一种方法将提供的令牌添加到每个传出的CXF请求中 我知道我可能会为此注册一个定制的CXF out拦截器。但是, 我如何在Spring Boot中注册拦截器 如果不使用拦截器,替代方案是什么 目前,Spring配置如下所示: @Configuration public class MyConfig {

我们正在Spring Boot中构建一个CXF客户端。用于对SOAP服务器进行身份验证/授权的SAML令牌在每个请求中都从外部身份验证代理以自定义HTTP头提供给我们的应用程序。因此,我需要一种方法将提供的令牌添加到每个传出的CXF请求中

我知道我可能会为此注册一个定制的CXF out拦截器。但是,

  • 我如何在Spring Boot中注册拦截器
  • 如果不使用拦截器,替代方案是什么
目前,Spring配置如下所示:

@Configuration
public class MyConfig {

  @Bean
  public PartnerServicePortType partnerServicePortType() {
    PartnerServicePortType partnerServicePortType = new PartnerServiceV0().getPartnerService();
PartnerServiceV0
是使用Maven从服务的WSDL生成的。)


在上面的config类中,我们目前没有声明/配置CXF总线bean。

一个可能的解决方案是:

@Configuration
public class MyConfig {

  @Bean
  public PartnerServicePortType partnerServicePortType() {
    PartnerServicePortType service = new PartnerServiceV0().getPartnerService();
    configure(service, path, baseUrl);
    return service;
  }

  private void configureService(BindingProvider bindingProvider, String path, String baseUrl) {
    // maybe try the approach outlined at https://github
    // .com/kprasad99/kp-soap-ws-client/blob/master/src/main/java/com/kp/swasthik/soap/CxfConfig.java#L24
    // as an alternative
    bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, baseUrl + path);

    Endpoint cxfEndpoint = ClientProxy.getClient(bindingProvider).getEndpoint();
    cxfEndpoint.getInInterceptors().add(cxfLoggingInInterceptor);
    cxfEndpoint.getInFaultInterceptors().add(cxfLoggingInInterceptor);
    cxfEndpoint.getOutInterceptors().add(addSamlAssertionInterceptor);
  }
}
拦截器呢

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.binding.soap.SoapHeader;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.XMLObjectBuilder;
import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.soap.wssecurity.Created;
import org.opensaml.soap.wssecurity.Expires;
import org.opensaml.soap.wssecurity.Security;
import org.opensaml.soap.wssecurity.Timestamp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.Element;

import javax.xml.namespace.QName;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;


/**
 * Adding SOAP header with SAML assertion to request.
 */
@Slf4j
@Component
public class AddSamlAssertionInterceptor extends AbstractSoapInterceptor {

  private final SamlAssertionExtractor samlAssertionExtractor;

  @Autowired
  public AddSamlAssertionInterceptor(SamlAssertionExtractor samlAssertionExtractor) {
    super(Phase.POST_LOGICAL);
    this.samlAssertionExtractor = samlAssertionExtractor;
  }

  @Override
  public void handleMessage(SoapMessage message) throws Fault {
    String decodedToken = SamlTokenHolder.getDecodedToken();
    if (StringUtils.isBlank(decodedToken)) {
      log.trace("Not adding SOAP header with SAML assertion because SAML token is blank.");
    } else {
      log.trace("Got decoded SAML token: {}", decodedToken);
      log.trace("Adding SOAP header with SAML assertion to request.");
      SoapHeader header = createSoapHeaderWithSamlAssertionFrom(decodedToken);
      message.getHeaders().add(header);
    }
  }

  private SoapHeader createSoapHeaderWithSamlAssertionFrom(String decodedToken) {
    Assertion assertion = samlAssertionExtractor.extractAssertion(decodedToken);

    Security security = createNewSecurityObject();
    security.getUnknownXMLObjects().add(createTimestampElementFrom(assertion));
    security.getUnknownXMLObjects().add(assertion);

    log.trace("Creating new SOAP header with WS-Security element for '{}'.",
      assertion.getSubject().getNameID().getValue());
    SoapHeader header = new SoapHeader(security.getElementQName(), marshallToDom(security));
    header.setMustUnderstand(config.isMustUnderstandHeader());
    return header;
  }

  @SneakyThrows(MarshallingException.class)
  private Element marshallToDom(Security security) {
    Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(security);
    return marshaller.marshall(security);
  }

  /*
   * SAML requirements documented at https://docs.oasis-open.org/wss/v1.1/wss-v1.1-spec-errata-os-SOAPMessageSecurity
   * .htm#_Toc118717167. Both timestamps must be in UTC and formatted to comply with xsd:dateTime.
   */
  private Timestamp createTimestampElementFrom(Assertion assertion) {
    Timestamp timestamp = (Timestamp) createOpenSamlXmlObject(Timestamp.ELEMENT_NAME);
    Created created = (Created) createOpenSamlXmlObject(Created.ELEMENT_NAME);
    Expires expires = (Expires) createOpenSamlXmlObject(Expires.ELEMENT_NAME);
    // alternative would be to use timestamp from assertion like so assertion.getConditions().getNotBefore()
    created.setValue(ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
    // security semantics should ensure that the expiry date here is the same as the expiry of the SAML assertion
    expires.setValue(assertion.getConditions().getNotOnOrAfter().toString());
    timestamp.setCreated(created);
    timestamp.setExpires(expires);
    return timestamp;
  }

  private Security createNewSecurityObject() {
    return (Security) createOpenSamlXmlObject(Security.ELEMENT_NAME);
  }

  private XMLObject createOpenSamlXmlObject(QName elementName) {
    XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory();
    XMLObjectBuilder<Security> builder = (XMLObjectBuilder<Security>) builderFactory.getBuilder(elementName);
    return builder.buildObject(elementName);
  }
}
导入lombok.rows;
导入lombok.extern.slf4j.slf4j;
导入org.apache.commons.lang3.StringUtils;
导入org.apache.cxf.binding.soap.SoapHeader;
导入org.apache.cxf.binding.soap.SoapMessage;
导入org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
导入org.apache.cxf.interceptor.Fault;
导入org.apache.cxf.phase.phase;
导入org.opensaml.core.xml.XMLObject;
导入org.opensaml.core.xml.XMLObjectBuilder;
导入org.opensaml.core.xml.XMLObjectBuilderFactory;
导入org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
导入org.opensaml.core.xml.io.Marshaller;
导入org.opensaml.core.xml.io.MarshallingException;
导入org.opensaml.saml.saml2.core.Assertion;
导入org.opensaml.soap.wssecurity.Created;
导入org.opensaml.soap.wssecurity.Expires;
导入org.opensaml.soap.wssecurity.Security;
导入org.opensaml.soap.wssecurity.Timestamp;
导入org.springframework.beans.factory.annotation.Autowired;
导入org.springframework.stereotype.Component;
导入org.w3c.dom.Element;
导入javax.xml.namespace.QName;
导入java.time.ZoneOffset;
导入java.time.ZonedDateTime;
导入java.time.format.DateTimeFormatter;
/**
*向请求添加带有SAML断言的SOAP头。
*/
@Slf4j
@组成部分
公共类AddSamlAssertionInterceptor扩展了AbstractSoapInterceptor{
专用最终SamlAssertionExtractor SamlAssertionExtractor;
@自动连线
公共addSamlasertionInterceptor(SamlasertionExtractor SamlasertionExtractor){
超级(阶段后逻辑);
this.samlasertionextractor=samlasertionextractor;
}
@凌驾
public void handleMessage(SoapMessage消息)引发错误{
字符串decodedToken=SamlTokenHolder.getDecodedToken();
if(StringUtils.isBlank(decodedToken)){
trace(“不使用SAML断言添加SOAP头,因为SAML令牌为空。”);
}否则{
trace(“获得解码的SAML令牌:{}”,decodedToken);
trace(“将带有SAML断言的SOAP头添加到请求中”);
SoapHeader header=CreateSOAPBHeaderWithSamlassertionFrom(decodedToken);
message.getHeaders().add(header);
}
}
私有SoapHeader createSoapHeaderWithSamlAssertionFrom(字符串已解码){
断言断言=samlasertionextractor.extractAssertion(decodedToken);
Security Security=createNewSecurityObject();
getUnknownXMLObjects().add(createTimestampElementFrom(断言));
security.getUnknownXMLObjects().add(断言);
trace(“正在为{}创建具有WS-Security元素的新SOAP头”,
断言.getSubject().getNameID().getValue());
SoapHeader header=新的SoapHeader(security.getElementQName(),marshallToDom(security));
setMustUnderstand(config.isMustUnderstandHeader());
返回头;
}
@Skillythrows(MarshallingException.class)
私有元素marshallToDom(安全性){
Marshaller Marshaller=XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(安全性);
返回马歇尔·马歇尔(保安);
}
/*
*SAML需求记录在https://docs.oasis-open.org/wss/v1.1/wss-v1.1-spec-errata-os-SOAPMessageSecurity
*.htm#Toc118717167。两个时间戳都必须使用UTC格式,并且格式必须符合xsd:dateTime。
*/
私有时间戳CreateTimestElementFrom(断言){
Timestamp Timestamp=(Timestamp)createOpenSamlXmlObject(Timestamp.ELEMENT_NAME);
Created Created=(Created)createOpenSamlXmlObject(Created.ELEMENT_NAME);
Expires Expires=(Expires)createOpenSamlXmlObject(Expires.ELEMENT\u NAME);
//另一种方法是使用断言的时间戳,如so assertion.getConditions().getNotBefore()
created.setValue(zoneDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
//安全语义应确保此处的到期日期与SAML断言的到期日期相同
expires.setValue(assertion.getConditions().getNoNorAfter().toString());
timestamp.setCreated(已创建);
timestamp.setExpires(expires);
返回时间戳;
}
私有安全createNewSecurityObject(){
return(Security)createOpenSamlXmlObject(Security.ELEMENT_NAME);
}
私有XMLObject createOpenSamlXmlObject(QName elementName){
XMLObjectBuilderFactory builderFactory=XMLObjectProviderRegistrySupport.getBuilderFactory();
XMLObjectBuilder=(XMLObjectBuilder)builderFactory.getBuilder(elementName);
返回builder.buildObject(elementName);
}
}

什么是SamlAssertionExtractor类?我在您的包中找不到它的定义,在一个扩展类中也找不到。它是一个仅以字符串形式返回SAML标记的自定义类吗?它只是提供了从XML片段中提取SAML断言的方法: