<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://fawnoos.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://fawnoos.com/" rel="alternate" type="text/html" /><updated>2026-02-14T23:19:44+00:00</updated><id>https://fawnoos.com/feed.xml</id><title type="html">Fawnoos</title><subtitle>Fawnoos || Open-Source Identity and Access Management Consulting Services</subtitle><author><name>Misagh Moayyed</name></author><entry><title type="html">Apereo CAS - Annotation Processing with Apache Maven on Java 25</title><link href="https://fawnoos.com/2026/02/14/cas80x-apache-maven-java25/" rel="alternate" type="text/html" title="Apereo CAS - Annotation Processing with Apache Maven on Java 25" /><published>2026-02-14T00:00:00+00:00</published><updated>2026-02-14T00:00:00+00:00</updated><id>https://fawnoos.com/2026/02/14/cas80x-apache-maven-java25</id><content type="html" xml:base="https://fawnoos.com/2026/02/14/cas80x-apache-maven-java25/"><![CDATA[<p>If you are building or overlaying Apereo CAS <code class="language-plaintext highlighter-rouge">8.x.x</code> with Apache Maven with Java <code class="language-plaintext highlighter-rouge">25</code>, you may have noticed that Lombok-generated constructs are mysteriously missing. This is a JDK behavior change that started in JDK <code class="language-plaintext highlighter-rouge">23</code> and now fully applies to Java <code class="language-plaintext highlighter-rouge">25</code>.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>This post tries to provide some background for change and offers a simple solution for deployments that use project Lombok directly. Our starting position is based on the following:</p>

<ul>
  <li>CAS <code class="language-plaintext highlighter-rouge">8.0.x</code></li>
  <li>Java <code class="language-plaintext highlighter-rouge">25</code></li>
</ul>

<h1 id="annotation-processors">Annotation Processors</h1>

<p>Beginning with JDK <code class="language-plaintext highlighter-rouge">23</code>, javac no longer automatically discovers and executes annotation processors from the classpath. Historically, annotation processors like Lombok were automatically detected and executed if they were present on the compile classpath. However, with modern JDKd annotation processors must be explicitly declared. This is a deliberate security hardening decision in javac.</p>

<p>For CAS overlays and extensions that rely on Lombok where custom source code is involved, this means the following constructs (and likely more) will silently stop generating code:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Getter</span>
<span class="nd">@Setter</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="nd">@Builder</span>
</code></pre></div></div>

<h1 id="fix">Fix</h1>

<p>If your CAS <code class="language-plaintext highlighter-rouge">8</code> deployment runs on Java <code class="language-plaintext highlighter-rouge">25</code> and builds via Apache Maven, the solution is simple and explicit: declare annotation processors via <code class="language-plaintext highlighter-rouge">maven-compiler-plugin</code>:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ni">&amp;#x3C;</span>properties<span class="ni">&amp;#x3E;</span>
    <span class="ni">&amp;#x3C;</span>lombok.version<span class="ni">&amp;#x3E;</span>1.18.42<span class="ni">&amp;#x3C;</span>/lombok.version<span class="ni">&amp;#x3E;</span>
<span class="ni">&amp;#x3C;</span>/properties<span class="ni">&amp;#x3E;</span>

<span class="ni">&amp;#x3C;</span>plugin<span class="ni">&amp;#x3E;</span>
    <span class="ni">&amp;#x3C;</span>groupId<span class="ni">&amp;#x3E;</span>org.apache.maven.plugins<span class="ni">&amp;#x3C;</span>/groupId<span class="ni">&amp;#x3E;</span>
    <span class="ni">&amp;#x3C;</span>artifactId<span class="ni">&amp;#x3E;</span>maven-compiler-plugin<span class="ni">&amp;#x3C;</span>/artifactId<span class="ni">&amp;#x3E;</span>
    <span class="ni">&amp;#x3C;</span>version<span class="ni">&amp;#x3E;</span>3.15.0<span class="ni">&amp;#x3C;</span>/version<span class="ni">&amp;#x3E;</span>
    <span class="ni">&amp;#x3C;</span>configuration<span class="ni">&amp;#x3E;</span>
        <span class="ni">&amp;#x3C;</span>release<span class="ni">&amp;#x3E;</span>25<span class="ni">&amp;#x3C;</span>/release<span class="ni">&amp;#x3E;</span>
        <span class="ni">&amp;#x3C;</span>annotationProcessorPaths<span class="ni">&amp;#x3E;</span>
            <span class="ni">&amp;#x3C;</span>path<span class="ni">&amp;#x3E;</span>
                <span class="ni">&amp;#x3C;</span>groupId<span class="ni">&amp;#x3E;</span>org.projectlombok<span class="ni">&amp;#x3C;</span>/groupId<span class="ni">&amp;#x3E;</span>
                <span class="ni">&amp;#x3C;</span>artifactId<span class="ni">&amp;#x3E;</span>lombok<span class="ni">&amp;#x3C;</span>/artifactId<span class="ni">&amp;#x3E;</span>
                <span class="ni">&amp;#x3C;</span>version<span class="ni">&amp;#x3E;</span>${lombok.version}<span class="ni">&amp;#x3C;</span>/version<span class="ni">&amp;#x3E;</span>
            <span class="ni">&amp;#x3C;</span>/path<span class="ni">&amp;#x3E;</span>
        <span class="ni">&amp;#x3C;</span>/annotationProcessorPaths<span class="ni">&amp;#x3E;</span>
    <span class="ni">&amp;#x3C;</span>/configuration<span class="ni">&amp;#x3E;</span>
<span class="ni">&amp;#x3C;</span>/plugin<span class="ni">&amp;#x3E;</span>
</code></pre></div></div>

<p>Being explicit about annotation processors makes builds deterministic, prevents accidental processor execution, and ultimately aligns with modern JDK expectations.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p><strong>Note:</strong> Apereo CAS builds that are using Gradle should already be doing the right thing. You should not have to change anything!</p>

<h1 id="need-help">Need Help?</h1>

<p>If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to <a href="/#contact-section-header">send us a note </a> and ask about consulting and support services.</p>

<h1 id="so">So…</h1>

<p>I hope the content here was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. If you have suggestions or ideas on how to improve this post, please feel free to reach out to us.</p>

<p><a href="https://fawnoos.com">Misagh Moayyed</a></p>]]></content><author><name>Misagh Moayyed</name></author><category term="CAS 8.0.x" /><category term="Apache Maven" /><summary type="html"><![CDATA[If you are building or overlaying Apereo CAS 8.x.x with Apache Maven with Java 25, you may have noticed that Lombok-generated constructs are mysteriously missing. This is a JDK behavior change that started in JDK 23 and now fully applies to Java 25.]]></summary></entry><entry><title type="html">Apereo CAS - Authentication Flow Interrupts</title><link href="https://fawnoos.com/2026/02/13/cas73x-loginflow-interrupt/" rel="alternate" type="text/html" title="Apereo CAS - Authentication Flow Interrupts" /><published>2026-02-13T00:00:00+00:00</published><updated>2026-02-13T00:00:00+00:00</updated><id>https://fawnoos.com/2026/02/13/cas73x-loginflow-interrupt</id><content type="html" xml:base="https://fawnoos.com/2026/02/13/cas73x-loginflow-interrupt/"><![CDATA[<blockquote>
  <p>The fastest route to a 10X engineer is to give them 0.1X the distractions. - Eric Meyer</p>
</blockquote>

<p>While that is generally sensible advice, when it comes to CAS there are times where you wish to interrupt the CAS authentication flow and the present the end-user with notifications and announcements. A common use case deals with presenting a message board during the authentication flow to select users and then optionally require the audience to complete a certain task before CAS is able to honor the authentication request and establish a session. Examples of such messages tasks may include: <em>“The kitchen’s menu today features <a href="https://www.wikiwand.com/en/Khash_(dish)">Khash</a>. Click here to get directions.”</em> or <em>“The office of compliance and regulations has announced a new policy on using forks. Click to accept, or forever be doomed with spoons”</em>.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>CAS has the ability to pause and interrupt the authentication flow to reach out to external services and resources, querying for status and settings that would then dictate how CAS should manage and control the SSO session. Interrupt services are able to present notification messages to the user, provide options for redirects to external services, etc.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>In this post, we are going to take a brief look at what it takes to interrupt the authentication flow. This tutorial specifically focuses on:</p>

<ul>
  <li>CAS <code class="language-plaintext highlighter-rouge">7.3.x</code></li>
  <li>Java <code class="language-plaintext highlighter-rouge">21</code></li>
</ul>

<h1 id="interrupt-source">Interrupt Source</h1>

<p>First and foremost, there needs to be an engine of some sort that is able to produce notifications and interruptions. CAS supports a range of such engines that are backed by JSON &amp; Groovy resources, REST endpoints or one you decide to create and inject into the runtime.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>For the purposes of this tutorial, I will be using a Groovy script. This strategy reaches out to a Groovy resource whose job is to dynamically calculate whether the authentication flow should be interrupted given the provided username and certain number of other parameters.</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.interrupt.groovy.location</span><span class="p">=</span><span class="s">file:/path/to/your/interrupt.groovy</span>
</code></pre></div></div>

<p>You can choose any path and filename you prefer.</p>

<p>Remember to include the below module in your CAS build if you intend to implement any sort of behavior with Apache Groovy and scripting. This includes evaluating policies, release attributes, modifying or customizing components, etc. Without the listed above module, Apache Groovy functionality and libraries will not be pulled into your CAS project and CAS would most likely fail to deliver at runtime.</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-core-scripting"</span>
</code></pre></div></div>

<h1 id="interrupt-rules">Interrupt Rules</h1>

<p>Once you have defined the above setting and assuming your overlay is prepped with relevant configuration module, CAS will attempt to understand the interruption rules that are defined in the Groovy file. My rules are defined as such:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">org.apereo.cas.interrupt.*</span>

<span class="kt">def</span> <span class="nf">run</span><span class="o">(</span><span class="kd">final</span> <span class="n">Object</span><span class="o">...</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
    <span class="kt">def</span> <span class="o">(</span><span class="n">principal</span><span class="o">,</span><span class="n">attributes</span><span class="o">,</span><span class="n">service</span><span class="o">,</span><span class="n">registeredService</span><span class="o">,</span><span class="n">requestContext</span><span class="o">,</span><span class="n">logger</span><span class="o">)</span> <span class="o">=</span> <span class="n">args</span>

    <span class="k">if</span> <span class="o">(</span><span class="n">dontYouLoveItWhenYouAreInterrupted</span><span class="o">(</span><span class="n">principal</span><span class="o">,</span> <span class="n">attributes</span><span class="o">))</span> <span class="o">{</span>
      <span class="kt">def</span> <span class="n">block</span> <span class="o">=</span> <span class="kc">false</span>
      <span class="kt">def</span> <span class="n">ssoEnabled</span> <span class="o">=</span> <span class="kc">true</span>
      <span class="k">return</span> <span class="k">new</span> <span class="nf">InterruptResponse</span><span class="o">(</span><span class="s2">"Message"</span><span class="o">,</span> <span class="o">[</span><span class="nl">link1:</span><span class="s2">"google.com"</span><span class="o">,</span> <span class="nl">link2:</span><span class="s2">"yahoo.com"</span><span class="o">],</span> <span class="n">block</span><span class="o">,</span> <span class="n">ssoEnabled</span><span class="o">)</span>
    <span class="o">}</span>
    <span class="k">return</span> <span class="k">new</span> <span class="nf">InterruptResponse</span><span class="o">(</span><span class="kc">false</span><span class="o">)</span>
<span class="o">}</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>The above ruleset says: <em>Evaluate the current principal and its attributes using <code class="language-plaintext highlighter-rouge">dontYouLoveItWhenYouAreInterrupted(...)</code> (something you, yes you, have to complete) and then present the <code class="language-plaintext highlighter-rouge">Message</code> to the user with a number of links. Do not block the user and allow the SSO session to be established.</em></p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>Note that this is the default strategy that allows the interrupt query and script to execute after the primary authentication event and before the single sign-on event. This means an authenticated user has been identified by CAS and by extension is made available to the interrupt, and interrupt has the ability to decide whether a single sign-on session can be established for the user.</p>

<h1 id="the-looks">The Looks</h1>

<p>Once that is all in place, <code class="language-plaintext highlighter-rouge">casuser</code> will see something similar to the following screen, after having authenticated successfully:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Authentication Flow Interrupts - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="https://user-images.githubusercontent.com/1205228/29816821-eb5a597a-8cca-11e7-8ee8-f5433b01f90d.png" title="Authentication Flow Interrupts" target="_blank">
    <img src="https://user-images.githubusercontent.com/1205228/29816821-eb5a597a-8cca-11e7-8ee8-f5433b01f90d.png?v=1771111184" style="width:60%;" title="Authentication Flow Interrupts - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<h1 id="custom-interrupt-sources">Custom Interrupt Sources</h1>

<p>In scenarios where the power of Groovy is not good enough for you, you can always create your own interrupt source in Java. If you wish to design your own interrupt strategy to make inquiries, register the following bean:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">InterruptInquiryExecutionPlanConfigurer</span> <span class="nf">myInterruptConfigurer</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">return</span> <span class="n">plan</span> <span class="o">-&gt;</span> <span class="o">{</span>
        <span class="n">plan</span><span class="o">.</span><span class="na">registerInterruptInquirer</span><span class="o">(</span><span class="k">new</span> <span class="nc">MyInterruptInquirer</span><span class="o">());</span>
    <span class="o">};</span>
<span class="o">}</span>
</code></pre></div></div>

<p>…and your implementation may potentially look like this:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MyInterruptInquirer</span> <span class="kd">extends</span> <span class="nc">BaseInterruptInquirer</span> <span class="o">{</span>
    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="nc">InterruptResponse</span> <span class="nf">inquireInternal</span><span class="o">(</span><span class="nc">Authentication</span> <span class="n">authentication</span><span class="o">,</span>
                                                <span class="nc">RegisteredService</span> <span class="n">registeredService</span><span class="o">,</span>
                                                <span class="nc">Service</span> <span class="n">service</span><span class="o">,</span> 
                                                <span class="nc">Credential</span> <span class="n">credential</span><span class="o">,</span>
                                                <span class="nc">RequestContext</span> <span class="n">requestContext</span><span class="o">)</span> <span class="o">{</span>
      <span class="c1">// Stuff happens...</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h1 id="interruptions-per-application">Interruptions Per Application</h1>

<p>There may be scenarios where you want to conditionally activate the interrupt flow for a specific or group of applications. One rather obvious though possibly cumbersome way of doing this would be to account for the condition and the application in your <code class="language-plaintext highlighter-rouge">InterruptInquirer</code> implementation or script the logic you need in Groovy. One easier way of doing this would be to activate the interrupt flow for an application only when the authenticated user carries a certain attributes that matches a value:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"@class"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apereo.cas.services.CasRegisteredService"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"serviceId"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"^https://my.application.org.*"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"MyApplication"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
  </span><span class="nl">"webflowInterruptPolicy"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"@class"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apereo.cas.services.DefaultRegisteredServiceWebflowInterruptPolicy"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributeName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"memberOf"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributeValue"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^st[a-z]ff$"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The above policy will activate the interrupt flow when logging into <code class="language-plaintext highlighter-rouge">https://my.application.org</code> or any of its child pages when the user has the attribute <code class="language-plaintext highlighter-rouge">memberOf</code> with at least a value matching the defined pattern.</p>

<p>A slightly more advanced way of doing this would be to move the trigger and activation logic inside the application record itself and use a modest Groovy script to build the condition:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">{</span>
  <span class="s2">"</span><span class="s">@class"</span> <span class="pi">:</span> <span class="s2">"</span><span class="s">org.apereo.cas.services.CasRegisteredService"</span><span class="pi">,</span>
  <span class="s2">"</span><span class="s">serviceId"</span> <span class="pi">:</span> <span class="s2">"</span><span class="s">^https://my.application.org.*"</span><span class="pi">,</span>
  <span class="s2">"</span><span class="s">name"</span> <span class="pi">:</span> <span class="s2">"</span><span class="s">MyApplication"</span><span class="pi">,</span>
  <span class="s2">"</span><span class="s">id"</span> <span class="pi">:</span> <span class="nv">1</span><span class="pi">,</span>
  <span class="s2">"</span><span class="s">webflowInterruptPolicy"</span> <span class="pi">:</span> <span class="pi">{</span>
    <span class="s2">"</span><span class="s">@class"</span> <span class="pi">:</span> <span class="s2">"</span><span class="s">org.apereo.cas.services.DefaultRegisteredServiceWebflowInterruptPolicy"</span><span class="pi">,</span>
    <span class="s2">"</span><span class="s">enabled"</span><span class="pi">:</span> <span class="nv">true</span><span class="pi">,</span>
    <span class="s2">"</span><span class="s">groovyScript"</span><span class="pi">:</span>
      <span class="s1">'</span><span class="s">'</span><span class="s1">'</span>
      <span class="s">groovy</span><span class="nv"> </span><span class="s">{</span>
        <span class="s">logger.info("Current</span><span class="nv"> </span><span class="s">attributes</span><span class="nv"> </span><span class="s">received</span><span class="nv"> </span><span class="s">are</span><span class="nv"> </span><span class="s">[{}]",</span><span class="nv"> </span><span class="s">attributes)</span>
        <span class="s">return</span><span class="nv"> </span><span class="s">username</span><span class="nv"> </span><span class="s">==</span><span class="nv"> </span><span class="s">'</span><span class="nv">testuser' &amp;&amp; attributes.containsKey('family_name')</span>
      <span class="pi">}</span>
      <span class="s1">'</span><span class="s">'</span><span class="s1">'</span>
  <span class="s">}</span>
<span class="s">}</span>
</code></pre></div></div>

<p>The above application policy will activate the interrupt flow when logging into <code class="language-plaintext highlighter-rouge">https://my.application.org</code> or any of its child pages when the authenticated username is <code class="language-plaintext highlighter-rouge">testuser</code> and the user has the attribute <code class="language-plaintext highlighter-rouge">family_name</code> with any value.</p>

<h1 id="need-help">Need Help?</h1>

<p>If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to <a href="/#contact-section-header">send us a note </a> and ask about consulting and support services.</p>

<h1 id="so">So…</h1>

<p>I hope the content here was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. If you have suggestions or ideas on how to improve this post, please feel free to reach out to us.</p>

<p><a href="https://fawnoos.com">Misagh Moayyed</a></p>]]></content><author><name>Misagh Moayyed</name></author><category term="CAS 7.3.x" /><category term="Spring Webflow" /><summary type="html"><![CDATA[The fastest route to a 10X engineer is to give them 0.1X the distractions. - Eric Meyer]]></summary></entry><entry><title type="html">Apereo CAS - Poking Your SAML2 Identity Provider Without Logging In</title><link href="https://fawnoos.com/2026/02/03/cas73x-saml2-metadata-resolution-testing/" rel="alternate" type="text/html" title="Apereo CAS - Poking Your SAML2 Identity Provider Without Logging In" /><published>2026-02-03T00:00:00+00:00</published><updated>2026-02-03T00:00:00+00:00</updated><id>https://fawnoos.com/2026/02/03/cas73x-saml2-metadata-resolution-testing</id><content type="html" xml:base="https://fawnoos.com/2026/02/03/cas73x-saml2-metadata-resolution-testing/"><![CDATA[<p>If you run Apereo CAS as a SAML2 Identity Provider, troubleshooting authentication requests that are rejected by the CAS server due to seemingly invalid/missing metadata for the service provider can be challenging. A service provider claims <em>CAS is rejecting our SAML requests</em> or you upgrade CAS, tweak metadata resolution slightly and suddenly you’re not sure which SPs still work or why.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>This post tries to present a simple approach to determine whether CAS can accept SAML2 <code class="language-plaintext highlighter-rouge">AuthnRequest</code>s and whether metadata attached to service providers sending the requests can be parsed and recognized. Our starting position is as follows:</p>

