removing mustUnderstand attribute from soap headers - java

How to remove mustunderstand attribute from soap header in axis client.even i dont set it especially, when i set soap header info mustundertand and actor attributes are automatically added to soap message.Does anybody know how to remove them ?
I am using Axis2 1.4 version's wsdl2java to create my ws client.

None of those solutions worked for me, as:
I am working with Axis (not Axis2)
I don't want the attribute to be specified at all, as my server counterpart doesn't support it ("The 'http://schemas.xmlsoap.org/soap/envelope/:mustUnderstand' attribute is not declared" is a typical answer in such a case).
Looking at the answer to "Adding ws-security to wsdl2java generated classes" helped me to write a solution that worked for me:
void addSecurityHeader(Stub stub, final String username, final String password) {
QName headerName = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security"); // Or any other namespace that fits in your case
AtomicReference<SOAPHeaderElement> header
= new AtomicReference<SOAPHeaderElement>
(new SOAPHeaderElement(headerName) {
{
SOAPElement utElem = addChildElement("UsernameToken");
utElem.addChildElement("Username").setValue(username);
utElem.addChildElement("Password").setValue(password);
}
#Override
public void setAttribute(String namespace, String localName, String value) {
if (!Constants.ATTR_MUST_UNDERSTAND.equals(localName)) { // Or any other attribute name you'd want to avoid
super.setAttribute(namespace, localName, value);
}
}
});
SOAPHeaderElement soapHeaderElement = header.get();
soapHeaderElement.setActor(null); // No intermediate actors are involved.
stub.setHeader(soapHeaderElement); // Finally, attach the header to the stub
}

If you want to disable the must understand check in the AXIS client you have
to add the following line to your code:
_call.setProperty(Call.CHECK_MUST_UNDERSTAND, new Boolean(false));
then the MustUnderstandChecker of the AXIS Client is never invoked.

In my case it worked manually remove the attribute from the SOAPHeader
SOAPHeader header = env.getHeader();
OMChildrenQNameIterator childrenWithName = (OMChildrenQNameIterator) header.getChildrenWithName(omElementauthentication.getQName());
while (childrenWithName.hasNext()) {
org.apache.axiom.om.OMElement omElement = (org.apache.axiom.om.OMElement) childrenWithName.next();
QName mustAnderstandQName = omElement.resolveQName("soapenv:mustUnderstand");
if (mustAnderstandQName == null) {
continue;
}
OMAttribute mustAnderstandAttribute = omElement.getAttribute(mustAnderstandQName);
if (mustAnderstandAttribute == null) {
continue;
}
omElement.removeAttribute(mustAnderstandAttribute);
}

I was recently struggling with similar situation and by doing some google-ing I managed to find the following solution.
Having used Axis2 you would've probably generated a MyWSStub file that contains the calls to your service.
Create an wrapper class (better not touch the auto-generated stub files) that extends your stub e.g. MyWSStubWrapper:
public class MyWSStubWrapper extends MyWSStub {
/**
* #throws AxisFault
*/
public MyWSStubWrapper() throws AxisFault {
}
/**
* #param configurationContext
* #throws AxisFault
*/
public MyWSStubWrapper(ConfigurationContext configurationContext) throws AxisFault {
super(configurationContext);
}
/**
* #param targetEndpoint
* #throws AxisFault
*/
public MyWSStubWrapper(String targetEndpoint) throws AxisFault {
super(targetEndpoint);
}
/**
* #param configurationContext
* #param targetEndpoint
* #throws AxisFault
*/
public MyWSStubWrapper(ConfigurationContext configurationContext, String targetEndpoint) throws AxisFault {
super(configurationContext, targetEndpoint);
}
/**
* #param configurationContext
* #param targetEndpoint
* #param useSeparateListener
* #throws AxisFault
*/
public MyWSStubWrapper(ConfigurationContext configurationContext, String targetEndpoint, boolean useSeparateListener) throws AxisFault {
super(configurationContext, targetEndpoint, useSeparateListener);
}
#Override
protected void addHeader(OMElement omElementToadd, SOAPEnvelope envelop, boolean mustUnderstand) {
SOAPHeaderBlock soapHeaderBlock = envelop.getHeader().addHeaderBlock(omElementToadd.getLocalName(), omElementToadd.getNamespace());
OMNode omNode = null;
// add child elements
for (Iterator iter = omElementToadd.getChildren(); iter.hasNext();) {
omNode = (OMNode) iter.next();
iter.remove();
soapHeaderBlock.addChild(omNode);
}
OMAttribute omatribute = null;
// add attributes
for (Iterator iter = omElementToadd.getAllAttributes(); iter.hasNext();) {
omatribute = (OMAttribute) iter.next();
soapHeaderBlock.addAttribute(omatribute);
}
}
}
Bear in mind that the above code will completely remove the soapenv:mustUnderstand="0|1" from your headers if you wish to added you need to call soapHeaderBlock.setMustUnderstand(true|false); somewhere in your code.

