What you would write without spring-addons

The honest objection to a third-party starter in the security layer is that it hides what it does. This page is the answer: for every concern these starters auto-configure, it names the bean and the hand-written Spring Security code it stands for.

It is worth reading even if the conclusion is not to take the dependency. Everything below is ordinary Spring Security, and knowing which bean owns which decision is what makes the framework configurable in the first place.

Two properties make this a menu rather than a package deal. Every bean listed here is @ConditionalOnMissingBean, so defining one of them in the application takes that single decision back and leaves the rest auto-configured. And the auto-configured filter chains have the lowest precedence, so an application can always add a chain of its own with a stricter security matcher in front of them.

  1. Resource servers
  2. Clients with oauth2Login, and BFFs
    1. The refresh token stampede
  3. REST clients
  4. Tests
  5. Taking a decision back

Resource servers

Auto-configured by SpringAddonsOidcResourceServerBeans and SpringAddonsOidcBeans.

Concern The auto-configured bean What it replaces
The chain itself springAddonsJwtResourceServerSecurityFilterChain A SecurityFilterChain wiring sessionManagement to stateless, csrf disabled, cors, authorizeHttpRequests, exceptionHandling and oauth2ResourceServer, and keeping the four consistent with each other.
Authorities mapping authoritiesConverter (a ClaimSetAuthoritiesConverter) A JwtGrantedAuthoritiesConverter subclass, or a Converter<Jwt, Collection<GrantedAuthority>>, reading the claim the provider actually uses. Keycloak nests roles under realm_access.roles and resource_access.*.roles, Auth0 and Cognito use other claims, so this code is rewritten for every provider.
Building the Authentication jwtAuthenticationConverter (a JwtAbstractAuthenticationTokenConverter, backing off for any Converter<Jwt, ? extends AbstractAuthenticationToken> bean whatever its name) A JwtAuthenticationConverter holding the converter above and setting the principal name from the right claim. Exposing it as a bean rather than a lambda inside the chain is also what makes @WithJwt able to run it in tests.
Several issuers authenticationManagerResolver and springAddonsJwtDecoderFactory A JwtIssuerAuthenticationManagerResolver with one JwtDecoder, one validator set and one authentication converter per issuer, plus the code which decides which is which.
Token introspection springAddonsIntrospectingResourceServerSecurityFilterChain A second chain for opaque tokens, with an OpaqueTokenIntrospector and an OpaqueTokenAuthenticationConverter. In the samples this is a Spring profile, not a code change.
CORS corsFilter A CorsConfigurationSource bean plus the cors configuration on the chain, and the decision about preflight requests reaching the chain anonymously.
Access control authorizePostProcessor The authorizeHttpRequests block. With spring-addons only the anonymous routes are listed in properties, the rest stays on @PreAuthorize next to the code it protects.
Escape hatch httpPostProcessor Nothing: this one exists so that an application can reach the HttpSecurity builder without redefining the whole chain.

The resource-server sample states the same thing in one paragraph: accepting Keycloak tokens with roles from realm_access.roles, preferred_username as name, one public route, CORS for a single-page application and 401 instead of a login redirect is a SecurityFilterChain with sessionManagement, csrf, cors and a CorsConfigurationSource, authorizeHttpRequests, exceptionHandling, and oauth2ResourceServer configured with a JwtAuthenticationConverter holding a custom JwtGrantedAuthoritiesConverter. Add a second issuer and a JwtIssuerAuthenticationManagerResolver joins the list.

Clients with oauth2Login, and BFFs

Auto-configured by SpringAddonsOidcClientWithLoginBeans and SpringAddonsOAuth2AuthorizedClientBeans. This is where the gap is widest, because a browser client consumed by a JavaScript frontend needs several behaviours Spring Security does not default to.