<ul>
  <li>CAS <code class="language-plaintext highlighter-rouge">7.3.x</code></li>
  <li>Java <code class="language-plaintext highlighter-rouge">21</code></li>
</ul>

<h1 id="background">Background</h1>

<p>To assist with troubleshooting and testing, instead of acting like a full SAML Service Provider, we do something much lighter:</p>

<p>We can generate minimal SAML2 <code class="language-plaintext highlighter-rouge">AuthnRequest</code>s and send them to CAS using <code class="language-plaintext highlighter-rouge">HTTP-Redirect</code> binding. Our chosen service provider is randomly chosen from a list of known SP entityIDs that are registered with CAS. Then, we of course observe CAS’s behavior with the following measurements:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<ul>
  <li>If the response to our request is a <em>redirect to login</em>: Great, the request is accepted.</li>
  <li>If the response to our request is a <code class="language-plaintext highlighter-rouge">4xx</code> or <code class="language-plaintext highlighter-rouge">5xx</code> error: Ouch, the request is denied.</li>
</ul>

<p>Again, we are not interested in receiving a SAML2 response, assertions, browser behavior, etc. All we care about is, <em>does CAS like this request?</em></p>

<p>This approach can be modestly useful because if CAS can’t resolve an SP’s metadata, you’ll see it immediately. CAS may load metadata but still reject an SP because of ACS mismatches, signature failures, registration issues, etc. It may also be helpful to do this as a smoke-test after upgrades; you can blast a few hundred requests at it and see what breaks.</p>

<h1 id="solution">Solution</h1>

<p>Here is our plan:</p>

<ul>
  <li>We generate just enough SAML to get CAS to engage:</li>
</ul>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;samlp:AuthnRequest</span>
  <span class="na">ID=</span><span class="s">"_abc123"</span>
  <span class="na">Version=</span><span class="s">"2.0"</span>
  <span class="na">IssueInstant=</span><span class="s">"2026-01-30T12:00:00Z"</span>
  <span class="na">Destination=</span><span class="s">"https://cas.example.org/idp/profile/SAML2/Redirect/SSO"</span>
  <span class="na">ProtocolBinding=</span><span class="s">"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"</span>
  <span class="na">AssertionConsumerServiceURL=</span><span class="s">"https://sp.example.org/saml/acs"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;saml:Issuer&gt;</span>https://sp.example.org/shibboleth<span class="nt">&lt;/saml:Issuer&gt;</span>
<span class="nt">&lt;/samlp:AuthnRequest&gt;</span>
</code></pre></div></div>

<ul>
  <li>We then use <code class="language-plaintext highlighter-rouge">HTTP-Redirect</code> encoding to deflate, base64-encode and then finally url-encode the result.</li>
  <li>For SP selection, we can use a simple <code class="language-plaintext highlighter-rouge">entities.txt</code> file:</li>
</ul>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://sp1.example.org/shibboleth
https://sp2.example.org/shibboleth
https://sp3.example.org/shibboleth
</code></pre></div></div>

<p>Each request randomly picks one entityID so you can try to get a healthy mix.</p>

<p>Here is how it works in practice:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-euo</span> pipefail

<span class="nv">IDP_SSO_URL</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">IDP_SSO_URL</span><span class="k">:-</span><span class="nv">https</span>://cas.example.edu/cas/idp/profile/SAML2/Redirect/SSO<span class="k">}</span><span class="s2">"</span>
<span class="nv">ENTITIES_FILE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">ENTITIES_FILE</span><span class="k">:-</span><span class="p">./entities.txt</span><span class="k">}</span><span class="s2">"</span>
<span class="nv">COUNT</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">COUNT</span><span class="k">:-</span><span class="nv">10</span><span class="k">}</span><span class="s2">"</span>
<span class="nv">MIN_DELAY</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">MIN_DELAY</span><span class="k">:-</span><span class="nv">1</span><span class="k">}</span><span class="s2">"</span>
<span class="nv">MAX_DELAY</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">MAX_DELAY</span><span class="k">:-</span><span class="nv">2</span><span class="k">}</span><span class="s2">"</span>

<span class="nv">SP_ACS_URL</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">SP_ACS_URL</span><span class="k">:-}</span><span class="s2">"</span>
<span class="nv">PROTOCOL_BINDING</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">PROTOCOL_BINDING</span><span class="k">:-</span><span class="nv">urn</span>:oasis:names:tc:SAML:2.0:bindings:HTTP-POST<span class="k">}</span><span class="s2">"</span>

<span class="nv">FORCE_AUTHN</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">FORCE_AUTHN</span><span class="k">:-</span><span class="nv">false</span><span class="k">}</span><span class="s2">"</span> 
<span class="nv">IS_PASSIVE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">IS_PASSIVE</span><span class="k">:-</span><span class="nv">false</span><span class="k">}</span><span class="s2">"</span>   

<span class="nv">LOG_FILE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">LOG_FILE</span><span class="k">:-</span><span class="p">./saml_idp_ping.log</span><span class="k">}</span><span class="s2">"</span>

<span class="k">while</span> <span class="o">[[</span> <span class="nv">$# </span><span class="nt">-gt</span> 0 <span class="o">]]</span><span class="p">;</span> <span class="k">do
  case</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="k">in</span>
    <span class="nt">-u</span><span class="p">)</span> <span class="nv">IDP_SSO_URL</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift </span>2 <span class="p">;;</span>
    <span class="nt">-f</span><span class="p">)</span> <span class="nv">ENTITIES_FILE</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift </span>2 <span class="p">;;</span>
    <span class="nt">-n</span><span class="p">)</span> <span class="nv">COUNT</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift </span>2 <span class="p">;;</span>
    <span class="nt">--min</span><span class="p">)</span> <span class="nv">MIN_DELAY</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift </span>2 <span class="p">;;</span>
    <span class="nt">--max</span><span class="p">)</span> <span class="nv">MAX_DELAY</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift </span>2 <span class="p">;;</span>
  <span class="k">esac</span>
<span class="k">done

if</span> <span class="o">[[</span> <span class="o">!</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$ENTITIES_FILE</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"Entities file not found: </span><span class="nv">$ENTITIES_FILE</span><span class="s2">"</span> <span class="o">&gt;</span>&amp;2
  <span class="nb">exit </span>2
<span class="k">fi

if</span> <span class="o">!</span> <span class="nb">command</span> <span class="nt">-v</span> python3 <span class="o">&gt;</span>/dev/null 2&gt;&amp;1<span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"python3 is required (used for DEFLATE+Base64+URL-encode)."</span> <span class="o">&gt;</span>&amp;2
  <span class="nb">exit </span>2
<span class="k">fi


</span>pick_random_entity<span class="o">()</span> <span class="o">{</span>
  <span class="nb">awk</span> <span class="s1">'
    NF &amp;&amp; $1 !~ /^#/ {
      lines[NR]=$0
    }
    END {
      if (NR &gt; 0) {
        srand()
        print lines[int(rand()*NR)+1]
      }
    }
  '</span> <span class="s2">"</span><span class="nv">$ENTITIES_FILE</span><span class="s2">"</span>
<span class="o">}</span>

rand_id<span class="o">()</span> <span class="o">{</span>
  <span class="k">if </span><span class="nb">command</span> <span class="nt">-v</span> openssl <span class="o">&gt;</span>/dev/null 2&gt;&amp;1<span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"_</span><span class="si">$(</span>openssl rand <span class="nt">-hex</span> 16<span class="si">)</span><span class="s2">"</span>
  <span class="k">else
    </span><span class="nb">echo</span> <span class="s2">"_</span><span class="si">$(</span><span class="nb">date</span> +%s%N<span class="si">)</span><span class="s2">_</span><span class="nv">$RANDOM</span><span class="s2">"</span>
  <span class="k">fi</span>
<span class="o">}</span>

iso_instant<span class="o">()</span> <span class="o">{</span>
  <span class="nb">date</span> <span class="nt">-u</span> +<span class="s2">"%Y-%m-%dT%H:%M:%SZ"</span>
<span class="o">}</span>

deflate_b64_urlencode<span class="o">()</span> <span class="o">{</span>
  python3 <span class="nt">-c</span> <span class="s1">'import sys,zlib,base64,urllib.parse
data = sys.stdin.buffer.read()
compressed = zlib.compress(data)[2:-4]
b64 = base64.b64encode(compressed).decode("ascii")
print(urllib.parse.quote(b64, safe="~()*!.\x27"))'</span>
<span class="o">}</span>

urlencode<span class="o">()</span> <span class="o">{</span>
  python3 <span class="nt">-c</span> <span class="s1">'import sys,urllib.parse
print(urllib.parse.quote(sys.stdin.read().strip(), safe="~()*!.\x27"))'</span>
<span class="o">}</span>

sleep_random_1to2<span class="o">()</span> <span class="o">{</span>
  <span class="nb">awk</span> <span class="nt">-v</span> <span class="nv">min</span><span class="o">=</span><span class="s2">"</span><span class="nv">$MIN_DELAY</span><span class="s2">"</span> <span class="nt">-v</span> <span class="nv">max</span><span class="o">=</span><span class="s2">"</span><span class="nv">$MAX_DELAY</span><span class="s2">"</span> <span class="s1">'
    BEGIN {
      srand()
      print min + rand() * (max - min)
    }
  '</span> | xargs <span class="nb">sleep</span>
<span class="o">}</span>

build_authn_request_xml<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">entity_id</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
  <span class="nb">local </span><span class="nv">req_id</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
  <span class="nb">local </span><span class="nv">issue_instant</span><span class="o">=</span><span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span>

  <span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="no">XML</span><span class="sh">
&lt;samlp:AuthnRequest
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
  ID="</span><span class="k">${</span><span class="nv">req_id</span><span class="k">}</span><span class="sh">"
  Version="2.0"
  IssueInstant="</span><span class="k">${</span><span class="nv">issue_instant</span><span class="k">}</span><span class="sh">"
  Destination="</span><span class="k">${</span><span class="nv">IDP_SSO_URL</span><span class="k">}</span><span class="sh">"
  ProtocolBinding="</span><span class="k">${</span><span class="nv">PROTOCOL_BINDING</span><span class="k">}</span><span class="sh">"
  ForceAuthn="</span><span class="k">${</span><span class="nv">FORCE_AUTHN</span><span class="k">}</span><span class="sh">"
  IsPassive="</span><span class="k">${</span><span class="nv">IS_PASSIVE</span><span class="k">}</span><span class="sh">"&gt;
  &lt;saml:Issuer&gt;</span><span class="k">${</span><span class="nv">entity_id</span><span class="k">}</span><span class="sh">&lt;/saml:Issuer&gt;
  &lt;samlp:NameIDPolicy
    Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
    AllowCreate="true"/&gt;
&lt;/samlp:AuthnRequest&gt;
</span><span class="no">XML
</span><span class="o">}</span>

send_one<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">i</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
  <span class="nb">local </span><span class="nv">entity_id</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>
  <span class="nb">local </span><span class="nv">req_id</span><span class="o">=</span><span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span>
  <span class="nb">local </span><span class="nv">issue_instant</span><span class="o">=</span><span class="s2">"</span><span class="nv">$4</span><span class="s2">"</span>
  <span class="nb">local </span><span class="nv">relay_state</span><span class="o">=</span><span class="s2">"rs-</span><span class="k">${</span><span class="nv">i</span><span class="k">}</span><span class="s2">-</span><span class="si">$(</span><span class="nb">date</span> +%s<span class="si">)</span><span class="s2">"</span>

  <span class="nb">local </span>xml
  <span class="nv">xml</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>build_authn_request_xml <span class="s2">"</span><span class="nv">$entity_id</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$req_id</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$issue_instant</span><span class="s2">"</span><span class="si">)</span><span class="s2">"</span>

  <span class="nb">local </span>saml_request

  <span class="nv">saml_request</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">printf</span> <span class="s2">"%s"</span> <span class="s2">"</span><span class="nv">$xml</span><span class="s2">"</span> | deflate_b64_urlencode<span class="si">)</span><span class="s2">"</span>

  <span class="nb">local </span>relay_enc
  <span class="nv">relay_enc</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">printf</span> <span class="s2">"%s"</span> <span class="s2">"</span><span class="nv">$relay_state</span><span class="s2">"</span> | urlencode<span class="si">)</span><span class="s2">"</span>

  <span class="nb">local </span><span class="nv">url</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">IDP_SSO_URL</span><span class="k">}</span><span class="s2">?SAMLRequest=</span><span class="k">${</span><span class="nv">saml_request</span><span class="k">}</span><span class="s2">&amp;RelayState=</span><span class="k">${</span><span class="nv">relay_enc</span><span class="k">}</span><span class="s2">"</span>

  <span class="nb">local </span>tmp_body
  <span class="nv">tmp_body</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">mktemp</span><span class="si">)</span><span class="s2">"</span>
  <span class="nb">local </span>hdrs
  <span class="nv">hdrs</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">mktemp</span><span class="si">)</span><span class="s2">"</span>

  <span class="nb">local </span>http_code
  <span class="nv">http_code</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>
    curl <span class="nt">-sS</span> <span class="nt">-D</span> <span class="s2">"</span><span class="nv">$hdrs</span><span class="s2">"</span> <span class="nt">-o</span> <span class="s2">"</span><span class="nv">$tmp_body</span><span class="s2">"</span> <span class="se">\</span>
      <span class="nt">--connect-timeout</span> 10 <span class="nt">--max-time</span> 20 <span class="se">\</span>
      <span class="s2">"</span><span class="nv">$url</span><span class="s2">"</span> <span class="se">\</span>
      <span class="nt">-w</span> <span class="s2">"%{http_code}"</span>
  <span class="si">)</span><span class="s2">"</span>

  <span class="nb">local </span>location
  <span class="nv">location</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>
    <span class="nb">tr</span> <span class="nt">-d</span> <span class="s1">'\r'</span> &lt; <span class="s2">"</span><span class="nv">$hdrs</span><span class="s2">"</span> <span class="se">\</span>
    | <span class="nb">awk</span> <span class="s1">'BEGIN{IGNORECASE=1} /^location:/ {sub(/^location:[[:space:]]*/,""); loc=$0} END{print loc}'</span>
  <span class="si">)</span><span class="s2">"</span>

  <span class="nb">local </span>ts
  <span class="nv">ts</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">date</span> <span class="nt">-u</span> +<span class="s2">"%Y-%m-%dT%H:%M:%SZ"</span><span class="si">)</span><span class="s2">"</span>


  <span class="nb">local </span><span class="nv">verdict</span><span class="o">=</span><span class="s2">"UNKNOWN"</span>
  <span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$http_code</span><span class="s2">"</span> <span class="o">=</span>~ ^30[12]<span class="nv">$ </span><span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">verdict</span><span class="o">=</span><span class="s2">"REDIRECT"</span>
  <span class="k">elif</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$http_code</span><span class="s2">"</span> <span class="o">=</span>~ ^2 <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">verdict</span><span class="o">=</span><span class="s2">"OK_2XX"</span>
  <span class="k">elif</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$http_code</span><span class="s2">"</span> <span class="o">=</span>~ ^4 <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">verdict</span><span class="o">=</span><span class="s2">"CLIENT_4XX"</span>
  <span class="k">elif</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$http_code</span><span class="s2">"</span> <span class="o">=</span>~ ^5 <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">verdict</span><span class="o">=</span><span class="s2">"SERVER_5XX"</span>
  <span class="k">fi</span>

  <span class="o">{</span>
    <span class="nb">echo</span> <span class="s2">"[</span><span class="nv">$ts</span><span class="s2">] #</span><span class="nv">$i</span><span class="s2"> entityID=</span><span class="se">\"</span><span class="nv">$entity_id</span><span class="se">\"</span><span class="s2"> reqID=</span><span class="se">\"</span><span class="nv">$req_id</span><span class="se">\"</span><span class="s2"> http=</span><span class="nv">$http_code</span><span class="s2"> verdict=</span><span class="nv">$verdict</span><span class="s2"> location=</span><span class="se">\"</span><span class="k">${</span><span class="nv">location</span><span class="k">}</span><span class="se">\"</span><span class="s2">"</span>
  <span class="o">}</span> | <span class="nb">tee</span> <span class="nt">-a</span> <span class="s2">"</span><span class="nv">$LOG_FILE</span><span class="s2">"</span>

  <span class="c"># If it looks like a failure, include a small snippet of the body in the log</span>
  <span class="c"># if [[ "$http_code" =~ ^4|^5 ]]; then</span>
  <span class="c">#   local snippet</span>
  <span class="c">#   snippet="$(tr '\n' ' ' &lt; "$tmp_body" | sed 's/[[:space:]]\+/ /g' | cut -c1-500)"</span>
  <span class="c">#   echo "  body_snippet: ${snippet}" | tee -a "$LOG_FILE"</span>
  <span class="c"># fi</span>

  <span class="nb">rm</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$tmp_body</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$hdrs</span><span class="s2">"</span>
