<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>DGM&apos;s blog</title>
    <description>Daniel Garnier-Moiroux is a software engineer, tinkerer of things and lover of automation. Currently working at VMware, on Spring.
</description>
    <link>https://garnier.wf/</link>
    <atom:link href="https://garnier.wf/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Thu, 19 Mar 2026 16:47:50 +0000</pubDate>
    <lastBuildDate>Thu, 19 Mar 2026 16:47:50 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Spring Security&apos;s ObjectPostProcessor</title>
        <description>&lt;p&gt;Spring Security executes a delicate balancing act: it provides “sane” defaults, that work well in
most cases, while being extremely customizable. Customization mostly happens in the
&lt;a href=&quot;https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/builders/HttpSecurity.html&quot;&gt;HttpSecurity&lt;/a&gt; builder you use to create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SecurityFilterChain&lt;/code&gt; (or
in &lt;a href=&quot;https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/web/server/ServerHttpSecurity.html&quot;&gt;ServerHttpSecurity&lt;/a&gt; if you’re using webflux).
An example would be to set the URL users are redirected to when they log out, like so:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;SecurityFilterChain&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;securityFilterChain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpSecurity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ... your rules ...&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;logout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logoutSuccessUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/welcome&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But exposing every configurable knob in the HttpSecurity API would make it too verbose. Users would
have a hard time getting started: the common options would be drowned in a sea of methods that they
probably wouldn’t need. Some configuration can be achieved through beans, but that’s on a
case-by-case basis. Refer to the excellent &lt;a href=&quot;https://docs.spring.io/spring-security/reference/&quot;&gt;reference documentation&lt;/a&gt; to learn about all the available configuration for the very specific feature you are using.
Sometimes, you won’t find exactly what you need, so the last resort way to modify the defaults from Spring Security is to use an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectPostProcessor&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-objectpostprocessor-api&quot;&gt;The ObjectPostProcessor API&lt;/h2&gt;

&lt;p&gt;Spring Framework has a &lt;a href=&quot;https://docs.spring.io/spring-framework/reference/core/beans/factory-extension.html#beans-factory-extension-bpp&quot;&gt;BeanPostProcessor&lt;/a&gt; concept, that allows to modify beans when they are created.
This is useful, because the beans in your Spring application context may have been created by some code
that you do not own, for example, by Spring Boot. With “post-processing”, you can modify them
without having to create full beans yourself. Trust Spring Boot, but sprinkle some custom behavior
on top.&lt;/p&gt;

&lt;p&gt;Spring Security creates of objects that are &lt;em&gt;not&lt;/em&gt; beans, they are not in the application context.
They are not intended to be used by the rest of the application, and so they are not exposed. If you
want to modify them, you can register an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObjectPostProcessor&lt;/code&gt;. It is an extremely simple interface,
with a single method that takes an object &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O&lt;/code&gt; and returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;? extends O&lt;/code&gt;, so either the object
itself, another instance of the same type, or a subclass. The generic structure is like so, say, for
post-processing all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AuthenticationProvider&lt;/code&gt;s:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyObjectPostProcessor&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ObjectPostProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuthenticationProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;O&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthenticationProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;O&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;postProcess&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;O&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// transform the &quot;object&quot; with your code&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then Spring Security uses that class to transform the objects it creates, essentially doing:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;someSpringSecurityObject&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;objectPostProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;postProcess&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;someSpringSecurityObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Spring Security uses
&lt;a href=&quot;https://github.com/spring-projects/spring-security/blob/d8043dc8a771e28cee24f1ce566734e787c719b4/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java#L331&quot;&gt;that&lt;/a&gt;
&lt;a href=&quot;https://github.com/spring-projects/spring-security/blob/d8043dc8a771e28cee24f1ce566734e787c719b4/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurer.java#L268&quot;&gt;pattern&lt;/a&gt;
&lt;a href=&quot;https://github.com/spring-projects/spring-security/blob/d8043dc8a771e28cee24f1ce566734e787c719b4/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurer.java#L135&quot;&gt;everywhere&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;when-and-how-to-use-objectpostprocessor&quot;&gt;When, and how to use ObjectPostProcessor&lt;/h2&gt;

&lt;p&gt;Let’s take a concrete example. Let’s say you want to add a custom “authentication result converter” to
the class &lt;a href=&quot;https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.html&quot;&gt;OAuth2LoginAuthenticationFilter&lt;/a&gt;.
The specific details of this class, or what that converter does in practice, are not relevant to
this example: they’re advanced features that very few users need, and the idea is to show you where
the configuration hooks are. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OAuth2LoginAuthenticationFilter&lt;/code&gt; is created when you do
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.oauth2Login(...)&lt;/code&gt; on your security filter chain configuration. It has a
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.setAuthenticationResultConverter(...)&lt;/code&gt; method, that you can call … But for this, you need an
instance of that object. You have two choices: create your own instance manually, or modify an
instance that Spring Security creates for you.&lt;/p&gt;

&lt;p&gt;If you were to do it manually, you’d have to do something like:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2LoginAuthenticationFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;clientRegistrationRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;authorizedClientService&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAuthenticationResultConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyCustomConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is fine, but where do you get those parameters for the constructor? You have to figure that out
by yourself, and you’ll probably have to read a fair bit of Spring Security code to get it right. So
instead of doing that, let’s modify the instance Spring Security creates for you, with a
post-processor. First, let’s create a post-processor:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2LoginFilterPostProcessor&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ObjectPostProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;OAuth2LoginAuthenticationFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;O&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2LoginAuthenticationFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;O&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;postProcess&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;O&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAuthenticationResultConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyCustomConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That post-processor will ONLY change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OAuth2LoginAuthenticationFilter&lt;/code&gt; instances, and will not
transform anything else. To make it do its magic, you only need to register it:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;SecurityFilterChain&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;securityFilterChain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpSecurity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// ... your custom configuration ...&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;oauth2Login&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;oauth2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;oauth2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withObjectPostProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2LoginFilterPostProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By registering it inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.oauth2Login&lt;/code&gt;, you scope it to only objects in that part of the
configuration, and you’re sure that it won’t modify anything else.&lt;/p&gt;

&lt;h2 id=&quot;advanced-usage-delegation&quot;&gt;Advanced usage: delegation&lt;/h2&gt;

&lt;p&gt;There’s one more fun thing you can do with post-processors. Sometimes, the object that Spring
Security gives you does not have the extension point you need, but has a lot of logic you’d like to
reuse. So you could create a subclass, but it is sometimes impossible because some Spring Security
classes are still final, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AuthorizedClientServiceOAuth2AuthorizedClientManager&lt;/code&gt;. Another
problem with subclasses is that you would have to think about all the dependencies of those classes
and figure out how they should be set up. For example, the authentication provider used for oauth2
login is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OidcUserService&lt;/code&gt;, with a total of 5 private fields. That’s a lot to think about!&lt;/p&gt;

