Wss4jSecurityInterceptor - My Custom Callback is interpreted as a CleanupCallback Object - java

I'm working in a project, made with Java 8 and Spring Boot, in which I want to add the Wss4jSecurityInterceptor for login purposes.
So far, this is what I've done in the WebServiceConfig class
#Bean
public AuthorizationCallBackHandler authorizationCallBackHandler(){
AuthorizationCallBackHandler callbackHandler = new AuthorizationCallBackHandler();
return callbackHandler;
}
#Bean
public Wss4jSecurityInterceptor securityInterceptor(){
Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();
securityInterceptor.setValidationActions("UsernameToken");
securityInterceptor.setValidationCallbackHandler(authorizationCallBackHandler());
return securityInterceptor;
}
#Override
public void addInterceptors(List interceptors) {
interceptors.add(securityInterceptor());
//interceptors.add(endPointInterceptor());
}
So, with this, every request that arrives at my Web Service, will be intercepted by the Wss4jSecurityInterceptor and will be handled by my custom callback, defined like this
public class AuthorizationCallBackHandler implements CallbackHandler{
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
VnWsCredentialRepository credentialsRepo;
#Autowired
AuthUtility authUtil;
#Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
if (callbacks[0] instanceof WSPasswordCallback) {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
String username = pc.getIdentifier();
VnWsCredential credentials = credentialsRepo.findByUsername(username);
logger.info("Request of authentication for username" + username);
String p = pc.getPassword();
// set the password on the callback. This will be compared to the
// password which was sent from the client.
if (credentials == null) {
pc.setPassword(null);
}else {
// String encodedPsw = authUtil.obtaindMD5Value(credentials.getPassword());
// pc.setPassword(encodedPsw);
pc.setPassword(credentials.getPassword());
}
}
if (callbacks[0] instanceof UsernameTokenPrincipalCallback) {
UsernameTokenPrincipalCallback pc = (UsernameTokenPrincipalCallback) callbacks[0];
pc.getPrincipal();
}
}
}
Here's my problem: when the Callback gets called, it receives an array that holds just 1 callback with a "CleanupCallback" type and I'm not, of course, able to do anything with it.
What am I missing in the following settings?
This is the SOAP call that I'm making with SOAP UI
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:it="some.fancy.ws">
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-3967AEB46D733EF6E2154990461080350">
<wsse:Username>Just a user</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">just a password</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">pUn8VjdpVaIamSAIwXEeXg==</wsse:Nonce>
<wsu:Created>2019-02-16T17:03:30.803Z</wsu:Created>
</wsse:UsernameToken></wsse:Security>
</soapenv:Header>
<soapenv:Body>
<it:getPOrderRequest>
<it:poNumber>2197111225-F03292</it:poNumber>
</it:getPOrderRequest>
</soapenv:Body>
</soapenv:Envelope>

For anyone interested, I solved this thing by first of all removing from the request the content inside the header.
After that, I setted up an Outgoing WSS with the credentials and, just like that, the Wss4j Security Handler converted the Callback to the instance I wanted (that is, WSPasswordCallback).
Lesson learned: if Wss4j detects a some kind of error in handling the SOAP Request, it will generate a CleanupCallback and not an instance of WSPasswordCallback

Related

Handling an empty SOAP message

