Apereo CAS Surrogate Authentication API providing core interfaces and classes for surrogate authentication functionality
—
Service-level access control strategies for surrogate authentication enable fine-grained authorization at the individual CAS service level. These strategies determine whether surrogate authentication is permitted for specific services and provide different authorization mechanisms including attribute-based and script-based control.
Abstract base class providing common functionality for surrogate-aware service access strategies.
public abstract class BaseSurrogateRegisteredServiceAccessStrategy extends BaseRegisteredServiceAccessStrategy {
/**
* Check if the current request is from a surrogate authentication session.
*
* @param request the service access strategy request
* @return true if this is a surrogate authentication session
*/
protected boolean isSurrogateAuthenticationSession(RegisteredServiceAccessStrategyRequest request) {
return request.getAttributes().containsKey(SurrogateAuthenticationService.AUTHENTICATION_ATTR_SURROGATE_ENABLED);
}
}Service access strategy that uses principal attributes to determine surrogate authentication authorization.
@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
public class SurrogateRegisteredServiceAccessStrategy extends BaseSurrogateRegisteredServiceAccessStrategy {
/**
* Defines the attribute aggregation behavior when checking for required attributes.
* Default requires that all attributes be present and match the principal's.
*/
protected boolean requireAllAttributes = true;
/**
* Indicates whether matching on required attribute values
* should be done in a case-insensitive manner.
*/
protected boolean caseInsensitive;
/**
* Required attributes for surrogate access authorization.
* Map of attribute names to sets of required values.
*/
private Map<String, Set<String>> surrogateRequiredAttributes = new HashMap<>(0);
/**
* Main authorization method for service access requests.
*
* @param request the service access request
* @return true if access is authorized
*/
@Override
public boolean authorizeRequest(RegisteredServiceAccessStrategyRequest request) {
return !isSurrogateAuthenticationSession(request) || doPrincipalAttributesAllowSurrogateServiceAccess(request);
}
/**
* Evaluate principal attributes against required surrogate attributes.
*
* @param request the service access request
* @return true if attributes meet requirements
*/
protected boolean doPrincipalAttributesAllowSurrogateServiceAccess(RegisteredServiceAccessStrategyRequest request) {
return RegisteredServiceAccessStrategyEvaluator.builder()
.caseInsensitive(this.caseInsensitive)
.requireAllAttributes(this.requireAllAttributes)
.requiredAttributes(this.surrogateRequiredAttributes)
.rejectedAttributes(new LinkedHashMap<>(0))
.build()
.apply(request);
}
}Service access strategy that uses Groovy scripts for flexible, programmatic authorization logic.
@Slf4j
@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
public class GroovySurrogateRegisteredServiceAccessStrategy extends BaseSurrogateRegisteredServiceAccessStrategy {
/**
* Path to Groovy script for authorization logic.
* Supports Spring Expression Language for dynamic script resolution.
*/
@ExpressionLanguageCapable
private String groovyScript;
/**
* Execute Groovy script to determine authorization.
*
* @param request the service access request
* @return true if access is authorized
* @throws Throwable if script execution fails
*/
@Override
public boolean authorizeRequest(RegisteredServiceAccessStrategyRequest request) throws Throwable {
if (isSurrogateAuthenticationSession(request)) {
try {
Object[] args = new Object[]{request.getPrincipalId(), request.getAttributes(), LOGGER};
Resource resource = ResourceUtils.getResourceFrom(SpringExpressionLanguageValueResolver.getInstance().resolve(this.groovyScript));
ExecutableCompiledScriptFactory scriptFactory = ExecutableCompiledScriptFactory.getExecutableCompiledScriptFactory();
return scriptFactory.fromResource(resource).execute(args, Boolean.class);
} catch (Exception e) {
LoggingUtils.error(LOGGER, e);
}
return false;
}
return super.authorizeRequest(request);
}
}import org.apereo.cas.services.SurrogateRegisteredServiceAccessStrategy;
import java.util.*;
// Create attribute-based access strategy
SurrogateRegisteredServiceAccessStrategy strategy = new SurrogateRegisteredServiceAccessStrategy();
// Configure to require all attributes and use case-insensitive matching
strategy.setRequireAllAttributes(true);
strategy.setCaseInsensitive(true);
// Set required attributes for surrogate access
Map<String, Set<String>> requiredAttributes = new HashMap<>();
requiredAttributes.put("role", Set.of("admin", "support"));
requiredAttributes.put("department", Set.of("IT", "Security"));
strategy.setSurrogateRequiredAttributes(requiredAttributes);
// Use in service registry
RegisteredService service = // ... create service
service.setAccessStrategy(strategy);import org.apereo.cas.services.GroovySurrogateRegisteredServiceAccessStrategy;
// Create script-based access strategy
GroovySurrogateRegisteredServiceAccessStrategy strategy = new GroovySurrogateRegisteredServiceAccessStrategy();
// Set Groovy script path (supports classpath:, file:, http: URLs)
strategy.setGroovyScript("classpath:surrogate-authorization.groovy");
// Use in service registry
RegisteredService service = // ... create service
service.setAccessStrategy(strategy);Example Groovy script (surrogate-authorization.groovy):
// Script receives: principalId, attributes, logger
def principalId = args[0]
def attributes = args[1]
def logger = args[2]
logger.debug("Evaluating surrogate access for principal: {}", principalId)
// Check if user has admin role
def roles = attributes.get("roles")
if (roles != null && roles.contains("admin")) {
logger.info("Granting surrogate access to admin user: {}", principalId)
return true
}
// Check business hours for support staff
def supportRoles = attributes.get("roles")
if (supportRoles != null && supportRoles.contains("support")) {
def hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY)
def isBusinessHours = hour >= 9 && hour <= 17
if (isBusinessHours) {
logger.info("Granting surrogate access to support user during business hours: {}", principalId)
return true
}
}
logger.warn("Denying surrogate access for principal: {}", principalId)
return falseimport org.apereo.cas.services.BaseSurrogateRegisteredServiceAccessStrategy;
import org.apereo.cas.services.RegisteredServiceAccessStrategyRequest;
public class TimeBoundSurrogateAccessStrategy extends BaseSurrogateRegisteredServiceAccessStrategy {
private int startHour = 9;
private int endHour = 17;
@Override
public boolean authorizeRequest(RegisteredServiceAccessStrategyRequest request) {
if (!isSurrogateAuthenticationSession(request)) {
return true; // Non-surrogate sessions are always allowed
}
// Check time bounds for surrogate sessions
Calendar now = Calendar.getInstance();
int currentHour = now.get(Calendar.HOUR_OF_DAY);
boolean withinBusinessHours = currentHour >= startHour && currentHour <= endHour;
if (!withinBusinessHours) {
LOGGER.warn("Surrogate authentication denied outside business hours for principal: {}",
request.getPrincipalId());
}
return withinBusinessHours;
}
// Getters and setters
public int getStartHour() { return startHour; }
public void setStartHour(int startHour) { this.startHour = startHour; }
public int getEndHour() { return endHour; }
public void setEndHour(int endHour) { this.endHour = endHour; }
}Service access strategies can be configured via JSON in the CAS service registry:
{
"@class": "org.apereo.cas.services.CasRegisteredService",
"serviceId": "^https://example\\.com/.*",
"name": "Example Service",
"id": 1,
"accessStrategy": {
"@class": "org.apereo.cas.services.SurrogateRegisteredServiceAccessStrategy",
"requireAllAttributes": true,
"caseInsensitive": false,
"surrogateRequiredAttributes": {
"@class": "java.util.HashMap",
"role": ["admin", "support"],
"department": ["IT"]
}
}
}These classes depend on types from:
import org.apereo.cas.services.BaseRegisteredServiceAccessStrategy;
import org.apereo.cas.services.RegisteredServiceAccessStrategyRequest;
import org.apereo.cas.services.util.RegisteredServiceAccessStrategyEvaluator;
import org.apereo.cas.authentication.surrogate.SurrogateAuthenticationService;
import org.apereo.cas.configuration.support.ExpressionLanguageCapable;
import org.apereo.cas.util.LoggingUtils;
import org.apereo.cas.util.ResourceUtils;
import org.apereo.cas.util.scripting.ExecutableCompiledScriptFactory;
import org.apereo.cas.util.spring.SpringExpressionLanguageValueResolver;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.core.io.Resource;
import java.io.Serial;
import java.util.*;Install with Tessl CLI
npx tessl i tessl/maven-org-apereo-cas--cas-server-support-surrogate-api