Hey there! I am trying to use Keycloak with Spring security but i cannot get it to work correctly. For some reason it tries to authenticate every request, it calls KeycloakAuthenticationProcessingFilter even tho Spring security already has a populated Authentication object in SecurityContextHolder. I don’t know if that’s how it should work. Plus i can’t get it to log out, I always get this error: o.k.a.RefreshableKeycloakSecurityContext : failed to invoke remote logout
org.keycloak.adapters.ServerRequest$HttpFailure: null
here’s my application.properties:
keycloak.realm=realm
keycloak.resource=client
keycloak.public-client=true
keycloak.ssl-required=external
keycloak.cors=true
keycloak.enabled=true
keycloak.bearer-only=false
keycloak.enable-basic-auth=true
keycloak.principal-attribute=preferred_username
client-secret={client_secret}
and here's my configuration class:
@Configuration
@KeycloakConfiguration
@EnableWebSecurity
public class KeyCloakConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(
AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider
= keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(
new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(sessionRegistry());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/v2/api-docs",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/swagger-resources",
"/swagger-resources/configuration/ui",
"/swagger-resources/configuration/security",
"/webjars/**").permitAll()
.antMatchers("/models/**", "/user")
.authenticated()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
}
}
@Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
@Override
@ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
@Bean
@Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
RequestMatcher requestMatcher =
new OrRequestMatcher(
new AntPathRequestMatcher("/sso/login"),
new QueryParamPresenceRequestMatcher(OAuth2Constants.ACCESS_TOKEN),
new IgnoreKeycloakProcessingFilterRequestMatcher());
return new KeycloakAuthenticationProcessingFilter(authenticationManagerBean(), requestMatcher);
}
private static class IgnoreKeycloakProcessingFilterRequestMatcher implements RequestMatcher {
public boolean matches(HttpServletRequest request) {
String authorizationHeaderValue = request.getHeader("Authorization");
return authorizationHeaderValue != null && !authorizationHeaderValue.startsWith("Basic ");
}
}
}
and this is how i get user token through openid:
private TokenDto getToken(String username, String password, String clientId, String realm) {
String tokenUri = cloakUri + "/realms/" + realm + "/protocol/openid-connect/token";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("password", password);
map.add("username", username);
map.add("grant_type", "password");
map.add("client_id", clientId);
map.add("client_secret", clientSecret);
map.add("scope", "openid");
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(map, headers);
ResponseEntity<Token> response = restTemplate.exchange(tokenUri, HttpMethod.POST, entity, Token.class);
if (response.getBody() != null) {
return new TokenDto(response.getBody().getAccessToken());
}
return null;
}