In 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!");
}
}
}
<