1) Are you using the Axis SOAPHeaderElement and if so, are you setting the mustUnderstand setter to false?
2) Since you're generating your client with wsdl2java, it's quite possible that the WSDL (or more accurately, the schema) contains the mustUnderstand attribute on an element referenced in your SOAP binding. So when wsdlToJava generates the client code, those attributes will naturally be added. See here for a description of the mustUnderstand attribute.
If modifying the WSDL is out of the question, and you must remove this attribute from the header, then I suppose you can try to do it with a handler
3) Not advisable, but if you really MUST remove this attribute then I suppose you can add a client side handler that alters the header: http://ws.apache.org/axis/java/apiDocs/org/apache/axis/handlers/package-summary.html

i am using axis 1.4 client with ws security
in my case as Reinhard said
this worked
MyService service = new MyServiceLocator();
MyServicePortType port = service.getMyServiceHttpsSoap11Endpoint();
((Stub) port)._setProperty(Call.CHECK_MUST_UNDERSTAND, Boolean.FALSE);

Related

Removing XML declaration in JAX-WS message

I'm trying to invoke a webservice using Java code.
So I used JAX-WS and JAXB to generate my object from wsdl file.
When I invoke the webservice it respond with this error:
Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: The [javax.xml.transform.TransformerException] occurred during XSLT transformation: javax.xml.transform.TransformerException: org.xml.sax.SAXParseException: The XML declaration must end with "?>".
Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: The [javax.xml.transform.TransformerException] occurred during XSLT transformation: javax.xml.transform.TransformerException: org.xml.sax.SAXParseException: The XML declaration must end with "?>".
at com.sun.xml.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:189)
at com.sun.xml.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:122)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:119)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:89)
at com.sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.java:118)
So with wireshark I analysed the xml message that is being sent. And tried to resend it with soapUI.
And found out that my xml contains the xml declaration
<?xml version='1.0' encoding='UTF-8'?>
When I remove it from SoapUI and resend it. The message goes ok.
My java code goes like this:
public static Data receiveSIBS(webserviceclient.Data input) {
webserviceclient.Starter service = new webserviceclient.Starter();
webserviceclient.PortType port = service.getSOAPEventSource();
BindingProvider bp = (BindingProvider) port;
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpoint);
return port.receiveSIBS(input);
}
How can I generate my message in Java without this xml declaration?
because the xml message is all generated with JAX-WS and JAXB.
Thanks in advanced!
Found my own solution!
First, as referred in other post, I implemented a SOAPHandler to edit this two properties:
soapMsg.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, "UTF-16");
soapMsg.setProperty(SOAPMessage.WRITE_XML_DECLARATION, "false");
But although this two properties change message instance inside handleMessage() method, it won't be sent like it, and message with default xml declaration is sent.
Instead of setting this properties the solution was to set this two NamespaceDeclaration:
SOAPEnvelope env = sp.getEnvelope();
env.addNamespaceDeclaration("xsd", "http://www.w3.org/2001/XMLSchema");
env.addNamespaceDeclaration("xsi", "http://www.w3.org/2001/XMLSchema-instance");
I don't understand why we get "The XML declaration must end with "?>"" error. Because my solution didn't removed xml declaration. Might be related to xml structure (but I don't have enough knowledge to confirm it).
I need to refer http://blog.jdevelop.eu/?p=67 post that let me to this solution, and some debug code is from this post.
Following I put my complete CustomHandler class so it can held anyone.
import java.io.ByteArrayOutputStream;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
/**
*
* #author Daniel Chang Yan
*/
public class CustomHandler implements SOAPHandler<SOAPMessageContext> {
public boolean handleMessage(SOAPMessageContext context) {
Boolean isOutbound
= (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (isOutbound != null && isOutbound) {
SOAPMessage soapMsg = context.getMessage();
try {
//Properties always rewritten by jaxws, no matter what is set here
//soapMsg.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, "UTF-16");
//soapMsg.setProperty(SOAPMessage.WRITE_XML_DECLARATION, "false");
// get SOAP-Part
SOAPPart sp = soapMsg.getSOAPPart();
//edit Envelope
SOAPEnvelope env = sp.getEnvelope();
env.addNamespaceDeclaration("xsd", "http://www.w3.org/2001/XMLSchema");
env.addNamespaceDeclaration("xsi", "http://www.w3.org/2001/XMLSchema-instance");
} catch (SOAPException e) {
throw new RuntimeException(e);
}
// print SOAP-Message
System.out.println("Direction=outbound (handleMessage)...");
dumpSOAPMessage(soapMsg);
} else {
// INBOUND
System.out.println("Direction=inbound (handleMessage)...");
SOAPMessage msg = ((SOAPMessageContext) context).getMessage();
dumpSOAPMessage(msg);
}
return true;
}
public Set<QName> getHeaders() {
return null;
}
public boolean handleFault(SOAPMessageContext context) {
System.out.println("ServerSOAPHandler.handleFault");
boolean outbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outbound) {
System.out.println("Direction=outbound (handleFault)...");
} else {
System.out.println("Direction=inbound (handleFault)...");
}
if (!outbound) {
try {
SOAPMessage msg = ((SOAPMessageContext) context).getMessage();
dumpSOAPMessage(msg);
if (context.getMessage().getSOAPBody().getFault() != null) {
String detailName = null;
try {
detailName = context.getMessage().getSOAPBody().getFault().getDetail().getFirstChild().getLocalName();
System.out.println("detailName=" + detailName);
} catch (Exception e) {
}
}
} catch (SOAPException e) {
e.printStackTrace();
}
}
return true;
}
public void close(MessageContext mc) {
}
/**
* Dump SOAP Message to console
*
* #param msg
*/
private void dumpSOAPMessage(SOAPMessage msg) {
if (msg == null) {
System.out.println("SOAP Message is null");
return;
}
//System.out.println("");
System.out.println("--------------------");
System.out.println("DUMP OF SOAP MESSAGE");
System.out.println("--------------------");
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
msg.writeTo(baos);
System.out.println(baos.toString(getMessageEncoding(msg)));
// show included values
String values = msg.getSOAPBody().getTextContent();
System.out.println("Included values:" + values);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Returns the message encoding (e.g. utf-8)
*
* #param msg
* #return
* #throws javax.xml.soap.SOAPException
*/
private String getMessageEncoding(SOAPMessage msg) throws SOAPException {
String encoding = "utf-8";
if (msg.getProperty(SOAPMessage.CHARACTER_SET_ENCODING) != null) {
encoding = msg.getProperty(SOAPMessage.CHARACTER_SET_ENCODING).toString();
}
return encoding;
}
}

Create SAML response based on SAML request

I have developed a Java web application, and I want to implement SAML. These are the steps I believe is right to implement SAML.
The Service Provider(SP, my application in this case) sends a SAML authentication request to IdP.
The IdP then validates it and create a SAML response assertion and signs it with the certificate, and send back to SP.
SP then validates it with public key of certificate in keystore, and proceeds further based on that.
I have got a sample code and I am able to create SAML request and its like this
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_c7b796f4-bc16-4fcc-8c1d-36befffc39c2" Version="2.0"
IssueInstant="2014-10-30T11:21:08Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
AssertionConsumerServiceURL="http://localhost:8080/mywebapp/consume.jsp">
<saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">http://localhost:8080/mywebapp
</saml:Issuer>
<samlp:NameIDPolicy
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified"
AllowCreate="true"></samlp:NameIDPolicy>
<samlp:RequestedAuthnContext Comparison="exact">
<saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
I can encode it and send to IdP.
I want to create sample Java code to get this SAML request and then create a SAML response.
How can I decode the request and validate it and create response? And Do I need to sign the saml response with certificate? and then send back to the SP?
Thanks.
Although this is an old post, I am adding sample code and references which I found useful.
SAMLResponse = hreq.getParameter("SAMLResponse");
InputSource inputSource = new InputSource(new StringReader(SAMLResponse));
SAMLReader samlReader = new SAMLReader();
response2 = org.opensaml.saml2.core.Response)samlReader.readFromFile(inputSource);
Now Validate the digital signature :
org.opensaml.saml2.core.Response response2 = (org.opensaml.saml2.core.Response)samlReader.readFromFile(inputSource);
//To fetch the digital signature from the response.
Signature signature = response2.getSignature();
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(domainName);
//pull out the public key part of the certificate into a KeySpec
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(certificate.getPublicKey().getEncoded());
//get KeyFactory object that creates key objects, specifying RSA - java.security.KeyFactory
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
//generate public key to validate signatures
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
//we have the public key
BasicX509Credential publicCredential = new BasicX509Credential();
//add public key value
publicCredential.setPublicKey(publicKey);
//create SignatureValidator
SignatureValidator signatureValidator = new SignatureValidator(publicCredential);
//try to validate
try{
signatureValidator.validate(signature);
catch(Exception e){
//
}
Now fetch the assertion map :
samlDetailsMap = setSAMLDetails(response2);
In the above logic use the below private method to pull all the assertion attributes. Finally you will have map of all the fields that are sent to you.
private Map<String, String> setSAMLDetails(org.opensaml.saml2.core.Response response2){
Map<String, String> samlDetailsMap = new HashMap<String, String>();
try {
List<Assertion> assertions = response2.getAssertions();
LOGGER.error("No of assertions : "+assertions.size());
for(Assertion assertion:assertions){
List<AttributeStatement> attributeStatements = assertion.getAttributeStatements();
for(AttributeStatement attributeStatement: attributeStatements){
List<Attribute> attributes = attributeStatement.getAttributes();
for(Attribute attribute: attributes){
String name = attribute.getName();
List<XMLObject> attributes1 = attribute.getAttributeValues();
for(XMLObject xmlObject : attributes1){
if(xmlObject instanceof XSString){
samlDetailsMap.put(name, ((XSString) xmlObject).getValue());
LOGGER.error("Name is : "+name+" value is : "+((XSString) xmlObject).getValue());
}else if(xmlObject instanceof XSAnyImpl){
String value = ((XSAnyImpl) xmlObject).getTextContent();
samlDetailsMap.put(name, value);
}
}
}
}
}
} catch (Exception e) {
LOGGER.error("Exception occurred while setting the saml details");
}
LOGGER.error("Exiting from setSAMLDetails method");
return samlDetailsMap;
}
Add new class SAMLReader as below :
import java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.opensaml.DefaultBootstrap;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.UnmarshallingException;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class SAMLReader {
private static DocumentBuilder builder;
static{
try{
DefaultBootstrap.bootstrap ();
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance ();
factory.setNamespaceAware (true);
builder = factory.newDocumentBuilder ();
}catch (Exception ex){
ex.printStackTrace ();
}
}
/**
*
* #param filename
* #return
* #throws IOException
* #throws UnmarshallingException
* #throws SAXException
*/
public XMLObject readFromFile (String filename)
throws IOException, UnmarshallingException, SAXException{
return fromElement (builder.parse (filename).getDocumentElement ());
}
/**
*
* #param is
* #return
* #throws IOException
* #throws UnmarshallingException
* #throws SAXException
*/
public XMLObject readFromFile (InputStream is)
throws IOException, UnmarshallingException, SAXException{
return fromElement (builder.parse (is).getDocumentElement ());
}
/**
*
* #param is
* #return
* #throws IOException
* #throws UnmarshallingException
* #throws SAXException
*/
public XMLObject readFromFile (InputSource is)
throws IOException, UnmarshallingException, SAXException{
return fromElement (builder.parse (is).getDocumentElement ());
}
/**
*
* #param element
* #return
* #throws IOException
* #throws UnmarshallingException
* #throws SAXException
*/
public static XMLObject fromElement (Element element)
throws IOException, UnmarshallingException, SAXException{
return Configuration.getUnmarshallerFactory ()
.getUnmarshaller (element).unmarshall (element);
}
}
The steps you've listed are more or less correct. The only thing I'd point to is that you have to be careful with the meaning if the word sends (ex. in "SP ... sends a SAML authentication request to IdP"). SAML allows authentications scenarios with zero direct communication between SP and IdP.
Another small addition is that SP may also sign his request, so you may have signature validation on both sides. Validation on the SP side is obligatory.
If you want to implement SAML, you may want to check one of the existing solutions, for example Shibboleth. If you're on platforms like Spring and JBoss you may want to check Spring Security SAML or JBoss PicketLink. If you want to go lower-level, check OpenSAML.
In my corp we have JBoss as standard and are very happy with PicketLink.

Adding custom HTTP headers to Axis 1.4 web service responses

I'm trying to add custom HTTP headers to Axis 1.4 web servers.
I've created a handler which extends BasicHandler:
public class HttpHeaderHandler extends BasicHandler {
.
.
.
#Override
public void invoke(org.apache.axis.MessageContext arg0) throws AxisFault {
LOG.trace("invoke called");
Hashtable ht = (Hashtable)ctx.getProperty(HTTPConstants.RESPONSE_HEADERS);
if(ht == null) {
ht = new Hashtable();
}
ht.put("custom-header", "Hello");
ctx.setProperty(HTTPConstants.RESPONSE_HEADERS, ht);
}
.
.
.
}
I've added the following to server-config.wsdd:
.
.
.
<transport name="http">
<requestFlow>
<handler type="URLMapper" />
<handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler" />
</requestFlow>
<responseFlow>
<handler type="java:com.my.package.HttpHeaderHandler" />
</responseFlow>
</transport>
.
.
.
I can see that the invoke method is being called as the logging is appearing in the log file but the custom header is not being added to the response.
Any suggestions appreciated.
I was able to do this on a org.apache.axis.Stub instance by doing the following:
private Stub setHeaders(Stub stub, Hashtable<String, String> headers){
stub._setProperty(HTTPConstants.REQUEST_HEADERS, headers);
return stub;
}
Note that it is REQUIRED that the value argument to _setProperty() be a java.util.Hashtable (it gets cast later on by Axis when the Stub is used)
I added apikey for request header thanks for #romeara answer here . And it works.
Axis 1.4 sending client request from java.
YourStub stub = new YourStub();
Hashtable<String, String> headers = new Hashtable<String, String>();
headers.put("apikey", "xxxxxxxxxxxxxxxxxxxx");
stub._setProperty(HTTPConstants.REQUEST_HEADERS, headers);
I remember using the stub files generated to add HTTP user and password, check this link and locate the code that says:
_call.setProperty(org.apache.axis.client.Call.SEND_TYPE_ATTR, Boolean.FALSE);
http://www.coderanch.com/t/225102/Web-Services/java/Axis-username-password-auth-stubs
That kind of modification works.
This is what we have done
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
/**
* This method is to be used for secure SOAP calls.
* Method created as Axis 1.4 strips the security header which compiling the Java classes.
* #param username
* #param password
* #return SOAP Header
* #throws SOAPException
*/
public static SOAPHeaderElement createCustomSOAPHeader(String username, String password) throws SOAPException {
SOAPHeaderElement oHeaderElement;
SOAPElement oElement;
//Header
oHeaderElement = new SOAPHeaderElement("http://siebel.com/webservices", "Security");
oHeaderElement.setPrefix("web");
oHeaderElement.setMustUnderstand(false);
//Elements for the Header
oElement = oHeaderElement.addChildElement("UsernameToken");
oElement.addTextNode(username);
oElement = oHeaderElement.addChildElement("PasswordText");
oElement.addTextNode(password);
oElement = oHeaderElement.addChildElement("SessionType");
oElement.addTextNode("None");
return oHeaderElement;
}
Hope this helps.

Can I redirect to a valid Wicket page when attempting to access a non-existent page?

Introduction
If a user of my Apache Wicket web application (which runs on GAE/J) attempts to visit a non-existent page, for example:
http://[MyURL]/admin/PageSubscribeX
the web framework logs the following warning:
org.apache.wicket.core.util.lang.WicketObjects resolveClass: Could not resolve class [[...].admin.PageSubscribeX]
java.lang.ClassNotFoundException: [...].admin.PageSubscribeX
at com.google.appengine.runtime.Request.process-78915baf06af5f31(Request.java)
at java.lang.Class.forName(Class.java:133)
at org.apache.wicket.application.AbstractClassResolver.resolveClass(AbstractClassResolver.java:108)
at org.apache.wicket.core.util.lang.WicketObjects.resolveClass(WicketObjects.java:71)
at org.apache.wicket.core.request.mapper.AbstractComponentMapper.getPageClass(AbstractComponentMapper.java:139)
at org.apache.wicket.core.request.mapper.PackageMapper.parseRequest(PackageMapper.java:148)
...
at com.google.tracing.TraceContext$TraceContextRunnable.run(TraceContext.java:458)
at java.lang.Thread.run(Thread.java:679)
and GAE/J responds with an error 404 page and the text Error: NOT_FOUND.
I know that currently with GAE/J I "cannot customize the 404 response page when no servlet mapping is defined for a URL".
My question
Is there any way I can specify a Wicket page for the response when no servlet mapping is defined for a URL? Alternatively, is there some servlet mapping I can define to map "all URLs not found" to a Wicket page of my choice?
My software environment is:
Wicket: 6.2.0
GAE/J: 1.7.3
Java: 1.6.0_37.
A failed attempt
Following the comments of #biziclop, I have tried the following, but this failed. All I got was my error page PageError showing every time....
My code in my WebApplication#init() was:
ICompoundRequestMapper crmRoot = getRootRequestMapperAsCompound();
URLNotFoundMapper mapperURLNotFound = new URLNotFoundMapper(null,
PageError.class);
crmRoot.add(mapperURLNotFound);
setRootRequestMapper(crmRoot);
My new mapper URLNotFoundMapper was
/**
* This mapper class is intended to be the mapper of last resort, to be used
* if all other mappers cannot handle the URL of the current request.
* <br/>
* This mapper will cause a defined (error) page to be shown.
*/
public class URLNotFoundMapper extends BookmarkableMapper
{
private IRequestMapper m_rmRoot = null;
private Class<? extends IRequestablePage> m_classPage = null;
/**
* Constructor.
* #param rmRoot
* The application's previous root request mapper.
* If this is <code>null</code> then it is ignored.
* #param clError
* The class of the page which should handle erroneous requests.
* This must not be <code>null</code>.
*/
public URLNotFoundMapper(IRequestMapper rmRoot,
final Class<? extends IRequestablePage> clError)
{
m_rmRoot = rmRoot;
m_classPage = clError;
}
/**
* Use this mapper as the last option.
* So let all other mappers try to handle the request first.
* #param request
* The request.
* #return
* The score of the application's previous root request mapper.
*/
#Override
public int getCompatibilityScore(Request request)
{
return Integer.MIN_VALUE;
}
/**
* This method returns an <code>IRequestHandler</code> for the bookmarkable
* error page.
* #param request
* #return
* An <code>IRequestHandler</code> capable of processing a bookmarkable
* request.
*
*/
#Override
public IRequestHandler mapRequest(Request request)
{
IRequestHandler rhResult = null;
if (m_rmRoot != null)
rhResult = m_rmRoot.mapRequest(request);
if (rhResult != null)
rhResult = null; // Another mapper can handle this
else
{
rhResult = processBookmarkable(m_classPage, null);
}
return rhResult;
}
#Override
public Url mapHandler(IRequestHandler requestHandler)
{
Url urlResult = null;
if (m_rmRoot != null)
urlResult = m_rmRoot.mapHandler(requestHandler);
if (urlResult != null)
urlResult = null; // Another mapper can handle this
else
{
PageInfo info = new PageInfo();
UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(info, null),
m_classPage, null);
urlResult = buildUrl(urlInfo);
}
return urlResult;
}
/**
* #return
* The URL info for the bookmarkable error page.
*/
#Override
protected UrlInfo parseRequest(Request request)
{
UrlInfo uiResult = null;
uiResult = new UrlInfo(null, m_classPage, null);
return uiResult;
}
}
you need to configure the 404 http error in your webapp, take a look here
The questioner's extract from the link above is:
<error-page>
<error-code>404</error-code>
<location>/[URI portion to point to a customer error page]</location>
</error-page>

How to obtain a kerberos service ticket via GSS-API?

Does anyone know how to get a service ticket from the Key Distribution Center (KDC) using the Java GSS-API?
I have a thick-client-application that first authenticates via JAAS using the Krb5LoginModule to fetch the TGT from the ticket cache (background: Windows e.g. uses a kerberos implementation and stores the ticket granting ticket in a secure memory area). From the LoginManager I get the Subject object which contains the TGT. Now I hoped when I create a specific GSSCredential object for my service, the service ticket will be put into the Subject's private credentials as well (I've read so somewhere in the web). So I have tried the following:
// Exception handling ommitted
LoginContext lc = new LoginContext("HelloEjbClient", new DialogCallbackHandler());
lc.login()
Subject.doAs(lc.getSubject(), new PrivilegedAction() {
public Object run() {
GSSManager manager = GSSManager.getInstance();
GSSName clientName = manager.createName("clientUser", GSSName.NT_USER_NAME);
GSSCredential clientCreds = manager.createCredential(clientName, 8 * 3600, createKerberosOid(), GSSCredential.INITIATE_ONLY);
GSSName serverName = manager.createName("myService#localhost", GSSName.NT_HOSTBASED_SERVICE);
manager.createCredential(serverName, GSSCredential.INDEFINITE_LIFETIME, createKerberosOid(), GSSCredential.INITIATE_ONLY);
return null;
}
private Oid createKerberosOid() {
return new Oid("1.2.840.113554.1.2.2");
}
});
Unfortunately I get a GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt).
My understanding of getting the service ticket was wrong. I do not need to get the credentials from the service - this is not possible on the client, because the client really doesn't have a TGT for the server and therefore doesn't have the rights to get the service credentials.
What's just missing here is to create a new GSSContext and to initialize it. The return value from this method contains the service ticket, if I have understood that correctly. Here is a working code example. It must be run in a PrivilegedAction on behalf of a logged in subject:
GSSManager manager = GSSManager.getInstance();
GSSName clientName = manager.createName("clientUser", GSSName.NT_USER_NAME);
GSSCredential clientCred = manager.createCredential(clientName,
8 * 3600,
createKerberosOid(),
GSSCredential.INITIATE_ONLY);
GSSName serverName = manager.createName("http#server", GSSName.NT_HOSTBASED_SERVICE);
GSSContext context = manager.createContext(serverName,
createKerberosOid(),
clientCred,
GSSContext.DEFAULT_LIFETIME);
context.requestMutualAuth(true);
context.requestConf(false);
context.requestInteg(true);
byte[] outToken = context.initSecContext(new byte[0], 0, 0);
System.out.println(new BASE64Encoder().encode(outToken));
context.dispose();
The outToken contains then contains the Service Ticket. However this is not the way the GSS-API was meant to be used. Its goal was to hide those details to the code, so it is better to establish a GSSContext using the GSS-API on both sides. Otherwise you really should know what you are doing because of potential security holes.
For more information read the Sun SSO tutorial with kerberos more carefully than I did.
EDIT:
Just forgot that I am using Windows XP with SP2. There is a new "feature" in this version of Windows that disallows using the TGT in the Windows RAM. You have to edit the registry to allow this. For more information have a look at the JGSS Troubleshooting page topic in case you experience a "KrbException: KDC has no support for encryption type (14)" like I did.
I had a lot of problems to use this code, but I have at least a solution. I post it here, perhaps it will help some of you...
/**
* Tool to retrieve a kerberos ticket. This one will not be stored in the windows ticket cache.
*/
public final class KerberosTicketRetriever
{
private final static Oid KERB_V5_OID;
private final static Oid KRB5_PRINCIPAL_NAME_OID;
static {
try
{
KERB_V5_OID = new Oid("1.2.840.113554.1.2.2");
KRB5_PRINCIPAL_NAME_OID = new Oid("1.2.840.113554.1.2.2.1");
} catch (final GSSException ex)
{
throw new Error(ex);
}
}
/**
* Not to be instanciated
*/
private KerberosTicketRetriever() {};
/**
*
*/
private static class TicketCreatorAction implements PrivilegedAction
{
final String userPrincipal;
final String applicationPrincipal;
private StringBuffer outputBuffer;
/**
*
* #param userPrincipal p.ex. <tt>MuelleHA#MYFIRM.COM</tt>
* #param applicationPrincipal p.ex. <tt>HTTP/webserver.myfirm.com</tt>
*/
private TicketCreatorAction(final String userPrincipal, final String applicationPrincipal)
{
this.userPrincipal = userPrincipal;
this.applicationPrincipal = applicationPrincipal;
}
private void setOutputBuffer(final StringBuffer newOutputBuffer)
{
outputBuffer = newOutputBuffer;
}
/**
* Only calls {#link #createTicket()}
* #return <tt>null</tt>
*/
public Object run()
{
try
{
createTicket();
}
catch (final GSSException ex)
{
throw new Error(ex);
}
return null;
}
/**
*
* #throws GSSException
*/
private void createTicket () throws GSSException
{
final GSSManager manager = GSSManager.getInstance();
final GSSName clientName = manager.createName(userPrincipal, KRB5_PRINCIPAL_NAME_OID);
final GSSCredential clientCred = manager.createCredential(clientName,
8 * 3600,
KERB_V5_OID,
GSSCredential.INITIATE_ONLY);
final GSSName serverName = manager.createName(applicationPrincipal, KRB5_PRINCIPAL_NAME_OID);
final GSSContext context = manager.createContext(serverName,
KERB_V5_OID,
clientCred,
GSSContext.DEFAULT_LIFETIME);
context.requestMutualAuth(true);
context.requestConf(false);
context.requestInteg(true);
final byte[] outToken = context.initSecContext(new byte[0], 0, 0);
if (outputBuffer !=null)
{
outputBuffer.append(String.format("Src Name: %s\n", context.getSrcName()));
outputBuffer.append(String.format("Target : %s\n", context.getTargName()));
outputBuffer.append(new BASE64Encoder().encode(outToken));
outputBuffer.append("\n");
}
context.dispose();
}
}
/**
*
* #param realm p.ex. <tt>MYFIRM.COM</tt>
* #param kdc p.ex. <tt>kerbserver.myfirm.com</tt>
* #param applicationPrincipal cf. {#link #TicketCreatorAction(String, String)}
* #throws GSSException
* #throws LoginException
*/
static public String retrieveTicket(
final String realm,
final String kdc,
final String applicationPrincipal)
throws GSSException, LoginException
{
// create the jass-config-file
final File jaasConfFile;
try
{
jaasConfFile = File.createTempFile("jaas.conf", null);
final PrintStream bos = new PrintStream(new FileOutputStream(jaasConfFile));
bos.print(String.format(
"Krb5LoginContext { com.sun.security.auth.module.Krb5LoginModule required refreshKrb5Config=true useTicketCache=true debug=true ; };"
));
bos.close();
jaasConfFile.deleteOnExit();
}
catch (final IOException ex)
{
throw new IOError(ex);
}
// set the properties
System.setProperty("java.security.krb5.realm", realm);
System.setProperty("java.security.krb5.kdc", kdc);
System.setProperty("java.security.auth.login.config",jaasConfFile.getAbsolutePath());
// get the Subject(), i.e. the current user under Windows
final Subject subject = new Subject();
final LoginContext lc = new LoginContext("Krb5LoginContext", subject, new DialogCallbackHandler());
lc.login();
// extract our principal
final Set<Principal> principalSet = subject.getPrincipals();
if (principalSet.size() != 1)
throw new AssertionError("No or several principals: " + principalSet);
final Principal userPrincipal = principalSet.iterator().next();
// now try to execute the SampleAction as the authenticated Subject
// action.run() without doAsPrivileged leads to
// No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)
final TicketCreatorAction action = new TicketCreatorAction(userPrincipal.getName(), applicationPrincipal);
final StringBuffer outputBuffer = new StringBuffer();
action.setOutputBuffer(outputBuffer);
Subject.doAsPrivileged(lc.getSubject(), action, null);
return outputBuffer.toString();
}
public static void main (final String args[]) throws Throwable
{
final String ticket = retrieveTicket("MYFIRM.COM", "kerbserver", "HTTP/webserver.myfirm.com");
System.out.println(ticket);
}
}

Categories