<span class="o">}</span>


<span class="nb">echo</span> <span class="s2">"Logging to: </span><span class="nv">$LOG_FILE</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"IdP SSO URL: </span><span class="nv">$IDP_SSO_URL</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"Entities:    </span><span class="nv">$ENTITIES_FILE</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"Count:       </span><span class="nv">$COUNT</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"Delay:       </span><span class="k">${</span><span class="nv">MIN_DELAY</span><span class="k">}</span><span class="s2">-</span><span class="k">${</span><span class="nv">MAX_DELAY</span><span class="k">}</span><span class="s2">s"</span>
<span class="nb">echo</span> <span class="s2">"ACS URL:     </span><span class="nv">$SP_ACS_URL</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"Binding:     </span><span class="nv">$PROTOCOL_BINDING</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"ForceAuthn:  </span><span class="nv">$FORCE_AUTHN</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"IsPassive:   </span><span class="nv">$IS_PASSIVE</span><span class="s2">"</span>

<span class="nb">rm</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$LOG_FILE</span><span class="s2">"</span> <span class="o">||</span> <span class="nb">true
echo</span> <span class="s2">"===================================="</span>
<span class="nb">echo</span> <span class="s2">"===================================="</span>

<span class="nb">echo</span> <span class="s2">"----"</span> | <span class="nt">---</span> <span class="nb">tee</span> <span class="nt">-a</span> <span class="s2">"</span><span class="nv">$LOG_FILE</span><span class="s2">"</span> <span class="o">&gt;</span>/dev/null 2&gt;&amp;1 <span class="o">||</span> <span class="nb">true
echo</span> <span class="s2">"----"</span> <span class="o">&gt;&gt;</span> <span class="s2">"</span><span class="nv">$LOG_FILE</span><span class="s2">"</span>

<span class="k">for</span> <span class="o">((</span><span class="nv">i</span><span class="o">=</span>1<span class="p">;</span> i&lt;<span class="o">=</span>COUNT<span class="p">;</span> i++<span class="o">))</span><span class="p">;</span> <span class="k">do
  </span><span class="nv">entity_id</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>pick_random_entity<span class="si">)</span><span class="s2">"</span>
  <span class="k">if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$entity_id</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"No entityIDs found in </span><span class="nv">$ENTITIES_FILE</span><span class="s2"> (empty or only comments)."</span> <span class="o">&gt;</span>&amp;2
    <span class="nb">exit </span>2
  <span class="k">fi

  </span><span class="nv">req_id</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>rand_id<span class="si">)</span><span class="s2">"</span>
  <span class="nv">issue_instant</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>iso_instant<span class="si">)</span><span class="s2">"</span>
  send_one <span class="s2">"</span><span class="nv">$i</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$entity_id</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$req_id</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$issue_instant</span><span class="s2">"</span>
  sleep_random_1to2
<span class="k">done</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>Once you are ready:</p>

<ul>
  <li>Make sure you can run the script: <code class="language-plaintext highlighter-rouge">chmod +x ./script.sh</code>.</li>
  <li>Modify the <code class="language-plaintext highlighter-rouge">./entities.txt </code>file with the list of entity IDs you want to test; one per line, as many as you like.</li>
  <li>Modify the script to adjust the variables at the top. Specifically, <code class="language-plaintext highlighter-rouge">IDP_SSO_URL</code> and <code class="language-plaintext highlighter-rouge">COUNT</code> (number of attempts to run). You can also adjust the delay between each request.</li>
  <li>Run the script: <code class="language-plaintext highlighter-rouge">./script.sh</code>.</li>
</ul>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>You will see the verdict and the resulting HTTP code for each attempt. Watch out for verdicts that result in a <code class="language-plaintext highlighter-rouge">4xx/5xx</code> error, and let the script run for a while for a large number of attempts.</p>

<p>To better simulate a <em>load test</em>, you can opt for shorter delays:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">MIN_DELAY</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">MIN_DELAY</span><span class="k">:-</span><span class="p">.1</span><span class="k">}</span><span class="s2">"</span>
<span class="nv">MAX_DELAY</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">MAX_DELAY</span><span class="k">:-</span><span class="p">.1</span><span class="k">}</span><span class="s2">"</span>
</code></pre></div></div>

<p>Or, replace the contents of <code class="language-plaintext highlighter-rouge">sleep_random_1to2()</code> with a benign statement like: <code class="language-plaintext highlighter-rouge">echo "..."</code>.</p>

<h1 id="need-help">Need Help?</h1>

<p>If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to <a href="/#contact-section-header">send us a note </a> and ask about consulting and support services.</p>

<h1 id="so">So…</h1>

<p>I hope the content here was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. If you have suggestions or ideas on how to improve this post, please feel free to reach out to us.</p>

<p><a href="https://fawnoos.com">Misagh Moayyed</a></p>]]></content><author><name>Misagh Moayyed</name></author><category term="CAS 7.3.x" /><category term="SAML" /><category term="SAML2" /><summary type="html"><![CDATA[If you run Apereo CAS as a SAML2 Identity Provider, troubleshooting authentication requests that are rejected by the CAS server due to seemingly invalid/missing metadata for the service provider can be challenging. A service provider claims CAS is rejecting our SAML requests or you upgrade CAS, tweak metadata resolution slightly and suddenly you’re not sure which SPs still work or why.]]></summary></entry><entry><title type="html">Apereo CAS - Bundled Deployments with jdeps &amp;amp; jlink</title><link href="https://fawnoos.com/2026/02/01/cas73x-deployment-bundle-jdeps-jlink/" rel="alternate" type="text/html" title="Apereo CAS - Bundled Deployments with jdeps &amp;amp; jlink" /><published>2026-02-01T00:00:00+00:00</published><updated>2026-02-01T00:00:00+00:00</updated><id>https://fawnoos.com/2026/02/01/cas73x-deployment-bundle-jdeps-jlink</id><content type="html" xml:base="https://fawnoos.com/2026/02/01/cas73x-deployment-bundle-jdeps-jlink/"><![CDATA[<p>When you build your Apereo CAS deployments and generate a <code class="language-plaintext highlighter-rouge">cas.war</code> file, the target machine that runs the CAS server is expected to have Java installed; ideally the same JDK distribution, version and from the same vendor. Without the likes of Docker and family, a traditional CAS deployment assumes a compatible JDK distribution is available both at build and runtime. Modern Java gives us the tools to work around this requirement.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>Our starting position is as follows:</p>

<ul>
  <li>CAS <code class="language-plaintext highlighter-rouge">7.3.x</code></li>
  <li>Java <code class="language-plaintext highlighter-rouge">21</code></li>
</ul>

<h1 id="the-goal">The Goal</h1>

<p>Ideally, when CAS deployments are built into a <code class="language-plaintext highlighter-rouge">.war</code> file, we would want to ship only the Java modules we actually need. We can package the modules and bundle them with the CAS application and have it look, feel and run as a native-looking executable. This way, there is no unpredictable runtime behavior, no external Java dependency and a smaller/simpler deployment with a self-contained CAS deployment.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>Our solution here does not require a Java dependency on the target machine that runs CAS. You simply drop the final executable in the target environment and run it. It also has a smaller attack surface, since we are not shipping with a full JDK but with a trimmed, smaller set of Java modules with fewer classes and native libs.</p>

<p>In other words, you have <em>Docker, but without Docker.</em> 😎</p>

<h1 id="the-how">The How</h1>

<p>Our approach is something along the following lines:</p>

<ul>
  <li>Use <code class="language-plaintext highlighter-rouge">jdeps</code> to determine which JDK modules are actually used by CAS.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">jlink</code> to build a custom Java runtime containing only those modules.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">jpackage</code> to bundle the runtime and CAS into a native launcher (for example, a <code class="language-plaintext highlighter-rouge">.app</code> on macOS).</li>
</ul>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>The end result is a single CAS bundle you can copy to another machine and run — even if Java is not installed there.</p>

<p>Here is a script that does that:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-euo</span> pipefail

<span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="k">:-}</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">""</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"ERROR: Missing WAR path."</span>
  <span class="nb">echo</span> <span class="s2">"Usage: </span><span class="nv">$0</span><span class="s2"> /path/to/app.war </span><span class="se">\"</span><span class="s2">MyApp</span><span class="se">\"</span><span class="s2">"</span>
  <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nv">APP_WAR</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">cd</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">dirname</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span><span class="si">)</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="si">)</span><span class="s2">/</span><span class="si">$(</span><span class="nb">basename</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span><span class="si">)</span><span class="s2">"</span>
<span class="k">if</span> <span class="o">[[</span> <span class="o">!</span> <span class="nt">-f</span> <span class="s2">"</span><span class="nv">$APP_WAR</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"ERROR: WAR not found: </span><span class="nv">$APP_WAR</span><span class="s2">"</span>
  <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nv">APP_NAME</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">2</span><span class="k">:-</span><span class="nv">MyApp</span><span class="k">}</span><span class="s2">"</span>

<span class="nv">WORK_DIR</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">WORK_DIR</span><span class="k">:-</span><span class="p">./build/jlink-work</span><span class="k">}</span><span class="s2">"</span>
<span class="nv">JPACKAGE_TYPE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">JPACKAGE_TYPE</span><span class="k">:-</span><span class="nv">app</span><span class="p">-image</span><span class="k">}</span><span class="s2">"</span>

<span class="nv">EXTRA_MODULES</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">EXTRA_MODULES</span><span class="k">:-}</span><span class="s2">"</span>
<span class="nv">JAVA_OPTS</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">JAVA_OPTS</span><span class="k">:-}</span><span class="s2">"</span>

need_cmd<span class="o">()</span> <span class="o">{</span>
  <span class="nb">command</span> <span class="nt">-v</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">&gt;</span>/dev/null 2&gt;&amp;1 <span class="o">||</span> <span class="o">{</span>
    <span class="nb">echo</span> <span class="s2">"ERROR: Required command not found on PATH: </span><span class="nv">$1</span><span class="s2">"</span>
    <span class="nb">exit </span>1
  <span class="o">}</span>
<span class="o">}</span>

join_by_comma<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">a</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="k">:-}</span><span class="s2">"</span>
  <span class="nb">local </span><span class="nv">b</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">2</span><span class="k">:-}</span><span class="s2">"</span>
  <span class="k">if</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$a</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$b</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">a</span><span class="k">}</span><span class="s2">,</span><span class="k">${</span><span class="nv">b</span><span class="k">}</span><span class="s2">"</span>
  <span class="k">elif</span> <span class="o">[[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$a</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$a</span><span class="s2">"</span>
  <span class="k">else
    </span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$b</span><span class="s2">"</span>
  <span class="k">fi</span>
<span class="o">}</span>

emit_jpackage_java_options<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">opts</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span>
  <span class="k">if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$opts</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    return </span>0
  <span class="k">fi
  for </span>o <span class="k">in</span> <span class="nv">$opts</span><span class="p">;</span> <span class="k">do
    </span><span class="nb">printf</span> <span class="nt">--</span> <span class="s2">" --java-options %q"</span> <span class="s2">"</span><span class="nv">$o</span><span class="s2">"</span>
  <span class="k">done</span>
<span class="o">}</span>

need_cmd jar
need_cmd jdeps
need_cmd jlink
need_cmd jpackage

<span class="nv">JAVA_VERSION</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>java <span class="nt">-version</span> 2&gt;&amp;1 | <span class="nb">head</span> <span class="nt">-n</span> 1 <span class="o">||</span> <span class="nb">true</span><span class="si">)</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"==&gt; Using Java: </span><span class="nv">$JAVA_VERSION</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"==&gt; WAR: </span><span class="nv">$APP_WAR</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"==&gt; App Name: </span><span class="nv">$APP_NAME</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"==&gt; Work Dir: </span><span class="nv">$WORK_DIR</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"==&gt; jpackage type: </span><span class="nv">$JPACKAGE_TYPE</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"==&gt; EXTRA_MODULES: </span><span class="nv">$EXTRA_MODULES</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"==&gt; JAVA_OPTS: </span><span class="nv">$JAVA_OPTS</span><span class="s2">"</span>
<span class="nb">echo

rm</span> <span class="nt">-rf</span> <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">"</span>
<span class="nb">cp</span> <span class="s2">"</span><span class="nv">$APP_WAR</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/app.war"</span>

<span class="nb">echo</span> <span class="s2">"==&gt; Step 1/3: Analyze with jdeps"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/war"</span>
<span class="o">(</span>
  <span class="nb">cd</span> <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/war"</span>
  jar xf ../app.war
<span class="o">)</span>

<span class="nv">JDEPS_MODULES</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>
  jdeps <span class="se">\</span>
    <span class="nt">--multi-release</span> 25 <span class="se">\</span>
    <span class="nt">--ignore-missing-deps</span> <span class="se">\</span>
    <span class="nt">--recursive</span> <span class="se">\</span>
    <span class="nt">--print-module-deps</span> <span class="se">\</span>
    <span class="nt">--class-path</span> <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/war/WEB-INF/lib/*"</span> <span class="se">\</span>
    <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/war/WEB-INF/classes"</span> <span class="se">\</span>
  | <span class="nb">tr</span> <span class="nt">-d</span> <span class="s1">'[:space:]'</span>
<span class="si">)</span><span class="s2">"</span>

<span class="k">if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$JDEPS_MODULES</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"ERROR: jdeps returned an empty module set."</span>
  <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nv">MODULES</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>join_by_comma <span class="s2">"</span><span class="nv">$JDEPS_MODULES</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$EXTRA_MODULES</span><span class="s2">"</span><span class="si">)</span><span class="s2">"</span>
<span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">FORCE_JAVA_DESKTOP</span><span class="k">:-</span><span class="nv">false</span><span class="k">}</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"true"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nv">MODULES</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$MODULES</span><span class="s2">"</span> | <span class="nb">sed</span> <span class="s1">'s/\(^\|,\)java\.desktop\(,\|$\)/\1\2/g'</span> <span class="se">\</span>
    | <span class="nb">sed</span> <span class="s1">'s/,,*/,/g; s/^,//; s/,$//'</span><span class="si">)</span><span class="s2">"</span>