Trying to handle an empty SOAP message with Spring Web Services but failing.
So, I have a request to provide an endpoint for a sort of a PING method. Basically the SOAP messages I can handle look like this:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:def="http://www.something.com/edf/services/defaultservice">
<soapenv:Header/>
<soapenv:Body>
<def:ServiceReqType>
<transactionId>1111</transactionId>
<subscriberId>2222</subscriberId>
</def:ServiceReqType>
</soapenv:Body>
</soapenv:Envelope>
and that I can handle with an endpoint that is handling ServiceReqType.
But the PING looks like this:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body/>
</soapenv:Envelope>
That I can't handle, Spring logs a Can't handle [SaajSoapMessage].
What I need to return is the exactly same message.
I understand there that there is a type/class missing that I would provide to #PayloadRoot.
So I am wondering what would an endpoint specification be for this empty bodied request?
Just for reference here is my endpoint for handling the ServiceReqType:
#PayloadRoot(namespace = NAMESPACE_EDF, localPart = "ServiceReqType")
#ResponsePayload
public ServiceRespType serviceResponse(#RequestPayload ServiceReqType request) {
LOGGER.debug("-----> ServiceReqType:{}", request);
return reqProcessor.process(request);
}
UPDATE 1:
So I tried with implementing the interceptor in the following way:
public class CustomEndpointInterceptor implements EndpointInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomEndpointInterceptor.class);
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
LOGGER.info("---> Message context: {}", messageContext.toString());
LOGGER.info("---> Message endpoint: {}", endpoint.toString());
return false;
}
#Override
public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception {
LOGGER.info("handleResponse");
return false;
}
#Override
public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception {
LOGGER.info("handleFault");
return false;
}
#Override
public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception {
LOGGER.info("afterCompletion");
}
}
and then in the WebServiceConfiguration class I added this:
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(new CustomEndpointInterceptor());
super.addInterceptors(interceptors);
}
But it still does not work. What I get is that the interceptor is called when SOAP message has a BODY but when it is sent a Ping that is BODYless then I get again the same error message and interceptor is not called. So it seems I must find an interceptor that is further up the chain...
UPDATE 2:
Here is how WSDL file looks like for this Ping...
<message name="PingRequest"/>
<message name="PingResponse"/>
There is nothing as a part inside of these...
For comparison this is how ServiceReqType looks:
<message name="ServiceReqType">
<part name="body" element="tns:ServiceReqTypeDefinition"/>
</message>
and then the ServiceReqTypeDefinition is defined in an accompanying xsd file.
UPDATE 3:
So, found the reason why interceptors won't work on this type of message :-/
Below code is from MessageDispatcher line 234.
// No triggering of interceptors if no endpoint is found
if (endpointNotFoundLogger.isWarnEnabled()) {
endpointNotFoundLogger.warn("No endpoint mapping found for [" + messageContext.getRequest() + "]");
}
throw ex;
So it's on purpose, now I need to find how to handle these incomplete or incorrect SOAP messages... any ideas because rewriting Dispatcher doesn't feel like the right way.
UPDATE 4
So, I managed to intercept the payload, the one that is invalid, by extending AbstractEndpointMapping class and then overriding getInternalEndpoint gives me the possibility to evaluate the message and see if it is this empty Ping request I have been trying to process.
I think this would all be easily solved if I knew how to define a defaultEndpoint because I see that in case SOAP message is not recognised, so no endpoint mapping is found, this defaultEndpoint is used to handle that message.
I noticed that when working with XML specification of Beans then there is a property to define the endpoint as a default one, but how to do it when using annotations?
Another way is to just create an endpoint and then return it from getInternalEndpoint so spring can handle processing, I guess, but I don't know yet how to create an endpoint object... working on that now.
p.s. This documentation mentions defaultEndpoint but not how to set it up in a non XML defining Bean way.
So, in the end I had to create a custom exception handler. Something like this:
public class CustomEndpointNotFoundException extends Exception {
public CustomEndpointNotFoundException(String message) {
super(message);
}
}
then:
#Component
#Order(Ordered.LOWEST_PRECEDENCE)
public class NoEndpointFoundEndpointMapping implements EndpointMapping {
#Override
public EndpointInvocationChain getEndpoint(MessageContext messageContext) throws Exception {
throw new CustomEndpointNotFoundException("");
}
}
and finally:
#Component
public class CustomEndpointExceptionResolver implements EndpointExceptionResolver {
#Override
public boolean resolveException(MessageContext messageContext, Object endpoint, Exception ex) {
if (messageIsPing()) {
return true;
}
return false;
}
}
So basically I am handling it as an error. It's not really ideal solution IMHO, I will still see if I can intercept the endpoint resolution, or define a default endpoint.

Customization soap response