&lt;p&gt;Much easier is to do composition - you create a class that implements the correct interface, grab
whatever Spring Security has wired in for you, and do pre or post-processing in addition to what the
base class does. For example, with our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OidcUserService&lt;/code&gt; above:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomOidcUserService&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OidcUserService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OidcUserService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomOidcUserService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;OidcUserService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delegate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OidcUser&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loadUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;OidcUserRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2AuthenticationException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Modify the parameters passed to the delegate&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modifiedUserRequest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modifyUserRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Call the delegate that was configured by Spring Security&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;delegate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;loadUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;modifiedUserRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Modify the result from the delegate&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modifiedUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modifyUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;modifiedUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You could imagine that you do some pre-processing for rate-limiting, or some post-processing for
checking people logging in against their work schedule. To use that, you would write the following
post-processor:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OidcUserServicePostProcessor&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ObjectPostProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;OidcUserService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OidcUserService&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;postProcess&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;OidcUserService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomOidcUserService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Don’t forget to register it, and voilà, you benefit from some Spring Security goodness with your
custom behavior added on top!&lt;/p&gt;
</description>
        <pubDate>Fri, 04 Jul 2025 00:00:00 +0000</pubDate>
        <link>https://garnier.wf/blog/2025/07/04/spring-security-object-postprocessor.html</link>
        <guid isPermaLink="true">https://garnier.wf/blog/2025/07/04/spring-security-object-postprocessor.html</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Spring learning path</title>
        <description>&lt;p&gt;&lt;em&gt;(Latest revision: 2025-05-23)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I sometimes get the odd e-mail with a general “I’ve learned the basics of Spring Boot, how do I learn more?”&lt;/p&gt;

&lt;p&gt;Here are a few recommendations on your Spring learning path.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting started&lt;/h2&gt;

&lt;p&gt;For the basics, the &lt;a href=&quot;https://spring.io/guides&quot;&gt;Guides&lt;/a&gt; section on the official website is a good starting point.
You do have to use the search bar field to find specific topics, but the most common scenarios are covered.&lt;/p&gt;

&lt;p&gt;A more structured learning path is available on &lt;a href=&quot;https://spring.academy/&quot;&gt;Spring Academy&lt;/a&gt;.
There is a list of courses, each focused on achieving a specific goal, for example “secure a REST API using
OAuth2”.
Some of these courses have videos content and some are just text.
Most have interactive labs where you can test your knowledge of the previous lessons.
On the platform, you’ll find courses that cover basics of &lt;a href=&quot;https://spring.academy/courses/building-a-rest-api-with-spring-boot&quot;&gt;Boot&lt;/a&gt;, &lt;a href=&quot;https://spring.academy/courses/spring-academy-secure-rest-api-oauth2&quot;&gt;Security with OAuth2&lt;/a&gt;, &lt;a href=&quot;https://spring.academy/courses/building-a-batch-application-with-spring-batch&quot;&gt;Batch&lt;/a&gt; and &lt;a href=&quot;https://spring.academy/courses/spring-cloud-stream&quot;&gt;Cloud Stream&lt;/a&gt;.
It is free, but you do need to register.&lt;/p&gt;

&lt;h2 id=&quot;going-further&quot;&gt;Going further&lt;/h2&gt;

&lt;p&gt;If you’re into the video format, Spring developer advocates produce up-to-date, good quality content on a weekly basis.
Check out &lt;a href=&quot;https://www.youtube.com/@DanVega&quot;&gt;Dan Vega&lt;/a&gt; and &lt;a href=&quot;https://www.youtube.com/@coffeesoftware&quot;&gt;Josh Long&lt;/a&gt;.
The content they produce is a mix of new things the Spring team is working on, and established technologies that the community wants to hear about.
&lt;a href=&quot;https://www.sivalabs.in/&quot;&gt;Siva&lt;/a&gt; is also a very prolific developer advocate, who regularly touches Spring.&lt;/p&gt;

&lt;p&gt;Going back to Spring Academy, Sergi Almar, the famous organizer of the &lt;a href=&quot;https://springio.net&quot;&gt;Spring I/O&lt;/a&gt; conference, has a course on &lt;a href=&quot;https://spring.academy/courses/spring-framework-essentials&quot;&gt;Spring Framework fundamentals&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are great books on Spring-related topics.
I can personally recommend two: &lt;a href=&quot;https://www.manning.com/books/spring-in-action-sixth-edition&quot;&gt;Spring In Action by Craig Walls&lt;/a&gt; (it is a bit dated, but should get a new edition some time soon), &lt;a href=&quot;https://www.manning.com/books/spring-security-in-action-second-edition&quot;&gt;Spring Security In Action by Laurentiu Spilca&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And of course, the reference docs for all the various Spring projects. They are a trove of information, but you may need to read some things over two or three times until you grasp the more complicated concepts.
For example, here’s an “architecture” page in the &lt;a href=&quot;https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html&quot;&gt;Spring Security docs&lt;/a&gt;, the intro to the &lt;a href=&quot;https://docs.spring.io/spring-framework/reference/testing/testcontext-framework.html&quot;&gt;Spring Test Context framework&lt;/a&gt;, or details about how &lt;a href=&quot;https://docs.spring.io/spring-boot/reference/features/external-config.html&quot;&gt;Spring Boot’s externalized configuration gets picked up&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I personally recommend the &lt;a href=&quot;https://www.youtube.com/@SpringIOConference&quot;&gt;Youtube channel of the Spring I/O conference&lt;/a&gt;. Go the videos tab, click “popular”, and voilà! Hours of content by experts for intermediate-level developers.&lt;/p&gt;

&lt;p&gt;And lastly, my friend Thomas already has a list, called &lt;a href=&quot;https://github.com/ThomasVitale/awesome-spring&quot;&gt;awesome-spring&lt;/a&gt;! It has not been updated in a little while but it still has very relevant content.&lt;/p&gt;
</description>
        <pubDate>Fri, 23 May 2025 00:00:00 +0000</pubDate>
        <link>https://garnier.wf/blog/2025/05/23/spring-learning-path.html</link>
        <guid isPermaLink="true">https://garnier.wf/blog/2025/05/23/spring-learning-path.html</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Intuitions and tacit knowledge</title>
        <description>&lt;p&gt;Uh. This plant looks a bit … tired. The leaves are starting to droop, and its shade of green is
slightly paler than usual. It probably needs watering. Touch the dirt - dry. Got that right!&lt;/p&gt;

&lt;p&gt;Would I have been able to explain what the plan should look like to someone who does not usually
deal with our potted friends? Probably not. It’s an intuition, knowledge built from experience: in
this case by not watering plants enough and seeing them wilt and wither. Now I can tell the signs
earlier on, and avoid damage.&lt;/p&gt;

&lt;p&gt;Same goes for many things in life. The sound the food makes in the frying pan, an increasingly loud
crackle that makes me turn the heat down two notches. Not “uh ho, I smell burning”. Not “uh ho this
very loud, and therefore very hot, and unless attended is going to burn my food”. But instead “it is
getting louder at a certain rate, and unless attended it is going to get too hot and burn my food”.
Third-order thinking, noticing the &lt;em&gt;acceleration&lt;/em&gt; and acting accordingly. Of course this knowledge
was acquired by overcooking, under-cooking and cooking just right long enough that I can recognize
the signs. Again, hard to convey.&lt;/p&gt;

&lt;p&gt;We all do this. Lots of people can catch a ball when it was launched at a reasonable speed - we’ve
seen enough parabolic movement and trained our eye-hand coordination enough to be able to do it.
Driving is also very similar, or riding a bike. Do you remember the first time you drove a car?&lt;/p&gt;

&lt;p&gt;There’s a similar feeling I have with codebases now. I’ve been doing this Daniel-talks-to-computers
thing professionally for over 10 years, and geeking out for 20 years. I’m starting to see patterns
emerge in my code when things start &lt;em&gt;drifting&lt;/em&gt;, before we’ve painted ourselves into a corner. I
can’t fully explain, mostly convey some ~ vibes ~. Given then right environment, that may be enough
to steer the project in a better direction. I’m navigating second-to-third order thinking, sometimes
missing subtle signs. I’m not sure how I could practice this skill, other than “do a lot of
projects” :)&lt;/p&gt;

