In this chapter, we will cover:
Authenticating a Web-Service call using the username token with a plain/digested password
Authenticating a Web-Service call using Spring security to authenticate a username token with a plain/digested password
Authenticating a Web-Service call using the JAAS service to authenticate a username token
Preparing pair and symmetric keystores
Securing SOAP messages using a digital signature
Authenticating a Web-Service call using X509 certificate
Encrypting/decrypting SOAP messages
WS-Security (WSS), published by OASIS, is an extension to SOAP to provide security-standard features to a Web-Service. XML and Web-Services Security (XWSS) is SUN's implementation of WSS, which is included in the Java Web-Services Developer Pack (WSDP).
XWSS is a form of message-level security in which security data is included within a SOAP message/attachment and allows security information to be transmitted with messages or attachments. For instance, while signing a message, a security token will be added to the message that is generated from the encryption of a part of the message for a specific receiver. When a sender sends this message, this token remains in the encrypted form and travels along with the message. When a receiver gets this message, the token can be decrypted only if he/she has the specific key for decryption. So if within transmission of this message, any non-authorized receiver (who doesn't have the specific key) gets this message, he/she cannot decrypt the token (this token will be used to check if the original message is altered). The originality of the message verification can be done by the regeneration of the token at the receiver's end (from the incoming message) and by comparing it with the incoming token that came along with the message.
An EndpointInterceptor
, as the name suggests, intercepts the request and performs some action prior to invoking the endpoint. EndpointInterceptors
are called before calling the appropriate endpoint to perform several processing aspects such as logging, validating, security, and so on. In earlier chapters, SoapEnvelopeLoggingInterceptor, PayloadLoggingInterceptor
, and PayloadValidatingInterceptor
were explained for logging and validation purposes.
In this chapter, and the next one, SecurityInterceptors
will be explained.
Spring-WS XwsSecurityInterceptor
is an EndpointInterceptor
for performing security operations on a request message before calling the endpoint. This interceptor, which is based on XWSS, requires a policy configuration file to operate. Here is a sample of the policy configuration file that can include several security requirements:
<xwss:SecurityConfiguration ...> <xwss:RequireTimestamp .../> <xwss:RequireUsernameToken ...../> ........ </xwss:SecurityConfiguration>
The security interceptor uses this configuration to find what security information to expect from incoming SOAP messages (on the receiver side), and what information is to be added to outgoing messages (on the sender side).
In addition, this interceptor needs one or more callBackHandlers
for security operations such as authentication, signing outgoing messages, verifying the signature of incoming messages, decryption, and encryption. These callBackHandlers
need to be registered in the application context file:
<sws:interceptors> <bean class="...XwsSecurityInterceptor"> <property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml" /> <property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml" /> <property name="callbackHandlers"> <list> <ref bean="callbackHandler1" /> <ref bean="callbackHandler2" /> .............. </list> </property> </bean> </sws:interceptors> <bean id="callbackHandler1" class=".....SimplePasswordValidationCallbackHandler"> <property name="users"> <props> <prop key="admin">secret</prop> <prop key="clinetUser">pass</prop> </props> </property> </bean> .........
This chapter presents how to apply Spring-WS XWSS to different security operations. In every recipe's project, the client applies a security operation by adding or modifying data in the outgoing message and sends it to the server. The server receives the message, extracts security information, and proceeds with the message if the security information matches the expected requirement; otherwise it returns a fault message back to the client.
For simplification, most of the recipes in this chapter use the projects used in the Integration testing using Spring-JUnit support recipe, discussed in Chapter 3, Testing and Monitoring Web Services, to set up a server and send and receive messages by client. However, in the last recipe, projects from the Creating Web-Service client for WS-Addressing endpoint recipe, discussed in Chapter 2, Building Clients for SOAP Web Services, are used for the server and client side.
Authentication simply means checking whether callers of a service are who they claim to be. One way of checking the authentication of a caller is to check the password.
XWSS provides APIs to get the usernames and passwords from incoming SOAP messages and compare them with what is defined in the configuration file. This goal will be accomplished by defining policy files for the sender and the receiver of the messages that on the sender side, client includes a username token in outgoing messages, and on the receiver side, the server expects to receive this username token along with the incoming messages for authentication.
Transmitting a plain password makes a SOAP message unsecured. XWSS provides the configuration setting in the policy file to include a digest of passwords (a hash generated from the password text by a specific algorithm) inside the sender message. On the server side, the server compares the digested password included in the incoming message with the digested password calculated from what is set in the configuration file (see the property users within the callbackHandler
bean inside spring-ws-servlet.xml)
using the same algorithms on the sender side. This recipe shows how to authenticate a Web-Service call using the username token with a plain/digest password. This recipe contains two cases. In the first case, the password will be transmitted in plain text format. However, in the second case, by changing the policy file configuration, the password will be transmitted in the digest format.
In this recipe, the project's name is LiveRestaurant_R-7.1
(for the server-side Web-Service) and has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
LiveRestaurant_R-7.1-Client
(for the client-side Web-Service) has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-ws-test-2.0.0.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
junit-4.7.jar
The following steps implement authentication using a username token with a plain password:
Register the security interceptor (XwsSecurityInterceptor
) and callbackHandler
(SimplePasswordValidationCallbackHandler
) in the application context file (applicationContext.xml
) of LiveRestaurant_R-7.1-Client
.
Add the security policy file (securityPolicy.xml
) for LiveRestaurant_R-7.1-Client
.
Register the security interceptor (XwsSecurityInterceptor
) and callbackHandler
(SimplePasswordValidationCallbackHandler
) in the application context file (spring-ws-servlet.xml
) of LiveRestaurant_R-7.1
.
Add the security policy file (securityPolicy.xml
) for LiveRestaurant_R-7.1
.
Run the following command from Liverestaurant_R-7.1:
mvn clean package tomcat:run
mvn clean package
The following is the client-side output (note the password's tag wsse:Password ...#PasswordText
) within the underlined section:
INFO: ==== Sending Message Start ====
<SOAP-ENV:Envelope ...">
<SOAP-ENV:Header>
<wsse:Security ..>
<wsu:Timestamp ...>
<wsu:Created>2011-11-06T07:19:16.225Z</wsu:Created>
<wsu:Expires>2011-11-06T07:24:16.225Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken .....>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ...#PasswordText">****</wsse:Password>
<wsse:Nonce ..#Base64Binary">...</wsse:Nonce>
<wsu:Created>2011-11-06T07:19:16.272Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest xmlns:tns="...">
.....
.......
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
.....
INFO: ==== Received Message Start ====
......
<SOAP-ENV:Envelope....">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse .....>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
The following steps implement authentication using the username token with the digest password:
Modify the security policy file (securityPolicy.xml
) of Liverestaurant_R-7.1
to get the digest password from the incoming message.
Modify the security policy file (securityPolicy.xml
) of Liverestaurant_R-7.1-Client
to send the digest password.
Run the following command from Liverestaurant_R-7.1:
mvn clean package tomcat:run
Run the following command from Liverestaurant_R-7.1-Client:
mvn clean package
The following is the client-side output (note the password's tag wsse:Password ...#PasswordDigest) within the underlined section:
Nov 6, 2011 12:19:25 PM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Sending Message Start ====
..
<SOAP-ENV:Envelope .../">
<SOAP-ENV:Header>
<wsse:Security ...>
<wsu:Timestamp ..>
<wsu:Created>2011-11-06T08:19:25.515Z</wsu:Created>
<wsu:Expires>2011-11-06T08:24:25.515Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken...>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ...#PasswordDigest">****</wsse:Password>
<wsse:Nonce ...#Base64Binary">...</wsse:Nonce>
<wsu:Created>2011-11-06T08:19:25.562Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest..">
......
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
........
INFO: ==== Received Message Start ====
<?xml version="1.0" ...>
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse ...>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
The Liverestaurant_R-7.1
project is a server-side Web-Service that requires its client to send a message along with the username token and password. The Liverestaurant_R-7.1-Client
project is a client-side test project that sends a message to the server along with the username token and password.
On the server side, XwsSecurityInterceptor
forces the server to apply the policy inside securityPolicy.xml
for all incoming messages and uses SimplePasswordValidationCallbackHandler
to compare incoming messages username/password with includes username/password in the server configuration file (see the property users within the callbackHandler
bean):
<sws:interceptors>
...
<bean class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml" />
<property name="callbackHandlers">
<list>
<ref bean="callbackHandler" />
</list>
</property>
</bean>
</sws:interceptors>
<bean id="callbackHandler"
class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler">
<property name="users">
<props>
<prop key="admin">secret</prop>
<prop key="clinetUser">pass</prop>
</props>
</property>
</bean>
In the securityPolicy.xml
file,<xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="true"/>
requires that the incoming messages have username tokens with non-encrypted passwords. useNonce="true
" indicates that each incoming message will have a random number that is not equal to the previous message:
<xwss:SecurityConfiguration dumpMessages="true" xmlns:xwss="http://java.sun.com/xml/ns/xwss/config"> <xwss:RequireTimestamp maxClockSkew="60" timestampFreshnessLimit="300"/> <xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="true"/> </xwss:SecurityConfiguration>
On the client side, XwsSecurityInterceptor
forces the client to apply the policy inside securityPolicy.xml
for all outgoing messages:
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate"> .... <property name="interceptors"> <list> <ref local="xwsSecurityInterceptor" /> </list> </property> </bean> <bean id="xwsSecurityInterceptor" class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor"> <property name="policyConfiguration" value="/securityPolicy.xml"/> <property name="callbackHandlers"> <list> <ref bean="callbackHandler"/> </list> </property> </bean> <bean id="callbackHandler" class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler"/>
In the securityPolicy.xml
file,<xwss:UsernameToken name="clinetUser" password="pass" digestPassword="false" useNonce="true"/>
includes the username token with the password for all outgoing messages:
<xwss:SecurityConfiguration dumpMessages="true" xmlns:xwss="http://java.sun.com/xml/ns/xwss/config"> <xwss:Timestamp /> <xwss:UsernameToken name="clinetUser" password="pass" digestPassword="false" useNonce="true"/> ... </xwss:SecurityConfiguration>
Here, useNonce="true
" indicates that each request will be sent out with a new random number for each message (Nonce
helps to protect against hijacking of the username token).
In the case of authentication using a username token with a plain password, since digestPassword="false
" is in both the client- and server-side policy files, you see in the output result that the message sent by the client has a username and a plain text password included in the username token:
<wsse:UsernameToken ....>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ..>****</wsse:Password>
...
</wsse:UsernameToken>
However, in the second case of authenticating using the digest username token with the digest password, since digestPassword="true
" is in both the client- and server-side policy files, the digest of the password is included in the username token:
<wsse:UsernameToken ....>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ...#PasswordDigest">****</wsse:Password>
...
</wsse:UsernameToken>
In this case, the server compares the incoming SOAP message digest password with the calculated digested password from inside spring-ws-servlet.xml
. In this way, communication will be more secure by comparison with the first case in which the password was transmitted in plain text (the plain text password could be easily extracted from the SOAP messages. However, using an SSL connection can secure such a communication).
The recipes Authenticating a Web-Service call using Spring security to authenticate a username token with plain/digested password, Authenticating a Web-Service call using JAAS service to authenticate a username token, and Authenticating a Web-Service call using X509 certificate, discussed in this chapter.
Here we make use of the same authentication method used in the first recipe. The only difference here is that the Spring Security framework is used for authentication. Since the Spring Security framework is beyond the scope of this book, it is not described here. However, you can read more about it in the Spring Security reference documentation (http://www.springsource.org/security).
Same as the first recipe of this chapter, this recipe also contains two cases. In the first case, the password will be transmitted in plain text format. In the second case, by changing the policy file's configuration, the password will be transmitted in digest format.
In this recipe, the project's name is LiveRestaurant_R-7.2
(for the server-side Web-Service) and has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
LiveRestaurant_R-7.2-Client
(for the client-side Web-Service) has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-ws-test-2.0.0.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
junit-4.7.jar
In this recipe, all the steps are the same as in the previous recipe, Authenticating a Web-Service call using username token with plain/digested password, except the server-side application context file (spring-ws.servlet.xml
) callback handler changes and uses the DAO layer to fetch data:
The following steps implement authentication of a Web-Service call using Spring Security to authenticate a username token with a plain password:
Register the security interceptor (XwsSecurityInterceptor
) and callbackHandler
(SpringPlainTextPasswordValidationCallbackHandler
) in the application context file (spring-ws-servlet.xml
) of LiveRestaurant_R-7.2
.
Add the DAO layer classes to fetch data.
Run the following command from Liverestaurant_R-7.2:
mvn clean package tomcat:run
Run the following command from Liverestaurant_R-7.2-Client:
mvn clean package
The following is the client-side output:
Nov 6, 2011 1:42:37 PM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Sending Message Start ====
...
<SOAP-ENV:Envelope ....>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsu:Timestamp....>
<wsu:Created>2011-11-06T09:42:37.391Z</wsu:Created>
<wsu:Expires>2011-11-06T09:47:37.391Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken ...>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ...#PasswordText">****</wsse:Password>
<wsse:Nonce ...#Base64Binary">...</wsse:Nonce>
<wsu:Created>2011-11-06T09:42:37.442Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest ...>
......
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
INFO: ==== Received Message Start ====
<SOAP-ENV:Envelope ...">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse ....">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
The following steps implement authentication of a Web-Service call using Spring Security to authenticate a digested username token:
Modify springSecurityHandler
to SpringDigestPasswordValidationCallbackHandler
in the server application context file (spring-ws-servlet.xml
).
Modify the security policy file (securityPolicy.xml
) in both the server side and client side to digest the password.
Run the following command from Liverestaurant_R-7.2:
mvn clean package tomcat:run
Run the following command from Liverestaurant_R-7.2-Client:
mvn clean package
The following is the client-side output:
Nov 6, 2011 2:04:37 PM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Sending Message Start ====
...
<SOAP-ENV:Envelope ...>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsu:Timestamp ...>
<wsu:Created>2011-11-06T10:04:36.622Z</wsu:Created>
<wsu:Expires>2011-11-06T10:09:36.622Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken...>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password #PasswordDigest">****</wsse:Password>
<wsse:Nonce #Base64Binary">...</wsse:Nonce>
<wsu:Created>2011-11-06T10:04:36.683Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema">
......
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
Nov 6, 2011 2:04:37 PM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Received Message Start ====
<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse...">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
In the Liverestaurant_R-7.2
project, every aspect of security for the client and server is almost the same as Liverestaurant_R-7.1
that we made use of in the recipe Authenticating a Web-Service call using username with plain/digested password token, except for validating the user on the server side. A Spring Security class is responsible for validating the user and password by comparison with the incoming message's username/password with fetched data from a DAO layer (instead of hardcoding the username/password in spring-ws-servlet.xml)
. In addition, other data (such as permissions, isAccountBlocked, isAccountExpired
, and so on) related to the successfully authenticated user (that matches the username and password) can be fetched from the DAO layer and returned for the authorization task or for any validation about the expiry date of the account and to check if the account is blocked or not.
In the first case, CallbackHandler SpringPlainTextPasswordValidationCallbackHandler
compares the plain password included in the incoming SOAP message with the plain password that is fetched from the DAO layer.
<sws:interceptors> <bean .... <bean class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor"> <property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml"/> <property name="callbackHandlers"> <list> <ref bean="springSecurityHandler"/> </list> </property> </bean> </sws:interceptors> <bean id="springSecurityHandler" class="org.springframework.ws.soap.security.xwss.callback.SpringPlainTextPasswordValidationCallbackHandler"> <property name="authenticationManager" ref="authenticationManager"/> </bean> ....
In the second test, however, CallbackHandler
is SpringDigestPasswordValidationCallbackHandler
that compares the digest password included in the incoming SOAP message with the digest of the password that is fetched from the DAO layer.
<bean id="springSecurityHandler" class="org.springframework.ws.soap.security.xwss.callback.SpringDigestPasswordValidationCallbackHandler"> <property name="userDetailsService" ref="userDetailsService"/> </bean>
springSecurityHandler
uses MyUserDetailService.java
, which should implement Spring's UserDetailService
to get the username from the provider and internally fetch all information for that user from a DAO layer (for example, password, roles, is expired, and so on).
public class MyUserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { return getUserDataFromDao(username); } private MyUserDetail getUserDataFromDao(String username) { /** *Real scenario: find user data from a DAO layer by userName, * if this user name found, populate MyUserDetail with its data(username, password,Role, ....). */ MyUserDetail mydetail=new MyUserDetail(username,"pass",true,true,true,true); mydetail.getAuthorities().add(new GrantedAuthorityImpl("ROLE_GENERAL_OPERATOR")); return mydetail; }
This service finally returns the populated data in MyUserDetails.java
, which should implement Spring's UserDetails
.
public class MyUserDetail implements UserDetails { private String password; private String userName; private boolean isAccountNonExpired; private boolean isAccountNonLocked; private boolean isCredentialsNonExpired; private boolean isEnabled; public static Collection<GrantedAuthority> authority = new ArrayList<GrantedAuthority>(); public MyUserDetail( String userName, String password,boolean isAccountNonExpired, boolean isAccountNonlocked,boolean isCredentialsNonExpired, boolean isEnabled){ this.userName=userName; this.password=password; this.isAccountNonExpired=isAccountNonExpired; this.isAccountNonLocked=isAccountNonlocked; this.isCredentialsNonExpired=isCredentialsNonExpired; this.isEnabled=isEnabled; } @Override public Collection<GrantedAuthority> getAuthorities() { return authority; } ..... }
Now, if the UserDetails
data matches the incoming message's username/password, it returns a response; otherwise, it returns a SOAP fault message.
Same as the 7.1 project, setting digestPassword
to true/false
in securityPolicy.xml
on the server/client-side causes the password to be transmitted in plain text or in the digested format.
The recipes Authenticating a Web-Service call using Spring security to authenticate a username token with plain/digested password, Authenticating a Web-Service call using JAAS service to authenticate a username token, and Authenticating a Web-Service call using X509 certificate, discussed in this chapter.
We make use of the same authentication task with a plain username token, as used in the first recipe. The only difference here is that Java Authentication and Authorization Service (JAAS) is used here for authentication and authorization. Since the JAAS framework is beyond the scope of this book, it is not described here. However, you can read more about JAAS in the reference documentation ( http://download.oracle.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html).
JaasPlainTextPasswordValidationCallbackHandler
from the xwss
package is the API that calls the Login
module that is configured inside the JAAS configuration file.
In this recipe, the project's name is LiveRestaurant_R-7.3
(for the server-side Web-Service) and has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
LiveRestaurant_R-7.3-Client
(for the client-side Web-Service) has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-ws-test-2.0.0.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
junit-4.7.jar
In this recipe, all the steps are the same as in the previous recipe, Authenticating a Web-Service call using username token with plain/digested password, except that the server-side application context file (spring-ws.servlet.xml
) callback handler changes and uses the JAAS framework as an authentication and authorization service:
Register the JAAS callbackHandler
(JaasPlainTextPasswordValidationCallbackHandler
) in the server-side application context file (spring-ws.servlet.xml
).
Add the JAAS framework's required classes (RdbmsPrincipal, RdbmsCredential
, and RdbmsPlainTextLoginModule)
and the configuration file (jaas.config
).
Run the following command from Liverestaurant_R-7.3:
mvn clean package tomcat:run -Djava.security.auth.login.config="src/main/resources/jaas.config"
Run the following command from Liverestaurant_R-7.3-Client:
mvn clean package
The following is the client-side output:
INFO: ==== Sending Message Start ====
....
<SOAP-ENV:Envelope ....">
<SOAP-ENV:Header>
<wsse:Security ....>
<wsu:Timestamp ...>
<wsu:Created>2011-11-06T11:59:09.712Z</wsu:Created>
<wsu:Expires>2011-11-06T12:04:09.712Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken ...>
<wsse:Username>clinetUser</wsse:Username>
<wsse:Password ....#PasswordText">****</wsse:Password>
<wsse:Nonce ...0#Base64Binary">...</wsse:Nonce>
<wsu:Created>2011-11-06T11:59:09.774Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest...>
.....
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
...
INFO: ==== Received Message Start ====
...
<SOAP-ENV:Envelope ...>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsu:Timestamp ....>
<wsu:Created>2011-11-06T11:59:11.630Z</wsu:Created>
<wsu:Expires>2011-11-06T12:04:11.630Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderResponse ...>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
In the Liverestaurant_R-7.3
project, everything about security for the client and server is almost the same as the Liverestaurant_R-7.1
project that we used in the recipe Authenticating a Web-Service call using a username with plain/digested password token except for validating a user on the server side. A JAAS framework is responsible for validating the user and password by comparison of incoming message's username/password with fetched data from a data source (database here).
The client sends a request SOAP message that contains the username token in plain text. The server receives this message and uses the JAAS framework to compare an incoming message username/password with what is fetched from the DAO layer by JAAS. If it matches, it returns a normal response; otherwise, it returns a failure message.
In spring-ws-servlet.xml, JaasPlainTextPasswordValidationCallbackHandler
is registered as a callback handler that uses RdbmsPlainText
as a pluggable JAAS login module for the username/password authentication:
<sws:interceptors> ....... <bean class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor"> <property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml" /> <property name="callbackHandlers"> <list> <ref bean="jaasValidationHandler" /> </list> </property> </bean> </sws:interceptors> <bean id="jaasValidationHandler" class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasPlainTextPasswordValidationCallbackHandler"> <property name="loginContextName" value="RdbmsPlainText" /> </bean>
When the server side is being run using mvn -Djava.security.auth.login.config="src/main/resources/jaas.config
", it uses the jaas.config
file to locate the JAAS login module (RdbmsPlainTextLoginModule
) that is registered in the server-side application context as RdbmsPlainText:
RdbmsPlainText { com.packtpub.liverestaurant.service.security.RdbmsPlainTextLoginModule Required; };
The login
method from RdbmsPlainTextLoginModule.java
will be called to fetch the user password and credentials from the DAO layer. If the fetched password matches the incoming message's password, then it sets credential and returns true
; otherwise, it throws an exception that leads the server to send back a fault message to the client:
public class RdbmsPlainTextLoginModule implements LoginModule { private Subject subject; private CallbackHandler callbackHandler; private boolean success; private List<RdbmsPrincipal> principals = new ArrayList<RdbmsPrincipal>(); private List<RdbmsCredential> credentials = new ArrayList<RdbmsCredential>(); @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { ..... } @Override public boolean login() throws LoginException { ...... } private List<String> getAllPermission(String username) { ...... } private boolean authenticate(String username,String password) { .... } public boolean commit() throws LoginException { ..... } @Override public boolean logout() throws LoginException { ..... } }
In important applications, even the username is encrypted. This provides more security and competitors can't guess which users are coming from which location using ISP-level filtering. Hackers guess or track a username and send duplicate requests to load servers with unnecessary data. In this recipe, since the password is being transmitted in plain-text format, using an SSL connection is recommended. Spring-WS also supports JaasCertificateValidationCallbackHandler
, which uses a certificate for authentication. This handler is not used here. However, you can find out more about it at the following URL:
The recipes Authenticating a Web-Service call using username token with plain/digested password, Authenticating a Web-Service call using Spring Security to authenticate a username token with plain/digested password, and Authenticating a Web-Service call using X509 certificate, discussed in this chapter.
In order to add more security measures for a Web-Service call, we do need some extra operations such as signing and verifying the signature of Web-Service messages, encryption/decryption, and authentication using certificates. XWSS provides these operations using keystores. The java.security.KeyStore
class provides a memory container for the cryptographic keys and certificates. This class can include three types of entries:
Private key entry, which contains a private key and a public key certificate (note that the public key here is wrapped within the X.509 certificate a combination of a private key and a public key certificate is known as a key pair)
Secret key entry, which contains a symmetric key
Trusted certificate entry, which contains a trusted certificate (this certificate is the other party certificate, imported as a trusted certificate, which means the owner keys store the public key within the other party's certificate that belongs to the third party)
A keystore may contain one to many entries. Aliases in a keystore are for distinguishing entries from one another. The private key and certificate are referred to by one alias while any other trusted certificates or secret key entries are referred to by different individual aliases within a keystore.
Earlier in this chapter, authentication of a Web-Service call using the username token was presented. A Web-Service call can be authenticated by using a certificate. Later in this chapter, in the recipe Authenticating a Web-Service call using X509 certificate, authentication using a certificate will be presented. In addition, these certificates can be used for certificate validation, signature verification, and encryption.
Java keytool is a tool that generates and stores the keys and certificates in a keystore file. This keystore is protected by a keystore password. In addition, there is another password that protects the private key.
In this recipe, using the keytool to generate keystores with symmetric key entries, private key entries (private keys and public key certificates), and trusted certificate entries is presented. These keys will be used later in this chapter and in Chapter 8, Securing SOAP Web-Services using WSS4J Library, for signing and verifying the signature of Web-Service messages, encryption/decryption, and authentication using certificates.
To generate a keystore with a secret key entry with the alias symmetric, run the following command (this keystore is to be used later for symmetric encryption/decryption):
keytool -genseckey -alias 'symmetric' -keyalg 'DESede' -keystore symmetricStore.jks -storepass 'symmetricPassword' -keypass 'keyPassword' -storetype "JCEKS"
To generate a keystore with a private key entry or a key pair (that contains private key and public certificate pairs) follow next steps:
To generate a receiver (server side here) keystore, run the following command and follow the command prompt:
keytool -genkey -alias server -keyalg RSA -keystore serverStore.jks -validity 3653
Enter keystore password:serverPassword
Re-enter new password:serverPassword
What is your first and last name?
[Unknown]: MyFirstName MyLastName
What is the name of your organizational unit?
[Unknown]: Software
What is the name of your organization?
[Unknown]: MyCompany
What is the name of your City or Locality?
[Unknown]: MyCity
What is the name of your State or Province?
[Unknown]: MyProvince
What is the two-letter country code for this unit?
[Unknown]: ME
Is CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME correct?
[no]: yes
Enter key password for <server>
(RETURN if same as keystore password):serPkPassword
Re-enter new password:serPkPassword
To generate a sender (client side here) keystore, run the following command and follow the command prompt:
keytool -genkey -alias client -keyalg RSA -keystore clientStore.jks -validity 3653
Enter keystore password:clientPassword
Re-enter new password:clientPassword
What is your first and last name?
[Unknown]: MyFirstName MyLastName
What is the name of your organizational unit?
[Unknown]: Software
What is the name of your organization?
[Unknown]: MyCompany
What is the name of your City or Locality?
[Unknown]: MyCity
What is the name of your State or Province?
[Unknown]: MyProvince
What is the two-letter country code for this unit?
[Unknown]: ME
Is CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME correct?
[no]: yes
Enter key password for <server>
(RETURN if same as keystore password):cliPkPassword
Re-enter new password:cliPkPassword
To see the generated private key entry in a keystore, run the following command (please note privateKeyEntry
within the underlined text):
keytool -list -v -keystore serverStore.jks -storepass serverPassword
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: server
Creation date: 26-Jul-2011
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebd0c
Valid from: Tue Jul 26 17:11:40 GST 2011 until: Mon Jul 26 17:11:40 GST 2021
Certificate fingerprints:
MD5: 9E:DF:5E:18:F5:F6:52:4A:B6:9F:67:04:39:C9:57:66
SHA1: C5:0B:8C:E6:B6:02:BD:38:56:CD:BB:50:CC:C6:BA:74:86:27:6C:C7
Signature algorithm name: SHA1withRSA
Version: 3
To generate a certificate (public key) from a keystore with a private key entry, run the following command for the client/server-side keystore:
keytool -export -file clientStore.cert -keystore clientStore.jks -storepass clientPassword -alias client
keytool -export -file serverStore.cert -keystore serverStore.jks -storepass serverPassword -alias server
To import the sender (client) public key certificate into the receiver (server) keystore, run the following command for the server-side keystore (this certificate will be stored as a trusted certificate entry in the keystore with the alias client):
keytool -import -file clientStore.cert -keystore serverStore.jks -storepass serverPassword -alias client
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebf1e
Valid from: Tue Jul 26 17:20:30 GST 2011 until: Mon Jul 26 17:20:30 GST 2021
Certificate fingerprints:
MD5: FD:BE:98:72:F0:C8:50:D5:4B:10:B0:80:3F:D4:43:E8
SHA1: 91:FB:9D:1B:69:E9:5F:0B:97:8C:E2:FE:49:0E:D8:CD:25:FB:D8:18
Signature algorithm name: SHA1withRSA
Version: 3
Trust this certificate? [no]: yes
Certificate was added to keystore
To import the receiver (server) public key certificate into the sender (client) keystore, run the following command for the sender (client side) keystore (this certificate will be stored as a trusted certificate entry in the keystore with the alias server):
keytool -import -file serverStore.cert -keystore clientStore.jks -storepass clientPassword -alias server
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebf1e
Valid from: Tue Jul 26 17:20:30 GST 2011 until: Mon Jul 26 17:20:30 GST 2021
Certificate fingerprints:
MD5: FD:BE:98:72:F0:C8:50:D5:4B:10:B0:80:3F:D4:43:E8
SHA1: 91:FB:9D:1B:69:E9:5F:0B:97:8C:E2:FE:49:0E:D8:CD:25:FB:D8:18
Signature algorithm name: SHA1withRSA
Version: 3
Trust this certificate? [no]: yes
Certificate was added to keystore
To see the server's private key entry and trusted certificate entry in the keystore, run the following command (please note trustedCertEntry
and privateKeyEntry within the underlined text):
keytool -list -v -keystore serverStore.jks -storepass serverPassword
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 2 entries
Alias name: client
Creation date: 26-Jul-2011
Entry type: trustedCertEntry
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebf1e
Valid from: Tue Jul 26 17:20:30 GST 2011 until: Mon Jul 26 17:20:30 GST 2021
Certificate fingerprints:
MD5: FD:BE:98:72:F0:C8:50:D5:4B:10:B0:80:3F:D4:43:E8
SHA1: 91:FB:9D:1B:69:E9:5F:0B:97:8C:E2:FE:49:0E:D8:CD:25:FB:D8:18
Signature algorithm name: SHA1withRSA
Version: 3
*******************************************
*******************************************
Alias name: server
Creation date: 26-Jul-2011
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebd0c
Valid from: Tue Jul 26 17:11:40 GST 2011 until: Mon Jul 26 17:11:40 GST 2021
Certificate fingerprints:
MD5: 9E:DF:5E:18:F5:F6:52:4A:B6:9F:67:04:39:C9:57:66
SHA1: C5:0B:8C:E6:B6:02:BD:38:56:CD:BB:50:CC:C6:BA:74:86:27:6C:C7
Signature algorithm name: SHA1withRSA
Version: 3
*******************************************
*******************************************
To see the client's private key entry and trusted certificate entry in the keystore, run the following command:
keytool -list -v -keystore clientStore.jks -storepass clientPassword
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 2 entries
Alias name: client
Creation date: 26-Jul-2011
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebf1e
Valid from: Tue Jul 26 17:20:30 GST 2011 until: Mon Jul 26 17:20:30 GST 2021
Certificate fingerprints:
MD5: FD:BE:98:72:F0:C8:50:D5:4B:10:B0:80:3F:D4:43:E8
SHA1: 91:FB:9D:1B:69:E9:5F:0B:97:8C:E2:FE:49:0E:D8:CD:25:FB:D8:18
Signature algorithm name: SHA1withRSA
Version: 3
*******************************************
******************************************
Alias name: server
Creation date: 26-Jul-2011
Entry type: trustedCertEntry
Owner: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Issuer: CN=MyFirstName MyLastName, OU=Software, O=MyCompany, L=MyCity, ST=MyProvince, C=ME
Serial number: 4e2ebd0c
Valid from: Tue Jul 26 17:11:40 GST 2011 until: Mon Jul 26 17:11:40 GST 2021
Certificate fingerprints:
MD5: 9E:DF:5E:18:F5:F6:52:4A:B6:9F:67:04:39:C9:57:66
SHA1: C5:0B:8C:E6:B6:02:BD:38:56:CD:BB:50:CC:C6:BA:74:86:27:6C:C7
Signature algorithm name: SHA1withRSA
Version: 3
*******************************************
*******************************************
In the beginning, a symmetric key store is generated that can be shared by a client and a server for encryption and decryption. This command generates the symmetric key store:
keytool -genseckey -alias 'symmetric' -keyalg 'DESede' -keystore symmetricStore.jks -storepass 'symmetricPassword' -keypass 'keyPassword' -storetype "JCEKS"
To generate a keystore with a private key entry and a trusted certificate entry, first a key pair (private key and public certificate) keystore for both the client and server side should be generated.
Then the public key certificate should be exported from the client/server keystore. Finally, the client certificate should be imported into the server keystore and the server certificate should be imported into the client keystore (this imported certificate will be called trusted certificate).
keytool -genkey -alias aliasName -keyalg RSA -keystore keyStoreFileName.jks -validity 3653
The preceding command generates a keystore with a private key entry for which aliasName
is an identifier of the keystore. Validity is the number of days that this key is valid.
keytool -export -file clientStore.cert -keystore clientStore.jks -storepass clientPassword -alias client
The preceding command exports the public key certificate that is embedded inside the private key entry in a keystore.
keytool -import -file clientStore.cert -keystore serverStore.jks -storepass serverPassword -alias client
The preceding command imports the generated public key certificate from the client keystore into the server keystore (this imported certificate will be called trusted certificate).
More information about cryptography and keystores can be found at the following URLs:
http://docs.oracle.com/javase/1.5.0/docs/api/java/security/KeyStore.html.
http://en.wikipedia.org/wiki/Category:Public-key_cryptography.
The purpose of digital signature is to verify whether a received message is altered to prove the sender is who he/she claims to be (authentication) and to prove the action from a specific sender. Digital signing of a message means adding hash data, that is, a piece of information (token) added to the SOAP envelop. The receiver needs to regenerate its own hash from the incoming message and compare it with the sender's one. If the receiver's hash matches the sender's one, the data integrity is achieved and the receiver will proceed; otherwise it returns a SOAP fault message to the sender.
In order to authenticate the sender, the sender should encrypt the signature token using his/her own private key. The receiver should have the sender's public-key certificate in the receiver keystore (the certificate is called a trusted certificate and comes under the trusted certificate entry) to decrypt the sender's signature token and repeat the already-explained step to check the message integrity. Now if the message integrity is achieved, the authentication of the sender is proved (since only the sender's certificate embedded in the receiver keystore could decrypt the encrypted signature of the sender). In addition, the action of sending the message by the sender also is proved (since successful decryption of the signature on the receiver's side shows that the sender has encrypted it by its own private key).
In this recipe, the sender (client) signs a message and uses its own private key (within the client keystore) for encryption of signature. On the receiver side (server), the client public key certificate in the server keystore (the certificate is called trusted certificate and comes under the trusted certificate entry within the keystore) will be used for decryption of the signature of the token; then the server verifies the signature token.
In this recipe, the project's name is LiveRestaurant_R-7.4
(for the server-side Web-Service) with the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
LiveRestaurant_R-7.4-Client
(for the client-side Web-Service) has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-ws-test-2.0.0.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
junit-4.7.jar
Copy serverStore.jks
to the server and clientStore.jks
to the client (these keystores are already generated in the recipe Preparing pair and symmetric keystores discussed in this chapter.
Configure the security policy file (securityPolicy.xml
) on the server side to expect a signature token along with the incoming message on the client side to sign outgoing messages.
Register keyStoreHAndler
(KeyStoreCallbackHandler) and trustStore
(KeyStoreFactoryBean) in the server-side application context file.
Register keyStoreHAndler
(KeyStoreCallbackHandler) and keyStore
(KeyStoreFactoryBean) in the client-side application context file.
Run the following command from Liverestaurant_R-7.4:
mvn clean package tomcat:run
Run the following command from Liverestaurant_R-7.4-Client:
mvn clean package
The following is the client-side output (note the tag ds:Signature
) within the underlined text:
INFO: ==== Sending Message Start ====
....
<SOAP-ENV:Envelope.....>
<SOAP-ENV:Header>
<wsse:Security ....>
...
<ds:Signature ....>
<ds:SignedInfo>
.....
</ds:SignedInfo>
<ds:SignatureValue>....</ds:SignatureValue>
<ds:KeyInfo>
<wsse:SecurityTokenReference .........>
<wsse:Reference ..../>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body....>
<tns:placeOrderRequest ...>
......
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====....
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse ....>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
Security policy on the server side requires the client to include a binary signature token in the message. Settings in the client-side policy file include the signature token in the outgoing messages. A client uses its own private key included in the client-side keystore to encrypt the signature token of the message. On the server side, the client public key certificate, included in the server keystore (the certificate is called trusted certificate and comes under the trusted certificate entry within the keystore), will be used for decrypting the incoming signature token. Then the server proceeds towards the verification of the signature.
The following server-side security configuration in the policy files causes the server to expect a security token from the incoming message (for verification of incoming messages):
<xwss:RequireSignature requireTimestamp="false" /> </xwss:SecurityConfiguration>
On the client side, however, this security configuration in the policy files causes the client to include a security token inside the SOAP message in the outgoing message:
<xwss:Sign includeTimestamp="false"> </xwss:Sign>
The following setting in the client-side application context causes the client to use the private key inside clientStore.jks
to encrypt the signature token of the message. The private key's password is cliPkPassword
, the alias of the private key entry is client
, and the keystore bean is generated by reading the keystore clientStore.jks
with the keystore password clientPassword:
<bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="password" value="clientPassword" /> <property name="location" value="/clientStore.jks" /> </bean> <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> <property name="keyStore" ref="keyStore" /> <property name="privateKeyPassword" value="cliPkPassword" /> <property name="defaultAlias" value="client" /> </bean>
On the server side, the following setting in the server configuration file causes the server to first decrypt the signature token using a client certificate in the server keystore (the certificate is called a trusted certificate). It then verifies the signature of the incoming messages (to see whether the original message is altered):
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> <property name="trustStore" ref="trustStore"/> </bean> <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="location" value="/WEB-INF/serverStore.jks" /> <property name="password" value="serverPassword" /> </bean>
In the previous recipe, Securing SOAP messages using digital signature, by changing the sender (client) security policy file, the sender can include the client's certificate along with the outgoing messages. Then on the receiver side (server), before the verification of signatures, the server tries to authenticate the sender by comparing the client's certificate along with incoming message with client certificate embedded in the server keystore (trusted certificate). Additionally in this recipe, the client certificate is included in the sender's outgoing message and to extract data included in the certificate for authentication and authorization purposes, on the receiver side.
SpringCertificateValidationCallbackHandler
, from the XWSS package, can extract the certificate data (such as CN=MyFirstName MyLastName)
and this data could be for authentication as well as authorization.
In this recipe, we make use of the Securing SOAP messages using digital signature recipe for the signing and verification of signatures. Then SpringCertificateValidationCallbackHandler
is used for authentication, using data fetching from the DAO layer as well as authorization for that Web-Service call.
In this recipe, the project's name is LiveRestaurant_R-7.5
(for the server-side Web-Service) and it has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
LiveRestaurant_R-7.5-Client
(for the client-side Web-Service) has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-ws-test-2.0.0.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
junit-4.7.jar
In this recipe, all the steps are the same as in the previous recipe, Securing SOAP messages using a digital signature, except for modifying the client's policy file, as that changes to include the client certificate along with the outgoing message and the server-side application context file (spring-ws.servlet.xml
) changes, and it uses the DAO layer to fetch data:
Register springSecurityCertificateHandler
in the server-side application context file (spring-ws-servlet.xml).
Modify the client-side security policy file to include the client certificate along with the outgoing messages.
Add the DAO layer classes to fetch data.
The following is the client-side output (note the X509 client certification) within the underlined text:
INFO: ==== Sending Message Start ====
<?xml...>
<SOAP-ENV:Header>
<wsse:Security ...>
<wsse:BinarySecurityToken...wss-x509-token-..>.....</wsse:BinarySecurityToken>
<ds:Signature .....>
<ds:SignedInfo>
......
</ds:SignedInfo>
<ds:SignatureValue>.....</ds:SignatureValue>
<ds:KeyInfo>
<wsse:SecurityTokenReference...>
<wsse:Reference ...wss-x509-token-profile-1.0.../>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body ....>
<tns:placeOrderRequest ...>
.....
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
INFO: ==== Received Message Start ====
<?xml version="1.0" ....>
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
Everything about signatures is the same as described in the recipe Securing SOAP messages using a digital signature. In addition, the client-side certificate is included in the outgoing messages and extracting a client's certificate data on the server side for some processing operations.
Once the client's certificate is extracted (that is, embedded within the incoming message), authentication can be done by retrieving the username or other information.
Including the following section in the client-side policy file causes the client to include its own public key certificate in the outgoing messages:
<xwss:X509Token certificateAlias="client" />
Embedding a client certificate in a caller message while signing the message causes the server to validate this certificate with the one included in the server keystore (sender trusted certificate entry) before signature validation. This validation confirms that the caller is the person he/she claims to be. However, if activation/locking of account needs to be checked or authorization of the caller to access specific resources is required, then springSecurityCertificateHandler
, configured in the server configuration file, handles these tasks:
<bean class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor"> <property name="policyConfiguration" value="/WEB-INF/securityPolicy.xml"/> <property name="secureResponse" value="false" /> <property name="callbackHandlers"> <list> <ref bean="keyStoreHandler"/> <ref bean="springSecurityCertificateHandler"/> </list> </property> </bean> </sws:interceptors> <bean id="springSecurityCertificateHandler" class="org.springframework.ws.soap.security.xwss.callback.SpringCertificateValidationCallbackHandler"> <property name="authenticationManager" ref="authenticationManager"/> </bean> <bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager"> <property name="providers"> <bean class="org.springframework.ws.soap.security.x509.X509AuthenticationProvider"> <property name="x509AuthoritiesPopulator"> <bean class="org.springframework.ws.soap.security.x509.populator.DaoX509AuthoritiesPopulator"> <property name="userDetailsService" ref="userDetailsService"/> </bean> </property> </bean> </property> </bean> <bean id="userDetailsService" class="com.packtpub.liverestaurant.service.dao.MyUserDetailService" />
This handler uses the authentication manager that calls DaoX509AuthoritiesPopulator
, which applies the customized service class MyUserDetailService
for authentication and extracts the user credentials for authorization purposes:
public class MyUserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { return findUserDetailFromDAO(username); } private UserDetails findUserDetailFromDAO(String userName)throws UsernameNotFoundException{ MyUserDetail mydetail=null; /** *Real scenario: Find user-name from DAO layer, if user found, get data from the DAO and set MyUserDetail otherwise throw UsernameNotFoundException. */ if(! userName.equals("MyFirstName MyLastName")){ throw new UsernameNotFoundException("User name not found"); } mydetail=new MyUserDetail(userName,"fetchedPassword",true,true,true,true,new GrantedAuthorityImpl("ROLE_GENERAL_OPERATOR")); return mydetail; } }
Encryption is the process of converting readable or plain text data format into an un-readable encrypted format or cipher text using specific algorithms. These algorithms, known as encryption algorithms, require an encryption key. Decryption is just the reverse operation of encryption; it converts back the cipher text into readable or plain text data format using a decryption key. The encryption and decryption keys could be the same or different. If encryption and decryption keys are the same and the sender and receiver share the key, then this key is known as symmetric or secret key. The encryption and decryption keys could be different, and in this case, the key is called asymmetric or public key.
The following diagram presents the usage of a symmetric key for encryption/decryption. The sender and receiver can share the same key, which is known as symmetric key. Those having this key can decrypt/encrypt messages. For example, a symmetric key is used for encryption by the sender and decryption by the receiver:
The following diagram presents the usage of the public/private key for encryption/decryption. Bob, as a sender, gets Alice's public key, encrypts a message, and sends it to Alice. Since only she is the holder of her own private key, she can decrypt the message:
In this recipe, the sender (client here) encrypts a message and sends it to a receiver (server here) in three different cases. In the first case, a symmetric key (which is in a store with the secret key entry that is the same for the client and server) is used for encryption on the client side and for decryption on the server side. Then, in the second case, the receiver's (server) public key certificate on the sender's (client) keystore (within the receiver trusted certificate entry) is used for data encryption and the receiver's (server) private key on the server-side keystore is used for decryption.
Since encryption of the whole payload in the annotation
endpoint mappings (PayloadRootAnnotationMethodEndpointMapping
) makes routing information (for example, localPart = "placeOrderRequest", namespace = "http://www.packtpub.com/liverestaurant/OrderService/schema
", which is included in payload) encrypted along with whole payload, and the annotation
endpoint mapping cannot be used. Instead, the SoapActionAnnotationMethodEndpointMapping
addressing style is used for endpoint mapping. In this case, routing data is included in the SOAP header whereas it is included in payload in the annotation endpoint mapping. Although encryption of a part of the payload can work with the payload annotation endpoint mapping, however for consistency, SoapActionAnnotationMethodEndpointMapping
addressing style is used for whole of the recipe.
For more information about endpoint mapping, refer to the recipes Setting up an endpoint by annotating the payload-root and Setting up a transport-neutral WS-Addressing endpoint, discussed in Chapter 1,Building SOAP Web-Services.
In the first two cases, the whole payload is used for encryption/decryption. The XWSS policy configuration file makes it possible to encrypt/decrypt the payload part. In the third case, only a part of the payload is set as the target for encryption/decryption.
In this recipe, the project's name is LiveRestaurant_R-7.6
(for the server-side Web-Service) and has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
mail-1.4.1.jar
saaj-api-1.3.jar
saaj-impl-1.3.2.jar
LiveRestaurant_R-7.6-Client
(for the client-side Web-Service) has the following Maven dependencies:
spring-ws-security-2.0.1.RELEASE.jar
spring-ws-test-2.0.0.RELEASE.jar
spring-expression-3.0.5.RELEASE.jar
log4j-1.2.9.jar
junit-4.7.jar
The following steps implement encryption/decryption using a shared symmetric key (symmetricStore.jks
):
Register keyStoreHandler
and symmetricStore
in the server/client application context. Copy the symmetric keystore (symmetricStore.jks
) to the server/client folder (this keystore is already generated in the recipe Preparing pair and symmetric keystores discussed in this chapter).
Configure the security policy file (securityPolicy.xml
) on the server side to expect encryption of messages from its client and on the client side to encrypt the outgoing messages.
Run the following command from Liverestaurant_R-7.6:
mvn clean package tomcat:run
Run the following command from Liverestaurant_R-7.6-Client:
mvn clean package
The following is the client-side output (note the underlined part in the output):
INFO: ==== Received Message Start ====
....
<SOAP-ENV:Envelope ....>
<SOAP-ENV:Header>
<wsse:Security ....>
.......
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<xenc:EncryptedData.....">
<xenc:EncryptionMethod ....>
<ds:KeyInfo ...xmldsig#">
<ds:KeyName>symmetric</ds:KeyName>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>
3esI76ANNDEIZ5RWJt.....
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
Nov 7, 2011 11:48:46 PM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Sending Message Start ====
<?xml version="1.0" ...
><SOAP-ENV:Envelope ...>
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
The following steps implement encryption using a server-trusted certificate (or public key) on the client-side keystore (clientStore.jks
) and decryption on the server private key on the server-side keystore (serverStore.jks
):
Modify securityPolicy.xml
for encryption of messages using a server-trusted certificate on the client side (included in clientStore.jks)
and decryption on the server side by the server private key (included in serverStore.jks)
.
Register keyStoreHandler
and keyStore
on the server side and keyStoreHandler
and trustStore
on the client-side application context. Copy clientStore.jks
to the client and serverStore.jks
to the server folder (this keystore is already generated in the recipe Preparing pair and symmetric Keystores discussed in this chapter).
Configure the security policy file (securityPolicy.xml
) on the server side to expect encryption of messages from its client and on the client side to encrypt the outgoing messages.
Run the following command from Liverestaurant_R-7.6:
mvn clean package tomcat:run
Run the following command from Liverestaurant_R-7.6-Client:
mvn clean package
The following is the client-side output (note the underlined part in the output):
INFO: ==== Sending Message Start ====
...
<SOAP-ENV:Envelope ....>
<SOAP-ENV:Header>
<wsse:Security ...>
........
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<xenc:EncryptedData....>
<xenc:EncryptionMethod .../>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference...>
<wsse:Reference ..../>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
...
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
Nov 8, 2011 12:12:11 AM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Received Message Start ====
<SOAP-ENV:Envelope ....>
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
The following steps implement encryption/decryption for a part of the payload:
Modify securityPolicy.xml
on client side/server side to set the target of the encryption.
Run the following command from Liverestaurant_R-7.6:
mvn clean package tomcat:run
Run the following command from Liverestaurant_R-7.6-Client:
mvn clean package
The following is the client-side output (note underlined part in the output):
INFO: ==== Sending Message Start ====
...
<SOAP-ENV:Envelope ....>
<SOAP-ENV:Header>
<wsse:Security ....>
........
</wsse:Security>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest ...>
<xenc:EncryptedData .....>
..........
<xenc:CipherData>
<xenc:CipherValue>NEeTuduV....
..........
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</tns:placeOrderRequest>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Sending Message End ====
Nov 8, 2011 12:18:39 AM com.sun.xml.wss.impl.filter.DumpFilter process
INFO: ==== Received Message Start ====
....
<SOAP-ENV:Envelope ...>
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<tns:placeOrderResponse ....>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
==== Received Message End ====
In the first case, both the client and the server share the symmetric key. The client encrypts the whole payload using a symmetric key and sends it to the server. On the server side, the same key will be used to decrypt the payload. However, in the second and third cases, the server certificate embedded in the client store is used for encryption of the payload and the server-side private key of the server store will be used for decryption.
The RequireEncryption/Encrypt
tag in the server/client policy files causes the client to encrypt a message and the server to decrypt it. The keyAlias
is the alias name that is set at the time of symmetric keystore generation. The following sections in the client- and server-side policy files target the part of a message envelop that is to be encrypted/decrypted. qname: {http://schemas.xmlsoap.org/soap/envelope/}Body
causes only the body part of a SOAP envelop to be used for encryption/decryption.
---server policy file <xwss:RequireEncryption> <xwss:SymmetricKey keyAlias="symmetric" /> <xwss:EncryptionTarget type="qname" value="{http://schemas.xmlsoap.org/soap/envelope/}Body" enforce="true" contentOnly="true" /> </xwss:RequireEncryption> ---client policy file <xwss:Encrypt> <xwss:SymmetricKey keyAlias="symmetric" /> <xwss:Target type="qname">{http://schemas.xmlsoap.org/soap/envelope/}Body </xwss:Target> </xwss:Encrypt>
This part in the server and client configuration files causes a symmetric store to be used for cryptography. The callbackHandler
(keyStoreHandlerBean
) uses a symmetric keystore (symmetricStore bean
) with the key password as keyPassword
. The KeyStore
bean will be generated by reading from a keystore location (symmetricStore.jks
) with the keystore password as symmetricPassword
and the type set to JCEKS (passwords and the type are set at the time of symmetric keystore generation).
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> <property name="symmetricStore" ref="symmetricStore" /> <property name="symmetricKeyPassword" value="keyPassword" /> </bean> <bean id="symmetricStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="password" value="symmetricPassword" /> <property name="location" value="/WEB-INF/symmetricStore.jks" /> <property name="type" value="JCEKS" /> </bean>
In the second case, almost all the settings are the same, except that the client is using the server public key for encrypting and the server is using the server store private key for decryption. The following section in the server-side configuration file causes the server to use a server private key in the server-side keystore for decryption. The private key password is serPkPasswords
and the alias of the private key entry in the keystore is server. The KeyStore
bean will be generated by reading from the keystore file (serverStore.jks
) with the password serverPassword
(passwords and the alias are set at the time of keystore generation).
---server configuration <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> <property name="keyStore" ref="keyStore" /> <property name="privateKeyPassword" value="serPkPassword" /> <property name="defaultAlias" value="server" /> </bean> <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="location" value="/WEB-INF/serverStore.jks" /> <property name="password" value="serverPassword" /> </bean>
This section in the client-side configuration file causes the client to use the server certificate (public key) in the client-side trust store for encryption. The KeyStore
(trust store here) bean will be generated by reading from clientStore.jks
with the password clientPAssword
.
---client configuration <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> <property name="trustStore" ref="trustStore"/> </bean> <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="location" value="/clientStore.jks" /> <property name="password" value="clientPassword" /> </bean>
In the policy file for the client and server side, the following line causes the server public key to be used for encrypting in the client and the private key in the server store to be used for decryption.
<xwss:X509Token certificateAlias="server"/>
In the third case, the following section in the policy files for the server and client causes only a part of the payload to be encrypted:
<xwss:Target type="qname">{http://www.packtpub.com/LiveRestaurant/placeOrderService/schema}OrderRequest</xwss:Target>
The recipes Securing SOAP messages using a digital signature and Preparing pair and symmetric keystores, discussed in this chapter.
The recipe Creating Web-Service client for WS-Addressing endpoint, discussed in Chapter 2,Building Clients for SOAP Web Services.