<span class="k">fi
if</span> <span class="o">[[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">FORCE_JFR</span><span class="k">:-</span><span class="nv">false</span><span class="k">}</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"true"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nv">MODULES</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$MODULES</span><span class="s2">"</span> | <span class="nb">sed</span> <span class="nt">-E</span> <span class="s1">'s/(^|,)(jdk\.jfr|jdk\.management\.jfr)(,|$)/\1\3/g'</span> <span class="se">\</span>
    | <span class="nb">sed</span> <span class="s1">'s/,,*/,/g; s/^,//; s/,$//'</span><span class="si">)</span><span class="s2">"</span>
<span class="k">fi

</span><span class="nb">echo</span> <span class="s2">"==&gt; jdeps modules:"</span>
<span class="nb">echo</span> <span class="s2">"    </span><span class="nv">$JDEPS_MODULES</span><span class="s2">"</span>
<span class="nb">echo</span> <span class="s2">"==&gt; final module set (with extras):"</span>
<span class="nb">echo</span> <span class="s2">"    </span><span class="nv">$MODULES</span><span class="s2">"</span>
<span class="nb">echo

echo</span> <span class="s2">"==&gt; Step 2/3: Build minimal runtime with jlink"</span>
jlink <span class="se">\</span>
  <span class="nt">--add-modules</span> <span class="s2">"</span><span class="nv">$MODULES</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">--strip-debug</span> <span class="se">\</span>
  <span class="nt">--no-header-files</span> <span class="se">\</span>
  <span class="nt">--no-man-pages</span> <span class="se">\</span>
  <span class="nt">--compress</span><span class="o">=</span>zip-9 <span class="se">\</span>
  <span class="nt">--output</span> <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/runtime"</span>

<span class="nb">echo</span> <span class="s2">"==&gt; Runtime image created at: </span><span class="nv">$WORK_DIR</span><span class="s2">/runtime"</span>
<span class="nb">echo


echo</span> <span class="s2">"==&gt; Step 3/3: Package with jpackage"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/dist"</span>
<span class="nb">cp</span> <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/app.war"</span> <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/dist/"</span>

<span class="nv">JPACKAGE_CMD</span><span class="o">=</span><span class="s2">"jpackage </span><span class="se">\</span><span class="s2">
  --type </span><span class="nv">$JPACKAGE_TYPE</span><span class="s2"> </span><span class="se">\</span><span class="s2">
  --name </span><span class="si">$(</span><span class="nb">printf</span> %q <span class="s2">"</span><span class="nv">$APP_NAME</span><span class="s2">"</span><span class="si">)</span><span class="s2"> </span><span class="se">\</span><span class="s2">
  --input </span><span class="si">$(</span><span class="nb">printf</span> %q <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/dist"</span><span class="si">)</span><span class="s2"> </span><span class="se">\</span><span class="s2">
  --main-jar app.war </span><span class="se">\</span><span class="s2">
  --runtime-image </span><span class="si">$(</span><span class="nb">printf</span> %q <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/runtime"</span><span class="si">)</span><span class="s2"> </span><span class="se">\</span><span class="s2">
  --dest </span><span class="si">$(</span><span class="nb">printf</span> %q <span class="s2">"</span><span class="nv">$WORK_DIR</span><span class="s2">/out"</span><span class="si">)</span><span class="s2">"</span>


JPACKAGE_CMD+<span class="o">=</span><span class="si">$(</span>emit_jpackage_java_options <span class="s2">"</span><span class="nv">$JAVA_OPTS</span><span class="s2">"</span><span class="si">)</span>

<span class="nb">echo</span> <span class="s2">"==&gt; Running:"</span>
<span class="nb">echo</span> <span class="s2">"    </span><span class="nv">$JPACKAGE_CMD</span><span class="s2">"</span>
<span class="nb">echo

eval</span> <span class="s2">"</span><span class="nv">$JPACKAGE_CMD</span><span class="s2">"</span>

<span class="nb">echo
echo</span> <span class="s2">"==&gt; Done."</span>
<span class="nb">echo</span> <span class="s2">"==&gt; Output located at: </span><span class="nv">$WORK_DIR</span><span class="s2">/out"</span>
<span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$JPACKAGE_TYPE</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"app-image"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">echo</span> <span class="s2">"==&gt; App image folder: </span><span class="nv">$WORK_DIR</span><span class="s2">/out/</span><span class="nv">$APP_NAME</span><span class="s2">"</span>
  <span class="nb">echo</span> <span class="s2">"    Try running the launcher inside that folder."</span>
<span class="k">else
  </span><span class="nb">echo</span> <span class="s2">"==&gt; Installer/package created under: </span><span class="nv">$WORK_DIR</span><span class="s2">/out"</span>
<span class="k">fi</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>To run this, you first need to build and package your CAS server into a <code class="language-plaintext highlighter-rouge">.war</code> file:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./gradlew clean build
</code></pre></div></div>

<p>Then:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./the-above-script.sh ./build/libs/cas.war cas
</code></pre></div></div>

<p>You should see the following output:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">==&gt;</span> Using Java: openjdk version <span class="s2">"21.0.8"</span> 2025-07-15 LTS
<span class="o">==&gt;</span> WAR: /work/cas-overlay-template/build/libs/cas.war
<span class="o">==&gt;</span> App Name: cas
<span class="o">==&gt;</span> Work Dir: ./build/jlink-work
<span class="o">==&gt;</span> jpackage <span class="nb">type</span>: app-image
<span class="o">==&gt;</span> EXTRA_MODULES: 
<span class="o">==&gt;</span> JAVA_OPTS: <span class="nt">-Xms512m</span> <span class="nt">-Xmx6048m</span> <span class="nt">-Xss64m</span> <span class="nt">-XX</span>:ReservedCodeCacheSize<span class="o">=</span>512m <span class="nt">-XX</span>:+UseG1GC

<span class="o">==&gt;</span> Step 1/3: Analyze with jdeps
<span class="o">==&gt;</span> jdeps modules:
    java.base,java.compiler,java.desktop,java.instrument,...
<span class="o">==&gt;</span> final module <span class="nb">set</span> <span class="o">(</span>with extras<span class="o">)</span>:
    java.base,java.compiler,java.desktop,java.instrument,...

<span class="o">==&gt;</span> Step 2/3: Build minimal runtime with jlink
<span class="o">==&gt;</span> Runtime image created at: ./build/jlink-work/runtime

<span class="o">==&gt;</span> Step 3/3: Package with jpackage
<span class="o">==&gt;</span> Running:
    jpackage <span class="nt">--type</span> app-image <span class="nt">--name</span> cas <span class="nt">--input</span> ./build/jlink-work/dist   <span class="se">\</span>
      <span class="nt">--main-jar</span> cas.war   <span class="nt">--runtime-image</span> ./build/jlink-work/runtime  <span class="se">\</span>
      <span class="nt">--dest</span> ./build/jlink-work/out <span class="nt">--java-options</span> <span class="nt">-Xms512m</span> <span class="nt">--java-options</span> <span class="nt">-Xmx6048m</span> <span class="se">\</span>
      <span class="nt">--java-options</span> <span class="nt">-Xss64m</span> <span class="nt">--java-options</span> <span class="nt">-XX</span>:ReservedCodeCacheSize<span class="o">=</span>512m <span class="se">\</span>
      <span class="nt">--java-options</span> <span class="nt">-XX</span>:+UseG1GC

<span class="o">==&gt;</span> Done.
<span class="o">==&gt;</span> Output located at: ./build/jlink-work/out
<span class="o">==&gt;</span> App image folder: ./build/jlink-work/out/cas
    Try running the launcher inside that folder.
</code></pre></div></div>

<p>Since I am running this on MacOS, the end result is:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls </span>build/jlink-work/out/cas.app

Permissions Size User   Group Date Modified Name
drwxr-xr-x     - misagh staff 31 Jan 09:59  Contents/
</code></pre></div></div>

<p>Which means I can also run the CAS server on its own now:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./build/jlink-work/out/cas.app/Contents/MacOS/cas
</code></pre></div></div>

<h1 id="need-help">Need Help?</h1>

<p>If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to <a href="/#contact-section-header">send us a note </a> and ask about consulting and support services.</p>

<h1 id="so">So…</h1>

<p>I hope the content here was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. If you have suggestions or ideas on how to improve this post, please feel free to reach out to us.</p>

<p><a href="https://fawnoos.com">Misagh Moayyed</a></p>]]></content><author><name>Misagh Moayyed</name></author><category term="CAS 7.3.x" /><category term="CAS 8.0.0" /><category term="Spring Boot" /><category term="Gradle" /><summary type="html"><![CDATA[When you build your Apereo CAS deployments and generate a cas.war file, the target machine that runs the CAS server is expected to have Java installed; ideally the same JDK distribution, version and from the same vendor. Without the likes of Docker and family, a traditional CAS deployment assumes a compatible JDK distribution is available both at build and runtime. Modern Java gives us the tools to work around this requirement.]]></summary></entry><entry><title type="html">Apereo CAS - Remembering Usernames</title><link href="https://fawnoos.com/2025/12/28/cas80x-remembering-usernames/" rel="alternate" type="text/html" title="Apereo CAS - Remembering Usernames" /><published>2025-12-28T00:00:00+00:00</published><updated>2025-12-28T00:00:00+00:00</updated><id>https://fawnoos.com/2025/12/28/cas80x-remembering-usernames</id><content type="html" xml:base="https://fawnoos.com/2025/12/28/cas80x-remembering-usernames/"><![CDATA[<p>The default CAS theme, controlled by the <code class="language-plaintext highlighter-rouge">src/main/resources/cas-theme-default.properties</code> configuration file presents a number of 
configuration options that allow one to control various user interface elements and behavior. One such capability is to allow the server
to capture and remember the username on submission for convenience and future use.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>In this post, we will take a quick look at the theme options available to CAS that allow one to remember the username. Our starting position must be:</p>

<ul>
  <li>CAS <code class="language-plaintext highlighter-rouge">8.0.x</code></li>
  <li>Java <code class="language-plaintext highlighter-rouge">25</code></li>
</ul>

<h1 id="theme-configuration">Theme Configuration</h1>

<p>More recent versions of CAS have the ability to capture and remember the submitted username. The captured username is tracked and stored by the browser’s <code class="language-plaintext highlighter-rouge">localStorage</code> facility and can be cleared on demand. While this capability of themes is off by default, it’s relatively simple to allow the server to enable this behavior.</p>

<p>To do so, you will need the following setting in a <code class="language-plaintext highlighter-rouge">src/main/resources/cas-theme-custom.properties</code> file:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.login-form.remember-username.enabled</span><span class="p">=</span><span class="s">true</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">cas-theme-custom.properties</code> file, intentionally left blank by default, allows you to override specific settings in the default CAS theme. When enabled, please note that this feature is only operational if the <em>public workstation</em> functionality if turned off, which is the default:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.public-workstation.enabled</span><span class="p">=</span><span class="s">false</span>
</code></pre></div></div>

<p>Once <em>public workstation</em> functionality enabled, usernames will no longer be captured and stored.</p>

<h1 id="need-help">Need Help?</h1>

<p>If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to <a href="/#contact-section-header">send us a note </a> and ask about consulting and support services.</p>

<h1 id="so">So…</h1>

<p>I hope the content here was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. If you have suggestions or ideas on how to improve this post, please feel free to reach out to us.</p>

<p><a href="https://fawnoos.com">Misagh Moayyed</a></p>]]></content><author><name>Misagh Moayyed</name></author><category term="CAS 8.0.0" /><category term="Thymeleaf" /><summary type="html"><![CDATA[The default CAS theme, controlled by the src/main/resources/cas-theme-default.properties configuration file presents a number of configuration options that allow one to control various user interface elements and behavior. One such capability is to allow the server to capture and remember the username on submission for convenience and future use.]]></summary></entry><entry><title type="html">Apereo CAS - Deployments with Java 25’s Compact Object Headers</title><link href="https://fawnoos.com/2025/12/22/cas80x-compact-object-headers/" rel="alternate" type="text/html" title="Apereo CAS - Deployments with Java 25’s Compact Object Headers" /><published>2025-12-22T00:00:00+00:00</published><updated>2025-12-22T00:00:00+00:00</updated><id>https://fawnoos.com/2025/12/22/cas80x-compact-object-headers</id><content type="html" xml:base="https://fawnoos.com/2025/12/22/cas80x-compact-object-headers/"><![CDATA[<p>Apereo CAS deployments that are based on the <code class="language-plaintext highlighter-rouge">8.0.x</code> series require Java <code class="language-plaintext highlighter-rouge">25</code> for all build and deployment tasks. As of this writing, Java <code class="language-plaintext highlighter-rouge">25</code> is the latest version of the Java platform for which many vendors offer long-term support and it brings along many significant enhancements and runtime improvements. One such improvement that is perhaps not as publicized as others Java’s ability to compact object headers, which is an ability that is not promoted as a standard Java platform feature.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>In this post, we will take a quick look at what <em>Compact Object Headers</em> are and how a CAS deployment can run with this feature enabled. Our starting position must be:</p>

<ul>
  <li>CAS <code class="language-plaintext highlighter-rouge">8.0.x</code></li>
  <li>Java <code class="language-plaintext highlighter-rouge">25</code></li>
</ul>

<h1 id="compact-object-headers">Compact Object Headers</h1>

<p>Compact Object Headers were an experimental feature in Java <code class="language-plaintext highlighter-rouge">24</code>, and you had to unlock experimental options to use them (e.g., <code class="language-plaintext highlighter-rouge">-XX:+UnlockExperimentalVMOptions</code>). This is no longer necessary with Java <code class="language-plaintext highlighter-rouge">25</code> and activation of this feature requires the flag <code class="language-plaintext highlighter-rouge">-XX:+UseCompactObjectHeaders</code>, as in:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java <span class="nt">-XX</span>:+UseCompactObjectHeaders <span class="nv">$OTHER_FLAGS_HERE</span> <span class="nt">-jar</span> build/libs/cas.war 
</code></pre></div></div>

<p>The premise here is that using Compact Object Headers will reduce the per-object memory overhead in the JVM and objects get smaller. This sort of compression improves memory efficiency and often performance because, say, a CAS deployment at runtime will require less total heap memory which leads to lower GC pressure and better cache management and object pointer chansing by the CPU.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>It would be an interesting exercise to benchmark CAS deployments under heavy load with Compact Object Headers enabled/disabled and compare results.</p>

<h1 id="need-help">Need Help?</h1>

<p>If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to <a href="/#contact-section-header">send us a note </a> and ask about consulting and support services.</p>

<h1 id="so">So…</h1>

<p>I hope the content here was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. If you have suggestions or ideas on how to improve this post, please feel free to reach out to us.</p>

<p><a href="https://fawnoos.com">Misagh Moayyed</a></p>]]></content><author><name>Misagh Moayyed</name></author><category term="CAS 8.0.0" /><summary type="html"><![CDATA[Apereo CAS deployments that are based on the 8.0.x series require Java 25 for all build and deployment tasks. As of this writing, Java 25 is the latest version of the Java platform for which many vendors offer long-term support and it brings along many significant enhancements and runtime improvements. One such improvement that is perhaps not as publicized as others Java’s ability to compact object headers, which is an ability that is not promoted as a standard Java platform feature.]]></summary></entry><entry><title type="html">Apereo CAS - Palantir Admin Dashboard</title><link href="https://fawnoos.com/2025/12/20/cas80x-palantir-dashboard/" rel="alternate" type="text/html" title="Apereo CAS - Palantir Admin Dashboard" /><published>2025-12-20T00:00:00+00:00</published><updated>2025-12-20T00:00:00+00:00</updated><id>https://fawnoos.com/2025/12/20/cas80x-palantir-dashboard</id><content type="html" xml:base="https://fawnoos.com/2025/12/20/cas80x-palantir-dashboard/"><![CDATA[<p>Apereo CAS provides an administrative dashboard, codenamed <em>Palantir</em>, whose responsibility is to provide insight and control over how the server operates. Using a simple web interface, it allows one to examine and monitor the server configuration and manage applications registered with CAS.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>In this blog post, we will briefly review the setup and configuration required to enable and run the Palantir admin dashboard. Our starting position is based on:</p>

<ul>
  <li>CAS <code class="language-plaintext highlighter-rouge">8.0.x</code></li>
  <li>Java <code class="language-plaintext highlighter-rouge">25</code></li>
</ul>

<h1 id="setup">Setup</h1>

<p>At its core, Palantir builds on top of the available and exposed <em>actuator</em> endpoints that are provided by the underlying frameworks such as Spring Boot as well as those that CAS itself presents. It does have the smarts to some degree to figure out which actuator endpoints are available and can then rearrange itself to show relevant content and operations.</p>

<p>The most basic setup requires that you enable the following CAS modules:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-palantir"</span>
<span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-reports"</span>
</code></pre></div></div>

<p>Once in place and to keep matters simple and demo-worthy, we may enable and expose all actuator endpoints that are available to our current CAS build for public access:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.monitor.endpoints.endpoint.defaults.access</span><span class="p">=</span><span class="s">ANONYMOUS</span>
<span class="py">management.endpoints.web.exposure.include</span><span class="p">=</span><span class="s">*</span>
<span class="py">management.endpoints.access.default</span><span class="p">=</span><span class="s">UNRESTRICTED</span>
</code></pre></div></div>

<div class="alert alert-warning">
  <strong>WATCH OUT!</strong><br />The above collection of settings <strong>MUST</strong> only be used for demo purposes and serve as an <strong>EXAMPLE</strong>. It is not wise to enable and expose all actuator endpoints to the web and certainly, the security of the exposed endpoints should be taken into account very seriously. None of the CAS or Spring Boot actuator endpoints are enabled by default. For production, you should carefully choose which endpoints to expose and how to secure them.
</div>

<p>Furthermore, we may define a single <em>admin</em> account that is able to access the Palantir dashboard:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">spring.security.user.name</span><span class="p">=</span><span class="s">casadmin</span>
<span class="py">spring.security.user.password</span><span class="p">=</span><span class="s">...YOUR_PASSWORD_HERE...</span>
</code></pre></div></div>

<p>For a slightly more advanced option, you may define the admin accounts in an external JSON file:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.monitor.endpoints.endpoint.defaults.access</span><span class="p">=</span><span class="s">ROLE</span>
<span class="py">cas.monitor.endpoints.endpoint.defaults.required-roles</span><span class="p">=</span><span class="s">ADMIN</span>

<span class="py">cas.monitor.endpoints.json.location</span><span class="p">=</span><span class="s">file:/path/to/MyUsers.json</span>
</code></pre></div></div>

