How to use Spring Security to authenticate a user/password web service implemented with CXF using WSS4J (Apache Java WS-Security):
In the Spring Application context, we define the namespaces, import CXF's xml and define the "Interceptor" to deal with security:
<?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:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:cxf="http://cxf.apache.org/core" xmlns:util="http://www.springframework.org/schema/util" xmlns:jaxws="http://cxf.apache.org/jaxws" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd" > <!-- Load CXF modules from cxf.jar --> <import resource="classpath:META-INF/cxf/cxf.xml" /> <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" /> <import resource="classpath:META-INF/cxf/cxf-servlet.xml" /> <bean id="WSS4JInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor"> <property name="properties"> <map> <entry key="action" value="UsernameToken Timestamp"/> <entry key="passwordType" value="PasswordDigest"/> <entry key="passwordCallbackRef"> <bean class="my.company.SecurityInPasswordHandler"/> </entry> </map> </property> </bean> </beans>
And then we add the interceptor to the webservice:
<jaxws:endpoint id="salaWebService" implementor="#salaService" address="/salas"> <jaxws:inInterceptors> <ref bean="WSS4JInInterceptor"/> </jaxws:inInterceptors> </jaxws:endpoint>
Assuming we have the following service:
@WebService @SOAPBinding public interface SalaService { @WebResult(name = "sala") public abstract Sala get(@WebParam(name = "id") Long id); } @Service("salaService") @WebService(serviceName = "SalaService", portName = "SalaPort", endpointInterface = "my.company.service.SalaService") public class SalaServiceImpl implements SalaService { public Sala get(Long id) { return (Sala) executor.execute(SalaBusinessOperations.GET, new Long(id)); } }
And CXF configured in web.xml as follows:
<servlet> <servlet-name>CXFServlet</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CXFServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping>
The spring security should be configured with something like this:
<http > <intercept-url pattern="/services/**" access="ROLE_ANONYMOUS" /> <intercept-url pattern="/**" access="ROLE_USER" /> <logout/> <anonymous/> </http>
Finally we could write the interceptor class:
public class SecurityInPasswordHandler implements CallbackHandler { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userService; public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException, AuthenticationException { WSPasswordCallback pwdCallback = (WSPasswordCallback) callbacks[0]; int usage = pwdCallback.getUsage(); if ((usage == WSPasswordCallback.USERNAME_TOKEN) || (usage == WSPasswordCallback.USERNAME_TOKEN_UNKNOWN)) { String password = pwdCallback.getPassword(); if (usage == WSPasswordCallback.USERNAME_TOKEN) { UserDetails userDetails = userService.loadUserByUsername(pwdCallback.getIdentifier()); password = userDetails.getPassword(); } Authentication authentication = new UsernamePasswordAuthenticationToken(pwdCallback.getIdentifier(), password); authentication = authenticationManager.authenticate(authentication); //throws AuthenticationException SecurityContextHolder.getContext().setAuthentication(authentication); // Return the password to the caller pwdCallback.setPassword(password); } } }
Now we could handle plain (PasswordText) or encrypted (PasswordDigest) passwords. In both cases creating a SecurityContext for the request to have the user roles and use them in our Spring Security "@Secured" annotations.
2 comments:
Excellent example... We're trying to use CXF within OSGi by contributing web services from multiple bundles.. any thoughts as to how we could configure the intercepts in that sort of multi-bundle environment?
Post a Comment