Concern The auto-configured bean What it replaces
The chain itself springAddonsClientFilterChain A SecurityFilterChain with oauth2Login, sessions, CSRF and a logout configuration.
Authorization code with PKCE oAuth2AuthorizationRequestResolver An OAuth2AuthorizationRequestResolver forcing PKCE, remembering where the frontend wants to land after login, and adding the extra parameters a provider requires (the Auth0 audience, the Keycloak kc_idp_hint).
Logout logoutRequestUriBuilder and logoutSuccessHandler A LogoutSuccessHandler building the RP-Initiated Logout URI, and, for Auth0 and Amazon Cognito, the code which papers over the fact that they do not implement the specification.
Back-Channel Logout oidcBackChannelLogoutHandler and oidcSessionRegistry The logout token endpoint, the session registry mapping provider sessions to server sessions, and the invalidation.
Statuses a SPA can consume authenticationSuccessHandler, authenticationFailureHandler, authorizationCodeRedirectStrategy Handlers returning 2xx with a Location header instead of 3xx, so that the frontend navigates itself rather than letting the browser follow a cross-origin redirection inside a fetch.
Unauthorized requests authenticationEntryPoint An AuthenticationEntryPoint answering 401 instead of redirecting to the login page, when the caller is JavaScript rather than a browser navigation.
Expired session invalidSessionStrategy The equivalent decision for a session which has gone.
CSRF for JavaScript the CSRF cookie configuration CookieCsrfTokenRepository.withHttpOnlyFalse() plus the CsrfTokenRequestHandler dance which makes the cookie actually written.
Authorities from the ID token grantedAuthoritiesMapper A GrantedAuthoritiesMapper doing for the login flow what the authorities converter does for access tokens.
Tokens in session authorizedClientRepository An HttpSessionOAuth2AuthorizedClientRepository so the tokens follow the session, rather than the request-scoped default.
Concurrent refresh authorizedClientManager and oauth2AuthorizedClientProvider Nothing you can reasonably write. See below.

The refresh token stampede

Most authorization servers rotate refresh tokens. When a user-agent fires parallel requests while the access token in session has expired, Spring Security runs one refresh_token flow per request. The first one succeeds and rotates the token, the others present a refresh token which is no longer valid and are answered with a 401, which the user sees as a random logout under load.

This was declined upstream as something the framework cannot solve generally, and the reasoning holds: a general solution would have to make assumptions about how sessions are stored. spring-addons-starter-oidc decorates the authorized client provider so that concurrent requests on the same session share a single token request, and the authorization server sees one flow rather than five.

REST clients

Auto-configured by spring-addons-starter-rest, which does not depend on the OIDC starter.

Concern What it replaces
Forwarding the incoming Bearer A ClientHttpRequestInterceptor reading the Authentication from the SecurityContextHolder, unwrapping the right token type, and skipping anonymous requests.
Calling as the application An OAuth2AuthorizedClientManager wired to a ClientRegistrationRepository and an OAuth2AuthorizedClientService, not the request-scoped repository Spring Boot defaults to in a web application, or a token is fetched for every call. Then an OAuth2ClientHttpRequestInterceptor with a fixed registration ID and a principal resolver tolerating the absence of a user.
Proxies Reading HTTP_PROXY, HTTPS_PROXY and NO_PROXY, implementing the wildcard and leading-dot rules of NO_PROXY by hand, and carrying proxy credentials through an HTTPS tunnel.
SSL and timeouts A ClientHttpRequestFactory per client, and the code to swap the underlying HTTP library when the default one sets headers some middleware rejects.
@HttpExchange proxies Wiring each group of generated proxies to a client carrying the right base URL, headers, authorization and request factory.

Tests

Concern What it replaces
A security context on a controller spring-security-test post-processors and mutators do this, but they build the Authentication themselves and never run the authentication converter of the application, so the authorities under test are not the authorities in production.
A security context anywhere else Building the Authentication by hand and pushing it into the SecurityContextHolder in a @BeforeEach, for every @Service and @Repository test.
Claims that match the provider Keeping the test fixtures honest with what the authorization server really emits. @WithJwt("brice.json") reads a claim-set from the test classpath, which can be copied from a real token.

Taking a decision back

Nothing here is all or nothing. To own the authorities mapping and keep everything else, define a ClaimSetAuthoritiesConverter bean. To own the whole resource server chain, define a SecurityFilterChain. At that point spring-addons-starter-oidc contributes nothing to the chain any more, and the dependency can be dropped whenever it suits, with the code already written.

That, rather than the number of lines saved, is the argument: the starters are a set of defaults for decisions which have to be taken anyway, and each one is reversible on its own.