How to retrieve information about certificate in WebServiceTemplate? - java

I want to retrieve Common Name (CN) property from client certificate in SOAP communication. I'm using Spring WebServiceTemplate to create my webservice endpoint. I have already implemented WS mutual authentication following the example.
Is there any solution to obtain certificate details from client request by means of WebServiceTemplate or some other library?

Fortunately, I have managed to figure it out!
Spring WS provides very convenient way to retrieve the X509Certificate.
Normally, You have an endpoint like this:
#Endpoint
public class CountryEndpoint {
private static final String NAMESPACE_URI = "http://spring.io/guides/gs-producing-web-service";
...
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")
#ResponsePayload
public GetCountryResponse getCountry(#RequestPayload GetCountryRequest request) {
//method body here
return response;
}
}
However, Spring allows to add additional parameters the method annotated as #PayloadRoot. It can be a MessageContext instance.
public GetCountryResponse getCountry(#RequestPayload MessageContext context, #RequestPayload GetCountryRequest request)`
Then You will be able to obtain the wsse:Security header as follows:
WebServiceMessage webServiceMessageRequest = context.getRequest();
SaajSoapMessage saajSoapMessage = (SaajSoapMessage) webServiceMessageRequest;
SOAPMessage doc = saajSoapMessage.getSaajMessage();
Element elem = WSSecurityUtil.getSecurityHeader(doc.getSOAPPart(), "");
Now get the right content of BinarySecurityToken tag:
String binarySecurityToken = elem.getElementsByTagName("BinarySecurityToken").item(0).getTextContent();
At the end, you should recreate the X509Certificate by passing binarySecurityToken as its constructor parameter. Later You can extract CN by many different ways for example by means of LDAP utlis.

There is another way.
Create AbstractSoapInterceptor with this body :
private final static QName SECURITY_QNAME = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security", "");
private static CertificateFactory certFactory;
public xxx() throws CertificateException {
super(Phase.PRE_PROTOCOL);
certFactory = CertificateFactory.getInstance("X.509");
}
#SneakyThrows
#Override
public void handleMessage(SoapMessage message) throws Fault {
SoapHeader header = (SoapHeader) message.getHeader(SECURITY_QNAME);
Node binarySignatureTag = ((Element) header.getObject()).getFirstChild();
BinarySecurity token = new X509Security((Element) binarySignatureTag, new BSPEnforcer());
InputStream in = new ByteArrayInputStream(token.getToken());
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(in);
}
Register it in configuration of endpoint :
#Bean
public Endpoint endpoint() throws CertificateException {
EndpointImpl endpoint = new EndpointImpl(springBus(), xxxPortType());
endpoint.setServiceName(xxxService().getServiceName());
endpoint.publish("/xxxx");
endpoint.getInInterceptors().add(new xxx());
return endpoint;
}

Related

OkHttp client not getting custom headers from response

I have an application A that has an endpoint which signs the response and puts the signature in a header. The header looks like:
X-Algorithm: SHA256withRSA
X-Signature: Zca8Myv4......PkH1E25hA=
When I call the application directly I see the headers.
I build application B and it's calling A via a proxy P.
Application B has an OkHttp client which sends the request and reads the response. I have a custom interceptor:
#Slf4j
public class SignatureValidatorInterceptor implements Interceptor {
private final Signature signer;
public SignatureValidatorInterceptor(final PublicKey publicKey) {
this.signer = getSigner(publicKey);
}
private static final String ALGORITHM_HEADER = "x-algorithm";
private static final String SIGNATURE_HEADER = "x-signature";
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
/**
* This interceptor verifies the signature of the response
*/
#Override
public Response intercept(final Interceptor.Chain chain) throws IOException {
final Response response = chain.proceed(chain.request());
final Headers headers = response.headers();
log.info("Received response for url={}} with headers \n{}", response.request().url(), headers);
final byte[] responseBodyBytes = response.peekBody(1000).bytes();
final String algorithmHeader = headers.get(ALGORITHM_HEADER);
final String signatureHeader = headers.get(SIGNATURE_HEADER);
if (StringUtils.isBlank(signatureHeader) || StringUtils.isBlank(algorithmHeader)) {
throw new Exception("No signature or algorithm header on response");
}
this.verifySignature(responseBodyBytes, algorithmHeader, signatureHeader);
return response;
}
private void verifySignature(final byte[] responseBodyBytes, final String algorithmHeader, final String signatureHeader) {
//code to validate signature
}
private Signature getSigner(final PublicKey publicKey) {
//code to create signer
}
}
The debug line logs the headers it receives. I see multiple standard http headers in my logs but I'm missing my custom headers!
I have no clue why. I first thought it was a network thing. But on doing a curl from the machine of B to application A, I see the headers are there. Also I had the proxy log the headers for me, and I can see they passed.
All applications are standard spring-boot java applications. Running on linux VM's.
What am I missing?
Thanks,
Rick

How do you authenticate to Cisco Contact Center Express Identity Service?

I'm building a 3rd party app to authenticate with Contact Center Express. The documentation is necessary, but insufficient to accomplish this. For example,
https://developer.cisco.com/docs/contact-center-express/#!cisco-identity-service-client-sdk-guide/during-agent-login
// Get Access Token for the received Authorization Code
String redirectURI = config.getRedirectUri();
AccessToken token = client.getAccessToken(authCode, redirectURI);
When and where do you redirect the user to Contact Center to authenticate? I observed that Finesse will redirect the user to
https://contactcenter.example.com:8553/ids/v1/oauth/authorize?redirect_uri=https%3A%2F%2Ffinesse.example.com%3A443%2Fdesktop%2Fsso%2Fauthcode&client_id=8a75xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&state=aHR0cHM6Ly92bS1mLWZpbi1hLmRldi5pbi5zcGluc2NpLmNvbS9kZXNrdG9wL2pfaWRlbnRpdHlfY2hlY2s%2FZXJyb3I9dHJ1ZQlhcHBsb2dpbg%3D%3D&response_type=code
But where is it specified to use the identity service (IDS) path /ids/v1/oauth/authorize? And is state a required parameter? And does the IDS SDK handle the callback path /desktop/sso/authcode? I imagine that it doesn't but what are the parameters that will be sent to it? I'm using Spring framework.
Am I to reverse engineer the whole process, or is there additional documentation that I am missing?
Even after I receive an OAuth token, how would I use it to make other REST calls to other Cisco products? The Finesse REST APIs only mention HTTP basic authentication. There is no mention of headers for "Authorization: Bearer" tokens.
https://developer.cisco.com/docs/finesse/#!sign-in-to-finesse/sign-in-to-finesse
I had to reverse engineer it following all the redirects.
#Controller
public class SSOController {
#Autowired
private IdSClientConfigurationImpl config;
#Autowired
private IdSClient client;
#PostMapping("/login")
public String login(#RequestParam(name="user", required=true) String user) {
// redirect the user to the Cisco Contact Center Express Identity Service
String redirectURI = config.getRedirectUri();
String clientId = config.getClientId();
URI uri = UriComponentsBuilder
.fromUriString("https://contact-center-express:8553/ids/v1/oauth/authorize")
.queryParam("redirect_uri", "{redirect_uri}")
.queryParam("client_id", "{client_id}")
// .queryParam("state", "{state}") // base64 encoded
.queryParam("response_type", "code")
.build(redirectURI, clientId);
return "redirect:"+uri.toString();
}
#GetMapping("/idscallback")
public String idscallback(
#RequestParam(name="code", required=true) String code,
#RequestParam(name="state", required=false) String state,
HttpSession session) throws IdSClientException {
// Get Access Token for the received Authorization Code
String redirectURI = config.getRedirectUri();
AccessToken token = client.getAccessToken(code, redirectURI); // why do I need redirectURI when it's already redirected?
String accessTokenString = token.getAccess_token();
session.setAttribute("token", accessTokenString);
// model.addAttribute("token", accessTokenString);
return "redirect:/";
}
And in a bean far, far away...
#Bean
public IdSClientConfigurationImpl config() throws IOException, IdSClientException {
ClassPathResource idsclientResource = new ClassPathResource("idsclient.properties");
IdSClientConfigurationImpl config = new IdSClientConfigurationImpl(idsclientResource.getFile().getPath());
// IdSClientConfigurationImpl config = new IdSClientConfigurationImpl("src/main/resources/idsclient.properties");
config.load();
return config;
}
#Bean
public IdSClient setupIdsClient() throws IOException, IdSClientException {
IdSClient client = IdSClientFactory.getIdSClient();
client.setTLSContext(createSSLTrustManager(), createHostnameVerifier());
// client.setTLSContext(arg0, arg1) // use secure trust manager and hostname verifier in production
client.init(config);
return client;
}
private X509TrustManager createSSLTrustManager() {
X509TrustManager tm = new TrustAllX509TrustManager();
return tm;
}
private HostnameVerifier createHostnameVerifier() {
HostnameVerifier hv = new SkipAllHostNameVerifier();
return hv;
}

Cannot Add Http Headers to Message with Spring's WebServiceTemplate

I have a fairly simple case where I am trying to add HTTP headers (not SOAP headers) to a request I am making using Spring's WebServiceTemplate.
I have defined a ClientInterceptor where I am doing:
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
try {
TransportContext context = TransportContextHolder.getTransportContext();
HttpComponentsConnection connection = (HttpComponentsConnection) context.getConnection();
connection.addRequestHeader("Authorization", String.format("Bearer %s", someAccessToken));
} catch (IOException exception) {
// Do nothing
}
return true;
}
This is how I configure my SomeClient which extends WebServiceConfigurationSupport:
#Bean
public SomeClient someClient() {
...
SomeClientImpl service = new SomeClientImpl();
service.setObjectFactory(new com.path.ObjectFactory());
service.setDefaultUri(someUri);
service.setMarshaller(marshaller);
service.setUnmarshaller(marshaller);
service.setxStreamMarshaller(xStreamMarshaller);
service.setInterceptors(new ClientInterceptor[]{wss4jSecurityInterceptor()});
service.setMessageSender(new HttpComponentsMessageSender());
service.setInterceptors(new ClientInterceptor[]{wss4jSecurityInterceptor(), addHttpHeaderInterceptor()});
return service;
}
#Bean
public ClientInterceptor addHttpHeaderInterceptor() {
return new AddHttpHeaderInterceptor(someAccessToken);
}
#Bean
public Wss4jSecurityInterceptor wss4jSecurityInterceptor() {
Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor();
interceptor.setSecurementActions(securementAction);
interceptor.setSecurementUsername(securementUsername);
interceptor.setSecurementPassword(securementPassword);
interceptor.setSecurementPasswordType(WSConstants.PW_TEXT);
interceptor.setSecurementMustUnderstand(false);
return interceptor;
}
But the Authorization header is not being added. I have also tried with a CustomMessageCallback:
public class CustomMessageCallback implements WebServiceMessageCallback {
private String headerKey;
private String headerValue;
public CustomMessageCallback(String headerKey, String headerValue) {
this.headerKey = headerKey;
this.headerValue = headerValue;
}
#Override
public void doWithMessage(WebServiceMessage webServiceMessage) throws IOException, TransformerException {
TransportContext context = TransportContextHolder.getTransportContext();
HttpComponentsConnection conn = (HttpComponentsConnection) context.getConnection();
HttpPost post = conn.getHttpPost();
post.addHeader(headerKey, headerValue);
}
}
But it does not seem to work as well. What am I doing wrong, why the Authorization header is not being added? Thanks!
Use the HeadersAwareSenderWebServiceConnection interface instead of the actual underlying connection.
TransportContext context = TransportContextHolder.getTransportContext();
HeadersAwareSenderWebServiceConnection connection = (HeadersAwareSenderWebServiceConnection) context.getConnection();
connection.addRequestHeader("Authorization", String.format("Bearer %s", "********"));
Now if you upgrade/switch HTTP library you don't need to change this code.
To answer your question about what you are doing wrong is that you are casting to the wrong class. Yes the class you are using is deprecated but it is part of the library you are using, you cannot just cast to a different class without changing the underlying HTTP library.
What I did in past is to use a WebServiceMessageCallback like this one:
public class WsHttpHeaderCallback implements WebServiceMessageCallback
{
private String headerKey;
private String headerValue;
private String soapAction;
public WsHttpHeaderCallback(String headerKey, String headerValue, String soapAction)
{
super();
this.headerKey = headerKey;
this.headerValue = headerValue;
this.soapAction = soapAction;
validateRequiredFields();
}
public WsHttpHeaderCallback()
{
super();
}
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException
{
validateRequiredFields();
addRequestHeader(headerKey, headerValue);
if (StringUtils.hasText(this.soapAction))
{
AxiomSoapMessage axiomMessage = (AxiomSoapMessage) message;
axiomMessage.setSoapAction(this.soapAction);
}
}
private void validateRequiredFields()
{
if( !StringUtils.hasText(headerKey) )
{
throw new IllegalArgumentException("Impossibile proseguire. Passato HEADER HTTP con chiave non valida: ["+headerKey+"]");
}
if( !StringUtils.hasText(headerValue) )
{
throw new IllegalArgumentException("Impossibile proseguire. Passato HEADER HTTP con valore non valido: ["+headerValue+"]");
}
}
private void addRequestHeader(String headerKey, String headerValue)
{
TransportContext context = TransportContextHolder.getTransportContext();
WebServiceConnection connection = context.getConnection();
if (connection instanceof HttpComponentsConnection)
{
HttpComponentsConnection conn = (HttpComponentsConnection) connection;
HttpPost post = conn.getHttpPost();
post.addHeader(headerKey, headerValue);
}
else if( connection instanceof ClientHttpRequestConnection )
{
ClientHttpRequestConnection conn = (ClientHttpRequestConnection)connection;
conn.getClientHttpRequest().getHeaders().add(headerKey, headerValue);
}
}
}
Then I used it in this way:
wsTemplate.marshalSendAndReceive(wsUrl, request, new WsHttpHeaderCallback(headerKey, headerValue, soapAction) );
In this way I successfully set all the needed HttpHeaders (in my case just one :) )
I hope it is usefull
Angelo
TL;DR
Your messageSender should be an instance of HttpComponentsMessageSender instead of HttpUrlConnectionMessageSender. Also you need to provide proper credentials.
getConnection() function of TransportContext returns an implementation of WebServiceConnection. Both HttpUrlConnection and HttpComponentsConnection are implementations of the same. So basically you are getting the wrong type of connection,hence the ClassCastException.
The ClientInterceptor will work for custom headers but not for Authorization header. For that, your HttpComponentsMessageSender needs to be configured with your credentials.
The proper configuration should be like this
#Value("${username}")
private String username;
#Value("${password}")
private String password;
#Bean
public SomeClient someClient() {
SomeClientImpl service = new SomeClientImpl();
service.setMessageSender();
//other configs
return service;
}
public HttpComponentsMessageSender getMessageSender(){
HttpComponentsMessageSender httpComponentsMessageSender = new HttpComponentsMessageSender();
httpComponentsMessageSender.setCredentials(getCredentials);
}
public UsernamePasswordCredentials getCredentials(){
return new UsernamePasswordCredentials(username, password);
}
I went through a similar exercise, for an endpointInterceptor the connection returns a HttpServletConnection. Therefore used the following and managed to get the HTTP headers added.
HttpServletConnection connection = (HttpServletConnection)context.getConnection();
HttpServletResponse response = connection.getHttpServletResponse();
HttpServletRequest request = connection.getHttpServletRequest();
response.addHeader("myheader", "myvalue");
Some additional tips:
If you want to send back the same header you received in the request, use following in the handleResponse method of the endpointInterceptor
response.addHeader("myheader", request.getHeader("myheader"));
If you are trying to add custom headers in an clientInterceptor to send to a downstream use below in the handleRequest method,
HttpUrlConnection connection = (HttpUrlConnection)context.getConnection();
connection.addRequestHeader("myheader", "myvalue");

