Introduction
Web Services provide excellent means for communicating information between a provider of information and community of interest. With increasing popularity of SOA based implementations development and usage of web services has increased more than ever. However, web service implementers often ignore securing these communications. There are several ways to secure web service communication (for both SOAP and REST flavors). In this article, we describe usage of http basic auth for securing web services. For demonstration, we are utilizing Apache CXF framework, but the concepts can be applied over any other web service frameworks. Additional mechanisms such as the WS-Security (SAML assertions, certificates, user id – password tokens) and application of SSL at the app server level will be discussed in a later article.
Basic Authentication
There are two ways to turn http style basic authentication on, only one is enough to secure web services:
1. At application server level – Tomcat 6.0.32 will be used for this
2. At End point level – Spring 3.0.5 / apache CXF 2.4.2 / Tomcat 6.0.32 will be used for this
Application Server Level
1. Add the following to server.xml under <Engine> tag
a. <Realm className="org.apache.catalina.realm.MemoryRealm"/>
2. Add the following to tomcat-users.xml
a. <role rolename="role1"/>
b. <user username="role1" password="tomcat" roles="role1"/>
3. In your webservice deployment descriptor (web.xml) file, add the following:
a. <security-constraint>
b. <web-resource-collection>
c. <web-resource-name>
d. EmployeeAuthService
e. </web-resource-name>
f.
g. <url-pattern> /* </url-pattern>
h. <http-method> GET </http-method>
i. <http-method> POST </http-method>
j. </web-resource-collection>
k. <auth-constraint>
l. <!-- the same like in your tomcat-users.conf file -->
m. <role-name>role1</role-name>
n. </auth-constraint>
o. </security-constraint>
p.
q.
r. <login-config>
s. <auth-method>BASIC</auth-method>
t. </login-config>
u.
v. <security-role>
w. <description> Test role </description>
x. <role-name> role1 </role-name>
y. </security-role>
End Point Level
1. Create a SoapHeaderInterceptor in apache CXF as listed in the code below
package com.company.auth.authorizer.basicauth;
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.common.util.Base64Exception;
import org.apache.cxf.common.util.Base64Utility;
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;
import org.springframework.beans.factory.annotation.Required;
public class BasicAuthAuthorizationInterceptor extends SoapHeaderInterceptor {
private Map<String, String> users;
@Required
public void setUsers(Map<String, String> users) {
System.out.println("Inside setUsers Map");
this.users = users;
}
@Override public void handleMessage(Message message) throws Fault {
Map<String, List<String>> requestHeaders;
requestHeaders = (Map<String, List<String>>) message.get(Message.PROTOCOL_HEADERS);
List<String> authorizationLines = requestHeaders.get("authorization");
List<String> aAuthorizationLines = requestHeaders.get("Authorization");
if (authorizationLines != null)
{
System.out.println("Yay 1");
}
if (aAuthorizationLines != null)
{
System.out.println("Yay 2");
}
// This is supposed to be set by CXF, but i had to set it explicitly, so don't
// need this anymore
AuthorizationPolicy policy = message.get(AuthorizationPolicy.class);
// If the policy is not set, the user did not specify credentials
// A 401 is sent to the client to indicate that authentication is required
if (policy == null) {
System.out.println ("User attempted to log in with no credentials");
sendErrorResponse(message, HttpURLConnection.HTTP_UNAUTHORIZED);
return;
}
System.out.println ("Logging in use: " + policy.getUserName());
// Verify the password
String realPassword = users.get(policy.getUserName());
if (realPassword == null || !realPassword.equals(policy.getPassword())) {
System.out.println ("Invalid username or password for user: " + policy.getUserName());
sendErrorResponse(message, HttpURLConnection.HTTP_FORBIDDEN);
}
}
private void sendErrorResponse(Message message, int responseCode) {
Message outMessage = getOutMessage(message);
outMessage.put(Message.RESPONSE_CODE, responseCode);
// Set the response headers
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 {
System.out.println("inmessage is: " + inMessage);
Exchange exchange = inMessage.getExchange();
if (exchange == null )
System.out.println("Exchnage is null");
EndpointReferenceType target = exchange.get(EndpointReferenceType.class);
if (target == null )
System.out.println("target is null");
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();
}
}
2. Create Spring application configuration that is the standard for instantiating CXF web services as shown below:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:http-conf="http://cxf.apache.org/transports/http/configuration"
xmlns:sec="http://cxf.apache.org/configuration/security"
xmlns:cxf="http://cxf.apache.org/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/transports/http/configuration
http://cxf.apache.org/schemas/configuration/http-conf.xsd
http://cxf.apache.org/configuration/security
http://cxf.apache.org/schemas/configuration/security.xsd"
>
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
<bean id="BasicAuthAuthorizationInterceptor" class="com.company.auth.authorizer.basicauth.BasicAuthAuthorizationInterceptor">
<property name="users">
<map>
<entry key="synapseadmin" value="welcome1"/>
</map>
</property>
</bean>
<jaxws:endpoint id="auth"
implementor="com.company.auth.service.AuthServiceImpl"
address="/cxfAuth">
<jaxws:inInterceptors>
<ref bean="BasicAuthAuthorizationInterceptor"/>
</jaxws:inInterceptors>
</jaxws:endpoint>
</beans>
Client
In both cases the client code for calling web service can be written as below. Please note that client methods using both CXF jars and regular java net URL calls is used to demonstrate that basic auth parameters can be passed from various clients:
package com.company.auth.service.client;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import javax.xml.ws.handler.MessageContext;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;
import com.company.auth.service.Employee;
import com.company.auth.service.AuthService;
import com.company.auth.service.EmployeeNotFoundException;
import com.company.auth.service.EmployeeNotFoundException_Exception;
public final class Client {
private Client() {
}
public static void main(String args[]) throws Exception {
method1();
// method2();
}
public static void method1() {
String ws_url = "http://localhost:8080/EmployeeAuthService/services/cxfAuth";
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
// factory.getInInterceptors().add(new BasicAuthAuthorizationInterceptor());
// factory.getOutInterceptors().add(new BasicAuthAuthorizationInterceptor());
factory.setServiceClass(AuthService.class);
factory.setAddress(ws_url);
// factory.setWsdlLocation("file:c://dev//junk//EmployeeAuthService.wsdl");
// factory.setWsdlLocation(ws_url);
AuthService client = (AuthService) factory.create();
org.apache.cxf.endpoint.Client proxy = ClientProxy.getClient(client);
//add username and password for container authentication
BindingProvider bp = (BindingProvider) client;
bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "synapseadmin");
bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "welcome1");
/*
*
AuthorizationPolicy policy = new AuthorizationPolicy ();
policy.setUserName("tomcat");
policy.setPassword("tomcat");
bp.getRequestContext().put(AuthorizationPolicy.class.getName(), policy);
*/
HTTPConduit conduit = (HTTPConduit) proxy.getConduit();
AuthorizationPolicy auth = conduit.getAuthorization();
if ( null == auth ) {
System.out.println (" auth policy is null");
auth = new AuthorizationPolicy();
}
auth.setUserName( "synapseadmin" );
auth.setPassword( "welcome1" );
com.company.auth.service.Employee employee;
try {
employee = client.getEmployee("0223938");
System.out.println("Server said: " + employee.getLastName() + ", " + employee.getFirstName());
} catch (EmployeeNotFoundException_Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.exit(0);
}
public static void method2() {
// String WS_URL = "http://localhost:8080/EmployeeAuthService/services/cxfAuth?wsdl";
String WS_URL = "file:c://dev//junk//EmployeeAuthService.wsdl";
URL url = null;
try {
url = new URL(WS_URL);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// {http://service.auth.company.com/}corporateAuthService
QName qname = new QName("http://service.auth.company.com/", "corporateAuthService");
Service service = Service.create(url, qname);
AuthService port = service.getPort(AuthService.class);
//add username and password for container authentication
BindingProvider bp = (BindingProvider) port;
bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, "synapseadmin");
bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, "welcome1");
try {
System.out.println(port.getEmployee("0223938").getFirstName());
} catch (EmployeeNotFoundException_Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Server
The web services are developed using apache CXF and are listed below:
AuthService.java
package com.company.auth.service;
import javax.jws.WebService;
import javax.jws.WebParam;
import com.company.auth.bean.Employee;
import com.company.auth.service.exceptions.EmployeeNotFoundException;
@WebService
public interface AuthService {
Employee getEmployee(@WebParam(name="gid") String gid) throws EmployeeNotFoundException;
boolean isEmployee(@WebParam(name="emp") Employee emp);
}
AuthServiceImpl.java
package com.company.auth.service;
import javax.jws.WebService;
import com.company.auth.bean.Employee;
import com.company.auth.dao.EmployeeDAO;
import com.company.auth.service.exceptions.EmployeeNotFoundException;
@WebService(endpointInterface = "com.company.auth.service.AuthService", serviceName = "corporateAuthService")
public class AuthServiceImpl implements AuthService {
public Employee getEmployee(String gid) throws EmployeeNotFoundException {
EmployeeDAO dao = new EmployeeDAO();
return dao.getEmployee(gid);
}
public boolean isEmployee(Employee emp) {
EmployeeDAO dao = new EmployeeDAO();
Employee emp2 = new Employee();
try {
emp2 = dao.getEmployee(emp.getGid());
if (emp2.getFirstName() == emp.getFirstName())
return true;
} catch (EmployeeNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
}
EmployeeDAO.java
package com.company.auth.dao;
import java.util.HashSet;
import java.util.Set;
import com.company.auth.bean.Employee;
import com.company.auth.service.exceptions.EmployeeNotFoundException;
public class EmployeeDAO {
private Employee employee = new Employee();
public EmployeeDAO() {
employee.setFirstName("john");
employee.setLastName("smith");
Set privileges = new HashSet();
privileges.add("tea boy");
employee.setPrivileges(privileges);
employee.setGid("0223938");
}
public Employee getEmployee(String gid) throws EmployeeNotFoundException {
if (gid.equals("0223938"))
return employee;
else
throw new EmployeeNotFoundException("Employee is not found: " + gid);
}
}
Employee.java
package com.company.auth.bean;
import java.io.Serializable;
import java.util.Set;
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String gid;
private String lastName;
private String firstName;
private Set<String> privileges;
public Employee() {}
public Set<String> getPrivileges() {
return privileges;
}
public void setPrivileges(Set<String> privileges) {
this.privileges = privileges;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public boolean isUserInRole(String role) {
if(privileges == null) { return false; }
else { return privileges.contains(role); }
}
}
EmployeeNotFoundException.java
package com.company.auth.service.exceptions;
public class EmployeeNotFoundException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
public EmployeeNotFoundException(String string) {
super(string);
}
}