&lt;p&gt;Reminds me of this article by Cedric Chin, &lt;a href=&quot;https://commoncog.com/tacit-knowledge-is-a-real-thing/&quot;&gt;Why Tacit Knowledge is More Important Than Deliberate
Practice&lt;/a&gt;. It describes this tacit knowledge
mechanism, you can’t really teach someone to ride a bicycle by just talking to them. It also
discusses an example of tacit knowledge in software engineering, in a much better way than here.
Check it out.&lt;/p&gt;
</description>
        <pubDate>Fri, 29 Nov 2024 00:00:00 +0000</pubDate>
        <link>https://garnier.wf/blog/2024/11/29/intuitions-and-tacit-knowledge.html</link>
        <guid isPermaLink="true">https://garnier.wf/blog/2024/11/29/intuitions-and-tacit-knowledge.html</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Beyond hasRole(&quot;...&quot;) in Spring Security</title>
        <description>&lt;p&gt;Another Spring Security question today:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;My users are part of a “company”, and are allowed to see the data from the company only.
Moreover, users have roles, e.g. “user”, “admin”. Only “admin” can see the admin endpoints.
How can I implement this?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are many ways to do this. I created a &lt;a href=&quot;https://github.com/Kehrlann/spring-security-samples/tree/main/authorization&quot;&gt;sample
application&lt;/a&gt; for this, which we will go over.
But first, let’s start by describing the use-case.&lt;/p&gt;

&lt;h2 id=&quot;the-use-case&quot;&gt;The use-case&lt;/h2&gt;

&lt;p&gt;Let’s start with our users:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;username&lt;/th&gt;
      &lt;th&gt;company&lt;/th&gt;
      &lt;th&gt;roles&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alice&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alpha Corp&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bob&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alpha Corp&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;carol&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Omega Inc&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dave&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Omega Inc&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Then the rules:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Users can only view pages in their own company. For example, users of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Alpha Corp&lt;/code&gt; can access
pages under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/company/alpha&lt;/code&gt;, but not under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/company/omega&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Only users with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;admin&lt;/code&gt; role can view the admin pages, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/company/alpha/admin&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Some examples:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Alice can access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/company/alpha&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/company/alpha/admin&lt;/code&gt;, but not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/company/omega&lt;/code&gt; because she
is not part of Omega Inc.&lt;/li&gt;
  &lt;li&gt;Carol can access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/company/omega&lt;/code&gt;, but neither &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/company/omega/admin&lt;/code&gt; because she’s not admin, nor
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/company/alpha&lt;/code&gt; because she is not part of Alpha Corp.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;implementation-setting-up-the-model-and-basic-security&quot;&gt;Implementation: setting up the model and basic security&lt;/h2&gt;

&lt;p&gt;The roles fit neatly into a user’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&amp;lt;GrantedAuthority&amp;gt;&lt;/code&gt;, usually
prefixed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ROLE_&lt;/code&gt;, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ROLE_admin&lt;/code&gt;.  This is described at length in the &lt;a href=&quot;https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html#authorize-requests&quot;&gt;reference
documentation&lt;/a&gt;.
The “company” bit here is more fuzzy, as it’s not really a “role”. You could imagine an authority
e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;COMPANY_&amp;lt;companyId&amp;gt;&lt;/code&gt; but this is structured data, that maybe you wouldn’t want to represent as
a String. You &lt;em&gt;could&lt;/em&gt; make a custom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GrantedAuthority&lt;/code&gt;, which Spring Security supports. But maybe
it’s also a property of the User object that you use for business reasons, so you could also store
it in the User object.&lt;/p&gt;

&lt;p&gt;Let’s make some custom users, that reference a company. All users use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;password&lt;/code&gt; password
(secure, right?!):&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.authority.AuthorityUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.userdetails.User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.crypto.factory.PasswordEncoderFactories&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.crypto.password.PasswordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomUser&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Company&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Don&apos;t do this in prod. It&apos;s just for demos.&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PasswordEncoder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;encoder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PasswordEncoderFactories&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createDelegatingPasswordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Company&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//@formatter:off&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;encoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// Encode authorities to &quot;roles&quot;&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;AuthorityUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createAuthorityList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ROLE_&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]::&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//@formatter:on&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;company&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CustomUser&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPassword&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAuthorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;company&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCompany&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Company&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getCompany&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// The company is a simple record&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;record&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Company&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’re going to allow users to log in with username and password, so we need to expose a
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserDetailsService&lt;/code&gt; bean. Since our users are custom, we need to make a custom implementation. The
default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InMemoryUserDetailsManager&lt;/code&gt; wouldn’t do, because it returns plain Spring-Security &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt;
instances. Notice that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loadByUsername&lt;/code&gt; needs to return a copy of the in-memory user object. This is
because Spring Security deletes the password from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt; when they log in, to avoid leaking it
to your application code and increase security (this way, you won’t ever print it in your logs!).
Here’s the implementation:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.function.Function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.userdetails.UserDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.userdetails.UserDetailsService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.userdetails.UsernameNotFoundException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Collectors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toUnmodifiableMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomUserDetailsService&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDetailsService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomUserDetailsService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CustomUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;stream&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toUnmodifiableMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;CustomUser:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getUsername&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;identity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDetails&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loadUserByUsername&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UsernameNotFoundException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can then use those constructs in the typical “Security Configuration class” to allow log-in. We
create our four users, and set up security. For brevity, we omit the “Company Repository”, which can
be a JPA repo, an in-memory map, etc:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.authorization.AuthorizationDecision&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.authorization.AuthorizationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.Customizer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.annotation.web.builders.HttpSecurity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.annotation.web.configuration.EnableWebSecurity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.userdetails.UserDetailsService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.web.SecurityFilterChain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.web.access.intercept.RequestAuthorizationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;springframework&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;security&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorization&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;AuthorityAuthorizationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hasRole&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;springframework&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;security&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorization&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;AuthorizationManagers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;allOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableWebSecurity&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SecurityConfiguration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;SecurityFilterChain&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;securityFilterChain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpSecurity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorizeHttpRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requestMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;favicon.ico&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;permitAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;anyRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authenticated&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;formLogin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Customizer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withDefaults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;httpBasic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Customizer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withDefaults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logoutSuccessUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;UserDetailsService&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;userDetailsService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CompanyRepository&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//@formatter:off&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomUserDetailsManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alice&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alpha&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bob&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;alpha&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;carol&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;omega&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dave&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;repository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;omega&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;//@formatter:on&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With this, any user can make a request, either by navigating to http://localhost:8080/foo and
logging in, or with curl:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl http://localhost:8080/foo --user alice:password
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;implementation-authorization-rules&quot;&gt;Implementation: authorization rules&lt;/h2&gt;

