Skip to main content

Spring SOAP Security

Security is a critical aspect of any web service implementation. When building SOAP services with Spring Web Services, you need to protect your endpoints from unauthorized access and ensure data integrity. This guide will walk you through implementing different security mechanisms in Spring SOAP Web Services.

Introduction to SOAP Security

SOAP (Simple Object Access Protocol) services often handle sensitive data and operations that need protection. Spring Web Services provides several security options that align with industry standards and best practices. Security in SOAP services typically addresses:

  • Authentication: Verifying the identity of the client
  • Authorization: Determining if an authenticated client has permission to access a resource
  • Message integrity: Ensuring messages haven't been tampered with
  • Confidentiality: Protecting the contents of messages from unauthorized viewing

Prerequisites

Before diving into SOAP security, make sure you have:

  • Basic knowledge of Spring Framework
  • Understanding of SOAP Web Services concepts
  • Java Development Kit (JDK) 8 or higher
  • Maven or Gradle for dependency management
  • An IDE (IntelliJ IDEA, Eclipse, etc.)

Setting Up a Basic Spring SOAP Web Service

Let's start by setting up a simple Spring SOAP web service before adding security.

First, add the necessary dependencies to your pom.xml:

xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-security</artifactId>
</dependency>
</dependencies>

Basic Authentication with Username Token

The simplest form of SOAP security is using Username Token authentication. This approach embeds username and password credentials in the SOAP header.

Step 1: Configure Spring Security

Create a security configuration class:

java
@EnableWs
@Configuration
public class WebServiceSecurityConfig extends WsSecurityInterceptor {

public WebServiceSecurityConfig() {
setValidationActions("UsernameToken");
setSecurementActions("UsernameToken");

Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();
securityInterceptor.setValidationCallbackHandler(callbackHandler());
setValidationCallbackHandler(callbackHandler());
}

@Bean
public SimplePasswordValidationCallbackHandler callbackHandler() {
SimplePasswordValidationCallbackHandler callbackHandler = new SimplePasswordValidationCallbackHandler();
Properties users = new Properties();
users.setProperty("admin", "secret");
users.setProperty("user", "password");
callbackHandler.setUsers(users);
return callbackHandler;
}
}

Step 2: Register the Security Interceptor

Add the security interceptor to your endpoint adapter:

java
@Bean
public EndpointInterceptor securityInterceptor() {
return new WebServiceSecurityConfig();
}

@Bean
public XwsSecurityInterceptor securityInterceptor() {
XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
securityInterceptor.setCallbackHandler(callbackHandler());
return securityInterceptor;
}

@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(securityInterceptor());
}

Step 3: Client-Side Implementation

For clients to authenticate, they need to include security headers. Here's how a client would include a username token:

java
@Bean
public WebServiceTemplate webServiceTemplate() {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();

Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();
securityInterceptor.setSecurementActions("UsernameToken");
securityInterceptor.setSecurementUsername("admin");
securityInterceptor.setSecurementPassword("secret");

ClientInterceptor[] interceptors = new ClientInterceptor[] {securityInterceptor};
webServiceTemplate.setInterceptors(interceptors);

return webServiceTemplate;
}

Example SOAP Request with Username Token

Here's what a SOAP request with a username token looks like:

xml
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken>
<wsse:Username>admin</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">secret</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
<!-- Your request content here -->
</soapenv:Body>
</soapenv:Envelope>

Digital Signatures with X.509 Certificates

For more advanced security, you can use digital signatures with X.509 certificates to ensure message integrity and authentication.

Step 1: Generate a Keystore and Certificate

First, generate a keystore and certificate using the Java keytool:

bash
keytool -genkey -alias server -keyalg RSA -keystore server-keystore.jks -validity 365

Step 2: Configure Signature Validation

Update your security configuration:

java
@Bean
public Wss4jSecurityInterceptor securityInterceptor() {
Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();

// For incoming request validation
securityInterceptor.setValidationActions("Signature");
securityInterceptor.setValidationSignatureCrypto(crypto());

// For outgoing response signing
securityInterceptor.setSecurementActions("Signature");
securityInterceptor.setSecurementUsername("server");
securityInterceptor.setSecurementPassword("serverpass");
securityInterceptor.setSecurementSignatureCrypto(crypto());

return securityInterceptor;
}

