How to Config Role-Base with Keycloak Spring Thymeleaf [duplicate] - spring

I updated to Spring Boot 3 in a project that uses the Keycloak Spring Adapter. Unfortunately, it doesn't start because the KeycloakWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter which was first deprecated in Spring Security and then removed. Is there currently another way to implement security with Keycloak? Or to put it in other words: How can I use Spring Boot 3 in combination with the Keycloak adapter?
I searched the Internet, but couldn't find any other version of the adapter.

You can't use Keycloak adapters with spring-boot 3 for the reason you found, plus a few others related to transitive dependencies. As most Keycloak adapters were deprecated in early 2022, it is very likely that no update will be published to fix that.
Directly use spring-security OAuth2 instead. Don't panic, it's an easy task with spring-boot.
spring-addons starters for resource server (app exposes a REST API)
I maintain 4 thin wrappers around "official" boot resource-server starter because, in my opinion, auto-configuration can be pushed one step further to:
make OAuth2 configuration more portable: with a configurable authorities converter, switching from an OIDC provider to another is just a matter of editing properties (Keycloak, Auth0, Cognito, Azure AD, etc.)
ease app deployment on different environments: CORS configuration is controlled from properties file
reduce drastically the amount of Java code (things get even more complicated if you are in multi-tenancy scenario)
reduce chances of misconfiguration (easy to de-synchronise CSRF protection and sessions configuration for instance)
It is very thin (each is composed of three files only) and greatly simplifies resource-servers configuration:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<!-- replace "webmvc" with "weblux" if your app is reactive -->
<!-- replace "jwt" with "introspecting" to use token introspection instead of JWT decoding -->
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<!-- this version is to be used with spring-boot 3.0.1, use 5.4.x for spring-boot 2.6.x or before -->
<version>6.0.13</version>
</dependency>
#Configuration
#EnableMethodSecurity
public static class WebSecurityConfig { }
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/realm1
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,ressource_access.some-client.roles,ressource_access.other-client.roles
com.c4-soft.springaddons.security.cors[0].path=/some-api
com.c4-soft.springaddons.security.permit=all=/actuator/health/readiness,/actuator/health/liveness,/v3/api-docs/**
Nothing more is needed to configure a multi-tenant resource-server with fine tuned CORS policy and authorities mapping. Bootiful, isn't it?
By "multi-tenant", I mean that, as you can guess from this issuers property being an array, you can trust as many OIDC authorization-server instances as you need (multiple Keycloak realms & instances, or even mix with other OIDC providers like Auth0, Cognito, etc.), each with it's own authorities mapping configuration.
Client configuration (UI with oauth2Login())
If your Spring application exposes secured UI elements you want to be accessible with a browser (with OAuth2 login), you'll have to provide a FilterChain with "client" configuration.
If this app exposes both a REST API and a UI to manipulate it (with oauth2Login()), then you'll have to setup two security filter-chains: one with client config and the other with resource-server config.
Add this to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
Here we demo a SecurityFilterChain applying only to a list of routes defined with a securityMatcher.
This assumes that an additional resource-server SecurityFilterChain is defined, with lower order and no securityMatcher so that all routes are intercepted after all filter chains are evaluated in order. This other filter chain could be defined either implicitly (by spring-addons as described above) or explicitly (with Spring Boot official starter as described below).
Remove the securityMatcher section if your app is solely a client:
// Give higher precedence to security filter-chains with "securityMatcher"
#Order(Ordered.HIGHEST_PRECEDENCE)
#Bean
SecurityFilterChain uiFilterChain(
HttpSecurity http,
ServerProperties serverProperties,
GrantedAuthoritiesMapper authoritiesMapper) throws Exception {
http.securityMatcher(new OrRequestMatcher(
// add path to your UI elements instead
new AntPathRequestMatcher("/ui/**"),
// those two are required to access Spring generated login page
// and OAuth2 client callback endpoints
new AntPathRequestMatcher("/login/**"),
new AntPathRequestMatcher("/oauth2/**")));
http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(authoritiesMapper);
http.authorizeHttpRequests()
.requestMatchers("/ui/index.html").permitAll()
.requestMatchers("/login/**").permitAll()
.requestMatchers("/oauth2/**").permitAll()
.anyRequest().authenticated();
// If SSL enabled, disable http (https only)
if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
http.requiresChannel().anyRequest().requiresSecure();
}
// Many defaults are kept compared to API filter-chain:
// - sessions (and CSRF protection) are enabled
// - unauthorized requests to secured resources will be redirected to login (302 to login is Spring's default response when authorisation is missing or invalid)
return http.build();
}
#Bean
GrantedAuthoritiesMapper userAuthoritiesMapper(Converter<Map<String, Object>, Collection<? extends GrantedAuthority>> authoritiesConverter) {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (authority instanceof OidcUserAuthority oidcAuth) {
mappedAuthorities.addAll(authoritiesConverter.convert(oidcAuth.getIdToken().getClaims()));
} else if (authority instanceof OAuth2UserAuthority oauth2Auth) {
mappedAuthorities.addAll(authoritiesConverter.convert(oauth2Auth.getAttributes()));
}
});
return mappedAuthorities;
};
}
The code above assumes that a Converter<Map<String, Object>, Collection<? extends GrantedAuthority>> bean is exposed. One is auto-configured by spring-addons starters for resource-server and the "official" starter section below defines one. Take the later as sample if your app is solely a client.
Last, client properties:
spring.security.oauth2.client.provider.keycloak.issuer-uri=https://localhost:8443/realms/master
spring.security.oauth2.client.registration.spring-addons-public.provider=keycloak
spring.security.oauth2.client.registration.spring-addons-public.client-name=spring-addons-public
spring.security.oauth2.client.registration.spring-addons-public.client-id=spring-addons-public
spring.security.oauth2.client.registration.spring-addons-public.scope=openid,offline_access,profile
spring.security.oauth2.client.registration.spring-addons-public.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.spring-addons-public.redirect-uri=http://bravo-ch4mp:8080/login/oauth2/code/spring-addons-public
"Official" Spring Boot resource-server starter
As spring-addons-{webmvc|webflux}-{jwt|introspecting}-resource-server are thin wrappers around spring-boot-starter-oauth2-resource-server, you can of course do the same with just the later.
Here is what it takes to configure a resource-server with a unique Keycloak realm as authorization-server:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity
public class WebSecurityConfig {
public interface Jwt2AuthoritiesConverter extends Converter<Jwt, Collection<? extends GrantedAuthority>> {
}
#SuppressWarnings("unchecked")
#Bean
public Jwt2AuthoritiesConverter authoritiesConverter() {
// This is a converter for roles as embedded in the JWT by a Keycloak server
// Roles are taken from both realm_access.roles & resource_access.{client}.roles
return jwt -> {
final var realmAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("realm_access", Map.of());
final var realmRoles = (Collection<String>) realmAccess.getOrDefault("roles", List.of());
final var resourceAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("resource_access", Map.of());
// We assume here you have "spring-addons-confidential" and "spring-addons-public" clients configured with "client roles" mapper in Keycloak
final var confidentialClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("spring-addons-confidential", Map.of());
final var confidentialClientRoles = (Collection<String>) confidentialClientAccess.getOrDefault("roles", List.of());
final var publicClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("spring-addons-public", Map.of());
final var publicClientRoles = (Collection<String>) publicClientAccess.getOrDefault("roles", List.of());
// Merge the 3 sources of roles and map it to spring-security authorities
return Stream.concat(
realmRoles.stream(),
Stream.concat(confidentialClientRoles.stream(), publicClientRoles.stream()))
.map(SimpleGrantedAuthority::new).toList();
};
}
// spring-boot looks for a Converter<Jwt, ? extends AbstractAuthenticationToken> bean
// that is a converter from Jwt to something extending AbstractAuthenticationToken (and not AbstractAuthenticationToken itself)
// In this conf, we use JwtAuthenticationToken as AbstractAuthenticationToken implementation
public interface Jwt2AuthenticationConverter extends Converter<Jwt, JwtAuthenticationToken> {
}
#Bean
public Jwt2AuthenticationConverter authenticationConverter(Jwt2AuthoritiesConverter authoritiesConverter) {
return jwt -> new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt));
}
// Give lower precedence to security filter-chains without "securityMatcher" so that the filter-chains with a "securityMatcher" get a chance to be matched
#Order(Ordered.LOWEST_PRECEDENCE)
#Bean
public SecurityFilterChain apiFilterChain(
HttpSecurity http,
ServerProperties serverProperties,
Converter<Jwt, ? extends AbstractAuthenticationToken> authenticationConverter) throws Exception {
// Enable OAuth2 with custom authorities mapping
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter);
// As the authentication bean is the one expected by spring-boot,
// an alternative would be to use just
// http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// Enable anonymous
http.anonymous();
// Enable and configure CORS
http.cors().configurationSource(corsConfigurationSource());
// State-less session (state in access-token only)
// with Disable CSRF because of disabled sessions
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.csrf().disable();
// Return 401 (unauthorized) instead of 302 (redirect to login) when authorization is missing or invalid
http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
});
// If SSL enabled, disable http (https only)
if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
http.requiresChannel().anyRequest().requiresSecure();
}
// Route security: authenticated to all routes but actuator and Swagger-UI
http.authorizeRequests()
.antMatchers("/actuator/health/readiness", "/actuator/health/liveness", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated();
return http.build();
}
private CorsConfigurationSource corsConfigurationSource() {
// Very permissive CORS config...
final var configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setExposedHeaders(Arrays.asList("*"));
// Limited to API routes (neither actuator nor Swagger-UI)
final var source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/greet/**", configuration);
return source;
}
}
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://localhost:8443/realms/master
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://localhost:8443/realms/master/protocol/openid-connect/certs
As mentioned in preamble, this is quite more verbose than spring-addons starters, it's not ready for multi-tenancy and each time CORS policy changes (new API routes for instance) or when the claims source for authorities change (new OAuth2 client with client-roles mapping or other OIDC provider than Keycloak), you'll have to edit source-code and re-publish your app...

Use the standard Spring Security OAuth2 client instead of a specific Keycloak adapter and SecurityFilterChain instead of WebSecurityAdapter.
Something like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
class OAuth2SecurityConfig {
#Bean
fun customOauth2FilterChain(http: HttpSecurity): SecurityFilterChain {
log.info("Configure HttpSecurity with OAuth2")
http {
oauth2ResourceServer {
jwt { jwtAuthenticationConverter = CustomBearerJwtAuthenticationConverter() }
}
oauth2Login {}
csrf { disable() }
authorizeRequests {
// Kubernetes
authorize("/readiness", permitAll)
authorize("/liveness", permitAll)
authorize("/actuator/health/**", permitAll)
// ...
// everything else needs at least a valid login, roles are checked at method level
authorize(anyRequest, authenticated)
}
}
return http.build()
}
And then in application.yml:
spring:
security:
oauth2:
client:
provider:
abc:
issuer-uri: https://keycloak.../auth/realms/foo
registration:
abc:
client-secret: ...
provider: abc
client-id: foo
scope: [ openid, profile, email ]
resourceserver:
jwt:
issuer-uri: https://keycloak.../auth/realms/foo

Using Keycloak adapters is not possible because the KeycloakWebSecurityConfigurerAdapter inherited from the WebSecurityConfigurerAdapter class, which was deprecated in Spring Security and subsequently removed in the newer release.
I have published a detailed article on integrating Keycloak with Spring Boot 3.0 on Medium, which provides a step-by-step guide on how to integrate Keycloak with Spring Boot 3.0.
This guide is particularly helpful for those who are new to integrating Keycloak with Spring Boot 3.0 or migrating to Spring Boot 3.0 from an older version.
You can check out the article (https://medium.com/geekculture/using-keycloak-with-spring-boot-3-0-376fa9f60e0b) for a comprehensive explanation of the integration process.
Hope this helps! If you have any questions, further clarifications or suggestions, Please feel free to leave a comment.

Related

Any Reference with: Spring Boot 3.0.x, spring security 6.0.x with keycloak integration Token based Authentication and Authorization [duplicate]

I updated to Spring Boot 3 in a project that uses the Keycloak Spring Adapter. Unfortunately, it doesn't start because the KeycloakWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter which was first deprecated in Spring Security and then removed. Is there currently another way to implement security with Keycloak? Or to put it in other words: How can I use Spring Boot 3 in combination with the Keycloak adapter?
I searched the Internet, but couldn't find any other version of the adapter.
You can't use Keycloak adapters with spring-boot 3 for the reason you found, plus a few others related to transitive dependencies. As most Keycloak adapters were deprecated in early 2022, it is very likely that no update will be published to fix that.
Directly use spring-security OAuth2 instead. Don't panic, it's an easy task with spring-boot.
spring-addons starters for resource server (app exposes a REST API)
I maintain 4 thin wrappers around "official" boot resource-server starter because, in my opinion, auto-configuration can be pushed one step further to:
make OAuth2 configuration more portable: with a configurable authorities converter, switching from an OIDC provider to another is just a matter of editing properties (Keycloak, Auth0, Cognito, Azure AD, etc.)
ease app deployment on different environments: CORS configuration is controlled from properties file
reduce drastically the amount of Java code (things get even more complicated if you are in multi-tenancy scenario)
reduce chances of misconfiguration (easy to de-synchronise CSRF protection and sessions configuration for instance)
It is very thin (each is composed of three files only) and greatly simplifies resource-servers configuration:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<!-- replace "webmvc" with "weblux" if your app is reactive -->
<!-- replace "jwt" with "introspecting" to use token introspection instead of JWT decoding -->
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<!-- this version is to be used with spring-boot 3.0.1, use 5.4.x for spring-boot 2.6.x or before -->
<version>6.0.13</version>
</dependency>
#Configuration
#EnableMethodSecurity
public static class WebSecurityConfig { }
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/realm1
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,ressource_access.some-client.roles,ressource_access.other-client.roles
com.c4-soft.springaddons.security.cors[0].path=/some-api
com.c4-soft.springaddons.security.permit=all=/actuator/health/readiness,/actuator/health/liveness,/v3/api-docs/**
Nothing more is needed to configure a multi-tenant resource-server with fine tuned CORS policy and authorities mapping. Bootiful, isn't it?
By "multi-tenant", I mean that, as you can guess from this issuers property being an array, you can trust as many OIDC authorization-server instances as you need (multiple Keycloak realms & instances, or even mix with other OIDC providers like Auth0, Cognito, etc.), each with it's own authorities mapping configuration.
Client configuration (UI with oauth2Login())
If your Spring application exposes secured UI elements you want to be accessible with a browser (with OAuth2 login), you'll have to provide a FilterChain with "client" configuration.
If this app exposes both a REST API and a UI to manipulate it (with oauth2Login()), then you'll have to setup two security filter-chains: one with client config and the other with resource-server config.
Add this to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
Here we demo a SecurityFilterChain applying only to a list of routes defined with a securityMatcher.
This assumes that an additional resource-server SecurityFilterChain is defined, with lower order and no securityMatcher so that all routes are intercepted after all filter chains are evaluated in order. This other filter chain could be defined either implicitly (by spring-addons as described above) or explicitly (with Spring Boot official starter as described below).
Remove the securityMatcher section if your app is solely a client:
// Give higher precedence to security filter-chains with "securityMatcher"
#Order(Ordered.HIGHEST_PRECEDENCE)
#Bean
SecurityFilterChain uiFilterChain(
HttpSecurity http,
ServerProperties serverProperties,
GrantedAuthoritiesMapper authoritiesMapper) throws Exception {
http.securityMatcher(new OrRequestMatcher(
// add path to your UI elements instead
new AntPathRequestMatcher("/ui/**"),
// those two are required to access Spring generated login page
// and OAuth2 client callback endpoints
new AntPathRequestMatcher("/login/**"),
new AntPathRequestMatcher("/oauth2/**")));
http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(authoritiesMapper);
http.authorizeHttpRequests()
.requestMatchers("/ui/index.html").permitAll()
.requestMatchers("/login/**").permitAll()
.requestMatchers("/oauth2/**").permitAll()
.anyRequest().authenticated();
// If SSL enabled, disable http (https only)
if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
http.requiresChannel().anyRequest().requiresSecure();
}
// Many defaults are kept compared to API filter-chain:
// - sessions (and CSRF protection) are enabled
// - unauthorized requests to secured resources will be redirected to login (302 to login is Spring's default response when authorisation is missing or invalid)
return http.build();
}
#Bean
GrantedAuthoritiesMapper userAuthoritiesMapper(Converter<Map<String, Object>, Collection<? extends GrantedAuthority>> authoritiesConverter) {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (authority instanceof OidcUserAuthority oidcAuth) {
mappedAuthorities.addAll(authoritiesConverter.convert(oidcAuth.getIdToken().getClaims()));
} else if (authority instanceof OAuth2UserAuthority oauth2Auth) {
mappedAuthorities.addAll(authoritiesConverter.convert(oauth2Auth.getAttributes()));
}
});
return mappedAuthorities;
};
}
The code above assumes that a Converter<Map<String, Object>, Collection<? extends GrantedAuthority>> bean is exposed. One is auto-configured by spring-addons starters for resource-server and the "official" starter section below defines one. Take the later as sample if your app is solely a client.
Last, client properties:
spring.security.oauth2.client.provider.keycloak.issuer-uri=https://localhost:8443/realms/master
spring.security.oauth2.client.registration.spring-addons-public.provider=keycloak
spring.security.oauth2.client.registration.spring-addons-public.client-name=spring-addons-public
spring.security.oauth2.client.registration.spring-addons-public.client-id=spring-addons-public
spring.security.oauth2.client.registration.spring-addons-public.scope=openid,offline_access,profile
spring.security.oauth2.client.registration.spring-addons-public.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.spring-addons-public.redirect-uri=http://bravo-ch4mp:8080/login/oauth2/code/spring-addons-public
"Official" Spring Boot resource-server starter
As spring-addons-{webmvc|webflux}-{jwt|introspecting}-resource-server are thin wrappers around spring-boot-starter-oauth2-resource-server, you can of course do the same with just the later.
Here is what it takes to configure a resource-server with a unique Keycloak realm as authorization-server:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity
public class WebSecurityConfig {
public interface Jwt2AuthoritiesConverter extends Converter<Jwt, Collection<? extends GrantedAuthority>> {
}
#SuppressWarnings("unchecked")
#Bean
public Jwt2AuthoritiesConverter authoritiesConverter() {
// This is a converter for roles as embedded in the JWT by a Keycloak server
// Roles are taken from both realm_access.roles & resource_access.{client}.roles
return jwt -> {
final var realmAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("realm_access", Map.of());
final var realmRoles = (Collection<String>) realmAccess.getOrDefault("roles", List.of());
final var resourceAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("resource_access", Map.of());
// We assume here you have "spring-addons-confidential" and "spring-addons-public" clients configured with "client roles" mapper in Keycloak
final var confidentialClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("spring-addons-confidential", Map.of());
final var confidentialClientRoles = (Collection<String>) confidentialClientAccess.getOrDefault("roles", List.of());
final var publicClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("spring-addons-public", Map.of());
final var publicClientRoles = (Collection<String>) publicClientAccess.getOrDefault("roles", List.of());
// Merge the 3 sources of roles and map it to spring-security authorities
return Stream.concat(
realmRoles.stream(),
Stream.concat(confidentialClientRoles.stream(), publicClientRoles.stream()))
.map(SimpleGrantedAuthority::new).toList();
};
}
// spring-boot looks for a Converter<Jwt, ? extends AbstractAuthenticationToken> bean
// that is a converter from Jwt to something extending AbstractAuthenticationToken (and not AbstractAuthenticationToken itself)
// In this conf, we use JwtAuthenticationToken as AbstractAuthenticationToken implementation
public interface Jwt2AuthenticationConverter extends Converter<Jwt, JwtAuthenticationToken> {
}
#Bean
public Jwt2AuthenticationConverter authenticationConverter(Jwt2AuthoritiesConverter authoritiesConverter) {
return jwt -> new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt));
}
// Give lower precedence to security filter-chains without "securityMatcher" so that the filter-chains with a "securityMatcher" get a chance to be matched
#Order(Ordered.LOWEST_PRECEDENCE)
#Bean
public SecurityFilterChain apiFilterChain(
HttpSecurity http,
ServerProperties serverProperties,
Converter<Jwt, ? extends AbstractAuthenticationToken> authenticationConverter) throws Exception {
// Enable OAuth2 with custom authorities mapping
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter);
// As the authentication bean is the one expected by spring-boot,
// an alternative would be to use just
// http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// Enable anonymous
http.anonymous();
// Enable and configure CORS
http.cors().configurationSource(corsConfigurationSource());
// State-less session (state in access-token only)
// with Disable CSRF because of disabled sessions
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.csrf().disable();
// Return 401 (unauthorized) instead of 302 (redirect to login) when authorization is missing or invalid
http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
});
// If SSL enabled, disable http (https only)
if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
http.requiresChannel().anyRequest().requiresSecure();
}
// Route security: authenticated to all routes but actuator and Swagger-UI
http.authorizeRequests()
.antMatchers("/actuator/health/readiness", "/actuator/health/liveness", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated();
return http.build();
}
private CorsConfigurationSource corsConfigurationSource() {
// Very permissive CORS config...
final var configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setExposedHeaders(Arrays.asList("*"));
// Limited to API routes (neither actuator nor Swagger-UI)
final var source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/greet/**", configuration);
return source;
}
}
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://localhost:8443/realms/master
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://localhost:8443/realms/master/protocol/openid-connect/certs
As mentioned in preamble, this is quite more verbose than spring-addons starters, it's not ready for multi-tenancy and each time CORS policy changes (new API routes for instance) or when the claims source for authorities change (new OAuth2 client with client-roles mapping or other OIDC provider than Keycloak), you'll have to edit source-code and re-publish your app...
Use the standard Spring Security OAuth2 client instead of a specific Keycloak adapter and SecurityFilterChain instead of WebSecurityAdapter.
Something like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
class OAuth2SecurityConfig {
#Bean
fun customOauth2FilterChain(http: HttpSecurity): SecurityFilterChain {
log.info("Configure HttpSecurity with OAuth2")
http {
oauth2ResourceServer {
jwt { jwtAuthenticationConverter = CustomBearerJwtAuthenticationConverter() }
}
oauth2Login {}
csrf { disable() }
authorizeRequests {
// Kubernetes
authorize("/readiness", permitAll)
authorize("/liveness", permitAll)
authorize("/actuator/health/**", permitAll)
// ...
// everything else needs at least a valid login, roles are checked at method level
authorize(anyRequest, authenticated)
}
}
return http.build()
}
And then in application.yml:
spring:
security:
oauth2:
client:
provider:
abc:
issuer-uri: https://keycloak.../auth/realms/foo
registration:
abc:
client-secret: ...
provider: abc
client-id: foo
scope: [ openid, profile, email ]
resourceserver:
jwt:
issuer-uri: https://keycloak.../auth/realms/foo
Using Keycloak adapters is not possible because the KeycloakWebSecurityConfigurerAdapter inherited from the WebSecurityConfigurerAdapter class, which was deprecated in Spring Security and subsequently removed in the newer release.
I have published a detailed article on integrating Keycloak with Spring Boot 3.0 on Medium, which provides a step-by-step guide on how to integrate Keycloak with Spring Boot 3.0.
This guide is particularly helpful for those who are new to integrating Keycloak with Spring Boot 3.0 or migrating to Spring Boot 3.0 from an older version.
You can check out the article (https://medium.com/geekculture/using-keycloak-with-spring-boot-3-0-376fa9f60e0b) for a comprehensive explanation of the integration process.
Hope this helps! If you have any questions, further clarifications or suggestions, Please feel free to leave a comment.

how microservice use jwt to communicate in springboot

I am using microservice in spring boot and i want to use jwt and oauth2 to access the server.But i just wonder that how microservice other than api gateway get the data in the jwt (id or name) .It seems that it is so tedious to set a decoder in every microservice.
I am thinking that is it possible to decode and add the data at the httprequest and route it the other microservice in apigateway.But it seems that i cant find a setheader method in webflux filter security.
Jwt filter:
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String authorizationheader= exchange.getRequest().getHeaders().get("Authorization").toString();
String token;
String Username = null;
String iss=null;
//check have tokem
if(authorizationheader !=null&& authorizationheader.startsWith("Bearer ")){
token=authorizationheader.substring(7);
Username=jwtDecoder.decode(token).getSubject();
iss= String.valueOf(jwtDecoder.decode(token).getIssuer());
} //verify by check username and iss
if(Username!=null && iss!=null&& SecurityContextHolder.getContext().getAuthentication()==null){
if(iss.equals("http://localhost:8080")){
UserDetails userDetails=new User(Username,null,null);
UsernamePasswordAuthenticationToken AuthenticationToken=new UsernamePasswordAuthenticationToken(
userDetails,null,userDetails.getAuthorities());
//set username and id to the request
SecurityContextHolder.getContext().setAuthentication(AuthenticationToken);
}
}
return chain.filter(exchange);
}
Securityfilter bean:
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity httpSecurity) throws Exception {
return httpSecurity
/*.csrf(csrf -> csrf.ignoringRequestMatchers("/Job/getRegionjobs/**",
"/Job/getalljobs","/login/oauth2/code/google"))*/
.csrf(csrf -> csrf.disable())
.authorizeExchange(auth->auth.anyExchange().authenticated())
.addFilterBefore(jwtFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt)
//.sessionManagement(session-> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(withDefaults())
.build();
}
Please help
It seems that it is so tedious to set a decoder in every microservice.
No, it is not. Configuring a resource-server (OAuth2 REST API) can be as simple as:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<!-- replace "webmvc" with "weblux" if your micro-service is reactive -->
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.12</version>
</dependency>
#Configuration
#EnableMethodSecurity
public static class WebSecurityConfig { }
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/realm1
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,ressource_access.some-client.roles,ressource_access.other-client.roles
com.c4-soft.springaddons.security.cors[0].path=/some-api
If you don't want to use my starters, you can still create your own copying from it (it is open source and each is composed of 3 files only).
If you don't implement access-control in each micro-service, then you can't bypass the gateway and it's going to be a hell to implement rules involving the resources itself (like only user who created that kind of resource can modify it).