&lt;p&gt;The first, simplest rule, is checking the roles for the admin endpoint. Filtering by roles is done
by the usual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.requestMatchers(&quot;.../admin&quot;).hasRole(&quot;admin&quot;)&lt;/code&gt;, see reference documentation
&lt;a href=&quot;https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html#match-requests&quot;&gt;Servlet &amp;gt; Authorization &amp;gt; HTTP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If we want to authorize based on the company, we can’t rely on roles or authorities. Before Spring
Security 6, we would have needed a special bean to check for access and maybe a SpEL expression like
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.access(&quot;hasRole(&apos;admin&apos;) &amp;amp;&amp;amp; @companyVerifier.isInCompany(authentication)&quot;)&lt;/code&gt;. With newer versions,
we can do this programmatically, with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AuthorizationManager&amp;lt;RequestAuthorizationContext&amp;gt;&lt;/code&gt; type.
It is a functional interface, that takes the authentication and the “authentication context” (here,
think “the request”) as parameters, and returns an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AuthorizationDecision&lt;/code&gt; which can be true or
false, see &lt;a href=&quot;https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html#remote-authorization-manager&quot;&gt;reference
docs&lt;/a&gt;.
We’d write something like:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.authorization.AuthorizationDecision&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.authorization.AuthorizationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.annotation.web.configuration.EnableWebSecurity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.web.access.intercept.RequestAuthorizationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableWebSecurity&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SecurityConfiguration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RequestAuthorizationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isInCompany&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requestAuthorizationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AuthorizationDecision&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPrincipal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomUser&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AuthorizationDecision&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;companyId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;requestAuthorizationContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getVariables&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;companyId&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AuthorizationDecision&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCompany&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;companyId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above states:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The user MUST be authenticated. It is redudant for what we want, but adds type-safety.&lt;/li&gt;
  &lt;li&gt;The user MUST be of type CustomUser. Again, redudant as is it will always be the case.&lt;/li&gt;
  &lt;li&gt;The user’s companyId MUST match that of the request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This can then be used:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.annotation.web.builders.HttpSecurity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.annotation.web.configuration.EnableWebSecurity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.web.SecurityFilterChain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableWebSecurity&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SecurityConfiguration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;SecurityFilterChain&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;securityFilterChain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpSecurity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorizeHttpRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requestMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;favicon.ico&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;permitAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requestMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/company/{companyId}/**&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isInCompany&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;anyRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denyAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{companyId}&lt;/code&gt; path variable must match what we had in our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isInCompany&lt;/code&gt;
authorization manager.&lt;/p&gt;

&lt;p&gt;But this only restricts by company, not by role. If we want to combine this check and the admin role
check, we can reuse the authorization manager above, that is designed to do one thing, and compose
it with other authorization rules. Here we’ll use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AuthorizationManagers.allOf(...)&lt;/code&gt; and combine our
authorization rule with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AuthorizationManagers.hasRole(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note that the admin rule is more specific than the company “non-admin” endpoints, so it must come
first:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.Customizer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.annotation.web.builders.HttpSecurity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.annotation.web.configuration.EnableWebSecurity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.web.SecurityFilterChain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;springframework&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;security&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorization&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;AuthorityAuthorizationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hasRole&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;springframework&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;security&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorization&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;AuthorizationManagers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;allOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableWebSecurity&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SecurityConfiguration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;SecurityFilterChain&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;securityFilterChain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpSecurity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorizeHttpRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requestMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;favicon.ico&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;permitAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;//@formatter:off&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requestMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/company/{companyId}/admin&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;allOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
                                    &lt;span class=&quot;n&quot;&gt;isInCompany&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt;
                                    &lt;span class=&quot;n&quot;&gt;hasRole&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                            &lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;//@formatter:on&lt;/span&gt;

            &lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requestMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/company/{companyId}/**&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isInCompany&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;auth&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;anyRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;denyAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;})&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;formLogin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Customizer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withDefaults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;httpBasic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Customizer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withDefaults&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;logoutSuccessUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Tadaaaa 🎉️&lt;/p&gt;

&lt;p&gt;Of course, there are many ways to go about this, and the above is just one of the possible
implementations. I like the composition of authorization rules, though.&lt;/p&gt;

&lt;p&gt;If you’d like to test the whole project, head over to the &lt;a href=&quot;https://github.com/Kehrlann/spring-security-samples/tree/main/authorization&quot;&gt;sample application&lt;/a&gt; and see for yourself!&lt;/p&gt;
</description>
        <pubDate>Mon, 04 Nov 2024 00:00:00 +0000</pubDate>
        <link>https://garnier.wf/blog/2024/11/04/spring-security-authorization.html</link>
        <guid isPermaLink="true">https://garnier.wf/blog/2024/11/04/spring-security-authorization.html</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Custom Authorization header in Reactive Spring Security OAuth2</title>
        <description>&lt;p&gt;Someone asked me a Spring Security question today, along the lines of:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;When using Spring Security OAuth2, in reactive apps, we use the &lt;a href=&quot;https://docs.spring.io/spring-security/reference/reactive/oauth2/client/authorized-clients.html#oauth2Client-webclient-webflux&quot;&gt;WebClient
integration&lt;/a&gt;
Is it possible to change the header that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServerOAuth2AuthorizedClientExchangeFilterFunction&lt;/code&gt;
uses? By default it uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization: Bearer &amp;lt;access_token&amp;gt;&lt;/code&gt;, but we’d like to drop the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Bearer&lt;/code&gt; part for **reasons**, and send &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization: &amp;lt;access_token&amp;gt;&lt;/code&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is no, you can’t customized the header used in
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ServerOAuth2AuthorizedClientExchangeFilterFunction&lt;/code&gt; (what a mouthful, let’s call it oauth-EFF),
because &lt;a href=&quot;https://github.com/spring-projects/spring-security/blob/7ba8986506daca7df716b7fed1ff23aee1cb1b92/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java#L422&quot;&gt;it is
hardcoded&lt;/a&gt;,
to follow RFC6750, specifically &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6750#section-2.1&quot;&gt;2.1.  Authorization Request Header
Field&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My first recommendation would be to update the resource servers to be spec compliant, and access the
specified header format, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization: Bearer &amp;lt;access_token&amp;gt;&lt;/code&gt;. If you have legacy clients that
cannot be updated, you could imagine a world where your resource server supports the OAuth2 spec,
and your legacy non-Bearer tokens.&lt;/p&gt;

&lt;p&gt;If &lt;em&gt;that&lt;/em&gt; is not possible, fret not, there is a way around it: you can make your own
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ExchangeFilterFunction&lt;/code&gt;, that “updates” the value of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Authorization&lt;/code&gt; header:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;reactor.core.publisher.Mono&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.reactive.function.client.ClientRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.reactive.function.client.ClientResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.reactive.function.client.ExchangeFilterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.reactive.function.client.ExchangeFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomAuthorizationExchangeFilterFunction&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ExchangeFilterFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ClientResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ClientRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ExchangeFunction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Mono&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;just&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// Grab the original authorization header created by Spring Security&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;originalHeader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getFirst&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// Extract the token from the header&lt;/span&gt;
      &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;originalHeader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toLowerCase&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;bearer &quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClientRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Remove the existing header entirely ; because calling &quot;.header&quot; would add an additional&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// header value, rather than replace the existing value&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Set the Authorization header to the desired value&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;next:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exchange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then you can use it in addition to the oauth-EFF, &lt;em&gt;after&lt;/em&gt; the oauth-EFF is applied. This way, all
the token management is handled by the oauth-EFF, and you just sprinkle some magic over the headers:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;WebClient&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;customHeaderWebClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ReactiveOAuth2AuthorizedClientManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authorizedClientManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nc&quot;&gt;ServerOAuth2AuthorizedClientExchangeFilterFunction&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oauth2ClientFilterFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 
      &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ServerOAuth2AuthorizedClientExchangeFilterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authorizedClientManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WebClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;oauth2ClientFilterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomAuthorizationExchangeFilterFunction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Tadaaaa 🎉️&lt;/p&gt;
</description>
        <pubDate>Wed, 30 Oct 2024 00:00:00 +0000</pubDate>
        <link>https://garnier.wf/blog/2024/10/30/oauth2-custom-authorization-header.html</link>
        <guid isPermaLink="true">https://garnier.wf/blog/2024/10/30/oauth2-custom-authorization-header.html</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Thoughts on using JetBrains fleet</title>
        <description>&lt;p&gt;I’ve been playing with Jetbrains’ &lt;a href=&quot;https://www.jetbrains.com/fleet/&quot;&gt;Fleet editor&lt;/a&gt; in the past
couple of weeks, and I’ve been pleasantly surprised!&lt;/p&gt;

&lt;p&gt;I’ve had to write some JS for WebAuthN / passkey support in Spring Security, and so I needed an
editor for that. My daily driver is IntelliJ for Java (and Go when I have to), and, while I know I
can do my JS in there (or in Webstorm), I thought it was time to try something else. My daily driver
for text-based editing is Neovim&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, and I tend to fall back to Visual Studio Code when I have to
do multi-file editing or code-related stuff that’s not Java, such as Python.&lt;/p&gt;

&lt;p&gt;I had tried Fleet a bit when it came out two-three years ago, but it didn’t click for me. It may be
a bit silly, but I missed the smooth caret movement from VS Code 😅️ I had a general feeling that VS
Code was, I don’t know, snappier? But it’s been quite a while, so time to try it out again!&lt;/p&gt;

&lt;h2 id=&quot;the-good&quot;&gt;The good&lt;/h2&gt;

&lt;p&gt;Remember, I live, I breathe IntelliJ. I opened the editor, selected the “IntelliJ” key bindings…
And boom, it was just like home. It feels very Jetbrains-y in its layout, the way it thinks about
different panes and how to access them - no need for a new mental model, that’s a plus.&lt;/p&gt;

&lt;p&gt;Most importantly, it &lt;em&gt;Just Works™&lt;/em&gt;. It picks up the JavaScript file, shows you syntax errors, helps
with autocomplete (as much as possible - it’s JavaScript after all) and generally does what you
would expect it to do.&lt;/p&gt;

&lt;p&gt;There’s this “smart mode” that allows you to do programming-related tasks, beyond text editing, and
it’s really, really smart. For example, I’m writing my tests using Mocha, complete with their
lingo &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;describe(...)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it(...)&lt;/code&gt;, etc. I press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl Shift R&lt;/code&gt;, and it runs my tests. No
configuration required, no settings to enable stuff, no plugin, it just works.&lt;/p&gt;

&lt;p&gt;The formatting uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prettier&lt;/code&gt; and gives you predictable, standard JS formatting.&lt;/p&gt;

&lt;p&gt;The general editor is discoverable: open the command palette, type familiar words you know from
IntelliJ, and you’ll find the commands you’re looking for. The settings has a simple search bar that
allows you to find stuff, and since there are not too many options, you don’t spend hours pondering
how to make it do what you want.&lt;/p&gt;

&lt;p&gt;I’d say the general “Out of the Box” experience is much nicer than VS Code, I don’t have to deal
with plugins, wonder why there 200 of them that seem relevant, configure a linter when I try to
format, etc. Great first contact!&lt;/p&gt;

&lt;h2 id=&quot;the-not-so-good&quot;&gt;The not-so-good&lt;/h2&gt;

&lt;p&gt;Nothing is either “bad” or “ugly” in Fleet, no deal-breakers. Mind you, it’s not perfect and has
its share of “not-so-good” items.&lt;/p&gt;

&lt;p&gt;While it’s discoverable, it’s only to a point. Some features are missing, e.g. “go to next splitter”
when you use multiple editing “splits”, and it’s not 100% clear at first whether it’s not there or
you are doing it wrong. I guess not everyone comes from IntelliJ, so the experience is not “IntelliJ
but different” and you do have to put in a little bit of work to understand all the things you can
do.&lt;/p&gt;

&lt;p&gt;Running a test was easy, but making a “configuration” to run all my tests was kind of shooting in
the dark - the docs tell you about &lt;a href=&quot;https://www.jetbrains.com/help/fleet/javascript-run-configs.html&quot;&gt;JavaScript run
configurations&lt;/a&gt;, but only list
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nodeAttach&lt;/code&gt;. Lo and behold, there’s an undocumented, un-discoverable
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mocha&lt;/code&gt; config type that … I made up? Found on Stack Overflow? Can’t remember. For the record, it
looks like so:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;configurations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;mocha&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Run all tests&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;file&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;test/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In general, the documentation is quite lackluster. For example,
&lt;a href=&quot;https://www.jetbrains.com/help/fleet/getting-started-with-javascript.html#prettier&quot;&gt;Prettier&lt;/a&gt; is
used for code formatting. You can configure prettier in many ways (e.g. with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.prettierrc&lt;/code&gt;,
a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prettier&lt;/code&gt; key in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt;, …), but only some configurations are supported by Fleet, and
that’s for you to discover.&lt;/p&gt;

&lt;p&gt;Some stuff feels less polished too - the directory I work in happens to have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build.gradle&lt;/code&gt; file
to orchestrate the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm&lt;/code&gt; build as part of the bigger project. But that is not what I use during my
JS development. Smart mode has decided that it’s a Gradle project and wants to import it, but it
lacks the rest of the project config, the wrapper, etc. And Fleet won’t let me exclude this
unrelated java artifact from my project (or at least I haven’t found how). So now I’m stuck with
this “can’t import Gradle project” error that pops up once in a while and that I can’t fix.&lt;/p&gt;

&lt;p&gt;It also had this weird behavior, every time I used the “Go To” command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cmd O&lt;/code&gt; to open a file, it
opens a new tab with this file. So when I navigated with this, I ended up with the same
tab open ten times… But that seemed fixed this week, so at least things are moving fast!&lt;/p&gt;

&lt;h2 id=&quot;verdict&quot;&gt;Verdict?&lt;/h2&gt;

&lt;p&gt;I like it. I think for basic stuff, I like it more than VS Code and its plugin hell. For complex
project, I’ll probably switch to a full-fledged editor, Webstorm or Pycharm, but for my small,
simple project, it’s been quite nice.&lt;/p&gt;

&lt;p&gt;Now, the elephant in the room - could it displace VS Code? I don’t think so, Visual Studio is
already too entrenched, you can customize it any way you like, it’s used everywhere, even in your
browser… So it’s probably a tough hill to climb for the new-ish kid on the block. But Fleet could
be the base for Jetbrains’ next generation of IDEs, so I’m not too worried about its future.&lt;/p&gt;

&lt;p&gt;My thoughts? It’s been fun to use, so give it a spin!&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;I’m writing this blog post using &lt;a href=&quot;https://neovim.io/&quot;&gt;Neovim&lt;/a&gt;! It like vim, but, I don’t know,
slightly more modern. I have to confess I don’t know vim enough to confidently tell them apart
🤫️ If you want to learn vim, the best recommendation I have is the &lt;a href=&quot;https://pragprog.com/titles/dnvim2/practical-vim-second-edition/&quot;&gt;Practical
vim&lt;/a&gt; book. The book doesn’t
try to explain all of vim, but instead presents a cookbook of things you can do, how to do it
“the vim way”, and progressively exposes you to the mindset you need to use vim. One of the best
tech books I’ve ever bought. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Fri, 12 Jul 2024 00:00:00 +0000</pubDate>
        <link>https://garnier.wf/blog/2024/07/12/jetbrains-fleet.html</link>
        <guid isPermaLink="true">https://garnier.wf/blog/2024/07/12/jetbrains-fleet.html</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Teaching: start with the big picture</title>
        <description>&lt;p&gt;In software engineering, like in most human endeavors, it’s all about the details. The philosophical
argument crumbles when a particular detail of the premise is wrong. The mayonnaise won’t form if the
ingredients are not mixed at room temperature. The server won’t accept connections if the listen
address does not match the address of the incoming request.&lt;/p&gt;

&lt;p&gt;Devil is in the details - but before chasing the devil, one must first be sure that they actually go
anywhere at all.&lt;/p&gt;

&lt;h2 id=&quot;boxes-too-big-and-arrows-unlabelled&quot;&gt;Boxes (too big) and arrows (unlabelled)&lt;/h2&gt;

&lt;p&gt;When introducing new concepts to someone, one must first give them a bird’s eye view of the context,
the landscape around the problem, if you will. I’ve been explaining &lt;a href=&quot;https://oauth.net/2/&quot;&gt;OAuth2&lt;/a&gt; a
lot lately, and it’s a perfect illustration of starting very general and then zooming in, into
different aspects.&lt;/p&gt;

&lt;p&gt;The original &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6749&quot;&gt;RFC6749&lt;/a&gt;, published in 2012 (!!) is 76
pages long. And it’s only one out of ~too many~ &lt;a href=&quot;https://oauth.net/specs/&quot;&gt;quite a few&lt;/a&gt;. To explain
this simply, I have to start very large. Pick the most common flow (out of 3, up to 7 depending
on how you count), explain what we’re trying to accomplish (&lt;strong&gt;Daniel&lt;/strong&gt; grants access to
&lt;strong&gt;photos.example.com&lt;/strong&gt; to his &lt;strong&gt;Google Photos album&lt;/strong&gt; without ever sharing his Google credentials),
who the different parties are in the protocol, and how this is generally achieved. And that’s it.
Don’t explain the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;authorization_code&lt;/code&gt;, don’t mention &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;client_credentials&lt;/code&gt;, don’t go into the
details of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scope&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;Of course, this first explanation is awfully wrong: it leaves many things out, and it is full of
blissfully incorrect statements (e.g. &lt;strong&gt;Daniel&lt;/strong&gt; gets a token from &lt;strong&gt;Google&lt;/strong&gt; and shares the token
with &lt;strong&gt;photos.example.com&lt;/strong&gt;). But the point here is not to be complete, or even correct. It’s to
draw the first boxes and arrows connecting them, even without a proper legend or labels.&lt;/p&gt;

&lt;p&gt;I’m conveying the &lt;em&gt;vibes&lt;/em&gt;, the &lt;em&gt;feels&lt;/em&gt; of this whole OAuth2 thing.&lt;/p&gt;

&lt;h2 id=&quot;higher-resolution&quot;&gt;Higher resolution&lt;/h2&gt;

&lt;p&gt;And then I can slowly increase the resolution of the initial picture. Add details on how things
actually work. Challenge the initial understanding the learners are trying to get familiar with (I
told you that Daniel gets a token from Google, but think about it: when you log in to a third party
website using Google, do you start by going to Google?)&lt;/p&gt;

&lt;p&gt;But this must happen with a purpose, one touch at a time. I never go too deep, or try to give all
the context. It’s always tailored to the audience, what they are trying to achieve, or what they
need to learn to broaden their perspectives.&lt;/p&gt;

&lt;p&gt;And then, the learner needs to zoom in by themselves, finding the rough edges, building their
own experience-based context before coming back with questions.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;

&lt;p&gt;Soon, the initial boxes-and-arrows diagram will be relegated to the bottom of the knowledge pile,
where it will be forgotten. It was just a trick, a stepping stone, nothing more.&lt;/p&gt;
</description>
        <pubDate>Wed, 03 Jul 2024 00:00:00 +0000</pubDate>
        <link>https://garnier.wf/blog/2024/07/03/the-big-picture.html</link>
        <guid isPermaLink="true">https://garnier.wf/blog/2024/07/03/the-big-picture.html</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>The key to &amp;lt;xyz&amp;gt; is ...</title>
        <description>&lt;p&gt;I was about to toot/tweet/post something along the lines “the key to good technical content is …”.
But I was not online at the time, so I had to sit on a thought for a while. And I realized - who am
I to blurt out such universal statements?&lt;/p&gt;

&lt;p&gt;First, there is no “one” key to good technical content. I love a lot of tech stuff - blogs, guides,
docs, conferences, videos. All of these have different “keys”, and even among the same category,
different producers have varying styles and specialties. I’ve been rewatching &lt;a href=&quot;https://www.youtube.com/@SpringIOConference&quot;&gt;Spring IO 2024
talks&lt;/a&gt;, and most of them are &lt;em&gt;great&lt;/em&gt;. High quality
content from wildly different speakers.&lt;/p&gt;

&lt;p&gt;Second, I’m speaking from my own subjectif point of view. What works for me doesn’t necessarily work
for everyone. I try to be attuned to how people think when possible, but that’s not always the case.&lt;/p&gt;

&lt;p&gt;So what am I saying, when I’m starting to type out “the key to &lt;xyz&gt; is...&quot;&lt;/xyz&gt;&lt;/p&gt;

&lt;h3 id=&quot;what-works-for-me--for-people-i-chat-with-is-&quot;&gt;“What works (for me / for people I chat with) is …”&lt;/h3&gt;

&lt;p&gt;I’m talking about what I like, and what other people tell me they like! Usually after I produce some
content, and people come to me and give me positive feedback, I reflect and try to extract what
worked out.&lt;/p&gt;

&lt;p&gt;But it works out usually means:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;It resonates with people - that’s good, there’s something you can take out of this&lt;/li&gt;
  &lt;li&gt;It fits my style, I’m comfortable executing it, I’m having fun, etc - that’s too personal to be
useful to other peeps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In no way is it universal. It was the right thing, for the right audience, that I’m comfortable
with. Not “the key to”.&lt;/p&gt;

&lt;h3 id=&quot;what-does-not-work-for-me-mostly-me-not-other-people-is-&quot;&gt;“What does NOT work for me (mostly me, not other people) is …”&lt;/h3&gt;

&lt;p&gt;The “what works” statement may actually be the negative image of what I observe and dislike. It’s a
polite way of “do NOT do X, do Y instead”. I’m proposiing an alternative, which is somewhat
constructive, but it’d probably be better to spell out the actual issue, why it does not work, and
then only come to what you &lt;em&gt;could&lt;/em&gt; do instead.&lt;/p&gt;

&lt;p&gt;-&lt;/p&gt;

&lt;p&gt;Anyways. Take stuff with a grain of salt. Even when I have conviction in my statements, at its core
it probably starts with &lt;em&gt;It Depends™&lt;/em&gt;, but I have not taken the time to think about it carefully
enough.&lt;/p&gt;
</description>
        <pubDate>Fri, 14 Jun 2024 00:00:00 +0000</pubDate>
        <link>https://garnier.wf/blog/2024/06/14/the-key-to-xyz.html</link>
        <guid isPermaLink="true">https://garnier.wf/blog/2024/06/14/the-key-to-xyz.html</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>OAuth2 and OpenID token expiration</title>
        <description>&lt;p&gt;A somewhat frequent question in the OAuth2-and-or-OpenID field is “how long should my access/refresh
tokens last?”, or, in other word, what should I set as an expiry time? The question often contains
the dreaded terms, &lt;em&gt;Best Practice™&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It’s an excellent security question. Like all excellent questions, the answer is (spoiler alert) &lt;em&gt;It
Depends™&lt;/em&gt;. After all, if there was a unique, simple &lt;em&gt;Certified One True Best Practice™&lt;/em&gt; answer, we
wouldn’t be asking the question, would we?&lt;/p&gt;

&lt;h2 id=&quot;it-depends&quot;&gt;It depends&lt;/h2&gt;

&lt;p&gt;In order to answer the question, we should start by assessing the security implications, think about
our threat modelling and our overall security posture. A few points we could think about:&lt;/p&gt;

&lt;h3 id=&quot;are-refresh_tokens-treated-differently-than-access_tokens&quot;&gt;Are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;refresh_token&lt;/code&gt;s treated differently than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;s?&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;access_token&lt;/code&gt;s are considered not-super-duper-safe&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, and recommendation are usually to keep them
with a low-ish expiry. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;refresh_token&lt;/code&gt;s, on the other hand, have a longer expiry, and they are
considered “sensitive” credentials, that MUST be stored “confidentially”:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Refresh tokens MUST be kept confidential in transit and storage, and
shared only among the authorization server and the client to whom the
refresh tokens were issued.  The authorization server MUST maintain
the binding between a refresh token and the client to whom it was
issued.  Refresh tokens MUST only be transmitted using TLS as
described in Section 1.6 with server authentication as defined by
[RFC2818].&lt;/p&gt;

  &lt;p&gt;RFC6749 - &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc6749.html#section-10.4&quot;&gt;Security Consideration &amp;gt; Refresh Tokens&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are no details about what “confidential storage” mean exactly, but it’s easy to imagine that
it means, at least, “dont’ store them like you store your access tokens”. So if we store them
together, with the same security protection, that’s one less reason to have different expiration
times - or even have refresh tokens entirely.&lt;/p&gt;

&lt;h3 id=&quot;are-there-specific-security-concerns-around-the-access-tokens&quot;&gt;Are there specific security concerns around the access tokens?&lt;/h3&gt;

&lt;p&gt;Maybe we want to have “soft revocation”, with short lived access tokens. That happens if we don’t
support true revocation, but we want to make sure that they can be somehow rejected by consumers if
they’re not “valid”. The definition of valid could be “was issued not too long ago, so less likely
to have been leaked and then re-used”.&lt;/p&gt;

&lt;p&gt;Or maybe the access tokens are visible in a user’s browser for some reason, and that opens up a few
attack vectors. Or they are sent to low-trust resource servers that could leak the tokens.&lt;/p&gt;

&lt;p&gt;In that case we want to harden our posture and trust those credentials as little as possible, set a
a time-to-live in the low minutes. We consider that when there’s a problem with a token, at least it
cannot be used for a long time. It’s obviously not sufficient for security-critical apps, or
sensitive data, but it might be enough for less cirtical use-cases.&lt;/p&gt;

&lt;h3 id=&quot;how-often-does-the-user-data-change&quot;&gt;How often does the user data change?&lt;/h3&gt;

&lt;p&gt;When use a JWT token, it bears all the authorization data, and so we are probably not doing token
introspection on every request. If we want our tokens to be an accurate representation of our user
data, we want either 1) the data to not change frequently or 2) our tokens to be short lived, so
incorrect information does not linger for too long.&lt;/p&gt;

&lt;p&gt;This is the case for the access tokens, which contain the user information such as the subject
identifier (aka &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sub&lt;/code&gt; claim) or scopes. But, depending on your authserver implementation, it might
also be true for refresh tokens. In some cases, the authserver does not re-check the user data when
exchanging a refresh token for an access token, but use a cached version of the access token that
was initially accessed. In that case, refresh tokens should be short-lived too, to ensure a new
“token creation context” is created for that user.&lt;/p&gt;

&lt;h3 id=&quot;what-about-offline-access&quot;&gt;What about offline access?&lt;/h3&gt;

&lt;p&gt;Does the app need offline access to the Resource Server, when the user is not interacting with the
app, e.g. to sync stuff periodically in the background? Otherwise, if we expect the user to be
present, how long is a user session, and how long can they be inactive for? Do we expect the user to
use the app absolutely transparently after a few days of inactivity (e.g. no blinking screen for
re-authorization)?&lt;/p&gt;

&lt;p&gt;For non-offline access, a short-ish refresh token, from 1 to 12 hours, can often be enough. This
means re-logging in after the week-end, but, hey, Mondays are for checking e-mail, coffee breaks and
SSO login anyways 🙃️&lt;/p&gt;

&lt;h3 id=&quot;what-happens-when-the-token-expires&quot;&gt;What happens when the token expires?&lt;/h3&gt;

&lt;p&gt;Is token expiry a problem for our users? Or is our SSO system set up in a way that obtaining a new
token is transparent as long as you’re logged in to the auth server?&lt;/p&gt;

&lt;p&gt;If the inconvenience is very minor, then it might be cheap to err on the side of short expiry and
frequent token re-issuing, making it harder to use old tokens that may have leaked.&lt;/p&gt;

&lt;h3 id=&quot;whats-the-worst-case-scenario-when-a-token-gets-leaked&quot;&gt;What’s the worst case scenario, when a token gets leaked?&lt;/h3&gt;

&lt;p&gt;What if an access token gets leaked? What about refresh tokens? What’s at risk? What’s the blast
radius? Do we have proper mitigations to make sure that systems are well isolated and that a token
stolen from system A can’t be used with system B (e.g. audience checks, certificate-bound tokens,
…)&lt;/p&gt;

&lt;p&gt;The higher the risk, the shorter the expiry.&lt;/p&gt;

&lt;h3 id=&quot;whats-the-trust-level-our-app-operate-in&quot;&gt;What’s the trust level our app operate in?&lt;/h3&gt;

&lt;p&gt;Who can request credentials from the authserver, and how are these credentials secured? Is it an
internal, well controlled corporate environment, or the Scary Open Internet™? And what about the
resource servers, are they trusted applications, or can anyone spin up an app and start collecting
access tokens, with no guarantees and almost no auditability?&lt;/p&gt;

&lt;p&gt;In a high-trust environment, we can afford longer-lived access tokens - we could even imagine doing
without refresh tokens, depending on our constraints. In a low trust environment, access tokens are
an environmental hazard that we need to dispose of quickly.&lt;/p&gt;

&lt;h3 id=&quot;are-there-latency-bandwitdh-availability-or-load-issues&quot;&gt;Are there latency, bandwitdh, availability or load issues?&lt;/h3&gt;

&lt;p&gt;How much do we care about hitting the auth-server frequently? Should we be conservative and make
sure that we are fault tolerant when the auth-server goes away for some time? How conservative do we
need to be with our auth server CPU and all those computationally expensive
crypto-signing-shenanigans? How many apps are hitting our auth server with requests in parallel?
What about peak times? (oh hey, good to see you again, Monday morning 👋️)&lt;/p&gt;

&lt;p&gt;Short expiry means more traffic, sometimes two orders of magnitude more traffic. The operational
burden for the team running the authserver can be a factor, toppling your central identity provider
is never good news.&lt;/p&gt;

&lt;h2 id=&quot;tldr-tradeoffs-tradeoffs-everywhere&quot;&gt;tl;dr: Tradeoffs! Tradeoffs everywhere!&lt;/h2&gt;

&lt;p&gt;Back to our main point: it’s all tradeoffs.&lt;/p&gt;

&lt;p&gt;Shorter-lived tokens are better for security, especially the access tokens who get passed around.
Refresh tokens have a higher impact, and the expiration must match the security posture, who they
are issued to, how they are stored, etc. Longer-lived tokens make for lower resource usage, and
generally for better end-user experience, with less redirects, flashing blank screens and
password-typing.&lt;/p&gt;

&lt;p&gt;As with in all things related to security, before making decisions, we MUST&lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; think carefully
about the environment we operate in, possible threats and impacts. We also need to consider what we
gain by relaxing security constraints, and how much those gains are worth in terms of risk.&lt;/p&gt;

&lt;p&gt;&lt;br /&gt;
&lt;br /&gt;&lt;/p&gt;

&lt;hr /&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Non-official technical term. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Haha Daniel you RFC nerd. &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Tue, 11 Jun 2024 00:00:00 +0000</pubDate>
        <link>https://garnier.wf/blog/2024/06/11/oauth2-token-expiry.html</link>
        <guid isPermaLink="true">https://garnier.wf/blog/2024/06/11/oauth2-token-expiry.html</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>Passkeys: first impressions on WebAuthN</title>
        <description>&lt;p&gt;Passkeys have been trending in the past few years. The term was first introduced by Apple in their
2022 WWDC conference. Then we’ve heard of FIDO. In 2023, more and more passkeys related-talks started
popping up during various conferences. And, in early 2024, the big SaaS and cloud vendors have been
nudging users to use passkeys to login - Okta, Google, Github, AWS…&lt;/p&gt;

&lt;p&gt;I gave my own take on a passkey talk in March 2024 &lt;a href=&quot;https://www.youtube.com/watch?v=FUWLYC1z1LU&quot;&gt;at Voxxed
Zürich&lt;/a&gt;, and then in April 2024 with Josh Long at
&lt;a href=&quot;https://www.youtube.com/watch?v=RAuXohxXbAQ&quot;&gt;Devoxx France&lt;/a&gt;. I wanted to do something a bit
different than what I had seen previously: focus on the actual implementation in a real app, rather
than going through slides and explaining all the details.&lt;/p&gt;

&lt;p&gt;On top of building a demo app, I’ve been collaborating with Rob Winch on bringing Passkey support to
Spring Security&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;And, friends, after the blood, sweat and tears of integrating passkeys, I have an Opinion™.&lt;/p&gt;

&lt;h2 id=&quot;user-adoption-is-still-very-low&quot;&gt;User adoption is still very low&lt;/h2&gt;

&lt;p&gt;Even at tech conferences, where the crowd is tech-curious and generally in the “early adopter”-ish
cohort, attendees did not really know what to expect about passkeys. At Devoxx France, out of
several hundred folks in the room, less than 50% had even heard of passkeys, and only 2 people
actually used passkeys.&lt;/p&gt;

&lt;p&gt;While there is a push from vendors, we are still a long way from the Peak of Inflated Expectations.
Some awareness, but a lot of inertia. Doing a demo using a security dongle (e.g. Yubikey) does not
seem super exciting to developers. However, showing newer stuff like logging in on your Mac with
your fingerprint reader, or using a nearby device, garners a lot more attention.&lt;/p&gt;

&lt;p&gt;The flow for logging in with a nearby device goes like so:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;On their computer, the user navigates to the target website and clicks “log in with a passkey”&lt;/li&gt;
  &lt;li&gt;A modal dialog shows up, and the user selects “log in with a QR code / using a nearby device”&lt;/li&gt;
  &lt;li&gt;Using their phone, the user scans the QR code, and log in using their phone authenticator (e.g.
FaceID on an iPhone)&lt;/li&gt;
  &lt;li&gt;Wait for a few seconds …&lt;/li&gt;
  &lt;li&gt;User is logged in on their computer, without having typed any password.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This very portable authentication method has quite the “wow effect”. I do think that, over the long
term, this technology will catch on, because it is &lt;em&gt;convenient&lt;/em&gt;. The added security is just a nice
added bonus.&lt;/p&gt;

&lt;h2 id=&quot;the-consumer-vs-entreprise-tension&quot;&gt;The “consumer vs entreprise” tension&lt;/h2&gt;

&lt;p&gt;There seems to be some tension in the use-cases for passkeys. Members of the FIDO alliance, such as
Apple, want their &lt;strong&gt;F&lt;/strong&gt;ast &lt;strong&gt;ID&lt;/strong&gt;entity &lt;strong&gt;O&lt;/strong&gt;nline for consumers: widespread adoption, ease of use.
Vendors of security solutions, such as Yubikey or entreprise-grade password managers, want extreme
extensibility with many security checks built-in.&lt;/p&gt;

&lt;p&gt;Those use-cases are at odds: you can’t have something simple that is also infinitely extensible. And
so the developer API for integrating passkeys is unspeakably complicated.&lt;/p&gt;

&lt;h2 id=&quot;the-webauthn-api-is--️&quot;&gt;The WebAuthN API is … 😱️&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.w3.org/TR/webauthn-3/&quot;&gt;WebAuthN spec&lt;/a&gt; specifies how to access authenticator-bound
credentials (“publick key credentials”) from Javascript code, and how to validate the signatures an
authenticator produces. Simply put, it’s the bit of JS code that opens up the “sign in with a
passkey” window.&lt;/p&gt;

&lt;p&gt;The spec is extremely complicated. The current “stable” recommendation is from 2021 ; and the
current “live” draft, v3, is from 2023. It’s not fully stable. It is not trivial to understand what
the browsers actually implement vs what’s in the spec.&lt;/p&gt;

&lt;p&gt;On top of this the API is … well … a W3C API. Arcane and complicated to implement. And doing
the server-side crypto is absolute bonkers - decoding some binary reprensentation of your payload
from &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7049&quot;&gt;CBOR&lt;/a&gt; encoded data, extracting a complicated
&lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc8152&quot;&gt;COSE&lt;/a&gt; key (COSE stands for CBOR Object Signing and
Encryption, here’s some more CBOR for you 🤯️), and then validating data scattered across multiple
properties, and storing all of it (“just in case”).&lt;/p&gt;

&lt;p&gt;Fortunately some brave souls have implemented libs for your to consume the data, such as
&lt;a href=&quot;https://github.com/webauthn4j/webauthn4j&quot;&gt;webauthn4j&lt;/a&gt;. That lib powers nothing less than Keycloak,
and it is maintained by … &lt;a href=&quot;https://github.com/webauthn4j/webauthn4j/graphs/contributors&quot;&gt;a single
contributor&lt;/a&gt; (it had 3 commits from
external contributors since jan 2023). The Yubico Java implementation is &lt;a href=&quot;https://github.com/Yubico/java-webauthn-server/graphs/contributors&quot;&gt;not much
better&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is worrying for the state of the ecosystem.&lt;/p&gt;

&lt;h2 id=&quot;where-do-we-go-from-here&quot;&gt;Where do we go from here?&lt;/h2&gt;

&lt;p&gt;I’m not sure. For WebAuthN to catch up, and for passkeys to be more than just a “big SaaS” player
thing, we need a lot of stubborn folks implementing it, and pushing the needs forward…&lt;/p&gt;

&lt;p&gt;The Spring Security team is working hard on releasing something usable and customizable. It will
require some work to move beyond the “default experience” we provide, but by providing Passkey
support, you’ll help move Passkey support forward.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Rob did all the hard work. I’ve only provided feedback, along with the occasional design and
API discussion. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Wed, 22 May 2024 00:00:00 +0000</pubDate>
        <link>https://garnier.wf/blog/2024/05/22/passkeys.html</link>
        <guid isPermaLink="true">https://garnier.wf/blog/2024/05/22/passkeys.html</guid>
        
        
        <category>blog</category>
        
      </item>
    
  </channel>
</rss>