@Bean
public CryptoFactoryBean crypto() {
CryptoFactoryBean crypto = new CryptoFactoryBean();
crypto.setKeyStoreLocation(new ClassPathResource("server-keystore.jks"));
crypto.setKeyStorePassword("serverpass");
return crypto;
}

Step 3: Client-Side Signature Implementation

The client needs to sign requests and verify responses:

java
@Bean
public Wss4jSecurityInterceptor clientSecurityInterceptor() {
Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();

// For outgoing request signing
securityInterceptor.setSecurementActions("Signature");
securityInterceptor.setSecurementUsername("client");
securityInterceptor.setSecurementPassword("clientpass");
securityInterceptor.setSecurementSignatureCrypto(clientCrypto());

// For incoming response validation
securityInterceptor.setValidationActions("Signature");
securityInterceptor.setValidationSignatureCrypto(clientCrypto());

return securityInterceptor;
}

Message Encryption

For confidentiality, you can encrypt SOAP messages so only the intended recipient can read them.

Step 1: Configure Encryption

Update your security configuration:

java
@Bean
public Wss4jSecurityInterceptor securityInterceptor() {
Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();

// For incoming request validation (decrypt)
securityInterceptor.setValidationActions("Encrypt");
securityInterceptor.setValidationDecryptionCrypto(crypto());
securityInterceptor.setValidationCallbackHandler(callbackHandler());

// For outgoing response security (encrypt)
securityInterceptor.setSecurementActions("Encrypt");
securityInterceptor.setSecurementEncryptionUser("client");
securityInterceptor.setSecurementEncryptionCrypto(crypto());

return securityInterceptor;
}

WS-Security Policy

For complex security requirements, you can define a WS-Security policy that describes the security requirements for your service.

Step 1: Define a Security Policy

Create a secure-policy.xml file:

xml
<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:ExactlyOne>
<wsp:All>
<sp:SupportingTokens>
<wsp:Policy>
<sp:UsernameToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:WssUsernameToken10/>
</wsp:Policy>
</sp:UsernameToken>
</wsp:Policy>
</sp:SupportingTokens>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>

Step 2: Add the Policy to Your WSDL

Configure your endpoint to use this policy:

java
@Bean
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema mySchema) {
DefaultWsdl11Definition wsdl = new DefaultWsdl11Definition();
wsdl.setPortTypeName("MyServicePort");
wsdl.setLocationUri("/ws");
wsdl.setTargetNamespace("http://mycompany.com/ws");
wsdl.setSchema(mySchema);

Properties soapActions = new Properties();
soapActions.setProperty("myOperation", "http://mycompany.com/myOperation");
wsdl.setSoapActions(soapActions);

WsdlDefinition definition = wsdl.getWsdlDefinition();
definition.addSchema(mySchema);

// Add policy reference
Policy policy = loadPolicy("classpath:secure-policy.xml");
((javax.wsdl.Definition) definition).addExtensibilityElement(policy);

return wsdl;
}

private Policy loadPolicy(String location) {
// Implementation to load the policy from the location
// This would typically use a PolicyBuilder from WSS4J
}

Real-World Example: Secure Banking Service

Let's put everything together in a real-world scenario: a secure banking service that allows checking account balances.

Step 1: Define the Schema

Create banking.xsd:

xml
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:tns="http://mybank.com/banking"
targetNamespace="http://mybank.com/banking"
elementFormDefault="qualified">

<xs:element name="BalanceRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="accountId" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:element name="BalanceResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="balance" type="xs:decimal"/>
<xs:element name="currency" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Step 2: Create the Endpoint with Security

java
@Endpoint
public class BankingEndpoint {

private static final String NAMESPACE_URI = "http://mybank.com/banking";
private BankingService bankingService;

@Autowired
public BankingEndpoint(BankingService bankingService) {
this.bankingService = bankingService;
}

@PayloadRoot(namespace = NAMESPACE_URI, localPart = "BalanceRequest")
@ResponsePayload
public BalanceResponse getBalance(@RequestPayload BalanceRequest request) {
// Access authenticated user information
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();

// Check if user has permission for this account
if (!bankingService.hasAccessToAccount(username, request.getAccountId())) {
throw new AccessDeniedException("Not authorized to access this account");
}

// Get balance if authorized
BalanceResponse response = new BalanceResponse();
AccountDetails details = bankingService.getAccountBalance(request.getAccountId());
response.setBalance(details.getBalance());
response.setCurrency(details.getCurrency());

return response;
}
}

