JAX-WS Basic authentication

webservicesIn the context of an HTTP transaction, BASIC access authentication is a method for a web browser or other client program to provide a user name and password when making a request.
This tutorial shows how to perform BASIC authentication using Apache CXF Interceptors and, as alternative, the JAX-WS WeServiceContext.


One advantage of the BASIC access authentication is that all web browsers support it. It is often used by Intranet private systems, where it’s not necessary a strict security policy.

How are credentials encoded with HTTP Basic authentication ?
Before transmission, the user name is appended with a colon and concatenated with the password. The resulting string is encoded with the Base64 algorithm. For example, given the user name 'Aladdin' and password 'open sesame', the string 'Aladdin:open sesame' is Base64 encoded, resulting in 'QWxhZGRpbjpvcGVuIHNlc2FtZQ=='.

Where are credentials stored ? not in the SOAP packet but in the HTTP Header.
Here is a DUMP of a SOAP request using BASIC Authentication:

POST /windcpms/ws/CfmsInterfaceService HTTP/1.1
Content-Type: text/xml; charset=UTF-8
Accept: */*
Authorization: Basic YWRtaW5BQUE6dGVzdG53aw==
SOAPAction: ""
User-Agent: Apache CXF 2.5.0
Cache-Control: no-cache
Pragma: no-cache
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 368

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>. . . . .</soap:Body>
</soap:Envelope>


That being said, there are several ways to achieve basic authentication. If you are using Apache CXF, a very simple and effective way is adding an Interceptor which is triggered when your Web services are Invoked.
Here is a sample one:

package com.sample.ws;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.cxf.binding.soap.interceptor.SoapHeaderInterceptor;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.Message;
import org.apache.cxf.transport.Conduit;
import org.apache.cxf.ws.addressing.EndpointReferenceType;

 

 

public class BasicAuthAuthorizationInterceptor extends
 SoapHeaderInterceptor {   

  @Override
  public void handleMessage(Message message) throws Fault {

  AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
 
 
  // If the policy is not set, the user did not specify credentials.
  // 401 is sent to the client to indicate that authentication is required.
  if (policy == null) {
    sendErrorResponse(message, HttpURLConnection.HTTP_UNAUTHORIZED);
  return;
  }

  String username = policy.getUserName();
  String password = policy.getPassword();


 // CHECK USERNAME AND PASSWORD
  if (!checkLogin(username,password)) {
    System.out.println("handleMessage: Invalid username or password for user: " +   policy.getUserName());
  sendErrorResponse(message, HttpURLConnection.HTTP_FORBIDDEN);
  }
 }

  private boolean checkLogin(String username, String password) {
   if (username.equals("admin") && password.equals("admin")) {
    return true;
  }
   return false;
  }

  private void sendErrorResponse(Message message, int responseCode) {
   Message outMessage = getOutMessage(message);
   outMessage.put(Message.RESPONSE_CODE, responseCode);
 
   // Set the response headers
   @SuppressWarnings("unchecked")
   Map<String, List<String>> responseHeaders =  (Map<String, List<String>>)    message.get(Message.PROTOCOL_HEADERS);
 
   if (responseHeaders != null) {
     responseHeaders.put("WWW-Authenticate", Arrays.asList(new String[] { "Basic realm=realm" }));
     responseHeaders.put("Content-Length", Arrays.asList(new String[] { "0" }));
  }
  message.getInterceptorChain().abort();
   try {
    getConduit(message).prepare(outMessage);
   close(outMessage);
   } catch (IOException e) {
      e.printStackTrace();
   }
 }

  private Message getOutMessage(Message inMessage) {
   Exchange exchange = inMessage.getExchange();
   Message outMessage = exchange.getOutMessage();
   if (outMessage == null) {
    Endpoint endpoint = exchange.get(Endpoint.class);
    outMessage = endpoint.getBinding().createMessage();
    exchange.setOutMessage(outMessage);
   }
    outMessage.putAll(inMessage);
     return outMessage;
    }

  private Conduit getConduit(Message inMessage) throws IOException {
   Exchange exchange = inMessage.getExchange();
   EndpointReferenceType target = exchange.get(EndpointReferenceType.class);
   Conduit conduit = exchange.getDestination().getBackChannel(inMessage, null, target);
   exchange.setConduit(conduit);
   return conduit;
  }

  private void close(Message outMessage) throws IOException {
   OutputStream os = outMessage.getContent(OutputStream.class);
   os.flush();
   os.close();
  }
 
}

The above interceptor needs to be registered either with a simple annotation at Class level of your Web service:


@InInterceptors(interceptors = "com.sample.ws.BasicAuthAuthorizationInterceptor")

or using Apache CXF configuration file:

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:cxf="http://cxf.apache.org/core"
 xmlns:jaxws="http://cxf.apache.org/jaxws"
 xsi:schemaLocation="
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
 http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
 http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

 <import resource="classpath:META-INF/cxf/cxf-all.xml" />

 <cxf:inInterceptors>
   <ref bean="basicAuthAuthorizationInterceptor"/>
 </cxf:inInterceptors>

 </cxf:bus>

 <bean id="basicAuthAuthorizationInterceptor"  class="com.sample.ws.BasicAuthAuthorizationInterceptor">
 </bean>
</beans>

 



Another option is using the @WebServiceContext to access the Authorization parameter contained in the Header. This is a portable solution although it needs a bit more of work to decode the credentials to Base64 to plain text.

 

 

This requires Apache commons-codec libraries. You can download them here:

 

http://commons.apache.org/codec/

 

Authentication can be performed using a simple function:


byte[] buf = Base64.decodeBase64(userpass.getBytes());

 

Here’s the full code:

@WebService
public class SecuredWSImpl implements SecuredWS {

 @Resource
 WebServiceContext wsctx;
 
 
 public void doSomething () {

 doAuthentication();
 
 // Execute WS business logic
 }
 
 private void doAuthentication() {

 

 

 MessageContext mctx = wsctx.getMessageContext();
 Map http_headers = (Map) mctx.get(MessageContext.HTTP_REQUEST_HEADERS);
 
  ArrayList list = (ArrayList) http_headers.get("Authorization");
  if (list == null || list.size() == 0) {
    throw new RuntimeException("Authentication failed! This WS needs BASIC Authentication!");
 }
 
  String userpass = (String) list.get(0);
  userpass = userpass.substring(5);
  byte[] buf = Base64.decodeBase64(userpass.getBytes());
  String credentials = new String(buf);
 
  String username = null;
  String password = null;
  int p = credentials.indexOf(":");
  if (p > -1) {
   username = credentials.substring(0, p);
   password = credentials.substring(p+1);
  }   
  else {
   throw new RuntimeException("There was an error while decoding the Authentication!");
  }
  // This should be changed to a DB / Ldap authentication check 
  if (username.equals("admin") && password.equals("admin")) { 
  System.out.println("============== Authentication OK =============");
  return;
  }
  else {
   throw new RuntimeException("Authentication failed! Wrong username / password!");
  }
 
 }
 
}


Advertisement
 

Search our tutorials