Tenant Properties - Multitenancy

The properties field of a tenant definition holds CAS settings that apply only to that tenant, such as its own email server, authentication handlers or attribute repositories.

The tenant properties field is a map of CAS properties that are effective for this tenant. CAS features and modules that do support multitenancy are able to read this map and apply the properties to the tenant context. Examples here may include defining email server settings, authentication handler construction and more.

Tenant definition properties may be defined and secured via CAS configuration security.

:information_source: Remember

Not every CAS configuration property is multitenant-aware, and this capability is limited to CAS features and modules that are explicitly designed to support multitenancy. Support for multitenancy is evolving and new features and support for more modules may be added in future releases. Please check the documentation for each feature or module to see if it supports multitenancy.

The following examples are available:

  • The following tenant definition is allowed to define its email server:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    [
      "java.util.ArrayList",
      [
        {
          "@class": "org.apereo.cas.multitenancy.TenantDefinition",
          "id": "shire",
          "properties": {
            "@class": "java.util.LinkedHashMap",
            "spring.mail.host": "localhost",
            "spring.mail.port": 25000
          }
        }
      ]
    ]
    
  • The following tenant definition is allowed to define its LDAP authentication:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    [
      "java.util.ArrayList",
      [
        {
          "@class": "org.apereo.cas.multitenancy.TenantDefinition",
          "id": "shire",
          "properties": {
            "@class": "java.util.LinkedHashMap",
            "cas.authn.ldap[0].type": "DIRECT",
            "cas.authn.ldap[0].dn-format": "uid=%s,ou=people,dc=example,dc=org",
            "cas.authn.ldap[0].ldap-url": "ldap://localhost:11389"
          }
        }
      ]
    ]
    
  • The following tenant definition is allowed to define its JDBC authentication:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    [
      "java.util.ArrayList",
      [
        {
          "@class": "org.apereo.cas.multitenancy.TenantDefinition",
          "id": "shire",
          "properties": {
            "@class": "java.util.LinkedHashMap",
            "cas.authn.jdbc.procedure[0].procedure-name": "sp_authenticate",
            "cas.authn.jdbc.procedure[0].user": "postgres",
            "cas.authn.jdbc.procedure[0].password": "...",
            "cas.authn.jdbc.procedure[0].driver-class": "org.postgresql.Driver",
            "cas.authn.jdbc.procedure[0].url": "jdbc:postgresql://localhost:5432/users",
            "cas.authn.jdbc.procedure[0].dialect": "org.hibernate.dialect.PostgreSQLDialect"
          }
        }
      ]
    ]
    
  • The following tenant definition is allowed to define its external identity provider:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    [
      "java.util.ArrayList",
      [
        {
          "@class": "org.apereo.cas.multitenancy.TenantDefinition",
          "id": "shire",
          "properties": {
            "@class": "java.util.LinkedHashMap",
            "cas.authn.pac4j.cas[0].login-url": "https://sso.example.org/cas/login"
          }
        }
      ]
    ]
    
  • The following tenant definition will activate Multifactor Authentication based on Duo Security:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    [
      "java.util.ArrayList",
      [
        {
          "@class": "org.apereo.cas.multitenancy.TenantDefinition",
          "id": "shire",
          "properties": {
            "@class": "java.util.LinkedHashMap",
            "cas.authn.mfa.triggers.global.global-provider-id": "mfa-duo"
          }
        }
      ]
    ]
    
  • Configuration properties assigned to a tenant definition may be secured via CAS configuration security:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    [
      "java.util.ArrayList",
      [
        {
          "@class": "org.apereo.cas.multitenancy.TenantDefinition",
          "id": "shire",
          "description": "Shire tenant",
          "properties": {
            "@class": "java.util.LinkedHashMap",
            "cas.some.property": "{cas-cipher}xoLrkVhqnyAmMHqxWi3t+AcXf/w6Mg3bltpdP1kmG9E="
          }
        }
      ]
    ]
    
  • CAS employs a special filter that is able to map an incoming request to a tenant definition based on the Host header that is ultimately picked up by HttpServletRequest#getServerName(). A matching request will be routed to the appropriate tenant url.

    • If the Host header is given as sso.example.org, i.e. via a reverse proxy, the shire tenant definition will allow CAS to route requests from https://sso.example.org/cas/login to https://${cas.server.domain}/cas/tenants/shire/login.

    • If the Host header is given as sso.example.com, the london tenant definition will allow CAS to route requests from https://sso.example.org/cas/login to https://${cas.server.domain}/cas/tenants/london/login.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    [
      "java.util.ArrayList",
      [
        {
          "@class": "org.apereo.cas.multitenancy.TenantDefinition",
          "id": "shire",
          "properties": {
            "@class": "java.util.LinkedHashMap",
            "cas.server.name": "https://sso.example.org"
          }
        },
        {
          "@class": "org.apereo.cas.multitenancy.TenantDefinition",
          "id": "london",
          "properties": {
            "@class": "java.util.LinkedHashMap",
            "cas.host.name": "sso.example.com"
          }
        }
      ]
    ]
    

    This setup is useful in scenarios where there is a reverse proxy that sits in front of CAS and is able to route traffic for predefined hosts to CAS tenants. For example, the setup below for nginx, combined with the tenant definitions, allows CAS to route traffic for sso.example.com to the london tenant definition that carries its own host name noted above:

    1
    2
    3
    4
    5
    6
    7
    
    location /cas {
        proxy_pass https://cas.example.org:8443;
        proxy_set_header Host "sso.example.com";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_cookie_path /cas/tenants/london /cas;
    }
    
    :information_source: Note

    Make note of the proxy_cookie_path setting which rewrite the cookie path for this tenant. This is required because the cookie path is always scoped to the tenant URL by CAS, and subsequently may not be made available to the browser since original request is passing through a reverse proxy.

    You can build your own tenant routing and filtering mechanism via the following bean definition:

    1
    2
    3
    4
    5
    6
    7
    8
    
    @Bean
    public FilterRegistrationBean tenantRoutingFilter() {
        var fr = new FilterRegistrationBean<MyTenantRoutingFilter>();
        /*
            fr.setFilter(new MyTenantRoutingFilter());
        */
        return fr;
    }
    
  • The following tenant definition is allowed to define its own attribute consent storage via MongoDb.

    Depending on your choice of storage, the proper extension module must be included in your CAS deployment. As ever, please verify that the module does actually support multitenancy.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    [
      "java.util.ArrayList",
      [
        {
          "@class": "org.apereo.cas.multitenancy.TenantDefinition",
          "id": "shire",
          "properties": {
            "@class": "java.util.LinkedHashMap",
            "cas.consent.mongo.host": "localhost",
            "cas.consent.mongo.port": "27017",
            "cas.consent.mongo.user-id": "...",
            "cas.consent.mongo.password": "...",
            "cas.consent.mongo.collection": "TenantsConsentRepository",
            "cas.consent.mongo.authentication-database-name": "admin",
            "cas.consent.mongo.database-name": "consent"
          }
        }
      ]
    ]
    

    In the event that the tenant does not specify its own attribute consent storage, the default storage defined globally in the CAS configuration for the entire CAS deployment will be used.