Download Ping Java SDK and Web Services

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

URL redirection wikipedia , lookup

Transcript
Ping Java SDK and Web Services (WS-Trust)
In an effort to get my head around Java Web Services I have thrown together a quick
Client/Service scenario to learn how everything works. I am not the most advanced Java
developer and I am continually confused by all the different frameworks and interfaces that
Java provides so I wanted to build a default sandbox that I could play with to test certain
web services scenarios.
I have a few scenarios here that all build from a common base. I have been using NetBeans
for this but you can pretty much insert your favourite Java IDE (ie Eclipse) as you wish –
you may need to google some of the IDE things like configuring handlers etc.
•
Step One: Basic Web Service provider (WS) and Web Service Client (WSC)
•
Step Two: Enabling WS-Security on the WS (require SAML token)
•
Step Three: Enabling WS-Security on the WSC (swap username token for SAML
token)
•
Step Four: Receiving a local SAML token for the WS and returning the subject to
the web service
This may help as a learning tool or as the basics for a POC, or at least to save time from
jumping through multiple examples and sample code to produce a simple WS-Trust
scenario.
Pre-requisites
•
•
•
•
Java IDE with EE capable web server (ie Netbeans with Glassfish)
PingFederate with STS enabled
Ping Java SDK
Ping WSS Username Token Translator
Disclaimer
This sample was farmed from various code snippets found all over the Internet,
SDK samples and via my own keyboard. This is not guaranteed to work or be
safe, secure or well-performing by any means. Good for a POC or to learn the
concepts but nothing else.
PingFederate Configuration
For all scenarios, the following PingFederate configuration is used:
Ping server is listening on https://localhost:9031.
https://localhost:9031/idp/sts.wst (and /sp/sts.wst)
So the STS endpoint is
I just have a self-signed SSL certificate so you will notice the following line in the
code: stsClientConfiguration.setIgnoreSSLTrustErrors(true);
code: stsClientConfiguration.setIgnoreSSLTrustErrors(true);
IDP Side (to handle the authentication of the Web Services client):
Token Processor
Username Token Processor 1.1 configured to verify a credential set (either
named users, LDAP etc). For this sample I used the “User Table with
Password” option to manually defined users
SP Connection
WS-Trust connection configured as the screenshot below:
SP Side (to service the WS (validate token + re-issue local token))
Token Generator
SAML 2.0 Token Generator 1.1 used to issue a local SAML token for the
Web Service and to validate an existing SAML token. Configuration for the
token generator is below:
IDP Connection
Used to interface with the Web Service. Defines the attribute contract,
signing cert, issuer etc of the service. Configuration used is included below:
Step One: Basic Web Service provider (WS) and Web Service Client (WSC)
Launch the NetBeans IDE and create a new Java Web Application project to create our
sample Web Service.
Give the project a name…
I am using the built-in Glassfish server. Shouldn’t need to modify anything on this screen
so you can hit Finish.
Now we have a basic Web Application. We need to create the Web Service to sit in here.
Right-click on the name of the project and choose New > Web Service… | Give the service
a name and a package name. Also check the box to implement as a “Stateless Session
Bean”
Now we have a basic web service that will say “Hello” to a name you provide. To test the
service:
Right-click the Project and select “Deploy” to deploy it to the Glassfish web server.
Expand the “Web Services” folder under the project, right-click on the service and
select “Test Web Service” to verify the service is correctly running.
Once you have created the service, we will now create a web service client to communicate
with this web service.
In the NetBeans IDE, create a new Java Application by choosing File > New Project:
Name the project and click Finish to create a basic Java application.
To create a reference to the Web Service we created, in the project explorer, right-click on
the application project name and select: New > Web Service Client…
Choose to specify the WSDL from the Web Service project we created earlier and click
Finish to create the reference.
Now we will add this reference into the Java code to call the web service from our Java
app.
Open the Java application source file in the editor.
On the Project explorer, expand the levels under the “Web Service References” until you
see the web service method (red dot) and drag that into your code (under the closing brace
of the “main method”)
Modify the main method to call this new method:
Right-click the project and choose “Run” to execute the client. You should see the results
of the web service call in the console:
Success. We now have a working web service and web service client configuration.
Step Two: Enabling WS-Security on the WS to require SAML security token
The next step is to protect the existing web service. The requirements now are that no one
can call this web service without providing a valid SAML security token.
To protect the web service, we are going to use the Ping Java SDK to integrate with the
Ping STS. We are going to create a SOAP Handler to handle the SAML validation without
having to change the core web service code.
Because we are using the Ping SDK, we need to add the required libraries to our project.
In the project explorer, right-click “Libraries” and select “Add JAR/Folder”. Browse to
the location where you expanded the Ping Java SDK and select all the jars in the “lib”
folder.
In the Web Service project (SampleWS). create a new Java class (we call it
SampleWSHandler):
Paste the following code into this Java file:
[You may need to modify the package, class name and STS endpoint appropriately]
package com.pingidentity.pmeyer.ws;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
java.net.MalformedURLException;
java.util.Set;
javax.xml.namespace.QName;
javax.xml.soap.SOAPBody;
javax.xml.soap.SOAPException;
javax.xml.soap.SOAPFault;
javax.xml.soap.SOAPHeader;
javax.xml.soap.SOAPMessage;
javax.xml.ws.handler.MessageContext;
javax.xml.ws.handler.soap.SOAPHandler;
javax.xml.ws.handler.soap.SOAPMessageContext;
javax.xml.ws.soap.SOAPFaultException;
org.w3c.dom.Element;
org.w3c.dom.NodeList;
/**
*
* @author pmeyer
*/
public class SampleWSHandler implements SOAPHandler<SOAPMessageContext> {
private static String STS_ENDPOINT_URL = "https://localhost:9031/sp/sts.wst";
@Override
public boolean handleMessage(SOAPMessageContext messageContext) {
Boolean isRequest = (Boolean)
messageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
//for response message only, true for outbound messages, false for inbound
if (!isRequest) {
try {
SOAPMessage soapMsg = messageContext.getMessage();
SOAPHeader soapHeader = soapMsg.getSOAPHeader();
NodeList secHeaders = soapHeader
.getElementsByTagNameNS(
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wsswssecurity-secext-1.0.xsd",
"Security");
if (secHeaders.getLength() == 0) {
generateSOAPErrMessage(soapMsg, "No Security Header");
}
Element securityHeader = (Element) secHeaders.item(0);
STSClientConfiguration stsClientConfiguration = new
STSClientConfiguration();
stsClientConfiguration.setStsEndpoint(STS_ENDPOINT_URL);
stsClientConfiguration.setIgnoreSSLTrustErrors(true);
STSClient client;
try {
client = new STSClient(stsClientConfiguration);
client = new STSClient(stsClientConfiguration);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
SamlToken token;
try {
token = client.extractTokenFromSecurityHeader(securityHeader);
} catch (SecurityTokenException e) {
throw new RuntimeException(e);
}
if (token == null) {
generateSOAPErrMessage(soapMsg, "No security token found");
}
boolean valid = false;
try {
valid = client.validateToken(token);
} catch (STSClientException e) {
generateSOAPErrMessage(soapMsg, e.getMessage());
}
if (!valid) {
generateSOAPErrMessage(soapMsg, "Security token invalid");
} else {
// We have a valid token...
System.out.println("WSP: VALID...");
}
} catch (SOAPException e) {
System.err.println(e);
}
}
}
return true;
@Override
public boolean handleFault(SOAPMessageContext context) {
return true;
}
@Override
public void close(MessageContext context) {
}
@Override
public Set<QName> getHeaders() {
return null;
}
}
private void generateSOAPErrMessage(SOAPMessage msg, String reason) {
try {
SOAPBody soapBody = msg.getSOAPPart().getEnvelope().getBody();
SOAPFault soapFault = soapBody.addFault();
soapFault.setFaultString(reason);
throw new SOAPFaultException(soapFault);
} catch (SOAPException e) {
}
}
Next we need to attach this handler to the existing web service code. In the project
explorer, right-click the “SampleWS” service under the “Web Services” folder in the project
and select “Configure Handlers…”.
Click the Add button and browse to the handler file we just created
(SimpleWSHandler.java). Click okay to apply that change.
To test the changes, re-deploy the web service by right-clicking on the Project name
(SampleWS) and selecting “Deploy”.
Right-click your Client project and choose “Run”. You should get an error indicating that
no security header was received.
Pretty cool that we only added the handler to the project. Minimal changes to the core web
service code were needed to secure it.
[TODO: Modify the WSDL to advertise that we are expecting a SAML security token]
Step Three: Enabling WS-Security on the WSC (swap username token for SAML token)
Now we will follow a similar process on the client side. We want to authenticate a user via
the STS and present a SAML 2.0 assertion in the SOAP headers for the Web Service to
consume.
As with the Web Service provider, in the Client project, right-click the “Libraries” folder
and add the JARs from the Java SDK.
We now can add a handler on the Client side. Right-click the project and create a new Java
Class…
In this java file, copy the following code. You can modify the contents appropriately:
package sampleclient;
import
import
import
import
import
import
import
import
import
import
import
import
com.pingidentity.sts.clientapi.STSClient;
com.pingidentity.sts.clientapi.STSClientConfiguration;
com.pingidentity.sts.clientapi.STSClientException;
com.pingidentity.sts.clientapi.utils.StringUtils;
java.net.MalformedURLException;
java.util.Set;
java.util.TreeSet;
javax.annotation.Resource;
javax.xml.namespace.QName;
javax.xml.soap.*;
javax.xml.ws.handler.MessageContext;
javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import org.w3c.dom.Element;
public class SampleClientHandler implements SOAPHandler<SOAPMessageContext> {
private
private
private
private
static
static
static
static
String
String
String
String
WSS_USERNAME_TOKEN_USERNAME = "user123";
WSS_USERNAME_TOKEN_PASSWORD = "User123";
STS_ENDPOINT_URL = "https://localhost:9031/idp/sts.wst";
APPLIES_TO = "http://localhost";
public SampleClientHandler() {
}
@Override
public boolean handleMessage(SOAPMessageContext context) {
Boolean outboundProperty = (Boolean)
context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
// We are a Web Services Client. We need to get a SAML token to send through
to our service.
// First of all, request a security token from the STS:
STSClientConfiguration stsClientConfiguration = new STSClientConfiguration();
stsClientConfiguration.setStsEndpoint(STS_ENDPOINT_URL);
stsClientConfiguration.setAppliesTo(APPLIES_TO);
stsClientConfiguration.setIgnoreSSLTrustErrors(true);
STSClient client;
// instantiate the STS client
try {
client = new STSClient(stsClientConfiguration);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
// Send in a Username token and receive the issued SAML token
Element token;
try {
token = client.issueToken(WSS_USERNAME_TOKEN_USERNAME,
WSS_USERNAME_TOKEN_PASSWORD);
} catch (STSClientException e) {
// deal with the exception
throw new RuntimeException(e);
}
// We now have a SAML token to include in the Web Services Request
System.out.println("WSC: RST swapped for SAML token");
StringUtils ppUtil = new StringUtils();
ppUtil.prettyPrint(token);
// Insert the SAML token into the SOAP Headers...
try {
SOAPEnvelope envelope = context.getMessage().getSOAPPart().getEnvelope();
SOAPFactory factory = SOAPFactory.newInstance();
String prefix = "wsse";
String uri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wsswssecurity-secext-1.0.xsd";
SOAPElement securityElem = factory.createElement("Security", prefix, uri);
SOAPElement tokenElement = factory.createElement(token);
securityElem.addChildElement(tokenElement);
SOAPHeader header = envelope.addHeader();
header.addChildElement(securityElem);
} catch (Exception e) {
e.printStackTrace();
}
} else {
// inbound
}
}
return true;
@Override
public Set<QName> getHeaders() {
return new TreeSet();
}
@Override
public boolean handleFault(SOAPMessageContext context) {
public boolean handleFault(SOAPMessageContext context) {
return false;
}
}
@Override
public void close(MessageContext context) {
//
}
Again we must attach this handler to the client code by performing the following steps.
In the project explorer, expand the “Web Service References” folder, right-click on the web
service (SampleWS) and choose “Configure Handlers…”
Click the Add button and browse to the Handler we just created. Click OK to apply the
handler.
Right-click the Client Project and select Run. Assuming the username/password you are
passing to the wss username token processor are correct you should be able to execute the
web service call successfully:
Now we have a web services client and server talking to each other and being secured via
SAML/WS-Security.
Useful tests at this point are to:
• Change the credentials and see what a failed authentication looks like.
• Change the certificate used to sign the SAML token on the IDP side to see what an
invalid assertion looks like.
Step Four: Issuing and consuming a local SAML token on the Web Service
Now that the service is protected we now want to personalize the web service. So now we
want to grab a local SAML token and say hello to the subject of the authentication.
In the web service project, update the Handler using the code below. Bold is changed lines:
package com.pingidentity.pmeyer.ws;
import javax.xml.soap.*;
import
import
import
import
import
javax.xml.soap.*;
javax.xml.ws.handler.MessageContext;
javax.xml.ws.handler.soap.SOAPHandler;
javax.xml.ws.handler.soap.SOAPMessageContext;
javax.xml.ws.soap.SOAPFaultException;
import java.net.MalformedURLException;
import java.util.Set;
import javax.xml.namespace.QName;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import
import
import
import
import
import
import
com.pingidentity.sts.clientapi.STSClient;
com.pingidentity.sts.clientapi.STSClientConfiguration;
com.pingidentity.sts.clientapi.STSClientException;
com.pingidentity.sts.clientapi.SecurityTokenException;
com.pingidentity.sts.clientapi.tokens.saml.SamlToken;
com.pingidentity.sts.clientapi.tokens.saml.Saml20Token;
com.pingidentity.sts.clientapi.utils.StringUtils;
/**
*
* @author pmeyer
*/
public class SampleWSHandler implements SOAPHandler<SOAPMessageContext> {
private static String STS_ENDPOINT_URL = "https://localhost:9031/sp/sts.wst";
@Override
public boolean handleMessage(SOAPMessageContext messageContext) {
System.out.println("In WSP getMessage...");
Boolean isRequest = (Boolean)
messageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
//for response message only, true for outbound messages, false for inbound
if (!isRequest) {
try {
SOAPMessage soapMsg = messageContext.getMessage();
SOAPHeader soapHeader = soapMsg.getSOAPHeader();
NodeList secHeaders = soapHeader
.getElementsByTagNameNS(
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wsswssecurity-secext-1.0.xsd",
"Security");
if (secHeaders.getLength() == 0) {
generateSOAPErrMessage(soapMsg, "No Security Header");
}
Element securityHeader = (Element) secHeaders.item(0);
STSClientConfiguration stsClientConfiguration = new
STSClientConfiguration();
stsClientConfiguration.setStsEndpoint(STS_ENDPOINT_URL);
stsClientConfiguration.setIgnoreSSLTrustErrors(true);
STSClient client;
try {
client = new STSClient(stsClientConfiguration);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
SamlToken token;
try {
token = client.extractTokenFromSecurityHeader(securityHeader);
} catch (SecurityTokenException e) {
throw new RuntimeException(e);
}
if (token == null) {
generateSOAPErrMessage(soapMsg, "No security token found");
}
boolean valid = false;
try {
valid = client.validateToken(token);
} catch (STSClientException e) {
generateSOAPErrMessage(soapMsg, e.getMessage());
}
}
if (!valid) {
generateSOAPErrMessage(soapMsg, "Security token invalid");
} else {
// We have a valid token... did we swap for local token?
System.out.println("WSP: VALID...");
try {
Element localTokenXML = client.issueToken(token);
SamlToken localToken = new Saml20Token(localTokenXML);
String nameID = localToken.getNameIdentifier();
messageContext.put("subject", nameID);
messageContext.setScope("subject",
MessageContext.Scope.APPLICATION);
System.out.println("WSP: SAML Identity is:");
System.out.println(nameID);
} catch (Exception stsE) {
// probably an invalid local SAML token returned
System.out.println(stsE.getMessage());
}
}
} catch (SOAPException e) {
System.err.println(e);
}
}
}
return true;
@Override
public boolean handleFault(SOAPMessageContext context) {
return true;
}
@Override
public void close(MessageContext context) {
}
@Override
public Set<QName> getHeaders() {
return null;
}
}
private void generateSOAPErrMessage(SOAPMessage msg, String reason) {
try {
SOAPBody soapBody = msg.getSOAPPart().getEnvelope().getBody();
SOAPFault soapFault = soapBody.addFault();
soapFault.setFaultString(reason);
throw new SOAPFaultException(soapFault);
} catch (SOAPException e) {
}
}
With these extra lines we are requesting a new local SAML token and parsing the subject
out of there. (Note: there is no reason you can’t use the subject from the initial token
received, I was just playing with the issuance of a local token at this point)
We are also passing along the subject of the SAML token in the message context. So to
retrieve that from the web services code we need to modify the web service as such:
package com.pingidentity.pmeyer.ws;
import
import
import
import
import
import
import
import
javax.annotation.Resource;
javax.jws.WebService;
javax.jws.WebMethod;
javax.jws.WebParam;
javax.ejb.Stateless;
javax.jws.HandlerChain;
javax.xml.ws.WebServiceContext;
javax.xml.ws.handler.MessageContext;
/**
*
* @author pmeyer
*/
*/
@WebService(serviceName = "SampleWS")
@Stateless()
@HandlerChain(file = "SampleWS_handler.xml")
public class SampleWS {
@Resource
private WebServiceContext context;
@WebMethod(operationName = "hello")
public String hello(@WebParam(name = "name") String txt) {
MessageContext msgContext = context.getMessageContext();
String thisUser = "[Unknown]";
if (msgContext.containsKey("subject")) {
thisUser = msgContext.get("subject").toString();
}
}
}
return "Hello " + thisUser + " !";
Now when you run the client you should see the subject of the security token being
welcomed:
Things to try at this point are to add attributes to the assertion and pass them through to the
web service.
Along the same lines, to pass values from the Web Services client through to the Handler,
you can modify the Web Services client code as following to pass the username and
password:
Web Services client code:
package sampleclient;
import javax.xml.ws.BindingProvider;
/**
*
* @author pmeyer
*/
public class SampleClient {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
System.out.println(hello("Oscar"));
}
private static String hello(java.lang.String name) {
com.pingidentity.pmeyer.ws.SampleWS_Service service = new
com.pingidentity.pmeyer.ws.SampleWS_Service();
com.pingidentity.pmeyer.ws.SampleWS port = service.getSampleWSPort();
com.pingidentity.pmeyer.ws.SampleWS port = service.getSampleWSPort();
((BindingProvider) port).getRequestContext().put("username", "user234");
((BindingProvider) port).getRequestContext().put("password", "User234");
}
}
return port.hello(name);
In addition we also need to modify the Web Services client SOAP Handler to use the
values provided in the request context rather than the hardcoded ones. This is a single line
of code change. Look for the “client.issueToken()” call and update with the following:
token = client.issueToken(context.get("username").toString(),
context.get("password").toString());
//
token = client.issueToken(WSS_USERNAME_TOKEN_USERNAME,
WSS_USERNAME_TOKEN_PASSWORD);
Running the web services client now will use the credentials passed in the client
application.