HttpServletRequest is coming as null in Apache cxf interceptor

I need to match request along with its corresponding response so i am trying to take session id for matching each request along with its response.
I am using Phase.PRE_STREAM in my constructor.
I am trying to take HttpServletRequest and session id as below in my interceptor
public void handleMessage(Message msg) {
HttpServletRequest req = (HttpServletRequest)msg.get("HTTP.REQUEST");
}
But i am getting null value. Could someone tell me how to take HttpServletRequest in apache cxf?
Do i need to set session id while creating client.I create my client as below
JAXRSClientFactoryBean sf = new JAXRSClientFactoryBean();
sf.setResourceClass(CustomerService.class);
sf.setAddress("http://localhost:9000/");
BindingFactoryManager manager = sf.getBus().getExtension(BindingFactoryManager.class);
JAXRSBindingFactory factory = new JAXRSBindingFactory();
factory.setBus(sf.getBus());
manager.registerBindingFactory(JAXRSBindingFactory.JAXRS_BINDING_ID, factory);
CustomerService service = sf.create(CustomerService.class);
WebClient wc = sf.createWebClient();
Implement SOAP Handler like so:
public class SOAPRequestHandler implements SOAPHandler<SOAPMessageContext>
And implement your handlers like so:
public boolean handleMessage(SOAPMessageContext msgContext)
{
HttpServletRequest request = (HttpServletRequest) msgContext.get("HTTP.REQUEST");
...
}

