Configure Service Access Strategy
The access strategy of a registered service provides fine-grained control over the service authorization rules. It describes whether the service is allowed to use the CAS server, allowed to participate in single sign-on authentication, etc. Additionally, it may be configured to require a certain set of principal attributes that must exist before access can be granted to the service. This behavior allows one to configure various attributes in terms of access roles for the application and define rules that would be enacted and validated when an authentication request from the application arrives.
Default Strategy
The default strategy allows one to configure a service with the following properties:
Field | Description |
---|---|
enabled |
Flag to toggle whether the entry is active; a disabled entry produces behavior equivalent to a non-existent entry. |
ssoEnabled |
Set to false to force users to authenticate to the service regardless of protocol flags (e.g. renew=true ). |
requiredAttributes |
A Map of required principal attribute names along with the set of values for each attribute. These attributes MUST be available to the authenticated Principal and resolved before CAS can proceed, providing an option for role-based access control from the CAS perspective. If no required attributes are presented, the check will be entirely ignored. |
requireAllAttributes |
Flag to toggle to control the behavior of required attributes. Default is true , which means all required attribute names must be present. Otherwise, at least one matching attribute name may suffice. Note that this flag only controls which and how many of the attribute names must be present. If attribute names satisfy the CAS configuration, at the next step at least one matching attribute value is required for the access strategy to proceed successfully. |
unauthorizedRedirectUrl |
Optional url to redirect the flow in case service access is not allowed. |
caseInsensitive |
Indicates whether matching on required attribute values should be done in a case-insensitive manner. Default is false |
rejectedAttributes |
A Map of rejected principal attribute names along with the set of values for each attribute. These attributes MUST NOT be available to the authenticated Principal so that access may be granted. If none is defined, the check is entirely ignored. |
Note that comparison of principal/required attribute names is case-sensitive. Exact matches are required for any individual attribute name.
Note that if the CAS server is configured to cache attributes upon release, all required attributes must also be released to the relying party. See this guide for more info on attribute release and filters.
Examples
The following examples demonstrate access policy enforcement features of CAS.
Disable Service Access
Service is not allowed to use CAS:
1
2
3
4
5
6
7
8
9
10
11
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "testId",
"name" : "testId",
"id" : 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : false,
"ssoEnabled" : true
}
}
Enforce Attributes
To access the service, the principal must have a cn
attribute with the value of admin
AND a
givenName
attribute with the value of Administrator
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "testId",
"name" : "testId",
"id" : 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : true,
"ssoEnabled" : true,
"requiredAttributes" : {
"@class" : "java.util.HashMap",
"cn" : [ "java.util.HashSet", [ "admin" ] ],
"givenName" : [ "java.util.HashSet", [ "Administrator" ] ]
}
}
}
To access the service, the principal must have a cn
attribute with the value of admin
OR a
givenName
attribute with the value of Administrator
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "testId",
"name" : "testId",
"id" : 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : true,
"ssoEnabled" : true,
"requireAllAttributes": false,
"requiredAttributes" : {
"@class" : "java.util.HashMap",
"cn" : [ "java.util.HashSet", [ "admin" ] ],
"givenName" : [ "java.util.HashSet", [ "Administrator" ] ]
}
}
}
To access the service, the principal must have a cn
attribute whose value is either of admin
, Admin
or TheAdmin
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "testId",
"name" : "testId",
"id" : 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : true,
"ssoEnabled" : true,
"requiredAttributes" : {
"@class" : "java.util.HashMap",
"cn" : [ "java.util.HashSet", [ "admin", "Admin", "TheAdmin" ] ]
}
}
}
Required values for a given attribute support regular expression patterns. For example, a phone
attribute could
require a value pattern of \d\d\d-\d\d\d-\d\d\d\d
.
Static Unauthorized Redirect URL
Service access is denied if the principal does not have a cn
attribute containing the value super-user
. If so, the user will be redirected to https://www.github.com
instead.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"@class": "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "testId",
"name" : "testId",
"id": 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"unauthorizedRedirectUrl" : "https://www.github.com",
"requiredAttributes" : {
"@class" : "java.util.HashMap",
"cn" : [ "java.util.HashSet", [ "super-user" ] ]
}
}
}
Dynamic Unauthorized Redirect URL
Service access is denied if the principal does not have a cn
attribute containing the value super-user
. If so, the redirect URL will be dynamically determined based on outcome of the specified Groovy script.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"@class": "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "testId",
"name" : "testId",
"id": 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"unauthorizedRedirectUrl" : "file:/etc/cas/config/unauthz-redirect-url.groovy",
"requiredAttributes" : {
"@class" : "java.util.HashMap",
"cn" : [ "java.util.HashSet", [ "super-user" ] ]
}
}
}
The script itself will take the following form:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.apereo.cas.*
import org.apereo.cas.web.support.*
import java.util.*
import java.net.*
import org.apereo.cas.authentication.*
URI run(final Object... args) {
def registeredService = args[0]
def requestContext = args[1]
def applicationContext = args[2]
def logger = args[3]
logger.info("Redirecting to somewhere, processing [{}]", registeredService.name)
/**
* Stuff Happens...
*/
return new URI("https://www.github.com");
}
The following parameters are provided to the script:
Field | Description |
---|---|
registeredService |
The object representing the matching registered service in the registry. |
requestContext |
The object representing the Spring Webflow RequestContext . |
applicationContext |
The object representing the Spring ApplicationContext . |
logger |
The object responsible for issuing log messages such as logger.info(...) . |
Enforce Combined Attribute Conditions
To access the service, the principal must have a cn
attribute whose value is either of admin
, Admin
or TheAdmin
,
OR the principal must have a member
attribute whose value is either of admins
, adminGroup
or staff
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "testId",
"name" : "testId",
"id" : 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : true,
"requireAllAttributes" : false,
"ssoEnabled" : true,
"requiredAttributes" : {
"@class" : "java.util.HashMap",
"cn" : [ "java.util.HashSet", [ "admin", "Admin", "TheAdmin" ] ],
"member" : [ "java.util.HashSet", [ "admins", "adminGroup", "staff" ] ]
}
}
}
Enforce Must-Not-Have Attributes
To access the service, the principal must have a cn
attribute whose value is either of admin
, Admin
or TheAdmin
, OR the principal must have a member
attribute whose value is either of admins
, adminGroup
or staff
. The principal also must not have an attribute “role” whose value matches the pattern deny.+
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "testId",
"name" : "testId",
"id" : 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : true,
"requireAllAttributes" : false,
"ssoEnabled" : true,
"requiredAttributes" : {
"@class" : "java.util.HashMap",
"cn" : [ "java.util.HashSet", [ "admin", "Admin", "TheAdmin" ] ],
"member" : [ "java.util.HashSet", [ "admins", "adminGroup", "staff" ] ]
},
"rejectedAttributes" : {
"@class" : "java.util.HashMap",
"role" : [ "java.util.HashSet", [ "deny.+" ] ]
}
}
}
Rejected values for a given attribute support regular expression patterns. For example, a role
attribute could
be designed with a value value pattern of admin-.*
.
Global Groovy Script
Access strategy and authorization decision can be carried using a Groovy script for all services and applications. This policy is not tied to a specific application and is invoked for all services and integrations.
The following settings and properties are available from the CAS configuration catalog:
Configuration Metadata
The collection of configuration properties listed in this section are automatically generated from the CAS source and components that contain the actual field definitions, types, descriptions, modules, etc. This metadata may not always be 100% accurate, or could be lacking details and sufficient explanations.
Be Selective
This section is meant as a guide only. Do NOT copy/paste the entire collection of settings into your CAS configuration; rather pick only the properties that you need. Do NOT enable settings unless you are certain of their purpose and do NOT copy settings into your configuration only to keep them as reference. All these ideas lead to upgrade headaches, maintenance nightmares and premature aging.
YAGNI
Note that for nearly ALL use cases, declaring and configuring properties listed here is sufficient. You should NOT have to explicitly massage a CAS XML/Java/etc configuration file to design an authentication handler, create attribute release policies, etc. CAS at runtime will auto-configure all required changes for you. If you are unsure about the meaning of a given CAS setting, do NOT turn it on without hesitation. Review the codebase or better yet, ask questions to clarify the intended behavior.
Naming Convention
Property names can be specified in very relaxed terms. For instance cas.someProperty
, cas.some-property
, cas.some_property
are all valid names. While all
forms are accepted by CAS, there are certain components (in CAS and other frameworks used) whose activation at runtime is conditional on a property value, where
this property is required to have been specified in CAS configuration using kebab case. This is both true for properties that are owned by CAS as well as those
that might be presented to the system via an external library or framework such as Spring Boot, etc.
When possible, properties should be stored in lower-case kebab format, such as cas.property-name=value
.
The only possible exception to this rule is when naming actuator endpoints; The name of the
actuator endpoints (i.e. ssoSessions
) MUST remain in camelCase mode.
Settings and properties that are controlled by the CAS platform directly always begin with the prefix cas
. All other settings are controlled and provided
to CAS via other underlying frameworks and may have their own schemas and syntax. BE CAREFUL with
the distinction. Unrecognized properties are rejected by CAS and/or frameworks upon which CAS depends. This means if you somehow misspell a property definition
or fail to adhere to the dot-notation syntax and such, your setting is entirely refused by CAS and likely the feature it controls will never be activated in the
way you intend.
Validation
Configuration properties are automatically validated on CAS startup to report issues with configuration binding, specially if defined CAS settings cannot be
recognized or validated by the configuration schema. The validation process is on by default and can be skipped on startup using a special system
property SKIP_CONFIG_VALIDATION
that should be set to true
. Additional validation processes are also handled
via Configuration Metadata and property migrations applied automatically on
startup by Spring Boot and family.
Indexed Settings
CAS settings able to accept multiple values are typically documented with an index, such as cas.some.setting[0]=value
. The index [0]
is meant to be
incremented by the adopter to allow for distinct multiple configuration blocks.
The outline of the script is as follows:
1
2
3
4
5
6
7
8
9
10
11
import org.apereo.cas.audit.*
import org.apereo.cas.services.*
def run(Object[] args) {
def context = args[0] as AuditableContext
def logger = args[1]
logger.debug("Checking access for ${context.registeredService}")
def result = AuditableExecutionResult.builder().build()
result.setException(new UnauthorizedServiceException("Service unauthorized"))
return result
}
The following parameters are passed to the script:
Parameter | Description |
---|---|
context |
An AuditableContext object that carries auditable data such as registered services, authentication, etc. |
logger |
The object responsible for issuing log messages such as logger.info(...) . |
Time-Based
The time-based access strategy allows one to configure a service with the following properties:
Field | Description |
---|---|
startingDateTime |
Indicates the starting date/time whence service access may be granted. (i.e. 2015-10-11T09:55:16.552-07:00 ) |
endingDateTime |
Indicates the ending date/time whence service access may be granted. (i.e. 2015-10-20T09:55:16.552-07:00 ) |
Service access is only allowed within startingDateTime
and endingDateTime
:
1
2
3
4
5
6
7
8
9
10
11
12
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^https://.+",
"name" : "test",
"id" : 62,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.TimeBasedRegisteredServiceAccessStrategy",
"startingDateTime" : "2015-11-01T13:19:54.132-07:00",
"endingDateTime" : "2015-11-10T13:19:54.248-07:00",
"zoneId" : "UTC"
}
}
The configuration of the public key component qualifies to use the Spring Expression Language syntax.
HTTP Request
This strategy allows one to configure a service with the following properties:
Field | Description |
---|---|
ipAddress |
(Optional) Regular expression pattern compared against the client IP address. |
userAgent |
(Optional) Regular expression pattern compared against the browser user agent. |
The objective of this policy is examine specific properties of the HTTP request and make service access decisions by comparing those properties with pre-defined rules and patterns, such as those that might be based on an IP address, user-agent, etc.
1
2
3
4
5
6
7
8
9
10
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^https://.+",
"id" : 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.HttpRequestRegisteredServiceAccessStrategy",
"ipAddress" : "192.\\d\\d\\d.\\d\\d\\d.101",
"userAgent": "Chrome.+"
}
}
Remote Endpoint
This strategy allows one to configure a service access strategy with the following properties:
Field | Description |
---|---|
endpointUrl |
Endpoint that receives the authorization request from CAS for the authenticated principal. |
acceptableResponseCodes |
Comma-separated response codes that are considered accepted for service access. |
The objective of this policy is to ensure a remote endpoint can make service access decisions by
receiving the CAS authenticated principal as url parameter of a GET
request. The response code that
the endpoint returns is then compared against the policy setting and if a match is found, access is granted.
Here is an example of the remote endpoint access strategy authorizing service access based on response code:
1
2
3
4
5
6
7
8
9
10
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^https://.+",
"id" : 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.RemoteEndpointServiceAccessStrategy",
"endpointUrl" : "https://somewhere.example.org",
"acceptableResponseCodes" : "200,202"
}
}
Groovy Per Service
This strategy delegates to a Groovy script to dynamically decide the access rules requested by CAS at runtime:
1
2
3
4
5
6
7
8
9
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^https://.+",
"id" : 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.GroovyRegisteredServiceAccessStrategy",
"groovyScript" : "file:///etc/cas/config/access-strategy.groovy"
}
}
The script itself may be designed as such by overriding the needed operations where necessary:
1
2
3
4
5
import org.apereo.cas.services.*
import java.util.*
class GroovyRegisteredAccessStrategy implements RegisteredServiceAccessStrategy {
}
The configuration of this component qualifies to use the Spring Expression Language syntax. Refer to the CAS API documentation to learn more about operations and expected behaviors.
Grouper
The grouper access strategy is enabled by including the following dependency in the WAR overlay:
1
2
3
4
5
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-grouper-core</artifactId>
<version>${cas.version}</version>
</dependency>
1
implementation "org.apereo.cas:cas-server-support-grouper-core:${project.'cas.version'}"
1
2
3
4
5
6
7
8
9
dependencyManagement {
imports {
mavenBom "org.apereo.cas:cas-server-support-bom:${project.'cas.version'}"
}
}
dependencies {
implementation "org.apereo.cas:cas-server-support-grouper-core"
}
This access strategy attempts to locate Grouper groups for the CAS principal. The groups returned by Grouper are collected as CAS attributes and examined against the list of required attributes for service access.
The following properties are available:
Field | Description | Values |
---|---|---|
groupField |
Attribute of the Grouper group used when converting the group to a CAS attribute. | NAME , EXTENSION , DISPLAY_NAME or DISPLAY_EXTENSION . |
You will also need to ensure grouper.client.properties
is available on the classpath (i.e. src/main/resources
)
with the following configured properties:
1
2
3
grouperClient.webService.url = http://grouper.example.com/grouper-ws/servicesRest
grouperClient.webService.login = banderson
grouperClient.webService.password = password
Grouper access strategy based on group’s display extension:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^https://.+",
"name" : "test",
"id" : 62,
"accessStrategy" : {
"@class" : "org.apereo.cas.grouper.services.GrouperRegisteredServiceAccessStrategy",
"requireAllAttributes" : true,
"requiredAttributes" : {
"@class" : "java.util.HashMap",
"grouperAttributes" : [ "java.util.HashSet", [ "faculty" ] ]
},
"groupField" : "DISPLAY_EXTENSION"
}
}
While the grouper.client.properties
is a hard requirement and must be presented, configuration properties can always be assigned to the strategy
to override the defaults:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^https://.+",
"name" : "test",
"id" : 62,
"accessStrategy" : {
"@class" : "org.apereo.cas.grouper.services.GrouperRegisteredServiceAccessStrategy",
"configProperties" : {
"@class" : "java.util.HashMap",
"grouperClient.webService.url" : "http://grouper.example.com/grouper-ws/servicesRest"
},
"groupField" : "DISPLAY_EXTENSION"
}
}
Chaining Strategies
Multiple access strategies can be combined together to form complex rules and conditions in a chain. Using chains, one can implement advanced Boolean logic to group results together. Note that chains can contain other chains as well.
The following access strategy chain allows service access if the authenticated principal,
- has an attribute
key1
with a value ofvalue1
AND an attributekey2
with a value ofvalue2
.
…OR…
- has an attribute
key3
with a value ofvalue3
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^https://.+",
"name" : "test",
"id" : 1,
"accessStrategy" : {
"@class": "org.apereo.cas.services.ChainingRegisteredServiceAccessStrategy",
"strategies": [ "java.util.ArrayList",
[ {
"@class": "org.apereo.cas.services.ChainingRegisteredServiceAccessStrategy",
"strategies": [ "java.util.ArrayList",
[
{
"@class": "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"requiredAttributes": {
"@class": "java.util.LinkedHashMap",
"key1": [ "java.util.LinkedHashSet", [ "value1" ] ]
}
},
{
"@class": "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"requiredAttributes": {
"@class": "java.util.LinkedHashMap",
"key2": [ "java.util.LinkedHashSet", [ "value2" ] ]
}
}
]
],
"operator": "AND"
},
{
"@class": "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"requiredAttributes": {
"@class": "java.util.LinkedHashMap",
"key3": [ "java.util.LinkedHashSet", [ "value3" ] ]
}
}
]
],
"operator": "OR"
}
}