I need two authentications. One secured with JWT to access Keycloak token and another ressource to provide access over Basic-Auth. Created two SecurityConfigs with the @Order Annotation.
WebSecurityConfig for Keycloak:
@Configuration
@Order(2)
class WebSecurityConfig {
@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
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());
return realmRoles.stream().filter(UserRole::isValidRole)
.map(SimpleGrantedAuthority::new).toList();
};
}
@Bean
public Jwt2AuthenticationConverter authenticationConverter(Jwt2AuthoritiesConverter authoritiesConverter) {
return jwt -> new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt));
}
@Bean
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Bean
public SecurityFilterChain resourceServerFilterChain(HttpSecurity http, Converter<Jwt, AbstractAuthenticationToken> authenticationConverter) throws Exception {
http
.authorizeHttpRequests()
.requestMatchers(HttpMethod.GET, "/api/**").hasAnyAuthority("EDIT")
.requestMatchers("/api/**").hasAuthority("EDIT")
.requestMatchers("/setup/**", "/*", "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt().jwtAuthenticationConverter(authenticationConverter);
return http.build();
}
public interface Jwt2AuthoritiesConverter extends Converter<Jwt, Collection<? extends GrantedAuthority>> {
}
public interface Jwt2AuthenticationConverter extends Converter<Jwt, AbstractAuthenticationToken> {
}
ExtWebSecurityConfig config for the Basic Auth:
@Configuration
@Order(1)
class ExtWebSecurityConfig {
@Bean
public SecurityFilterChain basicAuthFilterChain(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.securityMatcher("/ext/**")
.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
return http.build();
}
}
If i start the application, the JWT Keycloak ressource is secured correctly. If i access the enpoint /ext/test and provide the Basic-Auth user and password from my configuration it results to "Unauthorized 401". URL: http://localhost:8080/ext/v1
This is the related application.yml configuration:
spring:
security:
user:
name: admin
password: admin
oauth2:
resourceserver:
jwt:
issuer-uri: ${keycloak.auth-server-url}/realms/${keycloak.realm}
Put the @Order
on SecurityFilterChain
beans (not the @Configuration
beans): what is important is the order in which it is evaluated at runtime, not the one it is built at startup.
Also, you might define a securityMatcher
for the SecurityFilterChain
with the lowest @Order
(the one evaluated first) based on Authorization header pattern, rather than request path.
For instance:
@Configuration
class WebSecurityConfig {
@Bean
@Order(1)
public SecurityFilterChain basicAuthFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher((HttpServletRequest request) -> {
return Optional.ofNullable(request.getHeader(HttpHeaders.AUTHORIZATION)).map(h -> {
return h.toLowerCase().startsWith("basic ");
}).orElse(false);
}).httpBasic(withDefaults())
.userDetailsService((String username) -> {
return new User(username, "", List.of());
}).authenticationManager(...)
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(configurer -> configurer.disable());
http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain resourceServerFilterChain(HttpSecurity http, Converter<Jwt, AbstractAuthenticationToken> authenticationConverter) throws Exception {
http
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(configurer -> configurer.disable())
.authorizeHttpRequests()
.requestMatchers(HttpMethod.GET, "/api/**").hasAnyAuthority("EDIT")
.requestMatchers("/api/**").hasAuthority("EDIT")
.requestMatchers("/setup/**", "/*", "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter);
return http.build();
}
}
Sample taken from there.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With