Step 3: Complete Security Configuration

java
@Configuration
@EnableWs
@EnableWebSecurity
public class WebServiceConfig extends WsConfigurerAdapter {

@Bean
public XwsSecurityInterceptor securityInterceptor() {
XwsSecurityInterceptor securityInterceptor = new XwsSecurityInterceptor();
securityInterceptor.setPolicyConfiguration(new ClassPathResource("securityPolicy.xml"));
securityInterceptor.setCallbackHandler(callbackHandler());
return securityInterceptor;
}

@Bean
public SimplePasswordValidationCallbackHandler callbackHandler() {
SimplePasswordValidationCallbackHandler callbackHandler = new SimplePasswordValidationCallbackHandler();
callbackHandler.setUsersProperties(new ClassPathResource("users.properties"));
return callbackHandler;
}

@Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<>(servlet, "/ws/*");
}

@Bean(name = "banking")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema bankingSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("BankingPort");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://mybank.com/banking");
wsdl11Definition.setSchema(bankingSchema);
return wsdl11Definition;
}

@Bean
public XsdSchema bankingSchema() {
return new SimpleXsdSchema(new ClassPathResource("banking.xsd"));
}

@Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(securityInterceptor());
}
}

Step 4: Client-Side Implementation

java
@Configuration
public class SoapClientConfig {

@Bean
public WebServiceTemplate webServiceTemplate() throws Exception {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setMarshaller(marshaller());
webServiceTemplate.setUnmarshaller(marshaller());
webServiceTemplate.setDefaultUri("http://localhost:8080/ws");
webServiceTemplate.setInterceptors(new ClientInterceptor[] {securityInterceptor()});
return webServiceTemplate;
}

@Bean
public XwsSecurityInterceptor securityInterceptor() {
XwsSecurityInterceptor interceptor = new XwsSecurityInterceptor();
interceptor.setPolicyConfiguration(new ClassPathResource("client-security.xml"));

SimplePasswordValidationCallbackHandler callbackHandler = new SimplePasswordValidationCallbackHandler();
callbackHandler.setUsernamePassword("john", "password");
interceptor.setCallbackHandler(callbackHandler);

return interceptor;
}

@Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("com.mybank.schemas");
return marshaller;
}
}

Best Practices for SOAP Security

  1. Use TLS/SSL: Always secure your SOAP services with HTTPS transport-level security
  2. Combine security mechanisms: Use both transport-level and message-level security for critical applications
  3. Regular key rotation: Rotate credentials and cryptographic keys regularly
  4. Limit exposed data: Only expose the minimal data needed in responses
  5. Validate input: Always validate input data to prevent XML attacks like XXE
  6. Implement proper logging: Log security-related events for audit purposes
  7. Keep dependencies updated: Regularly update your libraries to patch security vulnerabilities

Common Security Issues and Solutions

IssueSolution
XML External Entity (XXE) attacksConfigure XML parsers to disable external entity processing
SOAP Action spoofingValidate SOAP Action headers against the operation being called
Parameter tamperingValidate all input parameters and use strict schemas
Message replay attacksImplement WS-Security timestamps and nonces
Man-in-the-middle attacksUse TLS/SSL for transport security

Summary

Spring SOAP Web Services offers robust security options to protect your services:

  • Basic authentication with username tokens for simple authentication needs
  • Digital signatures to ensure message integrity and sender authentication
  • Message encryption to protect sensitive data
  • WS-Security policy for comprehensive security policy definition

By implementing these security mechanisms, your SOAP services can be protected against common vulnerabilities and threats. Remember that security is a layered approach—combine multiple security measures for the best protection.

Additional Resources

Exercises

  1. Create a Spring SOAP service with basic username token authentication
  2. Extend the service to use X.509 certificates for message signing
  3. Implement message encryption for a service that handles sensitive data
  4. Develop a client that can communicate with all three security models
  5. Create a service that combines multiple security mechanisms (authentication, signing, and encryption)

By completing these exercises, you'll gain practical experience with the various security options available in Spring SOAP Web Services.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)