It seems that configuring Keycloak is impossible to do in new version of Spring Boot 3 and Spring Security.
I tried writing SecurityFilterChain but first it skipped the whole Chain and allowed everyone who has valid bearer token to view protected resource.
I found out that Spring Boot isn't picking up roles and putting them in Granted Authorities, just scope from JWT. Is it good idea to write my own token converter?
WebSecurityConfig.java
@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class WebSecurityConfig {
private final JwtAuthConverter jwtAuthConverter;
@Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(jwtAuthConverter);
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**","api/**","/api").hasAuthority(ADMINISTRATION)
.anyRequest().authenticated()
);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.cors().and().csrf().disable();
return http.build();
}
@Bean
public JwtDecoder jwtDecoder(OAuth2ResourceServerProperties oAuth2ResourceServerProperties) {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(oAuth2ResourceServerProperties.getJwt().getJwkSetUri()).build();
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithIssuer(oAuth2ResourceServerProperties.getJwt().getIssuerUri()));
return jwtDecoder;
}
private static final String ADMINISTRATION = "ROLE_administration";
}
When I added @Configuration
annotation to the Class all requests were rejected.
Here are the classes if you need them to answer the question.
JwtAuthConverter.java
@Component
public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {
private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
private final JwtAuthConverterProperties properties;
public JwtAuthConverter(JwtAuthConverterProperties properties) {
this.properties = properties;
}
@Override
public AbstractAuthenticationToken convert(Jwt jwt) {
Collection<GrantedAuthority> authorities = Stream.concat(
jwtGrantedAuthoritiesConverter.convert(jwt).stream(),
extractResourceRoles(jwt).stream()).collect(Collectors.toSet());
return new JwtAuthenticationToken(jwt, authorities, getPrincipalClaimName(jwt));
}
private String getPrincipalClaimName(Jwt jwt) {
String claimName = JwtClaimNames.SUB;
if (properties.getPrincipalAttribute() != null) {
claimName = properties.getPrincipalAttribute();
}
return jwt.getClaim(claimName);
}
private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) {
Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
Map<String, Object> resource;
Collection<String> resourceRoles;
if (resourceAccess == null
|| (resource = (Map<String, Object>) resourceAccess.get(properties.getResourceId())) == null
|| (resourceRoles = (Collection<String>) resource.get("roles")) == null) {
return Collections.emptySet();
}
return resourceRoles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toSet());
}
}
JwtAuthConverter.java
@Data
@Validated
@Configuration
@ConfigurationProperties(prefix = "jwt.auth.converter")
public class JwtAuthConverterProperties {
@NotBlank
private String resourceId;
private String principalAttribute;
}