Introduction
Purpose of this blog is to show how to integrate Spring Security framework with REST web services implemented using Apache CXF. To authenticate user within Spring Security framework external authentication mechanism based on token is used. The idea is to authenticate Spring Security application against external application – in our case Alfresco.
Dependencies
In order to get required libraries the following ivy script was used:
<ivy-module version="2.0">
<info organisation="icmr" module="alfrescoEnhancements"/>
<dependencies>
<dependency org="junit" name="junit" rev="4.11"/>
<dependency org="org.springframework" name="spring-web" rev="3.1.3.RELEASE"/>
<dependency org="org.springframework" name="spring-jdbc" rev="3.1.3.RELEASE"/>
<dependency org="org.springframework" name="spring-orm" rev="3.1.3.RELEASE"/>
<dependency org="org.springframework" name="spring-test" rev="3.1.3.RELEASE"/>
<dependency org="org.springframework.security" name="spring-security-core" rev="3.1.3.RELEASE" />
<dependency org="org.springframework.security" name="spring-security-web" rev="3.1.3.RELEASE" />
<dependency org="org.springframework.security" name="spring-security-config" rev="3.1.3.RELEASE" />
<dependency org="org.apache.cxf" name="cxf-rt-frontend-jaxrs" rev="2.7.1"/>
<dependency org="org.codehaus.jackson" name="jackson-jaxrs" rev="1.9.9"/>
<dependency org="org.codehaus.jackson" name="jackson-xc" rev="1.9.9"/>
<dependency org="org.hibernate" name="hibernate-core" rev="4.1.7.Final"/>
<dependency org="org.hibernate" name="hibernate-entitymanager" rev="4.1.7.Final"/>
<dependency org="org.hibernate.common" name="hibernate-commons-annotations" rev="4.0.1.Final"/>
<dependency org="org.eclipse.persistence" name="org.eclipse.persistence.core" rev="2.3.1"/>
<dependency org="org.eclipse.persistence" name="org.eclipse.persistence.jpa" rev="2.3.1"/>
<dependency org="org.hibernate.javax.persistence" name="hibernate-jpa-2.0-api" rev="1.0.0.Final" />
<dependency org="javassist" name="javassist" rev="3.12.1.GA" />
<dependency org="org.antlr" name="antlr" rev="3.5-rc-1" />
<dependency org="com.caucho" name="hessian" rev="4.0.7" />
</dependencies>
</ivy-module>
It might be necessary to remove older versions of some jars.
List of libraries required in the project that should be put in WEB-INF/lib is presented below:
activation-1.1.jar hessian-4.0.7.jar jetty-server-8.1.5.v20120716.jar spring-asm-3.1.3.RELEASE.jar
antlr-2.7.7.jar hibernate-commons-annotations-4.0.1.Final.jar jetty-servlet-8.1.5.v20120716.jar spring-beans-3.1.3.RELEASE.jar
antlr-runtime-3.5-rc-1.jar hibernate-core-4.1.7.Final.jar jetty-util-8.1.5.v20120716.jar spring-context-3.1.3.RELEASE.jar
aopalliance-1.0.jar hibernate-entitymanager-4.1.7.Final.jar jsf-api-1.2_08.jar spring-context-support-3.1.3.RELEASE.jar
aspectjweaver-1.7.1.jar hibernate-jpa-2.0-api-1.0.0.Final.jar json.jar spring-core-3.1.3.RELEASE.jar
blueprint-parser-1.0.0.jar hsqldb-1.8.0.10.jar json-path-0.8.1.jar spring-expression-3.1.3.RELEASE.jar
bsh-2.0b4.jar httpclient-4.2.jar json-smart-1.1.1.jar spring-jdbc-3.1.3.RELEASE.jar
c3p0-0.9.1.2.jar httpcore-4.2.jar jsp-api-2.0.jar spring-orm-3.1.3.RELEASE.jar
commons-codec-1.6.jar ibatis-sqlmap-2.3.4.726.jar jstl-1.2.jar spring-oxm-3.1.3.RELEASE.jar
commons-collections-3.2.jar jackson-annotations-2.0.1.jar junit-4.11.jar spring-security-config-3.1.3.RELEASE.jar
commons-fileupload-1.2.jar jackson-core-2.0.1.jar log4j-1.2.17.jar spring-security-core-3.1.3.RELEASE.jar
commons-httpclient-3.1.jar jackson-core-asl-1.9.9.jar openjpa-1.1.0.jar spring-security-web-3.1.3.RELEASE.jar
commons-io-1.3.jar jackson-databind-2.0.1.jar org.apache.aries.blueprint.api-1.0.0.jar spring-test-3.1.3.RELEASE.jar
commons-lang-2.6.jar jackson-jaxrs-1.9.9.jar org.apache.aries.blueprint.core-1.0.0.jar spring-tx-3.1.3.RELEASE.jar
commons-logging-1.1.1.jar jackson-mapper-asl-1.9.9.jar org.apache.aries.proxy.api-1.0.0.jar spring-web-3.1.3.RELEASE.jar
commons-pool-1.3.jar jackson-xc-1.9.9.jar org.apache.aries.quiesce.api-1.0.0.jar spring-webmvc-3.1.3.RELEASE.jar
cxf-api-2.7.1.jar javassist-3.12.1.GA.jar org.apache.aries.util-1.0.0.jar spring-webmvc-portlet-3.1.3.RELEASE.jar
cxf-rt-bindings-xml-2.7.1.jar javax.inject-1.jar org.eclipse.persistence.core-2.3.1.jar ST4-4.0.7-rc-1.jar
cxf-rt-core-2.7.1.jar javax.ws.rs-api-2.0-m10.jar org.eclipse.persistence.jpa-2.3.1.jar standard-1.1.2.jar
cxf-rt-frontend-jaxrs-2.7.1.jar jaxb-impl-2.1.13.jar org.osgi.compendium-4.2.0.jar stax2-api-3.1.1.jar
cxf-rt-transports-http-2.7.1.jar jaxrpc-api-1.1.jar org.osgi.core-4.2.0.jar stax-api-1.0-2.jar
derby-10.5.3.0_1.jar jboss-logging-3.1.0.CR2.jar persistence-api-1.0.jar stringtemplate-3.2.1.jar
derbyclient-10.5.3.0_1.jar jcommander-1.12.jar portlet-api-2.0.jar testng-6.5.2.jar
dom4j-1.6.1.jar jdo-api-3.0.jar rome-1.0.jar transaction-api-1.1.jar
geronimo-javamail_1.4_spec-1.7.1.jar jdom-1.0.jar saaj-api-1.3.jar woodstox-core-asl-4.1.4.jar
geronimo-jms_1.1_spec-1.0.1.jar jetty-continuation-8.1.5.v20120716.jar serp-1.13.1.jar wsdl4j-1.6.2.jar
geronimo-jta_1.1_spec-1.1.jar jetty-http-8.1.5.v20120716.jar slf4j-api-1.6.2.jar xml-apis-1.0.b2.jar
h2-1.0.71.jar jetty-io-8.1.5.v20120716.jar snakeyaml-1.6.jar xmlschema-core-2.0.3.jar
hamcrest-core-1.3.jar jetty-security-8.1.5.v20120716.jar spring-aop-3.1.3.RELEASE.jar xmlunit-1.2.jar
web.xml
Configuration of WEB-INF/web.xml file is presented below. It contains configuration of the following frameworks:
- Spring
- Spring Security
- Apache CXF
- Hibernate
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<!-- Define configuration file for Spring -->
<context-param>
<description>Spring configuration file</description>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/applicationContext.xml</param-value>
</context-param>
<!-- Define configuration file for log4j -->
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.properties</param-value>
</context-param>
<!-- Add log4j logging -->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<!-- Enable Spring support -->
<listener>
<description>Spring Loader</description>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--Define REST web services servlet -->
<servlet>
<servlet-name>CXFServlet</servlet-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>/service/*</url-pattern>
</servlet-mapping>
<!-- Hibernate filter to support opening session to get additional options -->
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/service/*</url-pattern>
</filter-mapping>
<!-- Spring Security -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/service/*</url-pattern>
</filter-mapping>
</web-app>
We are going to put all the files related to REST web service in path /service/*
, therefore in the configuration file above this path is set as url-pattern for Apache CXF Service filter (CXFServlet
). Spring Security filter (springSecurityFilterChain
) has the same url-pattern set in order to secure REST web services.
Spring configuration file
As defined in WEB-INF/web.xml file Spring configuration file is in the following location ‘/WEB-INF/classes/applicationContext.xml’ and is presented below. It includes files with configuration of beans required by Spring Security (resources/context/security.xml) and Apache CXF REST web service (resources/context/service.xml). Section with namespace ‘jaxrs’ corresponds to configuration of Apache CXF REST web service and section with namespace ‘security’ corresponds to configuration related to Spring Security. They will be described in more detail in the following sections.
<?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:jaxrs="http://cxf.apache.org/jaxrs"
xmlns:cxf="http://cxf.apache.org/core"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd
http://cxf.apache.org/core
http://cxf.apache.org/schemas/core.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd"
>
<!-- Enable use of annotations -->
<context:annotation-config />
<!-- Import files with Spring bean definitions -->
<import resource="classpath:resources/context/service.xml" />
<import resource="classpath:resources/context/security.xml" />
<!-- Define REST services -->
<jaxrs:server id="userService" address="/">
<jaxrs:serviceBeans>
<ref bean="userServiceBean"/>
</jaxrs:serviceBeans>
<jaxrs:extensionMappings>
<entry key="xml" value="application/xml" />
<entry key="json" value="application/json" />
<entry key="feed" value="application/atom+xml"/>
<entry key="html" value="text/html"/>
</jaxrs:extensionMappings>
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
<bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider"/>
</jaxrs:providers>
<jaxrs:features>
<cxf:logging/>
</jaxrs:features>
</jaxrs:server>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="myAuthenticationProvider" />
</security:authentication-manager>
<security:global-method-security authentication-manager-ref="authenticationManager" jsr250-annotations="enabled"/>
<security:http pattern="/service/initialize/**" security="none"/>
<security:http auto-config="false" entry-point-ref="http403ForbiddenEntryPoint" use-expressions="true" >
<security:intercept-url pattern="/service/**" access="isAuthenticated()" />
<security:custom-filter position="FORM_LOGIN_FILTER" ref="myAuthenticationProcessingFilter" />
</security:http>
</beans>
Apache CXF REST web service configuration
As defined in ‘/WEB-INF/classes/applicationContext.xml’ (jaxrs:server
) REST service consists of one service – userService. Corresponding Spring bean (jaxrs:serviceBeans
) is called ‘userServiceBean’ and is defined in /WEB-INF/classes/resources/context/service.xml file as follows:
<bean id="userServiceBean" class="com.jmuras.service.user.UserServiceImpl" />
In addition the configuration defines extension mappings (jaxrs:extensionMappings
), enables logging (cxf:logging
), and selects Jackson JSON-processor for processing JSON data (jaxrs:providers
). </p>
UserServiceImpl
class is presented below. It implements UserService interface, produces JSON content, and path to the service is /user
. It also autowires session factory, which is hibernate session factory, and defines one method retrieveProperties()
. retrieveProperties()
method can be accessed using the following URL: /service/user/retrieveProperties
. It responds to GET request and returns JSON (determined by @Produces annotation of UserServiceImpl class) with list of property names. The URL depends on configuration of url-pattern in Apache CXF filter (web.xml) and @Path annotations.
package com.jmuras.service.user;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author Joanna Muras
* @date 12/12/12
* Class that implements user service methods.
*/
@Path("/user")
@Produces(MediaType.APPLICATION_JSON + "; " + MediaType.CHARSET_PARAMETER +"=UTF-8")
public class UserServiceImpl implements UserService {
private static Log logger = LogFactory.getLog(UserServiceImpl.class);
@Autowired
private SessionFactory sessionFactory;
@GET
@Path("/retrieveProperties")
@Transactional
public Collection<String> retrieveProperties() {
if (logger.isDebugEnabled()) {
logger.debug("Retrieve user properties");
}
Session session = sessionFactory.getCurrentSession();
Query query = session.getNamedQuery("User.findProperties");
return query.list();
}
}
Spring Security related configuration
As mentioned in previous section Spring security configuration is located in ‘/WEB-INF/classes/resources/context/security.xml’. Let’s assume the following scenario of authentication in Spring Security framework. There is external authentication service that we use to authorise user within Spring Security framework. When request to /service/user/retrieveProperties
is made it also contains a ticket passed as parameter in URL, i.e., /service/user/retrieveProperties?ticket=...
. The ticket contains all the information necessary to authenticate user in external service and see whether the ticket is still valid. External service returns all the information required by Spring Security framework to authenticate the user. The related configuration is presented below and will be discussed in the following part of this section.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- My Authentication Provider -->
<bean id="myAuthenticationProvider" class="com.jmuras.security.AuthenticationProvider" />
<bean id="myAuthenticationProcessingFilter" class="com.jmuras.security.AuthenticationProcessingFilter" >
<constructor-arg value="/"/>
<property name="authenticationManager" ref="authenticationManager"/>
<property name="sessionAuthenticationStrategy" ref="sas" />
...
</bean>
<bean id="http403ForbiddenEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
<bean id="sas" class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
</beans>
myAuthenticationProcessingFilter bean – AuthenticationProcessingFilter class
Responsibility of myAuthenticationProcessingFilter bean is to take ticket from the request and issue request to external authentication service to get user details. The user details are then translated to the roles defined in Spring Security annotations. AuthenticationProcessingFilter
class extends AbstractAuthenticationProcessingFilter
class which demands implementation of Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
method. This method performs actual authentication – in our example obtains user details from external authentication service in the basis of the ticket.
attemptAuthentication
method is invoked only when boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response)
method returns ‘true’. Implementation of requiresAuthentication
in AbstractAuthenticationProcessingFilter
class matches filterProcessesUrl
(given in constructor of AuthenticationProcessingFilter
object) against request URL, because we do not want to define access to REST web service methods on the basis of their URLs, we have to overwrite this behaviour. We assume that if there is no authentication object in context then requiresAuthentication
method should return true. When authentication object is obtained getAuthenticationManager().authenticate(authentication)
is invoked in the code to authenticate the user using authentication provider (AuthenticationProvider
).
successfulAuthentication
method is invoked when credentials are retrieved from third party authentication system and attemptAuthentication
does not throw AuthenticationException
exception. The method saves authentication
object in security context and invokes next filter from the chain.
Implementation of myAuthenticationProcessingFilter bean, which checks ticket in Alfresco is presented below. It is assumed that there will be two additional parameters passed to request URL – alf_ticket, workgroup – /service/user/retrieveProperties?alf_ticket=...&workgroup=...
. There are the following roles defined in the system:
- ROLE_ADMINISTRATOR
- ROLE_WORKGROUP_ADMINISTRATOR
- ROLE_USER
Depending on the role user should be able to run only allowed web services.
public class AuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
private static Log logger = LogFactory.getLog(AuthenticationProcessingFilter.class);
public static final String TICKET = "alf_ticket";
public static final String WORKGROUP = "workgroup";
private String userPreferencesURL;
private int retries;
protected AuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
public void setUserPreferencesURL(String userPreferencesURL) {
this.userPreferencesURL = userPreferencesURL;
}
public void setRetries(int retries) {
this.retries = retries;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
AuthenticationToken authentication = null;
String ticket = httpServletRequest.getParameter(TICKET);
// Get workgroup parameter if present in URL
String[] uri = httpServletRequest.getRequestURI().split("/");
String workgroup = null;
if (uri != null && uri.length > 0) {
int workgroupIndex = -1;
for (int i = 0; i < uri.length; i++) {
if (uri[i].equals(WORKGROUP)) {
workgroupIndex = i;
}
}
if (workgroupIndex > -1 && workgroupIndex + 1 < uri.length) {
workgroup = uri[workgroupIndex + 1];
}
}
// Create an instance of HttpClient.
HttpClient client = new HttpClient();
// Create user preferences request URL
StringBuffer sb = new StringBuffer(userPreferencesURL);
sb.append("?");
sb.append(TICKET);
sb.append("=");
sb.append(ticket);
if (workgroup != null && workgroup.length() > 0) {
sb.append("&");
sb.append(WORKGROUP);
sb.append("=");
sb.append(workgroup);
}
if (logger.isDebugEnabled()) {
logger.debug("Obtain user details from Alfresco by the following request " + userPreferencesURL);
}
// Create a method instance.
HttpMethodBase method = new GetMethod(sb.toString());
// Forward accept header
method.setRequestHeader("Accept", "application/json");
// Provide custom retry handler is necessary
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(retries, true));
try {
// Execute the method.
int statusCode = client.executeMethod(method);
if (statusCode == HttpStatus.SC_OK) {
// Read the response body.
String responseString = method.getResponseBodyAsString();
if (logger.isDebugEnabled()) {
logger.debug("Following string with user details obtained from Alfresco " + userPreferencesURL);
}
if (responseString != null) {
JSONObject json = new JSONObject(responseString);
// Get authentication data from Alfresco response
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
// Depending on response obtained from Alfresco add appropriate roles to 'authorities' structure.
boolean isAdmin = json.getBoolean("isAdmin");
if (isAdmin) {
authorities.add(new SimpleGrantedAuthority("ROLE_ADMINISTRATOR"));
}
if (!json.isNull("workgroupRole")) {
String workGroupRole = json.getString("workgroupRole");
if (workGroupRole != null && workGroupRole.contentEquals("SiteManager")) {
authorities.add(new SimpleGrantedAuthority("ROLE_WORKGROUP_ADMINISTRATOR"));
}
}
String principal = json.getString("userName");
// Assign user role only if user is neither workGroup administrator nor administrator
if (principal != null && authorities.size() == 0) {
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
}
// Save authorities in authentication object
authentication = new AuthenticationToken(authorities);
authentication.setPrincipal(principal);
authentication.setCredentials(ticket);
if (logger.isDebugEnabled()) {
logger.debug("Authentication object filled in as follows " + authentication);
}
// Authenticate the user
return getAuthenticationManager().authenticate(authentication);
}
} else {
logger.error("User details could not be obtained from Alfresco " + method.getStatusLine().toString());
}
} catch (HttpException e) {
logger.error(e.getMessage(), e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
} catch (JSONException e) {
logger.error(e.getMessage(), e);
} finally {
// Release the connection.
method.releaseConnection();
}
// Throw exception when not possible to authenticate
throw new AuthenticationCredentialsNotFoundException("Authentication for ticket '" + ticket + "' unsuccessful");
}
@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
SecurityContext context = SecurityContextHolder.getContext();
if (context != null && context.getAuthentication() != null) {
return false;
}
return true;
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException{
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
myAuthenticationProvider bean – AuthenticationProvider class
This bean is responsible for returning information whether user is authenticated or not. It implements AuthenticationProvider
interface, which defines 2 methods:
Authentication authenticate(Authentication authentication)
– This method returns authentication object used by Spring Security for authorization. It checks data provided in authentication object passed as parameter and on that basis decides whether user should be authenticated (authentication.setAuthenticated(true)
) or not. In our example if user name (authentication.getPrincipal()
) was obtained from external service on the basis of the ticket, it is assumed that user is authenticated. If user is not authenticatedBadCredentialsException
is thrown.boolean supports(Class aClass)
– This method returns whether AuthenticationProvider object should process the request –authenticate
method should be invoked. In that case onlyauthentication
object given as a parameter toauthenticate
method which is ofAuthenticationToken
type should be processed byauthenticate
method.
Implementation of myAuthenticationProvider bean is presented below.
public class AuthenticationProvider implements AuthenticationProvider {
private static Log logger = LogFactory.getLog(AuthenticationProvider.class);
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// Assume that if principal present user was authenticated against external service
if (authentication != null && authentication.getPrincipal() != null) {
authentication.setAuthenticated(true);
if (logger.isDebugEnabled()) {
logger.debug("User " + authentication.getPrincipal() + " authenticated");
}
return authentication;
}
if (logger.isDebugEnabled()) {
logger.debug("Ticked not valid");
}
throw new BadCredentialsException("Ticked not valid");
}
@Override
public boolean supports(Class<?> aClass) {
return aClass.equals(AuthenticationToken.class);
}
}
Roles definition
Roles are defined in authentication
object (AuthenticationToken
class). Depending on roles assigned to the user it is possible to allow the user to invoke particular web services and forbid invocation of another.The roles defined in this example are:
- ROLE_ADMINISTRATOR – administrator
- ROLE_WORKGROUP_ADMINISTRATOR – workgroup administrator
- ROLE_USER – user
The roles are used in the annotations in user service (UserService
) interface. The interface is implemented by UserServiceImpl
class – userServiceBean
bean. In the code below two methods, which are web services, are presented – retrieveOptions
and createOption
. The first one allows users in all three roles to invoke the web service call, and the second one allows the invocation for only workgroup administrator. The roles are the can invoke particular method are listed in @RolesAllowed
annotation.
public interface UserService {
public final static String TICKET = "alf_ticket";
/**
* Return list of options for particular property matching criteria
* @param property name of the property
* @param parentProperty name of the parent property (optional)
* @param parentWorkgroup name of the parent workgroup (optional)
* @param parentValue parent value (optional)
* @param page number of page to start
* @param results number of results per page
* @return list of options
*/
@RolesAllowed({"ROLE_USER", "ROLE_WORKGROUP_ADMINISTRATOR", "ROLE_ADMINISTRATOR"})
public Collection<OptionEntityXML> retrieveOptions(String property, Long parentId, String parentProperty, String parentWorkgroup, String parentValue, int page, int results);
/**
* Create new WorkGroup specific option
* @param workgroup workgroup name for new option
* @param wrapper values of option to be added
* @return created option
*/
@RolesAllowed({"ROLE_WORKGROUP_ADMINISTRATOR"})
public OptionEntityXML createOption(String workgroup, OptionEntityWrapper wrapper);
}
The corresponding implementation class is presented below:
@Path("/user")
@Produces(MediaType.APPLICATION_JSON + "; " + MediaType.CHARSET_PARAMETER +"=UTF-8")
public class UserServiceImpl implements UserService {
private static Log logger = LogFactory.getLog(UserServiceImpl.class);
private AlfrescoSearch search;
@Autowired
private SessionFactory sessionFactory;
public void setSearch(AlfrescoSearch search) {
this.search = search;
}
@GET
@Path("/retrieveOptions/{property}")
@Transactional
@XmlElementWrapper(name = "options")
@XmlElement(name = "option")
public Collection<OptionEntityXML> retrieveOptions(@PathParam("property") String property, @QueryParam("parentId") Long parentId, @QueryParam("parentProperty") String parentProperty, @QueryParam("parentWorkgroup") String parentWorkgroup, @QueryParam("parentValue") String parentValue, @DefaultValue("0") @QueryParam("page") int page, @DefaultValue("0") @QueryParam("results") int results) {
List<OptionEntity> list = (List<OptionEntity>) retrieveOptionsEntity(property, parentId, parentProperty, parentWorkgroup, parentValue, page, results);
return getXMLList(list);
}
@POST
@Path("/createOption")
@Consumes(MediaType.APPLICATION_JSON)
@Transactional
public OptionEntityXML createOption(OptionEntityWrapper wrapper) {
if (wrapper == null) {
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("Create option with the following parameters" + wrapper.getValue() + "," + wrapper.getProperty() + "," + wrapper.getWorkgroup());
}
Session session = sessionFactory.getCurrentSession();
OptionEntity entity = wrapper.getEntity();
// Remove workgroup if set
wrapper.setWorkgroup(null);
try {
OptionEntity parentEntity = getParentOption(wrapper);
if (parentEntity != null) {
entity.setParent(parentEntity);
}
} catch (NotFoundException ex) {
logger.error("Parent entity for the following parameters not found: " + wrapper.getParentId() + ", " + wrapper.getParentProperty() + ", " + wrapper.getParentWorkgroup() + ", " + wrapper.getParentValue());
}
session.save(entity);
return new OptionEntityXML(entity);
}
}