i am working on web services project with Apache CXF.
I wan't to handle exception and customize response:
public class FaultInterceptor extends
AbstractSoapInterceptor {
public FaultInterceptor() {
super(Phase.MARSHAL);
}
public void handleMessage(SoapMessage soapMessage) throws Fault {
Fault fault = (Fault) soapMessage.getContent(Exception.class);
QName faultCode = new QName("11111");
fault.setFaultCode(faultCode);
So here is what i get in the response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:11111</faultcode>
<faultstring>Message</faultstring>
</soap:Fault>
</soap:Body>
How can i remove the text "soap:" and let only 11111?
Please help me and thanks in advance
To customize the SOAP response, you can implement either one of these:
JAX-WS handler;
CXF interceptor (extend AbstractSoapInterceptor);
Servlet Filter.
--EDIT 2019-02-20--
According to Javadoc (and SOAP spec), the faultcode must be in the form "prefix:localname" where "prefix" is the prefix of a declared XML namespace in your XML, or you can have no prefix - that you want - if it is declared as the default namespace, e.g. with xmlns="my-custom-faultcode-namespace-uri" somewhere, e.g. in the soap:Envelope element. So one way - not sure it's the easiest but it is SOAP standard compliant - consists to:
1) Make up your own custom namespace for this faultcode
2) Try changing the QName with empty string as namespace prefix:
QName faultCode = new QName("my-custom-faultcode-namespace-uri", "11111", "");
If this is not enough (I would be surprised it is that simple), you may have force CXF to use your custom namespace as the default (without prefix). According to this post, to customize namespaces and prefixes on the soap enveloppe in CXF, you change the Map in the jaxws 'soap.env.ns.map' property.
I was looking for the exactly same thing since migrating an old system to behave exactly the same.
I came up with the following Solution.
class SoapFaultEndpointInterceptor extends EndpointInterceptorAdapter
{
private static final Pattern SOAP_CODE_FAULT_SPLITTER = Pattern.compile(":");
#Override
public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception
{
SaajSoapMessage soapResponse = (SaajSoapMessage) messageContext.getResponse();
modifySoapFaultCode(soapResponse);
return super.handleFault(messageContext, endpoint);
}
private void modifySoapFaultCode(SaajSoapMessage soapResponse)
{
try {
SOAPMessage soapMessage = soapResponse.getSaajMessage();
SOAPBody body = soapMessage.getSOAPBody();
SOAPFault soapFault = body.getFault();
modifyFaultCodeIfPresent(soapFault);
} catch (SOAPException e) {
throw new SoapModifiyingException("Modifying faultcode did not work properly.", e);
}
}
private void modifyFaultCodeIfPresent(SOAPFault fault)
{
if (fault != null) {
String newFaultCode = cleanFaultCode(fault.getFaultCode());
fault.setFaultCode(newFaultCode);
}
}
private String cleanFaultCode(String oldFaultCode)
{
String[] cleanFaultCode = SOAP_CODE_FAULT_SPLITTER.split(oldFaultCode);
Assert.isTrue(cleanFaultCode.length == 2, "No proper faultcode provided!");
return cleanFaultCode[1].trim();
}
And by adding SoapFaultEndpointInterceptor to your Interceptor, it should work.
#EnableWs
#Configuration
public class SoapServerConfig extends WsConfigurerAdapter
{
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors)
{
interceptors.add(new SoapFaultEndpointInterceptor());
}
}

Spring WS Endpoint extract SOAP info