<p>…where the file would be:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"casadmin"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"password"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{sha512}b6c65a..."</span><span class="p">,</span><span class="w">
    </span><span class="nl">"authorities"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="s2">"ROLE_ADMIN"</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"casnone"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"password"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{noop}pa$$w0rd"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<h1 id="capabilities">Capabilities</h1>

<p>Remember that Palantir builds on top of the available and exposed <em>actuator</em> endpoints that are brought into the build by various CAS modules. Palantir functionality and features will become conditionally available depending on what is included in the build. Of course, not all functionality is exposed via dedicated actuator endpoints comprehensively, and not every feature behind an existing actuator endpoint is yet supported by Palantir. Having said that, the following capabilities are offered by the Palantir admin dashboard.</p>

<h2 id="application-dashboard">Application Dashboard</h2>

<p>Palantir offers an application dashboard that lists all current applications registered with CAS:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/dashboard.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/dashboard.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<p>You may choose to edit the application registration record directly:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/editor.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/editor.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<p>You may also choose a wizard-based approach to register a new application with CAS step by step:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/wizard.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/wizard.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<p>You can verify whether an application has access to CAS:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/authz.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/authz.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<p>Or get a quick view of SAML2 service providers and their metadata source:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/saml2.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/saml2.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<h2 id="system-status">System Status</h2>

<p>You may observe and monitor the server status, HTTP requests, etc.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/systemstatus.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/systemstatus.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<h2 id="ticket-registry">Ticket Registry</h2>

<p>You can interact with the effective ticket registry:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/ticketregistry.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/ticketregistry.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<p>You may also query for effective SSO sessions and administratively log users out:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/sso.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/sso.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<h2 id="logging">Logging</h2>

<p>You can control application log levels:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/logs.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/logs.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<p>Or monitor and watch log activity live:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/logging.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/logging.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<h2 id="cas-configuration">CAS Configuration</h2>

<p>You can examine CAS configuration properties and sources:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/config.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/config.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<p>Or look up users and their attributes from configured sources:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/attrrepos.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/attrrepos.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<p>Or look up configured authentication sources and providers:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/authn.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/authn.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<p>Or interact with protocol-specific functionality to run simulations, etc.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/protocols.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/protocols.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/saml2protocol.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/saml2protocol.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<h2 id="multifactor-authentication">Multifactor Authentication</h2>

<p>You can look up registered multifactor devices for users for providers that support device registration and management, such as Duo Security, Google Authenticator, etc.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="/images/blog/assets/palantir/mfa.png" title="Apereo CAS - Palantir Dashboard" target="_blank">
    <img src="/images/blog/assets/palantir/mfa.png?v=1771111184" style="width:70%;" title="Apereo CAS - Palantir Dashboard - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<h1 id="limitations--caveats">Limitations &amp; Caveats</h1>

<p>Palatir does offer a lot, and is being consistently improved and worked on to present more capabilities or enhance what is already available. That said, some things in Palantir are not available as of this writing, some of which are noted here:</p>

<ul>
  <li>The wizard-based application editor does not support every single configuration setting that can be assigned to an application record. YMMV.</li>
  <li>The wizard-based application editor does not support advanced concepts such as chaining policies, etc.</li>
</ul>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<ul>
  <li>Lots of functionality is presented in read-only mode only, and writes/updates are not possible. A concrete example would be updating a CAS configuration property.</li>
  <li>Palantir user interfaces may be entirely dysfunctional if the CAS user interface is modified and themed heavily or if certain core CSS and JavaScript libraries are removed during customizations. The Palantir admin dashboard and the rest of the CAS user interface inherit the same common layout and libraries, and changing one may have adverse effects on the other.</li>
</ul>

<h1 id="need-help">Need Help?</h1>

<p>If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to <a href="/#contact-section-header">send us a note </a> and ask about consulting and support services.</p>

<h1 id="so">So…</h1>

<p>I hope the content here was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. If you have suggestions or ideas on how to improve this post, please feel free to reach out to us.</p>

<p><a href="https://fawnoos.com">Misagh Moayyed</a></p>]]></content><author><name>Misagh Moayyed</name></author><category term="CAS 8.0.0" /><summary type="html"><![CDATA[Apereo CAS provides an administrative dashboard, codenamed Palantir, whose responsibility is to provide insight and control over how the server operates. Using a simple web interface, it allows one to examine and monitor the server configuration and manage applications registered with CAS.]]></summary></entry><entry><title type="html">Apereo CAS - Extracting Client IP Address from HTTP Requests</title><link href="https://fawnoos.com/2025/12/14/cas73x-extracting-client-ip/" rel="alternate" type="text/html" title="Apereo CAS - Extracting Client IP Address from HTTP Requests" /><published>2025-12-14T00:00:00+00:00</published><updated>2025-12-14T00:00:00+00:00</updated><id>https://fawnoos.com/2025/12/14/cas73x-extracting-client-ip</id><content type="html" xml:base="https://fawnoos.com/2025/12/14/cas73x-extracting-client-ip/"><![CDATA[<p>Client IP addresses are typically extracted from the likes of <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> HTTP header and passed back to applications. In the context of Apereo CAS deployments, this header is usually processed by load balancers and proxies that front traffic, terminate SSL and extract the header safely, allowing CAS to read this header directly. Alternatively, one may disable this capability in CAS and let a servlet container like Apache Tomcat to rewrite the header by evaluating the incoming request against configured internal and trusted proxies using its <code class="language-plaintext highlighter-rouge">RemoteIpValve</code>.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>In this post, we will briefly review options that allow a CAS deployment to safely extract the client IP address from HTTP requests. Our starting position is as follows:</p>

<ul>
  <li>CAS <code class="language-plaintext highlighter-rouge">7.3.x</code></li>
  <li>Java <code class="language-plaintext highlighter-rouge">21</code></li>
</ul>

<h1 id="the-problem">The Problem</h1>

<p>A CAS deployment (or any web application for that matter) must never trust the <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> header (or any other header) unless it is set/overwritten by a trusted proxy, and we must then make sure CAS is configured to only use that trusted information. By default, CAS is configured to extract and read the <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> header directly from the request, assuming that that header is validated and set by a trusted proxy. Failure to do so will have CAS blindly trust the <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> header as provided by the client, allowing certain security restrictions that are IP-based to be bypassed.</p>

<h1 id="reverse-proxies--load-balancers">Reverse Proxies &amp; Load Balancers</h1>

<p>One mitigation strategy is to ensure that only a trusted component (reverse proxy/load balancer) can set <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code>, and that CAS only uses that trusted info. To do this, one must terminate untrusted traffic at a reverse proxy (nginx, HAProxy, Apache httpd, etc.) and configure the proxy to strip any incoming <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> header from the client. The reverse proxy would then set a new <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> header (or Forwarded) based on the real <code class="language-plaintext highlighter-rouge">remote_addr</code> it sees.</p>

<p>In other words, you must not deploy CAS directly on the internet with <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> as the default mechanism, unless you are stripping and re-setting the header at a trusted boundary. Otherwise, the deployment is vulnerable to a wide range of security issues and problems.</p>

<p>An example setup with nginx might be:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">location</span> <span class="n">/cas</span> <span class="p">{</span>
  <span class="kn">proxy_pass</span> <span class="s">http://cas_upstream</span><span class="p">;</span>
  <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span>
  <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$remote_addr</span><span class="p">;</span>
  <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$remote_addr</span><span class="p">;</span>
  <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$scheme</span><span class="p">;</span>
  <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Port</span> <span class="nv">$server_port</span><span class="p">;</span>

  <span class="c1"># Optional: Forwarded header (RFC 7239)</span>
  <span class="kn">proxy_set_header</span> <span class="s">Forwarded</span> <span class="s">"for=</span><span class="nv">$remote_addr</span><span class="p">;</span><span class="kn">proto=</span><span class="nv">$scheme</span><span class="p">;</span><span class="kn">host=</span><span class="nv">$host</span><span class="s">"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This prevents spoofing because nginx never forwards what the client sent; it overwrites with <code class="language-plaintext highlighter-rouge">$remote_addr</code> (what nginx actually saw at the TCP layer). If a malicious client sends <code class="language-plaintext highlighter-rouge">X-Forwarded-For: 8.8.8.8</code>, it gets ignored and replaced.</p>

<p>A quick test with <code class="language-plaintext highlighter-rouge">curl</code> might be:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-k</span> <span class="nt">-H</span> <span class="s1">'X-Forwarded-For: 8.8.8.8'</span> https://sso.example.org/cas/login <span class="nt">-v</span>
</code></pre></div></div>

<p>If the reverse proxy is configured and doing its job correctly, CAS should see your actual client IP, not <code class="language-plaintext highlighter-rouge">8.8.8.8</code>.</p>

<h1 id="apache-tomcat">Apache Tomcat</h1>

<p>If your reverse proxy is unable to recreate headers, you may be able to take advantage of similar functionality provided by your servlet container. For example, Apache Tomcat has a <code class="language-plaintext highlighter-rouge">RemoteIpValve</code> whose purpose is to replace the apparent remote IP address, hostname, scheme (HTTP/HTTPS), and server port of an incoming request. To recap, the most common headers used are:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">X-Forwarded-For</code>: Contains a list of IP addresses that traversed the request, with the original client’s IP being the first one.</li>
  <li><code class="language-plaintext highlighter-rouge">X-Forwarded-Proto</code>: Indicates the protocol (e.g., http or https) the client used to connect to the proxy.</li>
  <li><code class="language-plaintext highlighter-rouge">X-Forwarded-Host</code>: Indicates the original hostname requested by the client.</li>
</ul>

<p>The <code class="language-plaintext highlighter-rouge">RemoteIpValve</code> reads these headers and uses the information to internally modify the request that CAS sees. By default, CAS (in collaboration with Spring Boot) presents an opinionated set of configurations that configure this component, and your task is mainly to define the internal and trusted proxies that allow the <code class="language-plaintext highlighter-rouge">RemoteIpValve</code> to operate on the request:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">server.tomcat.remoteip.internal-proxies</span><span class="p">=</span><span class="s">172</span><span class="se">\\</span><span class="s">..+</span>
<span class="py">server.tomcat.remoteip.trusted-proxies</span><span class="p">=</span><span class="s">172</span><span class="se">\\</span><span class="s">..+</span>
</code></pre></div></div>

<p>The above configuration says: <em>only if the immediate peer is 172.x.x.x (i.e., nginx in a docker network) will Apache Tomcat honor forwarding headers</em>.</p>

<p>What happens in practice is:</p>

<ul>
  <li>When a new request is submitted, <code class="language-plaintext highlighter-rouge">RemoteIpValve</code> decides whether to rewrite the request’s “remote address” based on whether the TCP peer (the original value found in <code class="language-plaintext highlighter-rouge">request.getRemoteAddr()</code>) matches your configured internal/trusted proxies.</li>
  <li>Let’s imagine that our TCP peer is <code class="language-plaintext highlighter-rouge">192.168.65.1</code>, which is commonly the host-side gateway/NAT address used by Docker Desktop when your host talks to containers. Since <code class="language-plaintext highlighter-rouge">192.168.65.1</code> doesn’t match the trusted/internal proxy patterns, Apache Tomcat correctly says: <em>“this request did not come from a trusted proxy, so I will ignore forwarded headers.”</em></li>
</ul>

<p>It is important to note that, in this scenario, Apache Tomcat does not remove headers from the request. So a spoofed <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> header will still be present and CAS may read it directly if configured. We can disable this behavior and remove the risk via:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.audit.engine.alternate-client-addr-header-name</span><span class="p">=</span>
</code></pre></div></div>

<p>Setting this property to blank will force CAS to not read the header directly, and instead uses <code class="language-plaintext highlighter-rouge">request.getRemoteAddr()</code> provided by the proxy.</p>

<h1 id="need-help">Need Help?</h1>

<p>If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to <a href="/#contact-section-header">send us a note </a> and ask about consulting and support services.</p>

<h1 id="so">So…</h1>

<p>I hope the content here was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. If you have suggestions or ideas on how to improve this post, please feel free to reach out to us.</p>

<p><a href="https://fawnoos.com">Misagh Moayyed</a></p>]]></content><author><name>Misagh Moayyed</name></author><category term="CAS 7.3.x" /><category term="CAS 8.0.0" /><category term="Spring Boot" /><category term="Apache Tomcat" /><summary type="html"><![CDATA[Client IP addresses are typically extracted from the likes of X-Forwarded-For HTTP header and passed back to applications. In the context of Apereo CAS deployments, this header is usually processed by load balancers and proxies that front traffic, terminate SSL and extract the header safely, allowing CAS to read this header directly. Alternatively, one may disable this capability in CAS and let a servlet container like Apache Tomcat to rewrite the header by evaluating the incoming request against configured internal and trusted proxies using its RemoteIpValve.]]></summary></entry><entry><title type="html">Apereo CAS - Reloading Code Dynamically With Gradle and Spring Boot</title><link href="https://fawnoos.com/2025/11/22/cas73x-boorun-jbr-hotswap-codereload/" rel="alternate" type="text/html" title="Apereo CAS - Reloading Code Dynamically With Gradle and Spring Boot" /><published>2025-11-22T00:00:00+00:00</published><updated>2025-11-22T00:00:00+00:00</updated><id>https://fawnoos.com/2025/11/22/cas73x-boorun-jbr-hotswap-codereload</id><content type="html" xml:base="https://fawnoos.com/2025/11/22/cas73x-boorun-jbr-hotswap-codereload/"><![CDATA[<p>At times, you may need to modify Java code to deliver a specific feature to your Apereo CAS deployment. This has traditionally been a tiresome and slow process that might have involved making modifications, then rebuilding and restarting the server to test the change. Depending on the nature of the modification, your development environment might be able to pick up the change and carry on once you recompile the modified class(es) but if the change is somewhat significant or <em>breaking</em> (i.e. adding or renaming existing fields or methods), you might be forced to rebuild the cycle again and restart. The feedback loop here is quite slow.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>In this post, we will briefly review an option that would allow one to hotswap code at runtime. Our starting position is as follows:</p>

<ul>
  <li>CAS <code class="language-plaintext highlighter-rouge">7.3.x</code></li>
  <li>Java <code class="language-plaintext highlighter-rouge">21</code></li>
</ul>

<h1 id="approach">Approach</h1>

<p>While there are commercial tools out there, such as <a href="http://jrebel.com/">JRebel</a>, free and/or open-source alternative solutions for dynamically loading or hot reloading Java code at runtime vary in capability or maturity from basic method-level changes to full structural class redefinitions, and often require specific JVM or IDE configurations. This post will focus on using the <strong>JetBrains Runtime (JBR)</strong>.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<h1 id="jetbrains-runtime-jbr-for-java">JetBrains Runtime (JBR) for Java</h1>

<p>JetBrains Runtime (JBR) is a customized OpenJDK distribution optimized for development, particularly with JetBrains IDEs like IntelliJ IDEA. It includes enhancements like improved font rendering, better desktop integration, optional Java Chromium Embedded Framework (JCEF) for browser embedding, and crucially, enhanced class redefinition (via DCEVM-like functionality) for dynamic code reloading during debugging—useful as an alternative to tools like DCEVM for hot-swapping changes without full restarts. This is enabled by the JVM flag <code class="language-plaintext highlighter-rouge">-XX:+AllowEnhancedClassRedefinition</code>.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>What this practically means that while you’re in debug mode, JBR’s enhanced redefinition allows reloading method bodies, adding/removing fields/methods, and more beyond standard HotSwap. Moreover, JBR passes OpenJDK TCK tests, ensuring compatibility.</p>

<h2 id="note-on-java-25">Note on Java 25</h2>

<p>As of this writing, JBR supports Java <code class="language-plaintext highlighter-rouge">25</code>, though adoption may lag slightly behind standard OpenJDK. IntelliJ IDEA <code class="language-plaintext highlighter-rouge">2025.2</code> and later fully support Java <code class="language-plaintext highlighter-rouge">25</code> development with JBR, including preview features like compact source files and module imports. If you’re not using a JetBrains IDE, worry not! JBR can still serve as your project’s JDK/JRE for building, running, and debugging apps.</p>

<h1 id="configuration">Configuration</h1>