Getting Request parameters from Spring WS Interceptor

I am using Jaxb 2 with a Spring WS, and I have an interceptor which is directed to a particular payload and it works fine.
Here my requirement is to read the request parameters from the handleRequest method of my interceptor. I know this should be fairly straight forward. However could not figure out a way to read the request parameters. At the moment my handleRequest method looks as below.
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint)
throws Exception {
boolean proceed = true;
SaajSoapMessage saajSoapMessage =
(SaajSoapMessage) messageContext.getRequest();
SOAPMessage soapMessage = saajSoapMessage.getSaajMessage();
Document doc = saajSoapMessage.getDocument();
Element element = doc.getElementById("request");
}
The relevant part of the my Endpoint class is
#PayloadRoot(namespace = NAMESPACE, localPart = "confirOrderRequest")
public #ResponsePayload ConfirmOrderResponse handleConfirmOrder(
#RequestPayload ConfirmOrderRequest confirmOrderRequest) {
...........
}
Here my requirement is to get the orderId which comes with the ConfirmOrderRequest in the interceptor handleRequest method, is there a way to do this directly, or do I need to do some XML parsing for that?
#VitualTroll, It helped me somewhat, thanks !
But answer on that question is incorrect(at least in my case). Body of my new handleRequest() method would looks as follows. Hope this would save some time for someone else in future. Here jaxb2Marshaller is my spring bean.
#Autowired
private Jaxb2Marshaller jaxb2Marshaller;
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
boolean proceed = true;
SaajSoapMessage saajSoapMessage = (SaajSoapMessage) messageContext.getRequest();
SoapBody requestBody = saajSoapMessage.getSoapBody();
Object obj = jaxb2Marshaller.unmarshal(requestBody.getPayloadSource());
if (obj instanceof ConfirmOrderRequest ) {
ConfirmOrderRequest cor = (ConfirmOrderRequest ) obj;
String orderId = cor.getOrderId();
...........
.......
}
.....
}

Categories

Resources