After reading doc on the Spring web site, still confused about how to extract information from a SOAP request.
For example, the SOAP request sent to server is like:
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope" xmlns:user="http://www.mysite.com/user/schemas">
<soapenv:Header/>
<soapenv:Body>
<user:UserRequest>
<!--You may enter the following 4 items in any order-->
<user:Key>key</user:Key>
<user:UserName>username</user:UserName>
<user:RequesterName>reqname</user:RequesterName>
<user:RequesterPassword>repw</user:RequesterPassword>
</user:UserRequest>
</soapenv:Body>
</soapenv:Envelope>
On my server side I create an Endpoint like:
#Endpoint
public class UserEndpoint {
private static final String NAMESPACE_URI = "http://www.mysite.com/user/schemas";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "UserRequest")
public void handleGetUserRequest() {
//Extract here...
}
}
How should I write extraction code here?
I would suggest having a look at the Spring WS samples for code ideas, depending on what else you are using in your application. For example: the HolidayEndpoint source code.
#Endpoint("myEndpoint")
public class MyEndpoint {
/**
* Spring-WS Endpoint
* #param submitSomethingRequest
* #param header
* #return SubmitSomethingResponse
*/
#PayloadRoot(namespace="http://my.namespace.org/spec/1.0.1", localPart="submitSomethingRequest")
#ResponsePayload
public SubmitSomethingResponse submitSomethingRequest(#RequestPayload SubmitSomethingRequest submitSomethingRequest, **SoapHeader header**) {
LOG.info("Received SOAP HEADER: " + header);
if(header != null) {
Iterator<SoapHeaderElement> hdrs = header.examineAllHeaderElements();
while(hdrs.hasNext()) {
SoapHeaderElement hdrEle = hdrs.next();
System.out.prinltn(hdrEle.getName().getPrefix() + ":" + hdrEle.getName().getLocalPart());
... //Do something here to parse DOM and extract headers you care about
}
}
...

How do I access SOAP headers in a spring soap endpoint?

Here is my SOAP request:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:str="http://app.strategyblocks.com/ws/schema/strategyblocks">
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="...">
<wsse:Username>admin</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">secret</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
<str:updateKpiRequest>
<str:company_id>1</str:company_id>
<str:kpi>
<str:external_id>1134511</str:external_id>
<str:title>title</str:title>
<str:description>description</str:description>
</str:kpi>
</str:updateKpiRequest>
</soapenv:Body>
</soapenv:Envelope>
Here is my Endpoint class:
#Endpoint
public class UpdateKpiEndpoint {
// The namespace of both request and response as declared in the XSD file
public static final String NAMESPACE_URI = "http://app.strategyblocks.com/ws/schema/strategyblocks";
// The local name of the expected request.
public static final String REQUEST_LOCAL_NAME = "updateKpiRequest";
#PayloadRoot(localPart = REQUEST_LOCAL_NAME, namespace = NAMESPACE_URI)
#ResponsePayload
public UpdateKpiResponse processUpdateKpi(#RequestPayload UpdateKpiRequest updateKpiRequest) {
try {
} catch (Exception e) {
UpdateKpiResponse response = new UpdateKpiResponse();
response.setCode("FAILURE");
response.setDescription("Problem with update kpi request");
return response;
}
UpdateKpiResponse response = new UpdateKpiResponse();
response.setCode("SUCCESS");
response.setDescription("Kpi has been updated");
return response;
}
}
At the moment I am passing a UsernameToken for authentication in the soap request, that is all working well and I have no problems with it what so ever. What I want to be able to achieve is to retrieve that username from the header in the body of processUpdateKpi method in my endpoint class, so that I can use it to find existing data for that user, I have tried to find examples of it being done and so far I have been unsuccessful, is it possible to do it? I have thought about also passing the username in the SOAP body as well, but I want to avoid it.
someone in the spring forums had a clear explanation on how to read the header from the endpoint class:
http://forum.springsource.org/showthread.php?109560-Unable-to-read-SoapHeader-in-Endpoint-class

Secure a java embedded web service with javax.jws without application server

I wrote the following code to implement a Java web service that communicates with an application written in another language on the same host:
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
#WebService(name = "MyWebService")
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.WRAPPED)
public class MyWebService {
#WebMethod(operationName = "methodName", action = "urn:#methodName")
#WebResult(name = "result", partName = "output")
public String methodName(#WebParam(name = "param1", partName = "input") String param1,
#WebParam(name = "param2", partName = "input") String param2){
// ...do something
return "You called this service with params: " + param1 + "," + param2;
}
Since requirements are not to use an application server to expose the web service I instantiated the service from another class as follows:
Endpoint endpoint = Endpoint.create(new MyWebService());
URL url = new URL("http://localhost:7777/MyWebService");
endpoint.publish(url.toString());
Questions:
1) Which is the simplest way to secure this service with username and password considering the architecture of this project?
Any code sample would be greatly appreciated.
2) I made some research and found the use of SOAPHandler and I think it would work for me.
In the case of using the SOAPHandler class how do I add headers to the message to require authentication from the client?
Thank you in advance
thanks so much for the response that's the direction I'm following too but
when I check any of the headers for example:
SOAPHeader header = soapContext.getMessage().getSOAPPart().getEnvelope().getHeader();
Iterator<SOAPElement> iterator = header.getAllAttributes();
I get a nullpointer exception...any ideas?
I did a working program. Just to add to what you already found out, following is a way to use handler
Endpoint endpoint = Endpoint.create(new MyWebService());
Binding binding = endpoint.getBinding();
List<Handler> handlerChain = new ArrayList<Handler>(1);
handlerChain.add(new MyHandler());
binding.setHandlerChain(handlerChain);
URL url = new URL("http://localhost:7777/MyWebService");
endpoint.publish(url.toString());
MyHandler is class extending Handler interface. Alternately, you can use #HandlerChain annotation which will need an xml configuration file for handlers. Configure this for incoming messages only
public class MyHandler implements SOAPHandler{
#Override
public Set<?> getHeaders() {
// TODO Auto-generated method stub
return null;
}
#Override
public void close(MessageContext context) {
// TODO Auto-generated method stub
}
#Override
public boolean handleFault(MessageContext context) {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean handleMessage(MessageContext context) {
System.out.println("Hehehe the handler");
SOAPMessageContext soapContext = (SOAPMessageContext)context;
try {
SOAPHeader header = soapContext.getMessage().getSOAPPart().getEnvelope().getHeader();
//Check there if the required data (username/password) is present in header or not and return true/false accordingly.
} catch (SOAPException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
}
From the client side also, if your client is using JAB-WS, you will have to use client handlers. Following is a typical JAX-WS client invocation example
Dispatch<Source> dispatch = … create a Dispatch<Source>
dispatch.getBinding().setHandlerChain(chain)
Source request = … create a Source object
Source response = dispatch.invoke(request);
Here the handler in chain will add header to outgoing request. Configure this for Outgoing messages only.
What you did is fair enough.
Concerning the authentication you can just expose a method for passing user name and password as login credentials.
Once the user has provided the correct credentials the user has been authenticated.
Note: Now you must maintain session data and make sure that an incoming request is from an authenticated user. The Endpoint just deploys internally a lightweight http server. You must design you web service implementation to keep "state" among requests.
You have 2 more options.
Do the authentication at the SOAP level. I would not really recomend
it. But if you do, note that the Endpoint does not deploy a
WSDL. So you must communicate exactly to the client connecting,
the SOAP header you expect. It is possible though to write a WSDL by
yourself and "attach" it to the Endpoint.
Do the authentication at the http request level. I.e. add a token or
cookie to the http request. To be honest I do not remember if this
is easy using the Endpoint

Categories

Resources