<p>You do not need to manually download and configure the JBR. This is something that can be achieved with Gradle’s support for Java Toolchains:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">java</span> <span class="o">{</span>
  <span class="n">toolchain</span> <span class="o">{</span>
    <span class="n">languageVersion</span> <span class="o">=</span> <span class="n">JavaLanguageVersion</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">21</span><span class="o">)</span>
    <span class="n">vendor</span> <span class="o">=</span> <span class="n">JvmVendorSpec</span><span class="o">.</span><span class="na">JETBRAINS</span>
  <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>For this to work, you must make sure your <code class="language-plaintext highlighter-rouge">settings.gradle</code> file contains the <code class="language-plaintext highlighter-rouge">foojay</code> plugin definition:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">plugins</span> <span class="o">{</span>
  <span class="n">id</span> <span class="s1">'org.gradle.toolchains.foojay-resolver-convention'</span> <span class="n">version</span> <span class="s1">'1.0.0'</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Now, Gradle will auto-download JBR 25 if not present already once you build the application with <code class="language-plaintext highlighter-rouge">./gradlew build</code>. The Foojay Toolchains plugin (via the <code class="language-plaintext highlighter-rouge">org.gradle.toolchains.foojay-resolver-convention</code> plugin) automatically downloads and provisions JBR (or other JDKs you need them) if it’s not already available on the system. It uses Gradle’s built-in auto-detection but falls back to downloading from the Foojay Disco API (which supports JetBrains as a vendor).</p>

<p>To verify, you can also run <code class="language-plaintext highlighter-rouge">./gradlew javaToolchains</code>, which lists all detected/provisioned toolchains with paths.</p>

<h1 id="spring-boot">Spring Boot</h1>

<p>Once you run CAS with <code class="language-plaintext highlighter-rouge">./gradlew bootRun</code>, Gradle will start CAS using JetBrains Runtime 25 and you will get much better hot-swapping capability for things like adding/removing/changing method bodies or methods in general, changing method signatures in many cases, some interface and/or interface hierarchy changes, etc. This is already excellent!</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>One important detail is still missing for full hot-swapping (adding/removing methods, fields, changing hierarchies, etc). For this to fully function, you will need to modify the <code class="language-plaintext highlighter-rouge">bootRun</code> task as well to include the special flag <code class="language-plaintext highlighter-rouge">XX:+AllowEnhancedClassRedefinition</code>. This is because JBR ships with the enhanced redefinition capability built-in, but it is disabled by default for stability reasons. <strong>You must explicitly turn it on</strong>.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bootRun</span> <span class="o">{</span>
    <span class="c1">// Other stuff happens here...</span>

    <span class="kt">def</span> <span class="n">list</span> <span class="o">=</span> <span class="o">[]</span>

    <span class="c1">// Other stuff happens here...</span>

    <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="s2">"-XX:+AllowEnhancedClassRedefinition"</span><span class="o">)</span>

    <span class="c1">// Other stuff happens here...</span>

    <span class="n">jvmArgs</span> <span class="o">=</span> <span class="n">list</span>
    
    <span class="c1">// Other stuff happens here...</span>
    <span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>

<p>After adding this line and applying the change, run:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./gradlew bootRun
</code></pre></div></div>

<h1 id="hotswapagent">HotSwapAgent</h1>

<p>For maximum benefit, you can also combine JBR and the flag above with <a href="https://github.com/HotswapProjects/HotswapAgent">HotSwapAgent</a>. This agent adds Spring bean reloads on config changes, Hibernate entity reloads, resource file reloads and a better overall framework integration.</p>

<p>You need to add the agent JAR (download <a href="https://github.com/HotswapProjects/HotswapAgent/releases">from here</a>) to the same construct you had above:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bootRun</span> <span class="o">{</span>
    <span class="c1">// Other stuff happens here...</span>

    <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="s2">"-XX:+AllowEnhancedClassRedefinition"</span><span class="o">)</span>
    <span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="s2">"-javaagent:/path/to/hotswap-agent.jar=autoHotswap=true"</span><span class="o">)</span>

    <span class="c1">// Other stuff happens here...</span>
<span class="o">}</span>
</code></pre></div></div>

<div class="alert alert-info">
  <strong>Note</strong><br />Remember that while in a remote debugging session and once you have made a code change, you will need to recompile the affected class(es) again in your development environment. No rebuilds or restarts; just a recompilation step directly inside the IDE will do.
</div>

<p>Now you’re really cooking with gas!</p>

<h1 id="need-help">Need Help?</h1>

<p>If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to <a href="/#contact-section-header">send us a note </a> and ask about consulting and support services.</p>

<h1 id="so">So…</h1>

<p>I hope the content here was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. If you have suggestions or ideas on how to improve this post, please feel free to reach out to us.</p>

<p><a href="https://fawnoos.com">Misagh Moayyed</a></p>]]></content><author><name>Misagh Moayyed</name></author><category term="CAS 7.3.x" /><category term="CAS 8.0.0" /><category term="Spring Boot" /><category term="Gradle" /><summary type="html"><![CDATA[At times, you may need to modify Java code to deliver a specific feature to your Apereo CAS deployment. This has traditionally been a tiresome and slow process that might have involved making modifications, then rebuilding and restarting the server to test the change. Depending on the nature of the modification, your development environment might be able to pick up the change and carry on once you recompile the modified class(es) but if the change is somewhat significant or breaking (i.e. adding or renaming existing fields or methods), you might be forced to rebuild the cycle again and restart. The feedback loop here is quite slow.]]></summary></entry><entry><title type="html">Apereo CAS 7.3.x Deployment - WAR Overlays</title><link href="https://fawnoos.com/2025/11/11/cas73x-gettingstarted-overlay/" rel="alternate" type="text/html" title="Apereo CAS 7.3.x Deployment - WAR Overlays" /><published>2025-11-11T00:00:00+00:00</published><updated>2025-11-11T00:00:00+00:00</updated><id>https://fawnoos.com/2025/11/11/cas73x-gettingstarted-overlay</id><content type="html" xml:base="https://fawnoos.com/2025/11/11/cas73x-gettingstarted-overlay/"><![CDATA[<p>This is a short and sweet tutorial on how to deploy CAS via <a href="https://apereo.github.io/cas/7.3.x/installation/WAR-Overlay-Installation.html">the WAR Overlay method</a>.</p>

<p>This tutorial specifically requires and focuses on:</p>

<ul>
  <li>CAS <code class="language-plaintext highlighter-rouge">7.3.x</code></li>
  <li>Java 21</li>
</ul>

<div class="alert alert-info">
  <strong>Need Help?</strong><br />If you ever get stuck and are in need of additional assistance, start by reviewing the suggestions <a href="https://apereo.github.io/cas/7.3.x/installation/Troubleshooting-Guide.html">provided here</a>. You may also look at available support options <a href="https://apereo.github.io/cas/Support.html">provided here</a>.
</div>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<h1 id="overlaywhat">Overlay…What?</h1>

<p>Overlays are a strategy to combat repetitive code and/or resources. Rather than downloading the CAS codebase and building it from source, overlays allow you to download a pre-built vanilla CAS web application server provided by the project itself, override/insert specific behavior into it and then merge it all back together to produce the final (web application) artifact. You can find a lot more about how overlays work <a href="https://apereo.github.io/cas/7.3.x/installation/WAR-Overlay-Installation.html">here</a>.</p>

<p>Please note that a CAS WAR Overlay can also be generated on demand using the <a href="/2021/02/28/cas64-cas-initializr/">CAS Initializr</a>.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>The concept of the WAR Overlay is NOT a CAS invention. It’s specifically an <em>Apache Maven</em> feature and of course, there are techniques and plugins available to apply the same concept to Gradle-based builds as well. For this tutorial, the Gradle overlay we will be working with is <a href="https://github.com/apereo/cas-overlay-template">available here</a>. Be sure to check out the appropriate branch, that is <code class="language-plaintext highlighter-rouge">7.3</code>.</p>

<div class="alert alert-info">
  <strong>Gradle WAR Overlay</strong><br />The Maven WAR overlay template is now deprecated and moved aside. The reference overlay project simply resides here and is transformed to use the Gradle build tool instead. This is done to reduce maintenance overhead and simplify the deployment strategy while allowing future attempts to make auto-generation of the overlay as comfortable as possible.
</div>

<p>The quickest way to generate a CAS WAR overlay starter template is via the following:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-k</span> https://getcas.apereo.org/starter.tgz  <span class="se">\</span>
  <span class="nt">-d</span> <span class="nb">type</span><span class="o">=</span>cas-overlay <span class="nt">-d</span> <span class="nv">baseDir</span><span class="o">=</span>overlay | <span class="nb">tar</span> <span class="nt">-xzvf</span> -
</code></pre></div></div>

<p>…if you prefer, you could always download and clone <a href="https://github.com/apereo/cas-overlay-template">this repository</a>.</p>

<p>Once you have forked and cloned the repository locally, or when you have generated the WAR overlay yourself using CAS Initializr, you’re ready to begin.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="alert alert-info">
  <strong>Note</strong><br />Remember to switch to the appropriate branch. Today, the <code>master</code> branch of the repository applies to CAS <code>7.3.x</code> deployments. That may not necessarily remain true when you start your own deployment. So examine the branches and make sure you <code>checkout</code> the one matching your intended CAS version.
</div>

<h1 id="overlays-anatomy">Overlay’s Anatomy</h1>

<p>Similar to Grey’s, a <em>Gradle</em> WAR overlay is composed of several facets the most important of which are the <code class="language-plaintext highlighter-rouge">build.gradle</code> and <code class="language-plaintext highlighter-rouge">gradle.properties</code> file. These are build-descriptor files whose job is to teach Gradle how to obtain, build, configure (and in certain cases deploy) CAS artifacts.</p>

<div class="alert alert-info">
  <strong>KISS</strong><br />You do not need to download Gradle separately. The project provides one for you automatically with the embedded Gradle Wrapper.
</div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>The CAS Gradle Overlay is composed of several sections. The ones you need to worry about are the following.</p>

<h2 id="gradle-properties">Gradle Properties</h2>

<p>In <code class="language-plaintext highlighter-rouge">gradle.properties</code> file, project settings, and versions are specified:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.version</span><span class="p">=</span><span class="s">7.3.0</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">gradle.properties</code> file describes what versions of CAS, Spring Boot, and Java are required for the deployment. You are in practice mostly concerned with the <code class="language-plaintext highlighter-rouge">cas.version</code> setting and as new (maintenance) releases come out, it would be sufficient to simply update that version and re-run the build.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>This might be a good time to review the CAS project’s <a href="https://apereo.github.io/cas/developer/Release-Policy.html">Release Policy</a> as well as <a href="https://apereo.github.io/cas/developer/Maintenance-Policy.html">Maintenance Policy</a>.</p>

<h2 id="to-upgrade">To Upgrade</h2>

<p>You should do your best to stay current with <a href="https://github.com/apereo/cas/releases">CAS releases</a>, particularly those that are issued as security or patch releases. Security releases are a critical minimal change on a release to address a serious confirmed security issue, and typically take on the format of <code class="language-plaintext highlighter-rouge">X.Y.Z.1</code>, <code class="language-plaintext highlighter-rouge">X.Y.Z.2</code>, etc. A patch release is a conservative incremental improvement that includes bug fixes and is absolutely backward compatible with previous patch releases and takes on the format of <code class="language-plaintext highlighter-rouge">X.Y.1</code>, <code class="language-plaintext highlighter-rouge">X.Y.2</code>, etc.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>Upgrading to a security or patch release is <strong>STRONGLY</strong> recommended, and should be a drop-in replacement. To upgrade to such releases, all you should have to do is to adjust the <code class="language-plaintext highlighter-rouge">cas.version</code> setting in your <code class="language-plaintext highlighter-rouge">gradle.proprties</code> file. For example, going from CAS <code class="language-plaintext highlighter-rouge">7.3.0</code> to <code class="language-plaintext highlighter-rouge">7.3.1</code> should be as easy as:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># cas.version=7.3.0
</span><span class="py">cas.version</span><span class="p">=</span><span class="s">7.3.1</span>
</code></pre></div></div>

<p>The best way to stay current with CAS releases and receive release notifications and announcements is via subscribing to the GitHub repository and watch for releases:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="image-wrapper rounded mx-auto d-block text-center">
    
    <a data-lightbox="image-fawnoos" data-title="Apereo CAS GitHub Repository Release Watch - Fawnoos || Open-Source Identity and Access Management Consulting Services" href="https://user-images.githubusercontent.com/1205228/164376845-b5d62b54-8ba0-4fe2-a4ed-cbd69d1e021c.png" title="Apereo CAS GitHub Repository Release Watch" target="_blank">
    <img src="https://user-images.githubusercontent.com/1205228/164376845-b5d62b54-8ba0-4fe2-a4ed-cbd69d1e021c.png?v=1771111184" style="width:80%;" title="Apereo CAS GitHub Repository Release Watch - Fawnoos || Open-Source Identity and Access Management Consulting Services" />
    </a>
</div>
<p />

<h2 id="dependencies">Dependencies</h2>