Use Keycloak Spring Adapter with Spring Boot 3

I updated to Spring Boot 3 in a project that uses the Keycloak Spring Adapter. Unfortunately, it doesn't start because the KeycloakWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter which was first deprecated in Spring Security and then removed. Is there currently another way to implement security with Keycloak? Or to put it in other words: How can I use Spring Boot 3 in combination with the Keycloak adapter?
I searched the Internet, but couldn't find any other version of the adapter.
You can't use Keycloak adapters with spring-boot 3 for the reason you found, plus a few others related to transitive dependencies. As most Keycloak adapters were deprecated in early 2022, it is very likely that no update will be published to fix that.
Directly use spring-security OAuth2 instead. Don't panic, it's an easy task with spring-boot.
spring-addons starters for resource server (app exposes a REST API)
I maintain 4 thin wrappers around "official" boot resource-server starter because, in my opinion, auto-configuration can be pushed one step further to:
make OAuth2 configuration more portable: with a configurable authorities converter, switching from an OIDC provider to another is just a matter of editing properties (Keycloak, Auth0, Cognito, Azure AD, etc.)
ease app deployment on different environments: CORS configuration is controlled from properties file
reduce drastically the amount of Java code (things get even more complicated if you are in multi-tenancy scenario)
reduce chances of misconfiguration (easy to de-synchronise CSRF protection and sessions configuration for instance)
It is very thin (each is composed of three files only) and greatly simplifies resource-servers configuration:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<!-- replace "webmvc" with "weblux" if your app is reactive -->
<!-- replace "jwt" with "introspecting" to use token introspection instead of JWT decoding -->
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<!-- this version is to be used with spring-boot 3.0.1, use 5.4.x for spring-boot 2.6.x or before -->
<version>6.0.13</version>
</dependency>
#Configuration
#EnableMethodSecurity
public static class WebSecurityConfig { }
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/realm1
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,ressource_access.some-client.roles,ressource_access.other-client.roles
com.c4-soft.springaddons.security.cors[0].path=/some-api
com.c4-soft.springaddons.security.permit=all=/actuator/health/readiness,/actuator/health/liveness,/v3/api-docs/**
Nothing more is needed to configure a multi-tenant resource-server with fine tuned CORS policy and authorities mapping. Bootiful, isn't it?
By "multi-tenant", I mean that, as you can guess from this issuers property being an array, you can trust as many OIDC authorization-server instances as you need (multiple Keycloak realms & instances, or even mix with other OIDC providers like Auth0, Cognito, etc.), each with it's own authorities mapping configuration.
Client configuration (UI with oauth2Login())
If your Spring application exposes secured UI elements you want to be accessible with a browser (with OAuth2 login), you'll have to provide a FilterChain with "client" configuration.
If this app exposes both a REST API and a UI to manipulate it (with oauth2Login()), then you'll have to setup two security filter-chains: one with client config and the other with resource-server config.
Add this to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
Here we demo a SecurityFilterChain applying only to a list of routes defined with a securityMatcher.
This assumes that an additional resource-server SecurityFilterChain is defined, with lower order and no securityMatcher so that all routes are intercepted after all filter chains are evaluated in order. This other filter chain could be defined either implicitly (by spring-addons as described above) or explicitly (with Spring Boot official starter as described below).
Remove the securityMatcher section if your app is solely a client:
// Give higher precedence to security filter-chains with "securityMatcher"
#Order(Ordered.HIGHEST_PRECEDENCE)
#Bean
SecurityFilterChain uiFilterChain(
HttpSecurity http,
ServerProperties serverProperties,
GrantedAuthoritiesMapper authoritiesMapper) throws Exception {
http.securityMatcher(new OrRequestMatcher(
// add path to your UI elements instead
new AntPathRequestMatcher("/ui/**"),
// those two are required to access Spring generated login page
// and OAuth2 client callback endpoints
new AntPathRequestMatcher("/login/**"),
new AntPathRequestMatcher("/oauth2/**")));
http.oauth2Login().userInfoEndpoint().userAuthoritiesMapper(authoritiesMapper);
http.authorizeHttpRequests()
.requestMatchers("/ui/index.html").permitAll()
.requestMatchers("/login/**").permitAll()
.requestMatchers("/oauth2/**").permitAll()
.anyRequest().authenticated();
// If SSL enabled, disable http (https only)
if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
http.requiresChannel().anyRequest().requiresSecure();
}
// Many defaults are kept compared to API filter-chain:
// - sessions (and CSRF protection) are enabled
// - unauthorized requests to secured resources will be redirected to login (302 to login is Spring's default response when authorisation is missing or invalid)
return http.build();
}
#Bean
GrantedAuthoritiesMapper userAuthoritiesMapper(Converter<Map<String, Object>, Collection<? extends GrantedAuthority>> authoritiesConverter) {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (authority instanceof OidcUserAuthority oidcAuth) {
mappedAuthorities.addAll(authoritiesConverter.convert(oidcAuth.getIdToken().getClaims()));
} else if (authority instanceof OAuth2UserAuthority oauth2Auth) {
mappedAuthorities.addAll(authoritiesConverter.convert(oauth2Auth.getAttributes()));
}
});
return mappedAuthorities;
};
}
The code above assumes that a Converter<Map<String, Object>, Collection<? extends GrantedAuthority>> bean is exposed. One is auto-configured by spring-addons starters for resource-server and the "official" starter section below defines one. Take the later as sample if your app is solely a client.
Last, client properties:
spring.security.oauth2.client.provider.keycloak.issuer-uri=https://localhost:8443/realms/master
spring.security.oauth2.client.registration.spring-addons-public.provider=keycloak
spring.security.oauth2.client.registration.spring-addons-public.client-name=spring-addons-public
spring.security.oauth2.client.registration.spring-addons-public.client-id=spring-addons-public
spring.security.oauth2.client.registration.spring-addons-public.scope=openid,offline_access,profile
spring.security.oauth2.client.registration.spring-addons-public.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.spring-addons-public.redirect-uri=http://bravo-ch4mp:8080/login/oauth2/code/spring-addons-public
"Official" Spring Boot resource-server starter
As spring-addons-{webmvc|webflux}-{jwt|introspecting}-resource-server are thin wrappers around spring-boot-starter-oauth2-resource-server, you can of course do the same with just the later.
Here is what it takes to configure a resource-server with a unique Keycloak realm as authorization-server:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity
public class WebSecurityConfig {
public interface Jwt2AuthoritiesConverter extends Converter<Jwt, Collection<? extends GrantedAuthority>> {
}
#SuppressWarnings("unchecked")
#Bean
public Jwt2AuthoritiesConverter authoritiesConverter() {
// This is a converter for roles as embedded in the JWT by a Keycloak server
// Roles are taken from both realm_access.roles & resource_access.{client}.roles
return jwt -> {
final var realmAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("realm_access", Map.of());
final var realmRoles = (Collection<String>) realmAccess.getOrDefault("roles", List.of());
final var resourceAccess = (Map<String, Object>) jwt.getClaims().getOrDefault("resource_access", Map.of());
// We assume here you have "spring-addons-confidential" and "spring-addons-public" clients configured with "client roles" mapper in Keycloak
final var confidentialClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("spring-addons-confidential", Map.of());
final var confidentialClientRoles = (Collection<String>) confidentialClientAccess.getOrDefault("roles", List.of());
final var publicClientAccess = (Map<String, Object>) resourceAccess.getOrDefault("spring-addons-public", Map.of());
final var publicClientRoles = (Collection<String>) publicClientAccess.getOrDefault("roles", List.of());
// Merge the 3 sources of roles and map it to spring-security authorities
return Stream.concat(
realmRoles.stream(),
Stream.concat(confidentialClientRoles.stream(), publicClientRoles.stream()))
.map(SimpleGrantedAuthority::new).toList();
};
}
// spring-boot looks for a Converter<Jwt, ? extends AbstractAuthenticationToken> bean
// that is a converter from Jwt to something extending AbstractAuthenticationToken (and not AbstractAuthenticationToken itself)
// In this conf, we use JwtAuthenticationToken as AbstractAuthenticationToken implementation
public interface Jwt2AuthenticationConverter extends Converter<Jwt, JwtAuthenticationToken> {
}
#Bean
public Jwt2AuthenticationConverter authenticationConverter(Jwt2AuthoritiesConverter authoritiesConverter) {
return jwt -> new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt));
}
// Give lower precedence to security filter-chains without "securityMatcher" so that the filter-chains with a "securityMatcher" get a chance to be matched
#Order(Ordered.LOWEST_PRECEDENCE)
#Bean
public SecurityFilterChain apiFilterChain(
HttpSecurity http,
ServerProperties serverProperties,
Converter<Jwt, ? extends AbstractAuthenticationToken> authenticationConverter) throws Exception {
// Enable OAuth2 with custom authorities mapping
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter);
// As the authentication bean is the one expected by spring-boot,
// an alternative would be to use just
// http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
// Enable anonymous
http.anonymous();
// Enable and configure CORS
http.cors().configurationSource(corsConfigurationSource());
// State-less session (state in access-token only)
// with Disable CSRF because of disabled sessions
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.csrf().disable();
// Return 401 (unauthorized) instead of 302 (redirect to login) when authorization is missing or invalid
http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
});
// If SSL enabled, disable http (https only)
if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
http.requiresChannel().anyRequest().requiresSecure();
}
// Route security: authenticated to all routes but actuator and Swagger-UI
http.authorizeRequests()
.antMatchers("/actuator/health/readiness", "/actuator/health/liveness", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated();
return http.build();
}
private CorsConfigurationSource corsConfigurationSource() {
// Very permissive CORS config...
final var configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setExposedHeaders(Arrays.asList("*"));
// Limited to API routes (neither actuator nor Swagger-UI)
final var source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/greet/**", configuration);
return source;
}
}
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://localhost:8443/realms/master
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://localhost:8443/realms/master/protocol/openid-connect/certs
As mentioned in preamble, this is quite more verbose than spring-addons starters, it's not ready for multi-tenancy and each time CORS policy changes (new API routes for instance) or when the claims source for authorities change (new OAuth2 client with client-roles mapping or other OIDC provider than Keycloak), you'll have to edit source-code and re-publish your app...
Use the standard Spring Security OAuth2 client instead of a specific Keycloak adapter and SecurityFilterChain instead of WebSecurityAdapter.
Something like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
class OAuth2SecurityConfig {
#Bean
fun customOauth2FilterChain(http: HttpSecurity): SecurityFilterChain {
log.info("Configure HttpSecurity with OAuth2")
http {
oauth2ResourceServer {
jwt { jwtAuthenticationConverter = CustomBearerJwtAuthenticationConverter() }
}
oauth2Login {}
csrf { disable() }
authorizeRequests {
// Kubernetes
authorize("/readiness", permitAll)
authorize("/liveness", permitAll)
authorize("/actuator/health/**", permitAll)
// ...
// everything else needs at least a valid login, roles are checked at method level
authorize(anyRequest, authenticated)
}
}
return http.build()
}
And then in application.yml:
spring:
security:
oauth2:
client:
provider:
abc:
issuer-uri: https://keycloak.../auth/realms/foo
registration:
abc:
client-secret: ...
provider: abc
client-id: foo
scope: [ openid, profile, email ]
resourceserver:
jwt:
issuer-uri: https://keycloak.../auth/realms/foo
Using Keycloak adapters is not possible because the KeycloakWebSecurityConfigurerAdapter inherited from the WebSecurityConfigurerAdapter class, which was deprecated in Spring Security and subsequently removed in the newer release.
I have published a detailed article on integrating Keycloak with Spring Boot 3.0 on Medium, which provides a step-by-step guide on how to integrate Keycloak with Spring Boot 3.0.
This guide is particularly helpful for those who are new to integrating Keycloak with Spring Boot 3.0 or migrating to Spring Boot 3.0 from an older version.
You can check out the article (https://medium.com/geekculture/using-keycloak-with-spring-boot-3-0-376fa9f60e0b) for a comprehensive explanation of the integration process.
Hope this helps! If you have any questions, further clarifications or suggestions, Please feel free to leave a comment.

Spring Security Configuration: Basic Auth + Spring Cloud Gateway

I've got a Reactive Spring Boot application, which is responsible for routing requests to downstream services, using Spring Cloud Gateway (i.e. it's an API gateway). The app has some actuator endpoints, that need to be secured, hence I want to use just a simple security for this like basic auth.
I'd like to configure the app, to require requests to /actuator/refresh to be authorized using basic auth (with a configured Spring security user and password). All requests to other endpoints, even if they include basic auth, only need to be passed to the downstream service.
My current Spring security configuration:
#Bean
#Order(1)
SecurityWebFilterChain securityWebFilterChain(final ServerHttpSecurity http) {
http.authorizeExchange(exchanges -> {
exchanges.matchers(EndpointRequest.toAnyEndpoint().excluding(HealthEndpoint.class, InfoEndpoint.class)).hasRole("ACTUATOR"); // requires Http Basic Auth
});
http.httpBasic(withDefaults()); // if not enabled, you cannot get the ACTUATOR role
return http.build();
}
#Bean
#Order(2)
SecurityWebFilterChain permitAllWebFilterChain(final ServerHttpSecurity http) {
http.authorizeExchange(exchanges -> exchanges.anyExchange().permitAll()); // allow unauthenticated access to any endpoint (other than secured actuator endpoints?)
http.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable); // disable Http Basic Auth for all other endpoints
return http.build();
}
The request meant for the downstream service is not propagated by the API gateway. The spring boot service returns a 401 in this setup, while a 200 is expected / required.
Any ideas why this configuration is not working / how it should be configured otherwise?
Im not sure what is broken, but have you tried combining them and just have one filter?
#EnableWebFluxSecurity
public class MyExplicitSecurityConfiguration {
#Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("ACTUATOR")
.build();
return new MapReactiveUserDetailsService(user);
}
#Bean
SecurityWebFilterChain securityWebFilterChain(final ServerHttpSecurity http) {
http.authorizeExchange(exchanges -> {
exchanges.matchers(EndpointRequest.toAnyEndpoint()
.excluding(HealthEndpoint.class, InfoEndpoint.class))
.hasRole("ACTUATOR");
exchanges.anyExchange().permitAll();
}).httpBasic(withDefaults());
return http.build();
}
}
another good thing could be to enable debug logging and see what fails.
this is done by defining in application.properties
logging.level.org.springframework.security=DEBUG

Spring Boot 2.0 web flux custom authentication -- how to?

There are plenty of examples of minimal configurations of Spring Boot 2.0 security which compile or don't depending on which milestone or release candidate you try.
What is a minimal configuration that is not HTTP Basic, that will (1) let me access the HTTP request (headers, cookies, etc.) and also call my own authentication manager?
I would like to look at the headers and cookies, and decide from those who the user is, and whether or not the user is authenticated. How I do that should not matter to this answer -- the question is, what is the minimal Spring security config in order to allow me to hook in to the security infrastructure, so that my authentication is there in the reactive endpoints?
EDIT:
This works with Spring Boot 2.0.0.RC2, so my question could be, is this a correct way to introduce custom authentication into Spring Security?
#Configuration
#EnableWebFluxSecurity
public class SecurityConfiguration {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(authentication -> {
authentication.setAuthenticated(true);
return Mono.just(authentication);
});
authenticationFilter.setAuthenticationConverter(serverWebExchange ->
Mono.just(new AbstractAuthenticationToken(new ArrayList<>()) {
#Override
public Object getCredentials() {
return null;
}
#Override
public Object getPrincipal() {
return "jim";
}
}));
return http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.FORM_LOGIN)
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.build();
}
}
You can imagine that in the converter, I am free to look into the request by way of serverWebExchange and inspect any headers or cookies I wish, and that later in the upper lambda (standing in for ReactiveAuthenticationManager) I can actually decide whether or not it should be authenticated.

Resources