Developing CXF WS-Security with Spring & Acegi Security
Introduction
In this article we are going to develop a web service by using Spring with CXF and the Acegi Security. This article provides steps (step by step) to create & deploy web services by using Spring,CXF andAcegi Security. Please go through below to find sample web services.
Software Requirements
- EClipse (Java IDE)- Optional
- CXF Jars (Required for Compilation-Download from http://cxf.apache.org/download.html)
The Code
In this example, we are going to create a Hello service. In this example, we are going to use a code-first approach for this service using JAX-WS annotations.
Creating Server Application
Step 1: Download Following Jar files.
activation.jar
aopalliance-1.0.jar
commons-collections-3.2.jar
commons-lang-2.1.jar
commons-logging-1.1.jar
geronimo-activation-2.0.1.jar
geronimo-annotation_1.0_spec-1.1.jar
geronimo-javamail_1.4_mail-1.2.jar
geronimo-servlet_2.5_spec-1.1.jar
geronimo-ws-metadata_2.0_spec-1.1.1.jar
jaxb-api.jar
jaxb-api-2.0.jar
jaxb-impl-2.0.5.jar
jaxb-xjc.jar
jaxws-api.jar
mail.jar
neethi-2.0.jar
opensaml-1.0.1.jar
saaj-api.jar
saaj-impl.jar
spring-beans-2.0.6.jar
spring-context-2.0.6.jar
spring-core-2.0.6.jar
spring-web-2.0.6.jar
stax-api-1.0.1.jar
velocity-1.5.jar
wsdl4j-1.6.1.jar
wstx-asl-3.2.1.jar
xalan-2[1].6.0.jar
xalan-2[1].7.0.jar
xml-resolver-1.2.jar
xmlsec-1.2.1.jar
cxf-bundle-2.0.4-incubator.jar
XmlSchema-1.3.2.jar
wss4j-1.5.1.jar
acegi-security-1.0.5.jar
spring-dao-2.0.7.jar
Step 2: Create New Java project in eclipse (CXFAcegiSecurity).
Step 3: Create WEB-INF folder inside project folder.
Step 4: Create classes folder inside WEB-INF folder.
Step 5: Create lib folder inside WEB-INF folder.
Step 6: Copy all the jar file into lib folder.
Step 7: Add all jar files into classpath (In Eclipse set java build path->Libraries). Add set Default output folder into CXFAcegiSecurity/WEB-INF/classes
Step 8: Create Remote Interface IHello.java
package com.sungard.cxf.example.server;
import javax.jws.WebService;
@WebService
public interface IHello {
public String sayHello(String value);
}
Step 9: Create Implementation Class IHello_Impl.java
package com.sungard.cxf.example.server;
import javax.jws.WebService;
@WebService(endpointInterface = “com.sungard.cxf.example.server.IHello”)
public class IHello_Impl implements IHello {
public String sayHello(String value) {
return “You Said” + value;
}
}
Step 10: Create User.java to store User details in java object. Acegi Security Service is using this object.
package com.sungard.cxf.example.server;
public class User {
private String userId;
private String password;
private String role;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public User(String userId, String password, String role) {
super();
this.userId = userId;
this.password = password;
this.role = role;
}
}
Step 11: Create MyGrantedAuthority.java to grand Authority.
package com.sungard.cxf.example.server;
import org.acegisecurity.GrantedAuthority;
public class MyGrantedAuthority implements GrantedAuthority {
private String authority = null;
public MyGrantedAuthority(String authority) {
this.authority = authority;
}
public String getAuthority() {
return authority;
}
}
Step 12: Create MyUserDetails.java class to Store User details.
package com.sungard.cxf.example.server;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.userdetails.UserDetails;
public class MyUserDetails implements UserDetails {
private GrantedAuthority[] authorities = null;
private String password = null;
private String username = null;
private String additionalData = null;
public MyUserDetails(GrantedAuthority[] authorities, String password,
String username, String additionalData) {
super();
this.authorities = authorities;
this.password = password;
this.username = username;
this.additionalData = additionalData;
}
public GrantedAuthority[] getAuthorities() {
return authorities;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean isAccountNonExpired() {
return true;
}
public boolean isAccountNonLocked() {
return true;
}
public boolean isCredentialsNonExpired() {
return true;
}
public boolean isEnabled() {
return true;
}
}
Step 13: Create MyUserDetailsService.java to load user details. By creating these classes we are customizing Acegi security framework
In this class I have used peter as user. You can use any name.
tUsers.put(“peter”, new User(“peter”, “arockiaraj”, “ROLE_ADMIN”));
package com.sungard.cxf.example.server;
import java.util.HashMap;
import java.util.Map;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.userdetails.UserDetails;
import org.acegisecurity.userdetails.UserDetailsService;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.springframework.dao.DataAccessException;
public class MyUserDetailsService implements UserDetailsService {
private Map users = init();
private Map init() {
Map tUsers = new HashMap();
tUsers.put(“scott”, new User(“scott”, “tiger”, “ROLE_USER”));
tUsers.put(“harry”, new User(“harry”, “potter”, “ROLE_ADMIN”));
tUsers.put(“frodo”, new User(“frodo”, “baggins”, “ROLE_USER”));
tUsers.put(“peter”, new User(“peter”, “arockiaraj”, “ROLE_ADMIN”));
return tUsers;
}
public UserDetails loadUserByUsername(String s)
throws UsernameNotFoundException, DataAccessException {
User user = (User) users.get(s);
GrantedAuthority authority = new MyGrantedAuthority(user.getRole());
UserDetails userDetails = new MyUserDetails(
new GrantedAuthority[] { authority }, user.getUserId(), user
.getPassword(), “Additional Data”);
return userDetails;
}
}
Step 14: Create PasswordHandler.java file to handle usernames and passwords.
if (pc.getIdentifer().equals(“satnewpubcert”)) {
pc.setPassword(“satsat”);
}
package com.sungard.cxf.example.server;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
public class PasswordHandler implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
System.out.println(“Enterd PasswordHandler::handle”);
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
if (pc.getIdentifer().equals(“satnewpubcert”)) {
pc.setPassword(“satsat”);
}
System.out.println(“Leaving PasswordHandler::handle”);
}
}
Step 15: Create ValidateUserTokenAcegiInterceptor.java class to handle soap requests. If you have proper Acegi Database setup then in this you have to UserDetailsService instead of MyUserDetailsService class. And MyGrantedAuthority.java, MyUserDetails.java, MyUserDetailsService.java, User.java classes are not required in this project.
package com.sungard.cxf.example.server;
import java.util.Vector;
import javax.servlet.http.HttpServletRequest;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.acegisecurity.ui.WebAuthenticationDetails;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.apache.ws.security.WSSecurityEngineResult;
import org.apache.ws.security.WSUsernameTokenPrincipal;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.apache.ws.security.handler.WSHandlerResult;
/**
* A WS-Security handler used to validate the user token passed in to the web
* service via the header of the soap packet.
*
* You shouldn’t have to change this class at all unless you want to inspect
* specific properties of the incoming WS-Security message.
*
*/
public class ValidateUserTokenAcegiInterceptor extends AbstractSoapInterceptor {
private MyUserDetailsService userDetailsService=new MyUserDetailsService();
public ValidateUserTokenAcegiInterceptor(String s) {
super(s);
}
public ValidateUserTokenAcegiInterceptor() {
super(Phase.UNMARSHAL);
}
public void handleMessage(SoapMessage message) throws Fault {
boolean userTokenValidated = false;
// if user still has a security context session open from a previous
// request, we probably don’t need to re-auth them
// debug stuff the message has…
// for(String key: message.keySet()) {
// System.out.println(“key: [“+key+”,”+message.get(key)+”]”);
// }
Vector result = (Vector) message
.getContextualProperty(WSHandlerConstants.RECV_RESULTS);
for (int i = 0; i < result.size(); i++) {
WSHandlerResult res = (WSHandlerResult) result.get(i);
for (int j = 0; j < res.getResults().size(); j++) {
WSSecurityEngineResult secRes = (WSSecurityEngineResult) res
.getResults().get(j);
WSUsernameTokenPrincipal principal = (WSUsernameTokenPrincipal) secRes
.getPrincipal();
// hack, we are just doing plain text…
if (principal.getPassword() != null) {
userTokenValidated = true;
} else {
throw new RuntimeException(
“Invalid Security Header: Please use a password”);
}
// old code to make sure all WS-Security headers are passed in
// and aren’t null
/*
* if(!principal.isPasswordDigest() || principal.getNonce() ==
* null || principal.getPassword() == null) { ||
* principal.getCreatedTime() == null) { throw new
* RuntimeException(“Invalid Security Header”); } else {
* userTokenValidated = true; }
*/
if (userTokenValidated) {
HttpServletRequest request = (HttpServletRequest) message
.get(“HTTP.REQUEST”);
request.getSession(true).getId();// hack to make sure we
// get a session id for
// acegi to use – this
// is needed for the
// concurrent filter
// authenticate with acegi
final UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(
principal.getName(), principal.getPassword());
// message.HTTP_REQUEST_METHOD
authReq.setDetails(new WebAuthenticationDetails(request));
SecurityContextHolder.getContext().setAuthentication(
authReq);
System.out
.println(“ValidateUserTokenAcegiInterceptor::handleMessage::principal.getName()=”
+ principal.getName());
userDetailsService.loadUserByUsername(
principal.getName());
}
}
}
if (!userTokenValidated) {
throw new RuntimeException(“Security processing failed”);
}
}
}
Step 16: Create beans.xml file to setup the application context for the server. If you are proper Acegi Database setup then you have to configure data source.
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:jaxws=”http://cxf.apache.org/jaxws”
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”>
<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” />
<jaxws:endpoint id=”helloWorld”
implementor=”com.sungard.cxf.example.server.IHello_Impl”
address=”/HelloService”>
<jaxws:inInterceptors>
<bean id=”logIn”
/>
<bean id=”logOut”
/>
<bean
/>
<bean
>
<property name=”properties”>
<map>
<entry key=”action”
value=”UsernameToken” />
<entry key=”passwordType” value=”PasswordText” />
<entry key=”passwordCallbackClass”
value=”com.sungard.cxf.example.server.PasswordHandler” />
</map>
</property>
</bean>
<bean
/>
</jaxws:inInterceptors>
</jaxws:endpoint>
<bean id=”userDetailsService”
>
</bean>
</beans>
Step 17: Create web.xml file
<?xml version=”1.0″ encoding=”ISO-8859-1″?>
<!DOCTYPE web-app
PUBLIC “-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN”
“http://java.sun.com/dtd/web-app_2_3.dtd”>
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/beans.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>CXFServlet</servlet-name>
<display-name>CXF Servlet</display-name>
<servlet-class>
org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Step 18: Create ant folder inside project. And Create build.xml file inside ant folder.
<?xml version=”1.0″ encoding=”UTF-8″?>
<project name=”ws” basedir=”../” default=”archive”>
<target name=”archive”>
<jar destfile=”acegisecurity.war”>
<fileset dir=”${basedir}”>
<include name=”**/*.class” />
</fileset>
<fileset dir=”${basedir}”>
<include name=”**/*.jar” />
</fileset>
<fileset dir=”${basedir}”>
<include name=”**/*.xml” />
<exclude name=”**/*build*” />
</fileset>
</jar>
</target>
</project>
Step 19: Run build.xml using Ant.
Step 20: Deploy acegisecurity.war into Web/Application Server (Tomcat/JBoss).
Step 21: Verify application deployed successfully or by using following url.
http://localhost:8080/acegisecurity/HelloService?wsdl
Step 22: Browser will show wsdl file our web service.
Creating Client Application.
Step 1: Create New Java project in Eclipse
Step 2: Create folder Structure as like above application
Step 3: Use same jar files used for Server application.
Step 4: Set all the jars files into classpath.
Step 5: Create Remote Interface in client (IHello.java) (You can use wsdl2java for creating same)
package com.sungard.cxf.example.server;
import javax.jws.WebService;
@WebService
public interface IHello {
public String sayHello(String value);
}
Step 6: Create ClientPasswordCallback.java for handling soap request in client side.
pc.setPassword(“arockiaraj”);
package com.sungard.cxf.example.server;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
public class ClientPasswordCallback implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
// set the password for our message.
pc.setPassword(“arockiaraj”);
}
}
Step 7: Create the service factory (AuthServiceFactory.java), which is extremely easy since all the work was done in the Spring file:
package com.sungard.cxf.example.server;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class AuthServiceFactory {
private static final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { “cxfClient.xml” });
public AuthServiceFactory() {
}
public IHello getService() {
return (IHello) context.getBean(“client”);
}
}
Step 8: Create Client.java to invoke the service.
package com.sungard.cxf.example.server;
public final class Client {
private Client() {
}
public static void main(String args[]) throws Exception {
AuthServiceFactory af = new AuthServiceFactory();
IHello client1 = af.getService();
String response1 = client1.sayHello(“Hello”);
System.out.println(“Response: ” + response1);
}
}
Step 9: Create cxfClient.xml to setup the application context for the client.
<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:jaxws=”http://cxf.apache.org/jaxws”
xsi:schemaLocation=”http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd”>
<bean id=”proxyFactory”
>
<property name=”serviceClass”
value=”com.sungard.cxf.example.server.IHello” />
<property name=”address”
value=”http://localhost:8080/acegisecurity/HelloService” />
<property name=”inInterceptors”>
<list>
<ref bean=”logIn” />
</list>
</property>
<property name=”outInterceptors”>
<list>
<ref bean=”logOut” />
<ref bean=”saajOut” />
<ref bean=”wss4jOut” />
</list>
</property>
</bean>
<bean id=”client”
factory-bean=”proxyFactory” factory-method=”create” />
<bean id=”logIn”
/>
<bean id=”logOut”
/>
<bean id=”saajOut”
/>
<bean id=”wss4jOut”
>
<constructor-arg>
<map>
<entry key=”action” value=”UsernameToken” />
<entry key=”user” value=”peter” />
<entry key=”passwordType” value=”PasswordDigest” />
<entry key=”passwordCallbackClass”
value=”com.sungard.cxf.example.server.ClientPasswordCallback” />
</map>
</constructor-arg>
</bean>
</beans>
Step 10: Run Client.java
You will get response like as follows.
Response: You SaidHello
Note:
Client Side:
We Set User name in client cxfClient.xml file. (We can set the same through program also and we can read it xml/properties files. We can pass the same in runtime also)
<entry key=“user” value=“peter”/>
We Set password in ClientPasswordCallback.java class (We can pass same in runtime also)
// set the password for our message.
pc.setPassword(“arockiaraj”);
You can see the In & Outbound Messages in Client Side.
INFO: Outbound Message
—————————
Encoding: UTF-8
Headers: {SOAPAction=[“”], Accept=[*]}
Messages:
Payload: <soap:Envelope xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/”>
<soap:Header>
<wsse:Security xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd” soap:mustUnderstand=”1″><wsse:UsernameToken xmlns:wsu=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd” wsu:Id=”UsernameToken-23954271″ xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd”><wsse:Username xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd”>arsenal</wsse:Username><wsse:Password Type=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest” xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd”>U6FK/CmMuPoKaB+SzgY4VNYed2U=</wsse:Password><wsse:Nonce xmlns:wsse=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd”>CdPEvSgU87L+4VR4SZxPQQ==</wsse:Nonce><wsu:Created xmlns:wsu=”http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd”>2008-03-27T12:53:36.713Z</wsu:Created></wsse:UsernameToken></wsse:Security></soap:Header><soap:Body><ns1:sayHello xmlns:ns1=”http://server.example.cxf.sungard.com/”><arg0>Hello</arg0></ns1:sayHello></soap:Body></soap:Envelope>
————————————–
Mar 27, 2008 6:23:37 PM org.apache.cxf.interceptor.LoggingInInterceptor logging
INFO: Inbound Message
—————————-
Encoding: UTF-8
Headers: {content-type=[text/xml;charset=UTF-8], Date=[Thu, 27 Mar 2008 12:53:37 GMT], Content-Length=[230], SOAPAction=[“”], Server=[Apache-Coyote/1.1]}
Messages:
Message:
Payload: <soap:Envelope xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/”><soap:Body><ns1:sayHelloResponse xmlns:ns1=”http://server.example.cxf.sungard.com/”><return>You SaidHello</return></ns1:sayHelloResponse></soap:Body></soap:Envelope>
————————————–
Server Side:
User Name and password are got validated in PasswordHandler.java file. These values we can read it from xml/properties or from configuration files.
if (pc.getIdentifer().equals(“peter”)) {
pc.setPassword(“arockiaraj”);
}
2 Comments »
Leave a comment
-
Recent
- Integrating JSF, Spring Security and LDAP
- Developing Web Services by using Mule, CXF, and Spring
- Developing Web Services by Using Metro Webservices Framework
- Developing CXF WS-Security with Spring & Acegi Security
- Developing CXF WS-Security with SAML
- Developing CXF WS-Security with Signature(Certificates)
- Developing CXF Web services with WS-Security
- RESTful web services using the Jersey framework
- Developing Web Services By Using Spring and CXF
- Developing Web Services by Using Spring and XFire
- Developing Web services Using Spring Framework
- Developing Simple Web Services by Using JWSDP
-
Links
-
Archives
- January 2010 (1)
- November 2009 (1)
- October 2009 (1)
- September 2009 (9)
-
Categories
-
RSS
Entries RSS
Comments RSS
While running this I am facing the issue in beans.xml interceptor.Actually the bean name/id is not given to set the properties.
If you provide the whole code in zip file ,it will be very much useful to me.