Clearspace has multiple facilities to handle the three primary facets of network application security. This section will discuss each and highlight APIs commonly of interest to developers customizing Clearspace installations.
The following terms are used commonly in the remainder of this section and are outlined here for clarification:
AnonymousUser class to represent the security context of
the current request. Clearspace relies on common security patterns established in the Spring Security (formerly Acegi Security) library. By leveraging Spring Security, Clearspace uses terminology familiar to Spring users in an effort to standardize integration and leverage existing Spring libraries and idioms.
Fundamentally, authentication in Clearspace is performed by a series of Spring Security filter (implementations of J2EE Servlet Filters) chains, linked together. Each element in a given chain has a dedicated responsibility, while each chain is responsible for accomplishing high-level goals towards the handling of a request. Ultimately, these chains must prepare a request to fulfill a single contract enforced by the last link in the primary security filter chain.
Each thread of execution in Clearspace, including background jobs and asynchronous tasks, is associated with a Spring Security SecurityContext instance. The SecurityContext holds information about the Authentication associated with the request.
Internally within Clearspace code, Jive extends the Spring Security Authentication interface with the JiveAuthentication class. This class serves a number of purposes, including directly exposing a Clearspace User implementation representing the current user through a strongly-typed
contract as well as exposing meta data about the user such as whether or not the user is anonymous.
Each URI handled by the Clearspace system passes through a series of J2EE Servlet Filters at the Clearspace Security Layer before entering the Clearspace Application Layer. The following URI contexts are defined in a standard Clearspace installation:
The series of filters handling each request can be altered through the Clearspace Plugin system when customization of authentication behavior is needed (see below).
Clearspace defines several Security Filter Chains, each mapped to a specific URL pattern described above. The default filter chain is defined in spring-securityContext.xml as the following set of filters:
| Place in Chain | Filter Used | Description | As Defined in spring.xml |
|---|---|---|---|
| 1 | Session Integration filter | Associates HTTP requests with a security context when a user has previously authenticated or entered the system as a guest. | httpSessionContextIntegrationFilter |
| 2 | Authentication filters | The default authentication filter is an implementation of Spring Security's FormAuthenticationProcessingFilter which delegates to an internal set of AuthenticationProvider implementations. |
formAuthenticationFilter |
| 3 | Cookie Authentication filter | Processes "RememberMe" cookies, long-lived HTTP cookies used to authenticate a given user beyond any given session. | rememberMeProcessingFilter |
| 4 | Feed Basic Authentication filter | Performs HTTP Basic Authentication of requests for RSS/Atom feeds. It is generally intended to authenticate standalone feed readers and not browser-based requests. | feedBasicAuthenticationFilter |
| 5 | Exception Translation filter | Routes redirects of various security-related exceptions to URLs within Clearspace. Security-related exceptions from application-level code are caught and processed by this filter and interceptors in the Struts 2 layer depending on the exception. | exceptionTranslationFilter |
| 6 | Authentication Translation filter | Enforces the authentication contract between the Clearspace Security Layer and Clearspace Application Code. | jiveAuthenticationTranslationFilter |
The authentication contract is a fundamental set of assumptions made by application-level code about the security context of any given request. In a standalone Clearspace configuration (one in which Clearspace is the system of record for user information), the authentication contract is met by out of the box Clearspace functionality. Likewise, for LDAP-based authentication Clearspace fufills the contract. In the case of custom authentication, third-party code must meet the terms of the contract in order to perform a successful authentication.
The authentication contract is enforced by the last filter in the Clearspace Security Filter Chain, the JiveAuthenticationTranslationFilter. This ensures that the authentication associated with the SecurityContext is a valid JiveAuthentication before transferring control of the request
handling to the Clearspace application layer downstream.
The contract between the Clearspace security layer and the Clearspace application layer requires that one of the following is true before control is passed from the security layer to the application layer:
SecurityContext of the request contains an instance of the JiveAuthentication interface (established through the SecurityContext.setAuthentication method).Authentication associated with the SecurityContext returns true for isAuthenticated() and an implementation of Jive's User interface is present in either the getPrincipal() method or in the getDetails() method.As part of the authentication contract, if no authentication is present when the JiveAuthenticationTranslationFilter is invoked, the AnonymousAuthentication will be set to the SecurityContext prior to transferring control to the application layer. As a result, application-level code needn't check to see if user references obtained from the SecurityContext are null.
Clearspace includes several implementations of the JiveAuthentication interface, a subclass of Spring Security's Authentication interface. Most commonly used is JiveUserAuthentication which requires an implementation of the Jive User interface as it's sole constructor argument.
As an example, once a handle to a User implementation has been obtained (directly created or through the UserManager API), that implementation instance can fulfill the authentication contract by creating an instance of JiveUserAuthentication and setting that instance to the
SecurityContext.
UserTemplate ut = new UserTemplate(); SecurityContextHolder.getContext().setAuthentication(new JiveUserAuthentication(ut));
Authorization in Clearspace is addressed via three constructs in the Clearspace Application Layer.
Permissions behavior is governed by the PermissionsManager API, group membership by the GroupManager API. Proxies are used to secure access to Clearspace API methods and domain objects as they move through the system. Proxies enforce security based on the Acegi SecurityContext associated
with a request. Clearspace associates instances of an Acegi subclass — JiveAuthentication — with each request by the time the servlet stack leaves the filter chain. That JiveAuthentication contains the effective user for the current call stack, which is in turn used to drive proxy
authorization checks.
When customizing authentication or authorization in Clearspace, you'll need to understand where to integrate with various security concerns. The following provides a high-level overview of common customizations for Clearspace 2.0 (and later), including code samples and common strategies for integrating with third-party authentication systems (integration commonly referred to as single sign-on, or SSO).
Jive customers have commonly made several kinds of customizations. Generally, these customizations are of the following kinds:
Depending on your requirements, you can exclude any of these steps. For example, if your Clearspace deployment is not intended to integrate with a centralized group management system, step three is unnecessary. Likewise, if you're using Clearspace to manage all user profile information, step two above may not be necessary.
The recommended way to perform any of the operations described above is to implement a J2EE Filter. A filter commonly:
UserManager and GroupManager implementations.formAuthenticationFilter bean defined in the default Clearspace installation. The formAuthenticationFilter processes username/password authentication. Commonly, customized solutions wish to disable form-based authentication. As such, customizations replace the formAuthenticationFilter bean with a custom-defined filter managed as a Spring bean.SecurityContext indicates that the current authentication is null. The following XML fragment demonstrates how to define a Spring-managed Filter and replace the default formAuthenticationFilter bean with the newly defined filter:
<!-- Alter the default mapping chains, switching formAuthenticationFilter with the federatedIdentityAuthFilter defined below. This definition uses the federated identity filter for normal app requests and Web Service requests, but leaves the form mechanism in place for admin and upgrade purposes. The desired behavior will vary depending on the application. --> <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy"> <property name="filterInvocationDefinitionSource"> <value> <!-- The URL mappings here are broken for readability, but in your XML file they must be unbroken. --> CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /upgrade/**=httpSessionContextIntegrationFilter, upgradeAuthenticationFilter, \ upgradeExceptionTranslationFilter, jiveAuthenticationTranslationFilter /post-upgrade/**=httpSessionContextIntegrationFilter, postUpgradeAuthenticationFilter, \ postUpgradeExceptionTranslationFilter,jiveAuthenticationTranslationFilter /admin/**=httpSessionContextIntegrationFilter, adminAuthenticationFilter, \ adminExceptionTranslationFilter,jiveAuthenticationTranslationFilter /rpc/xmlrpc=wsRequireSSLFilter, httpSessionContextIntegrationFilter, federatedIdentityAuthFilter, \ wsExceptionTranslator, jiveAuthenticationTranslationFilter, wsAccessTypeCheckFilter /rpc/rest/**=wsRequireSSLFilter, httpSessionContextIntegrationFilter, federatedIdentityAuthFilter, \ wsExceptionTranslator, jiveAuthenticationTranslationFilter, wsAccessTypeCheckFilter /rpc/soap/**=wsRequireSSLFilter, httpSessionContextIntegrationFilter, federatedIdentityAuthFilter, \ jiveAuthenticationTranslationFilter /**=httpSessionContextIntegrationFilter, federatedIdentityAuthFilter, \ jiveAuthenticationTranslationFilter </value> </property> </bean> <!-- Define a new filter for externally-managed users. Inject dependencies as needed. --> <bean id="federatedIdentityAuthFilter" class="com.jivesoftware.plugins.aaa.FederatedIdentityAuthFilter"> <property name="userManager" ref="userManagerImpl"/> <property name="groupManager" ref="groupManagerImpl"/> <property name="userAgent" ref="agent"/> <property name="groupAgent" ref="agent"/> </bean>
As of Clearspace 2.5, you can include J2EE Filters in the Jive Spring context by using a Clearspace plugins. In this way, you can develop custom authentication solutions for Clearspace without changing the contents of its WAR file or changing the application server class path.
The following code excerpts are taken from a sample Clearspace 2.5 plugin you'll find on the public Jive Subversion repository. The excerpts demonstrate a working (although contrived) implementation of the common customization objectives described above.
Highlights from the sample code include:
com.jivesoftware.plugins.aaa.FederatedIdentityAuthFilter — This class defines common SSO-related behavior such as creation of local Clearspace user representation when a user does not already exist. Generally, SSO implementations will not need to change this class.com.jivesoftware.plugins.aaa.IdentityProviderUserAgent — A micro-API to be implemented by SSO customizations. Clearspace uses implementations of this interface to extract user information from the incoming request. The FederatedIdentityAuthFilter uses an implementation of this interface
to load user information specific to the SSO implementation. SSO implementations must create implementations specific to their requirements and configure their FederatedIdentityAuthFilter bean to use the implementation of this interface.com.jivesoftware.plugins.aaa.IdentityProviderGroupAgent — A second micro-API to optionally be implemented by SSO customizations if they desire to synchronize user-to-group information with an external system, or based on data in the incoming HTTP request (with SAML assertions for
example). Implementing this interface is normal and instances of the FederatedItentityAuthFilter will operate normally when a group agent is not configured.com.jivesoftware.plugins.aaa.SampleAgent — A sample implementation of the user and group agents described above. The sample implementation demonstrates how to create an instance of a Jive User object via the UserTemplate class. If deployed in a test environment, this agent will
authenticate any request with the doAuthenticate parameter set on the incoming request as demonstrated in the extractUserFromRequest method.As with any code sample, the following may be out of date. Be sure to view the code in the Jive public Subversion repository. [TODO: get updated URL]
Note that while the sample is delivered as a Clearspace plugin, the code and patterns in the plugin are equally valid for versions of Clearspace after 2.0 and before 2.5. The main difference being older versions of Clearspace require manipulation of the classpath or WAR artifact to deploy the code.
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
xmlns:cxf="http://cxf.apache.org/core"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.directwebremoting.org/schema/spring-dwr http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd"
default-autowire="no">
<!-- Alter the default mapping chains, switching formAuthenticationFilter
with the federatedIdentityAuthFilter defined below. This definition uses the federated
identity filter for normal app requests and web service requests, but leaves the form
mechanism in place for admin and upgrade purposes. The desired behavior will vary
depending on the application. -->
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
<!-- The URL mappings here are broken for readability, but
in your XML file they must be unbroken. -->
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/upgrade/**=httpSessionContextIntegrationFilter, upgradeAuthenticationFilter, \
upgradeExceptionTranslationFilter, jiveAuthenticationTranslationFilter
/post-upgrade/**=httpSessionContextIntegrationFilter, postUpgradeAuthenticationFilter, \
postUpgradeExceptionTranslationFilter,jiveAuthenticationTranslationFilter
/admin/**=httpSessionContextIntegrationFilter, adminAuthenticationFilter, \
adminExceptionTranslationFilter,jiveAuthenticationTranslationFilter
/rpc/xmlrpc=wsRequireSSLFilter, httpSessionContextIntegrationFilter, \
federatedIdentityAuthFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, \
wsAccessTypeCheckFilter
/rpc/rest/**=wsRequireSSLFilter, httpSessionContextIntegrationFilter, \
federatedIdentityAuthFilter, wsExceptionTranslator, jiveAuthenticationTranslationFilter, \
wsAccessTypeCheckFilter
/rpc/soap/**=wsRequireSSLFilter, httpSessionContextIntegrationFilter, \
federatedIdentityAuthFilter, jiveAuthenticationTranslationFilter
/**=httpSessionContextIntegrationFilter, federatedIdentityAuthFilter, \
jiveAuthenticationTranslationFilter
</value>
</property>
</bean>
<!-- Define a new filter for externally-managed users. Inject dependencies as needed. -->
<bean id="federatedIdentityAuthFilter"
class="com.jivesoftware.plugins.aaa.FederatedIdentityAuthFilter">
<property name="userManager" ref="userManagerImpl"/>
<property name="groupManager" ref="groupManagerImpl"/>
<property name="userAgent" ref="agent"/>
<property name="groupAgent" ref="agent"/>
</bean>
<!-- The Sample Agent - Custom implementations would change this to a more
meaningful implementation. This particular implementation merges group
and user agents into one class which is not required. -->
<bean id="agent" class="com.jivesoftware.plugins.aaa.SampleAgent">
<property name="groupManager" ref="groupManagerImpl"/>
</bean>
</beans>
package com.jivesoftware.plugins.aaa;
// Imports omitted for brevity.
public class FederatedIdentityAuthFilter implements Filter {
private static final Logger log = LogManager.getLogger(FederatedIdentityAuthFilter.class);
private IdentityProviderUserAgent userAgent;
private IdentityProviderGroupAgent groupAgent;
private UserManager userManager;
private GroupManager groupManager;
private boolean active;
/**
* Ultimately this filter will do nothing if the request is already authenticated.
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
final SecurityContext context = SecurityContextHolder.getContext();
final HttpServletRequest servletRequest = (HttpServletRequest)request;
Authentication auth = context.getAuthentication();
if(auth == null || !auth.isAuthenticated() && active) {
long timer = System.currentTimeMillis();
//attempt to resolve the user from the agent
User externalUser = null;
try {
externalUser = userAgent.extractUserFromRequest(servletRequest);
if(externalUser == null) {
log.info("User agent failed to load user.");
}
else {
User coerced = coerceExternalUser(externalUser);
if(log.isDebugEnabled()) {
log.debug("Loaded and coerced representation for user " + coerced);
}
// Create the user if not currently in the local system
User jiveRepresentation = userManager.getUser(coerced);
if(jiveRepresentation == null) {
jiveRepresentation = userManager.createUser(coerced);
}
else {
jiveRepresentation = userManager.updateUser(coerced);
}
if(groupAgent != null && groupManager != null) {
handleGroupMappings(groupAgent, jiveRepresentation, groupManager,
servletRequest);
}
// Map the user to the security context
setUserToAuthenticationContext(jiveRepresentation, context);
}
}
catch(Exception ex) {
log.info("User agent failed to load user with exception.", ex);
}
if(log.isDebugEnabled()) {
log.debug("Federated identity processing time=" +
(System.currentTimeMillis() - timer));
}
}
chain.doFilter(request, response);
}
/**
* Map a user to system groups given the group membership for a user from
* the IdentityProviderGroupAgent. This implementation will not attempt to
* create a group if it does not exist.
*
* Subclasses should override this method if different mapping behavior is desired.
*
* @param agent
* @param user
* @param groupManager
* @param request
*/
protected void handleGroupMappings(IdentityProviderGroupAgent agent, User user,
GroupManager groupManager, HttpServletRequest request) {
long timer = System.currentTimeMillis();
long[] groupIDs = agent.getUserGroups(user, request);
if(null != groupIDs) {
Arrays.sort(groupIDs);
Iterable<Group> currentGroups = groupManager.getUserGroups(user);
if(currentGroups != null) {
// Put the groups into a more meaningful form
Map<Long, Group> groups = new HashMap<Long, Group>();
Iterator<Group> groupIterator = currentGroups.iterator();
Group workingGroup = null;
while(groupIterator.hasNext()) {
workingGroup = groupIterator.next();
groups.put(new Long(workingGroup.getID()), workingGroup);
}
// For any group the user doesn't belong to, add them.
for(long groupID : groupIDs) {
if(!groups.containsKey(new Long(groupID))) {
try {
workingGroup = groupManager.getGroup(groupID);
if(!workingGroup.isMember(user)) {
workingGroup.addMember(user);
groupManager.update(workingGroup);
}
}
catch(GroupNotFoundException gnfe) {
log.info("Invalid group agent mapping for groupID=" + groupID);
}
catch(Exception ge) {
log.info("Unhandled exception mapping user " + user.getID() +
" to group " + groupID, ge);
}
}
}
// For any group the user currently belongs to and shouldn't, remove them.
for(Long groupID : groups.keySet()) {
// Note that the binary search relies on the IDs being sorted above
if(Arrays.binarySearch(groupIDs, groupID.longValue()) == -1) {
try {
workingGroup = groupManager.getGroup(groupID.longValue());
workingGroup.removeMember(user);
groupManager.update(workingGroup);
}
catch(Exception ex) {
log.info("Unable to unmap user " + user.getID() + " from group " +
groupID.longValue(), ex);
}
}
}
}
}
if(log.isDebugEnabled()) {
log.debug("Federated identity group processing time=" +
(System.currentTimeMillis() - timer));
}
}
/**
* Template method to bind a user object to the system security context.
*
* Subclasses should override if changes to this behavior are needed.
* @param user
* @param context
*/
protected void setUserToAuthenticationContext(User user, SecurityContext context) {
if(user != null && context != null) {
context.setAuthentication(new JiveUserAuthentication(user));
}
}
/**
* Template method to coerce the user returned from an agent into a
* user for addition or update to the Jive local store.
*
* Subclasses may override this method to achieve different behavior.
* @param user
* @return
*/
protected User coerceExternalUser(User user) {
UserTemplate ut = new UserTemplate(user);
// Mark as federated.
ut.setFederated(true);
// Update last logged time.
ut.setLastLoggedIn(new Date());
return ut;
}
/**
* Establishes a user agent to use for fetching user information from
* an external identity provider.
* @param userAgent
*/
public void setUserAgent(IdentityProviderUserAgent userAgent) {
this.userAgent = userAgent;
active = true;
}
/**
* Establishes a group agent to use for fetching user information from
* an external identity provider.
* @param groupAgent
*/
public void setGroupAgent(IdentityProviderGroupAgent groupAgent) {
this.groupAgent = groupAgent;
}
/**
* Sets the user manager to use for updating the user login information.
* @param userManager
*/
public void setUserManager(UserManager userManager) {
this.userManager = userManager;
}
/**
* Sets the group manager to use for updating the user login information.
* @param groupManager
*/
public void setGroupManager(GroupManager groupManager) {
this.groupManager = groupManager;
}
public void init(FilterConfig arg0) throws ServletException {
//no-op
}
public void destroy() {
if(userAgent != null) {
userAgent.destroy();
}
if(groupAgent != null) {
groupAgent.destroy();
}
}
}
package com.jivesoftware.plugins.aaa;
import javax.servlet.http.HttpServletRequest;
import com.jivesoftware.base.User;
/**
* Defines the contract between the FederatedIdentityAuthFilter and the
* identity provider for user information.
*/
public interface IdentityProviderUserAgent {
/**
* Invoked by the FederatedidentityAuthFilter to extract a User object
* from the incoming request. The User returned by this method will
* be used to load an internal Jive representation of the user, or
* to create one if an internal representation does not exist.
*
* The User returned from this method should be fully populated and
* should not hold any backing resources -- i.e. field reads should
* not result in subsequent calls to the identity provider.
*
* Implementations if this method should be safe for use across
* multiple threads.
*
* @param request
* @return
*/
public User extractUserFromRequest(HttpServletRequest request);
/**
* To be called by the containing filter to allow the agent to properly
* release any resources.
*/
public void destroy();
}
package com.jivesoftware.plugins.aaa;
import javax.servlet.http.HttpServletRequest;
import com.jivesoftware.base.User;
/**
* Contract between the FederatedUserAuthFilter
* and the identity provider for group information.
*/
public interface IdentityProviderGroupAgent {
/**
* Returns a long array of group IDs that the given user should
* map to in Clearspace. The FederatedIdentityAuthFilter will
* add the user to each group in the array, and for any group
* the user currently belongs to not in the array, remove the user.
*
* Implementations if this method should be safe for use across
* multiple threads.
*
* @param user
* @param request
* @return
*/
public long[] getUserGroups(User user, HttpServletRequest request);
/**
* Called by the FederatedIdentityAuthFilter to allow the agent to
* properly release any resources.
*/
public void destroy();
}
package com.jivesoftware.plugins.aaa;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import com.jivesoftware.base.Group;
import com.jivesoftware.base.GroupManager;
import com.jivesoftware.base.GroupNotFoundException;
import com.jivesoftware.base.User;
import com.jivesoftware.base.UserTemplate;
public class SampleAgent implements IdentityProviderUserAgent,
IdentityProviderGroupAgent {
private static final String TEST_GROUP = "Test Group";
private static final Logger log = LogManager.getLogger(SampleAgent.class);
private long testGroupID = -1;
private GroupManager groupManager;
/**
* Initializes the agent. Spring will call this method after all dependencies have
* been injected.
*/
public void init() {
log.info("Initializing sample user/group agent.");
//create sample group
if(groupManager != null) {
try {
Group testGroup = groupManager.getGroup(TEST_GROUP);
testGroupID = testGroup.getID();
}
catch(GroupNotFoundException gnfe) {
try {
Group newGroup = groupManager.createGroup(TEST_GROUP);
testGroupID = newGroup.getID();
log.info("Test group created.");
}
catch(Exception ex) {
log.error("Failed to create test group.", ex);
}
}
}
}
/**
* Over simplified implementation of this method that
* always returns a test user effectively resulting
* in unauthenticated requests always becoming this user
* when the doAuth parameter is present.
*
* Real implementations would do things like reading SAMLResponse
* headers, looking for SiteMinder headers, NTLM headers, etc.
*/
public User extractUserFromRequest(HttpServletRequest request) {
String isAuth = request.getParameter("doAuth");
if(isAuth == null) return null;
UserTemplate ut = new UserTemplate();
ut.setUsername("testauth");
ut.setEmail("test@jivesoftware.com");
ut.setName("Test Auth User");
return ut;
}
public long[] getUserGroups(User user, HttpServletRequest request) {
//return the test group
if(testGroupID != -1) {
return new long[] { testGroupID };
}
return null;
}
/**
* Spring-managed dependency.
* @param groupManager
*/
public void setGroupManager(GroupManager groupManager) {
this.groupManager = groupManager;
}
public void destroy() {
//no-op
}
}
The sample SSO plugin is designed to be extended by SSO implementations and leverages the Maven2 build and dependency management system. You'll most likely customize this plugin by overriding template methods defined in FederatedIdentityAuthFilter. This class is designed to
provide boiler plate code used in the vast majority of SSO customizations. In the case of common extension points, the filter defines overrideable behavior as protected methods that can be redefined in subclasses and changed if necessary.
Implementations of the IdentityProviderUserAgent can perform nearly any action desired by the implementors including making web services calls to remote services, accessing other Jive subsystems or contacting an LDAP directory. In effect, this class is similar to a J2EE filter with the
exception that it does not need to be concerned with HTTP response or filter chain management. The implementation of the agent should be packaged as a Spring-managed bean. As such, that agent can expose setters for desired Jive manager objects which it can use to process requests as
desired.
Many of the classes and interfaces above extend base Spring Security classes. This gives implementors a wide variety of well tested, vetted code on which to base customizations. For example, Spring Security has several classes to assist with processing X509-based authentication. Implementors are encouraged to consult the Spring Security Documentation for more information and to favor existing code wherever possible.