<p>The next piece describes the <em>dependencies</em> of the overlay build. These are the set of components almost always provided by the CAS project that will be packaged up and put into the final web application artifact.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>Here is an example:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dependencies</span> <span class="o">{</span>
  <span class="cm">/**
    * CAS dependencies and modules may be listed here.
    *
    * There is no need to specify the version number for each dependency
    * since versions are all resolved and controlled by the dependency management
    * plugin via the CAS bom.
    **/</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Note that when you include dependencies in the CAS build, you do not need to specify the CAS version itself. Each release of CAS provides a curated list of dependencies it supports. In practice, you do not need to provide a version for any of these dependencies in your build configuration as the CAS distribution is managing that for you. When you upgrade CAS itself, these dependencies will be upgraded as well in a consistent way.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>The curated list of dependencies contains a refined list of third-party libraries. The list is available as a standard Bill of Materials (BOM).</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">depndencies</span> <span class="o">{</span>
    <span class="n">implementation</span> <span class="nf">enforcedPlatform</span><span class="o">(</span><span class="s2">"org.apereo.cas:cas-server-support-bom:${project.'cas.version'}"</span><span class="o">)</span>
    <span class="n">implementation</span> <span class="nf">platform</span><span class="o">(</span><span class="n">org</span><span class="o">.</span><span class="na">springframework</span><span class="o">.</span><span class="na">boot</span><span class="o">.</span><span class="na">gradle</span><span class="o">.</span><span class="na">plugin</span><span class="o">.</span><span class="na">SpringBootPlugin</span><span class="o">.</span><span class="na">BOM_COORDINATES</span><span class="o">)</span>

    <span class="c1">// Include the CAS reports module without its version</span>
    <span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-reports"</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Including a CAS module/dependency in the <code class="language-plaintext highlighter-rouge">build.gradle</code> simply advertises to CAS <em>your intention</em> of turning on a new feature or a variation of current behavior. Do NOT include something in your build just because it looks and sounds cool. Remember that the point of an overlay is to only keep track of things you need and care about, and no more.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="alert alert-warning">
  <strong>Remember</strong><br />Keep your build clean and tidy. A messy build often leads to a messy deployment, complicates your upgrade path and is a documented cause of early hair loss. Keep changes down to the absolute essentials and document their need for your deployment. If you review the configuration a year from now, you should have an idea of why things are the way they are.
</div>

<h1 id="the-build">The Build</h1>

<p>Now that you have a basic understanding of the build descriptor, it’s time to run the build. A Gradle build is often executed by passing specific goals/commands to Gradle itself, aka <code class="language-plaintext highlighter-rouge">gradlew</code>. So for instance in the terminal and once inside the project directory you could execute things like:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>cas-overlay-template
./gradlew clean
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>The WAR Overlay project provides you with an embedded Gradle <em>wrapper</em> whose job is to first determine whether you have Gradle installed. If not, it will download and configure one for you based on the project’s needs. The <code class="language-plaintext highlighter-rouge">gradlew tasks</code> command describes the set of available operations you may carry out with the build script.</p>

<div class="alert alert-info">
  <strong>Remember</strong><br />Docs grow old. Always consult the overlay project's <code>README</code> file to keep to date.
</div>

<p>As an example, here’s what I see if I were to run the build command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./gradlew clean copyCasConfiguration build

...
Starting a Gradle Daemon <span class="o">(</span>subsequent builds will be faster<span class="o">)</span>
Configuration on demand is an incubating feature.

BUILD SUCCESSFUL <span class="k">in </span>14s
2 actionable tasks: 2 executed
...
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>You can see that the build attempts to download, clean, compile and package all artifacts, and finally, it produces a <code class="language-plaintext highlighter-rouge">build/libs/cas.war</code> which you can then use for actual deployments.</p>

<h1 id="configuration">Configuration</h1>

<p>I am going to skip over the configuration of <code class="language-plaintext highlighter-rouge">/etc/cas/config</code> and all that it deals with. If you need the reference, you may always <a href="https://apereo.github.io/cas/7.3.x/configuration/Configuration-Management.html">use this guide</a> to study various aspects of CAS configuration.</p>

<p>Suffice it to say that, quite simply, CAS deployment expects <em>the main</em> configuration file to be found under <code class="language-plaintext highlighter-rouge">/etc/cas/config/cas.properties</code>. This is a key-value store that can dictate and alter the behavior of the running CAS software.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>As an example, you might encounter something like:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.server.name</span><span class="p">=</span><span class="s">https://cas.example.org:8443</span>
<span class="py">cas.server.prefix</span><span class="p">=</span><span class="s">${cas.server.name}/cas</span>
<span class="py">logging.config</span><span class="p">=</span><span class="s">file:/etc/cas/config/log4j2.xml</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>…which at a minimum, identifies the CAS server’s URL and prefix and instructs the running server to locate the logging configuration at <code class="language-plaintext highlighter-rouge">file:/etc/cas/config/log4j2.xml</code>. The overlay by default ships with a <code class="language-plaintext highlighter-rouge">log4j2.xml</code> that you can use to customize logging locations, levels, etc. Note that the presence of all that is contained inside <code class="language-plaintext highlighter-rouge">/etc/cas/config/</code> is optional. CAS will continue to fall back onto defaults if the directory and the files within are not found.</p>

<h2 id="keep-track">Keep Track</h2>

<p>It is <strong>VERY IMPORTANT</strong> that you contain and commit the entire overlay directory (save the obvious exclusions such as the <code class="language-plaintext highlighter-rouge">build</code> directory) into some sort of source control system, such as <code class="language-plaintext highlighter-rouge">git</code>. Treat your deployment just like any other project with tags, releases, and functional baselines.</p>

<h1 id="logging">Logging</h1>

<p>CAS server logs are <strong>THE BEST RESOURCE</strong> for determining the root cause of a problem, provided you have configured the appropriate log levels. Specifically, you want to make sure <code class="language-plaintext highlighter-rouge">DEBUG</code> or <code class="language-plaintext highlighter-rouge">TRACE</code> levels are turned on for the relevant packages and components in your logging configuration. Know where the logging configuration is, become familiar with its syntax when changes are due and know where the output data is saved.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<h2 id="configuration-1">Configuration</h2>

<p>The CAS server web application by default ships with a <code class="language-plaintext highlighter-rouge">log4j2.xml</code> file that provides sensible logging configuration and levels for basic use cases. This option typically is activated when no external logging configuration is available and provided by the CAS build or its configuration. In reality, the CAS build provides dedicated settings by default to control the loggig configuration via the following setting:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">logging.config</span><span class="p">=</span><span class="s">file:/etc/cas/config/log4j2.xml</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>The logging configuration is then expected to be found and loaded from <code class="language-plaintext highlighter-rouge">/etc/cas/config/log4j2.xml</code>. If you deactivate or remove this setting, the default logging described earlier will begin to activate.</p>

<h2 id="log-output">Log Output</h2>

<p>Log messages are routed to console, and a <code class="language-plaintext highlighter-rouge">cas.log</code> file at <code class="language-plaintext highlighter-rouge">/tmp/logs</code>. Here are a few points about the default logging facility:</p>

<ul>
  <li>You can change the base directory by passing along a system property to the runtime when you start or deploy CAS via <code class="language-plaintext highlighter-rouge">-DbaseDir=/my/directory</code>.</li>
  <li>If you need the full stacktrace output of the exceptions, you can define the system property <code class="language-plaintext highlighter-rouge">-Dlog.file.stacktraces=true</code> for the runtime when you start or deploy CAS.</li>
  <li>If you need to change CAS logging levels, you can define the system property <code class="language-plaintext highlighter-rouge">-Dcas.log.level=debug</code> for the runtime when you start or deploy CAS. This will generally affect all log messages that would be submitted via components from the <code class="language-plaintext highlighter-rouge">org.apereo.cas</code> namespace, including all sub-packages and components.</li>
</ul>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>If you prefer to control the logging levels a bit more forcefully and dynamically, you can define the log level for the package you prefer when you start and run CAS particularly with an embedded servlet container:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java <span class="nt">-jar</span> build/libs/cas.war <span class="nt">--logging</span>.level.org.apereo.cas<span class="o">=</span>debug
</code></pre></div></div>

<p>Or alternatively, you could define the same setting in your <code class="language-plaintext highlighter-rouge">cas.properties</code>, though note that this technique only affects log messages once the CAS configuration file has been loaded and processed by the runtime:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">logging.level.org.apereo.cas</span><span class="p">=</span><span class="s">debug</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="alert alert-info">
  <strong>Remember</strong><br />If you are starting out, we <strong>STRONGLY</strong> recommend that you set the CAS logging level to either <code>debug</code> (or <code>trace</code> for more verbose and thorough logging). This is the most effective insight you have into the running software and your best troubleshooting tool to determine what exactly the system might be doing, and why.
</div>

<p>These options work for all packages and components, regardless of whether they’re owned or developed by CAS.</p>

<h1 id="ldap-authentication">LDAP Authentication</h1>

<p>We need to first establish a primary mode of validating credentials by sticking with <a href="https://apereo.github.io/cas/7.3.x/installation/LDAP-Authentication.html">LDAP authentication</a>. The strategy here, as indicated by the CAS documentation, is to declare the intention/module in the build script:</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implemntation</span> <span class="s2">"org.apereo.cas:cas-server-support-ldap"</span>
</code></pre></div></div>

<p>…and then configure the relevant <code class="language-plaintext highlighter-rouge">cas.authn.ldap[x]</code> settings for the directory server in use. Most commonly, that would translate into the following settings:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cas.authn.ldap[0].type=AUTHENTICATED
cas.authn.ldap[0].ldap-url=ldaps://ldap1.example.org
cas.authn.ldap[0].base-dn=dc=example,dc=org
cas.authn.ldap[0].search-filter=cn={user}
cas.authn.ldap[0].bind-dn=cn=Directory Manager,dc=example,dc=org
cas.authn.ldap[0].bind-credential=...
</code></pre></div></div>

<p>To resolve and fetch the needed attributes which will be used later by CAS for release, the simplest way would be to let LDAP authentication retrieve the attributes directly from the directory server.  The following setting allows us to do just that:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cas.authn.ldap[0].principal-attribute-list=memberOf,cn,givenName,mail
</code></pre></div></div>

<h1 id="registering-applications">Registering Applications</h1>

<p>Client applications that wish to use the CAS server for authentication must be registered with the server apriori. CAS provides several <a href="https://apereo.github.io/cas/7.3.x/services/Service-Management.html#storage">facilities to keep track of the registration records</a> and you may choose any that fits your needs best. In more technical terms, CAS deals with service management using two specific components: Individual implementations that support a form of a database are referred to as <em>Service Registry</em> components and they are many. There is also a parent component that sits on top of the configured service registry as more of an orchestrator that provides a generic facade and entry point for the rest of CAS without entangling all other operations and subsystems with the specifics and particulars of storage technology.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>In this tutorial, we are going to try to configure CAS with <a href="https://apereo.github.io/cas/7.3.x/services/JSON-Service-Management.html">the JSON service registry</a>.</p>

<h2 id="configuration-2">Configuration</h2>

<p>First, ensure you have declared the appropriate module/intention in the build:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-json-service-registry"</span>
</code></pre></div></div>

<p>Next, you must teach CAS how to look up JSON files to read and write registration records. This is done in the <code class="language-plaintext highlighter-rouge">cas.properties</code> file:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.service-registry.core.init-from-json</span><span class="p">=</span><span class="s">false</span>
<span class="py">cas.service-registry.json.location</span><span class="p">=</span><span class="s">file:/etc/cas/services</span>
</code></pre></div></div>

<p>…where a sample <code class="language-plaintext highlighter-rouge">ApplicationName-1001.json</code> would then be placed inside <code class="language-plaintext highlighter-rouge">/etc/cas/services</code>:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"@class"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apereo.cas.services.CasRegisteredService"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"serviceId"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://app.example.org"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"ApplicationName"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1001</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Or perhaps a slightly more advanced version would be an application definition that allows for the release of certain attributes that we previously retrieved from LDAP as part of authentication:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"@class"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apereo.cas.services.CasRegisteredService"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"serviceId"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://app.example.org"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"ApplicationName"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1001</span><span class="p">,</span><span class="w">
  </span><span class="nl">"attributeReleasePolicy"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"@class"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"allowedAttributes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"java.util.ArrayList"</span><span class="p">,</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"cn"</span><span class="p">,</span><span class="w"> </span><span class="s2">"mail"</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h1 id="ticketing">Ticketing</h1>

<p>A robust CAS deployment requires the presence and configuration of an <em>internal</em> database that is responsible for <a href="https://apereo.github.io/cas/7.3.x/ticketing/Configuring-Ticketing-Components.html">keeping track of tickets</a> issued by CAS. CAS itself comes by default with a memory-based node-specific cache that is often more than sufficient for smaller deployments or certain variations of a <a href="https://apereo.github.io/cas/7.3.x/high_availability/High-Availability-Guide.html">clustered deployment</a>. Just like the service management facility, a large variety of databases and storage options are supported by CAS under the facade of a <em>Ticket Registry</em>.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>In this tutorial, we are going to configure CAS to use a <a href="https://apereo.github.io/cas/7.3.x/ticketing/Hazelcast-Ticket-Registry.html">Hazelcast Ticket Registry</a> with the assumption that our deployment is going to be deployed in an AWS-sponsored environment. Hazelcast Ticket Registry is often a decent choice when deploying CAS in a cluster and can take advantage of AWS’s native support for Hazelcast to read node metadata properly and locate other CAS nodes in the same cluster to present a common, global and shared ticket registry. This is an ideal choice that requires very little manual work and/or troubleshooting, compared to using options such as Multicast or manually noting down the address and location of each CAS server in the cluster.</p>

<h2 id="configuration-3">Configuration</h2>

<p>First, ensure you have declared the appropriate module/intention in the build:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-hazelcast-ticket-registry"</span>
</code></pre></div></div>

<p>Next, the AWS-specific configuration of Hazelcast would go into our <code class="language-plaintext highlighter-rouge">cas.properties</code>:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.ticket.registry.hazelcast.cluster.discovery.enabled</span><span class="p">=</span><span class="s">true</span>
<span class="py">cas.ticket.registry.hazelcast.cluster.discovery.aws.access-key</span><span class="p">=</span><span class="s">...</span>
<span class="py">cas.ticket.registry.hazelcast.cluster.discovery.aws.secret-key</span><span class="p">=</span><span class="s">...</span>
<span class="py">cas.ticket.registry.hazelcast.cluster.discovery.aws.region</span><span class="p">=</span><span class="s">us-east-1</span>
<span class="py">cas.ticket.registry.hazelcast.cluster.discovery.aws.security-group-name</span><span class="p">=</span><span class="s">...</span>
<span class="c"># cas.ticket.registry.hazelcast.cluster.discovery.aws.tag-key=
# cas.ticket.registry.hazelcast.cluster.discovery.aws.tag-value=
</span></code></pre></div></div>

<p>That should do it.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>Of course, if you are working on a more modest CAS deployment in an environment that is more or less owned by you and you prefer more explicit control over CAS node registrations in your cluster, the following settings would be more ideal:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># cas.ticket.registry.hazelcast.cluster.instance-name=localhost
# cas.ticket.registry.hazelcast.cluster.network.port=5701
# cas.ticket.registry.hazelcast.cluster.network.port-auto-increment=true
</span><span class="py">cas.ticket.registry.hazelcast.cluster.network.members</span><span class="p">=</span><span class="s">123.321.123.321,223.621.123.521,...</span>
</code></pre></div></div>

<h1 id="audit-logs">Audit Logs</h1>

<p>CAS provides a facility for auditing authentication activity, allowing them to be recorded to a variety of storage services. Essentially, audited authentication events attempt to provide the <em>who, what, when, how</em>, along with any additional contextual information that might be useful to track activity. By default, auditable records are sent to the CAS log file and they may look like this:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>WHO: casuser
WHAT: supplied credentials: ...
ACTION: AUTHENTICATION_SUCCESS
APPLICATION: CAS
WHEN: Mon Aug 26 12:35:59 IST 2013
CLIENT IP ADDRESS: 172.16.5.181
SERVER IP ADDRESS: 192.168.200.22
</code></pre></div></div>

<p>It’s often useful to track audit records in a relational database for future monitoring, data mining and querying features that may be done outside CAS. Here, we try to configure CAS to push audit data into a PostgreSQL database.</p>

<h2 id="configuration-4">Configuration</h2>

<p>First, ensure you have declared the appropriate module/intention in the build:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dependencies</span> <span class="o">{</span>
  <span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-audit-jdbc"</span>
<span class="o">}</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="alert alert-info">
  <strong>Remember</strong><br />You should not have to include additional modules or dependencies to provide database drivers. Those will be automatically provided by CAS to the build with the inclusion of the module above.
</div>

<p>Then, put specific audit settings in <code class="language-plaintext highlighter-rouge">cas.properties</code>:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.audit.jdbc.user</span><span class="p">=</span><span class="s">postgres</span>
<span class="py">cas.audit.jdbc.password</span><span class="p">=</span><span class="s">password</span>
<span class="py">cas.audit.jdbc.driver-class</span><span class="p">=</span><span class="s">org.postgresql.Driver</span>
<span class="py">cas.audit.jdbc.url</span><span class="p">=</span><span class="s">jdbc:postgresql://localhost:5432/audit</span>
<span class="py">cas.audit.jdbc.dialect</span><span class="p">=</span><span class="s">org.hibernate.dialect.PostgreSQL10Dialect</span>
</code></pre></div></div>

<p>You may also note that the audit record includes a special field for <em>Client IP Address</em>, which typically notes the IP address of the end-user attempting to authenticate, etc. Deployments that are behind a proxy or a load balancer often tend to mask the real IP address by default, and expose it using a dedicated header, such as <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code>. This can be configured with CAS as well, so the correct IP is then recorded into the audit log:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.audit.engine.alternate-client-addr-header-name</span><span class="p">=</span><span class="s">X-Forwarded-For</span>
</code></pre></div></div>

<h1 id="multifactor-authentication-via-duo-security">Multifactor Authentication via Duo Security</h1>

<p>As a rather common use case, the majority of CAS deployments that intend to turn on multifactor authentication support tend to do so via <a href="https://apereo.github.io/cas/7.3.x/mfa/DuoSecurity-Authentication.html">Duo Security</a>. We will be going through the same exercise here where we let CAS trigger Duo Security for users who belong to the <code class="language-plaintext highlighter-rouge">mfa-eligible</code> group, indicated by the <code class="language-plaintext highlighter-rouge">memberOf</code> attribute on the LDAP user account.</p>

<h2 id="configuration-5">Configuration</h2>

<p>First, ensure you have declared the appropriate module/intention in the build:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-duo"</span>
</code></pre></div></div>

<p>Then, put specific Duo Security settings in <code class="language-plaintext highlighter-rouge">cas.properties</code>. Things such as the secret key, integration key, etc which should be provided by your Duo Security subscription:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cas.authn.mfa.duo[0].duo-secret-key=
cas.authn.mfa.duo[0].duo-integration-key=
cas.authn.mfa.duo[0].duo-api-host=
</code></pre></div></div>

<p>At this point, we have enabled Duo Security and we just need to find a way to instruct CAS to route the authentication flow over to Duo Security in the appropriate condition. Our task here is to build a special condition that activates multifactor authentication if any of the values assigned to the attribute <code class="language-plaintext highlighter-rouge">memberOf</code> contain the value <code class="language-plaintext highlighter-rouge">mfa-eligible</code>. This condition is placed in the <code class="language-plaintext highlighter-rouge">cas.properties</code> file:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.authn.mfa.triggers.principal.global-principal-attribute-name-triggers</span><span class="p">=</span><span class="s">memberOf</span>
<span class="py">cas.authn.mfa.triggers.principal.global-principal-attribute-value-regex</span><span class="p">=</span><span class="s">mfa-eligible</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>If the above condition holds true and CAS is to route to a multifactor authentication flow, that would be one supported and provided by Duo Security since that’s the only provider that is currently configured to CAS.</p>

<h1 id="openid-connect">OpenID Connect</h1>

<p>We can also turn on support for the <a href="https://apereo.github.io/cas/7.3.x/authentication/OIDC-Authentication.html">OpenID Connect</a> protocol, allowing CAS to act as an OP (OpenID Connect Provider). OpenId Connect is a continuation of the OAuth protocol with some additional variations. If you enable OpenId Connect, you will have automatically enabled OAuth as well. “Two birds for one stone” sort of thing, though no disrespect to the avian community!</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="alert alert-info">
  <strong>Let There Be SSO</strong><br />Remember that any successful authentication activity that allows CAS to establish a single sign-on session will be seen as valid, regardless of what protocol is used to interact and communicate with CAS. Switching the protocol and sending authentication requests between various applications integrated with CAS does not invalidate an existing single sign-on session and end-users will be not be asked to login again unless forcefully asked or indicated by the coming request.
</div>

<p>By turning on support for <a href="https://apereo.github.io/cas/7.3.x/authentication/OIDC-Authentication.html">OpenID Connect</a>, CAS begins to act as an authorization server, allowing client applications to verify the identity of the end-user and to obtain basic profile information in an interoperable and REST-like manner. For this tutorial, our focus is to mainly on integrating web-based client applications using the <em>Authorization Code</em> flow of OpenID Connect, which is quite similar to the CAS protocol; you receive a <em>code</em>, you validate the <em>code</em> and receive an access token as well as an ID token.</p>

<h2 id="configuration-6">Configuration</h2>

<p>First, ensure you have declared the appropriate module/intention in the build:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-oidc"</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>Then, we teach CAS about specific aspects of the authorization server functionality:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.authn.oidc.core.issuer</span><span class="p">=</span><span class="s">https://sso.example.org/cas/oidc</span>
<span class="py">cas.authn.oidc.jwks.file-system.jwks-file</span><span class="p">=</span><span class="s">file:///etc/cas/config/keystore.jwks</span>
</code></pre></div></div>

<p>The JWKS resource is used by CAS to create (or use an existing) JSON web keystore composed of private and public keys that enable clients to validate a JSON Web Token (JWT) such as an id token, issued by CAS as an OpenID Connect Provider. Here, we define the global keystore as a path on the file system.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="alert alert-info">
  <strong>Clustered Deployments</strong><br />When deploying CAS in a cluster, you must make sure all CAS server nodes have access to and share an identical and exact copy of the keystore file. Keystore differences will lead to various validation failures and application integration issues.
</div>

<p>That should be all. Now, you can proceed to register your client web application with CAS similar to the approach described earlier:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"@class"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apereo.cas.services.OidcRegisteredService"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"clientId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"my-client-id"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"clientSecret"</span><span class="p">:</span><span class="w"> </span><span class="s2">"my-client-secret"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"serviceId"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"^https://my.application.com/oidc/.+"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"OIDC"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"A sample OIDC client application"</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h1 id="saml2">SAML2</h1>

<p>We can also turn on support for the <a href="https://apereo.github.io/cas/7.3.x/authentication/Configuring-SAML2-Authentication.html">SAML2</a> protocol, allowing CAS to act as a SAML2 identity provider. By turning on support for <a href="https://apereo.github.io/cas/7.3.x/authentication/Configuring-SAML2-Authentication.html">SAML2</a>, CAS begins to accept SAML2 authentication requests and will in the end produce SAML2 assertions and responses. In doing so, CAS will also generate its own SAML2 identity provider metadata along with other needed artifacts and certificates, all of which should immediately get you started with service provider registrations.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="alert alert-info">
  <strong>Let There Be SSO</strong><br />Remember that any successful authentication activity that allows CAS to establish a single sign-on session will be seen as valid, regardless of what protocol is used to interact and communicate with CAS. Switching the protocol and sending authentication requests between various applications integrated with CAS does not invalidate an existing single sign-on session and end-users will be not be asked to login again unless forcefully asked or indicated by the coming request.
</div>

<h2 id="configuration-7">Configuration</h2>

<p>First, ensure you have declared the appropriate module/intention in the build:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-saml-idp"</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>Then, we need to decide what our SAML2 entity id should be and where to keep our SAML2 metadata. To keep matters simple, we’ll choose the filesystem to track and store metadata and its artifacts:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">cas.authn.saml-idp.core.entity-id</span><span class="p">=</span><span class="s">https://cas.apereo.org/saml/idp,</span>
<span class="py">cas.authn.saml-idp.metadata.file-system.location</span><span class="p">=</span><span class="s">file:///path/to/metadata/directory</span>
</code></pre></div></div>

<p>An entity id is a globally unique name for your identity provider. It’s used to identify the IdP during SAML transactions. It is typically a URI, although it doesn’t have to point to an actual resource. It’s often set to the IdP’s base URL or a specific URL that describes the entity. For example, it could be something like <code class="language-plaintext highlighter-rouge">https://cas.apereo.org/saml/idp</code>. This id is included in the metadata that CAS shares with its partners, and it’s used in SAML messages to indicate the sender or the intended recipient. It’s important that the entity id is unique to avoid confusion or conflicts.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>Metadata is an XML document that contains information about a SAML entity, such as an Identity Provider (IdP) or a Service Provider (SP). This metadata is used to facilitate the exchange of information for SAML transactions.</p>

<div class="alert alert-info">
  <strong>SAML2 Metadata Generation</strong><br />CAS metadata and its related artifacts are generated on startup, if and only if they are not found by the system. Once generated, these artifacts will sit there and are ready to be reused as you start and stop the server. This means, it's very very important that you do not lose or misplace the metadata artifacts. Once lost, CAS will re-generate them for you and that potentially will break every existing SAML2 integration you have already registered with CAS.
</div>

<p>The metadata typically includes:</p>

<ul>
  <li><strong>Entity ID</strong>: A unique identifier for the entity.</li>
  <li><strong>Endpoints</strong>: URLs where the entity sends or receives SAML messages. These include Assertion Consumer Service (ACS) endpoints, Single Logout Service (SLS) endpoints, etc.</li>
  <li><strong>Certificates</strong>: Public key certificates used for signing and/or encrypting SAML assertions.</li>
  <li><strong>Binding</strong>: The protocol binding that the entity supports (e.g., <code class="language-plaintext highlighter-rouge">HTTP-Redirect</code>, <code class="language-plaintext highlighter-rouge">HTTP-POST</code>, etc.).
The metadata is usually exchanged out-of-band (i.e., not through the SAML protocol itself) and is often made available at a publicly accessible URL. This allows partners to fetch and refresh the metadata as needed. For CAS, this typically would be: <code class="language-plaintext highlighter-rouge">https://sso.example.org/cas/idp/metadata</code></li>
</ul>

<p>Now, you can proceed to register your client web application with CAS similar to the approach described earlier:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"@class"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apereo.cas.support.saml.services.SamlRegisteredService"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"serviceId"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"the-entity-id-for-saml2-service-provider"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"Sample"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
  </span><span class="nl">"metadataLocation"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://saml2.example.org/sp/metadata"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"attributeReleasePolicy"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"@class"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apereo.cas.services.ReturnAllAttributeReleasePolicy"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h1 id="proxyexternal-authentication">Proxy/External Authentication</h1>

<p>We can also turn on support for external/delegated authentication, to hand the authentication off to an external identity provider and allow CAS to act as proxy in between. In the most common use case, CAS is made entirely invisible to the end-user such that the redirect to the external identity provider simply happens automatically and as far as the audience is concerned, there are only the external identity provider and the target application that is, of course, prepped to speak to the CAS server.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<h2 id="configuration-8">Configuration</h2>

<p>The initial setup is in fact simple; as the <a href="https://apereo.github.io/cas/7.3.x/integration/Delegate-Authentication.html">documentation describes</a> you need to add the required dependency in your overlay:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-pac4j-saml"</span>
</code></pre></div></div>

<p>…and then in your <code class="language-plaintext highlighter-rouge">cas.properties</code>, instruct CAS to hand off authentication to the, in this case, SAML2 identity provider:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cas.authn.pac4j.saml[0].keystore-password=pac4j-demo-passwd
cas.authn.pac4j.saml[0].private-key-password=pac4j-demo-passwd
cas.authn.pac4j.saml[0].service-provider-entity-id=cas:apereo:pac4j:saml
cas.authn.pac4j.saml[0].service-provider-metadata-path=/etc/cas/config/sp-metadata.xml
cas.authn.pac4j.saml[0].keystore-path=$/path/to/samlKeystore.jks
cas.authn.pac4j.saml[0].identity-provider-metadata-path=https://idp.example.org/metadata.php
cas.authn.pac4j.saml[0].destination-binding=urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
cas.authn.pac4j.saml[0].client-name=SAML2Client
</code></pre></div></div>

<p>The above settings instruct CAS to:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<ul>
  <li>Generate the service-provider metadata at <code class="language-plaintext highlighter-rouge">/etc/cas/config/sp-metadata.xml</code> using entity id <code class="language-plaintext highlighter-rouge">urn:mace:saml:pac4j.org</code> automatically. This metadata is created on CAS startup once the login page is rendered. This metadata is expected to be shared <em>somehow</em> with the SAML2 identity provider.</li>
  <li>The URL to the identity provider metadata is also taught to CAS; note that in this case, we are using Okta as the SAML2 external identity provider.</li>
</ul>

<p>Likewise, if you need to configure an exteral identity provider via the OpenID Connect protocol, the following settings would be more or less appropriate:</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cas.authn.pac4j.oidc[0].generic.id=...
cas.authn.pac4j.oidc[0].generic.secret=...
cas.authn.pac4j.oidc[0].generic.client-name=Keycloak
cas.authn.pac4j.oidc[0].generic.discovery-uri=https://ext.idp.org/.well-known/openid-configuration
</code></pre></div></div>

<p>Be sure to include the following dependency as well in your build:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-pac4j-oidc"</span>
</code></pre></div></div>

<h1 id="monitoring--status">Monitoring &amp; Status</h1>

<p>Many CAS deployments rely on the <code class="language-plaintext highlighter-rouge">/status</code> endpoint for monitoring the health and activity of the CAS deployment. This endpoint is typically secured via an IP address, allowing external monitoring tools and load balancers to reach the endpoint and parse the output. In this quick exercise, we are going to accomplish that task, allowing the <code class="language-plaintext highlighter-rouge">status</code> endpoint to be available over HTTP to <code class="language-plaintext highlighter-rouge">localhost</code>.</p>

<h2 id="configuration-9">Configuration</h2>

<p>First, ensure you have declared the appropriate module/intention in the build:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">implementation</span> <span class="s2">"org.apereo.cas:cas-server-support-monitor"</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>To enable and expose the <code class="language-plaintext highlighter-rouge">status</code> endpoint, the following settings should come in handy:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">management.endpoints.web.base-path</span><span class="p">=</span><span class="s">/actuator</span>
<span class="py">management.endpoints.web.exposure.include</span><span class="p">=</span><span class="s">status</span>
<span class="py">management.endpoint.status.enabled</span><span class="p">=</span><span class="s">true</span>

<span class="py">cas.monitor.endpoints.endpoint.status.access</span><span class="p">=</span><span class="s">IP_ADDRESS</span>
<span class="py">cas.monitor.endpoints.endpoint.status.required-ip-addresses</span><span class="p">=</span><span class="s">127.0.0.1</span>
</code></pre></div></div>

<p>Remember that the default path for endpoints exposed over the web is at <code class="language-plaintext highlighter-rouge">/actuator</code>, such as <code class="language-plaintext highlighter-rouge">/actuator/status</code>.</p>

<h1 id="overlay-customization">Overlay Customization</h1>

<p>The <code class="language-plaintext highlighter-rouge">build/libs</code> directory contains the results of the overlay process. Since I have not actually customized and overlaid anything yet, all configuration files simply match their default and are packaged as such. As an example, let’s grab <a href="https://apereo.github.io/cas/7.3.x/ux/User-Interface-Customization-Localization.html">the default message bundle</a> and change the text associated with <code class="language-plaintext highlighter-rouge">screen.welcome.instructions</code>.</p>

<div class="alert alert-warning">
  <strong>Remember</strong><br />Do NOT ever make changes in the <code>build</code> directory. The changesets will be cleaned out and set back to defaults every time you do a build. Follow the overlay process to avoid surprises.
</div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>First, I will need to move the file to my project directory so that during the overlay process Gradle can use that instead of what is provided by default.</p>

<p>Here we go:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./gradlew getResource <span class="nt">-PresourceName</span><span class="o">=</span>messages.properties
</code></pre></div></div>

<p>Then I’ll leave everything in that file alone, except the line I want to change.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
screen.welcome.instructions=Speak friend and enter.
...
</code></pre></div></div>

<p>Then I’ll package things up as usual.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./gradlew clean build
</code></pre></div></div>

<p>If I <code class="language-plaintext highlighter-rouge">explode</code> the built web application again and look at <code class="language-plaintext highlighter-rouge">build/cas/WEB-INF/classes/messages.properties</code> after the build, I should see that the overlay process has picked and overlaid onto the default <em>my version</em> of the file.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="alert alert-info">
  <strong>Remember</strong><br />Only overlay and modify files you need and try to use externalized resources and configuration as much as possible. Just because you CAN override something in the default package, it doesn't mean that you should.
</div>

<h1 id="user-interface-customizations">User Interface Customizations</h1>

<p>To modify the CAS HTML views, each file first needs to be brought over into the overlay. You can use the <code class="language-plaintext highlighter-rouge">./gradlew listTemplateViews</code> command to see what HTML views are available for customizations. Once chosen, simply use <code class="language-plaintext highlighter-rouge">./gradlew getResource -PresourceName=footer.html</code> to bring the view into your overlay. Once you have the <code class="language-plaintext highlighter-rouge">footer.html</code> brought into the overlay, you can simply modify the file at <code class="language-plaintext highlighter-rouge">src/main/resources/templates/fragments/footer.html</code>, and then repackage and run the build as usual.</p>

<h1 id="deploy">Deploy</h1>

<p>You have several options when it comes to deploying the final <code class="language-plaintext highlighter-rouge">cas.war</code> file. The easiest approach would be to simply use the <code class="language-plaintext highlighter-rouge">./gradlew run</code> command and have the overlay be deployed inside an embedded container. By default, the CAS web application expects to run on the secure port <code class="language-plaintext highlighter-rouge">8443</code> which requires that you create a keystore file at <code class="language-plaintext highlighter-rouge">/etc/cas/</code> named <code class="language-plaintext highlighter-rouge">thekeystore</code>.</p>

<h2 id="deploy-behind-a-proxy">Deploy Behind a Proxy</h2>

<p>Using the embedded Apache Tomcat container provided by CAS automatically is the recommended approach in almost all cases (The embedded bit; not the Apache Tomcat bit) as the container configuration is entirely automated by CAS and its version is guaranteed to be compatible with the running CAS deployment. Furthermore, updates and maintenance of the servlet container are handled at the CAS project level where you as the adopter are only tasked with making sure your deployment is running the latest available release to take advantage of such updates.</p>

<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>

<p>If you wish to run CAS via the embedded Apache Tomcat container behind a proxy or load balancer and have that entity terminate SSL, you will need to open up a communication channel between the proxy and CAS such that (as an example):</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<ul>
  <li>Apache Tomcat runs on port 8080, assuming that’s what the proxy uses to talk to CAS.</li>
  <li>Apache Tomcat has SSL turned off.</li>
  <li>Apache Tomcat connector listening on the above port is marked as secure.</li>
</ul>

<p>The above task list translates to the following properties expected to be found in your <code class="language-plaintext highlighter-rouge">cas.properties</code>:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">server.port</span><span class="p">=</span><span class="s">8080</span>
<span class="py">server.ssl.enabled</span><span class="p">=</span><span class="s">false</span>
<span class="py">cas.server.tomcat.http.enabled</span><span class="p">=</span><span class="s">false</span>
<span class="py">cas.server.tomcat.http-proxy.enabled</span><span class="p">=</span><span class="s">true</span>
<span class="py">cas.server.tomcat.http-proxy.secure</span><span class="p">=</span><span class="s">true</span>
<span class="py">cas.server.tomcat.http-proxy.scheme</span><span class="p">=</span><span class="s">https</span>
</code></pre></div></div>

<h2 id="deploy-via-docker">Deploy via Docker</h2>

<p>The overlay embraces the Jib Gradle Plugin to provide easy-to-use out-of-the-box tooling for building CAS docker images. Jib is an open-source Java containerizer from Google that handles all the steps of packaging CAS into a container image. It does not require you to write a <code class="language-plaintext highlighter-rouge">Dockerfile</code> and it is directly integrated into the overlay.</p>

<p>Building a CAS docker image via jib is as simple as:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./gradlew build jibDockerBuild
</code></pre></div></div>

<p>If you prefer a more traditional approach, there is always:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./gradlew build
docker-compose build
</code></pre></div></div>

<p>You may also build Docker images using the <a href="/2020/10/24/cas63x-spring-boot-docker/">Spring Boot Gradle plugin</a>.</p>

<h2 id="deploy-via-embedded-container">Deploy via Embedded Container</h2>

<p>If the WAR overlay is prepped with an embedded servlet container such as Apache Tomcat, then you may run the CAS web application directly and once built, using:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>java <span class="nt">-jar</span> build/libs/cas.war
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>The choice of the embedded servlet container is noted by the <code class="language-plaintext highlighter-rouge">appServer</code> property found in the <code class="language-plaintext highlighter-rouge">gradle.properties</code> file:</p>

<div class="language-properties highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Use -tomcat, -jetty, -undertow for deployment to other embedded containers
# if the overlay application supports or provides the chosen type.
# You should set this to blank if you want to deploy to an external container.
# and want to set up, download, and manage the container (i.e. Apache Tomcat) yourself.
</span><span class="py">appServer</span><span class="p">=</span><span class="s">-tomcat</span>
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>All servlet containers presented here, embedded or otherwise, aim to be production-ready. This means that CAS ships with useful defaults out of the box that may be overridden, if necessary and by default, CAS configures everything for you from development to production in today’s platforms. In terms of their production quality, there is almost no difference between using an embedded container vs. an external one.</p>

<p>Unless there are specific, technical, and reasonable objections, choosing an embedded servlet container is almost always the better choice.</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>If you forget to specify the correct servlet container type and yet choose to run CAS directly, it is likely that you would receive the following error:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR <span class="o">[</span>org.springframework.boot.SpringApplication] - &lt;Application run failed&gt;
  org.springframework.context.ApplicationContextException: Unable to start web server<span class="p">;</span>
    nested exception is org.springframework.context.ApplicationContextException: 
    Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
</code></pre></div></div>

<h1 id="gradle-tasks">Gradle Tasks</h1>

<p>The Gradle WAR overlay provides many additional commands that might prove helpful for troubleshooting purposes:</p>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Run the CAS web application in standalone executable mode</span>
./gradlew executable

<span class="c"># Debug the CAS web application in embedded mode on port 5005</span>
./gradlew debug

<span class="c"># Run the CAS web application in embedded container mode</span>
./gradlew run

<span class="c"># Display the CAS version</span>
./gradlew casVersion

<span class="c"># Export collection of CAS properties</span>
./gradlew exportConfigMetadata
</code></pre></div></div>
<div id="adscode" style="width:100%">
     <script async="" src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?v=1771111184"></script>
     <ins class="adsbygoogle" style="display:block; text-align:center;" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-8081398210264173" data-ad-slot="3789603713"></ins>
     <script>
          (adsbygoogle = window.adsbygoogle || []).push({});
     </script>
</div>
<p>The <code class="language-plaintext highlighter-rouge">exportConfigMetadata</code> task can be quite useful as it produces a comprehensive catalog of all CAS settings that one could potentially use, along with documentation for each setting, default values, and more.</p>

<h1 id="need-help">Need Help?</h1>

<p>If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to <a href="/#contact-section-header">send us a note </a> and ask about consulting and support services.</p>

<h1 id="so">So…</h1>

<p>I hope the content here was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. If you have suggestions or ideas on how to improve this post, please feel free to reach out to us.</p>

<p><a href="https://fawnoos.com">Misagh Moayyed</a></p>]]></content><author><name>Misagh Moayyed</name></author><category term="CAS 7.3.x" /><category term="Getting Started" /><category term="Gradle" /><category term="Spring Boot" /><summary type="html"><![CDATA[This is a short and sweet tutorial on how to deploy CAS via the WAR Overlay method.]]></summary></entry></feed>