[keycloak-user] Can I pass a principal with rest template if I'm using an async task wiht Spring Boot and Keycloak?

Dominik Guhr pinguwien at gmail.com
Wed May 2 13:12:29 EDT 2018


Hi Matteo,

to use @async etc. together with keycloak, you want to use another 
strategy for your securitycontextholder in spring. Spring uses a 
ThreadLocal SecurityContextHolder per default, which gets lost when 
you're executing an asynchronous task and thus spawn a new thread, by 
using @async, or @scheduled or s.th. similar.

For an example securityconfig, please have a look at my demo's config 
located here: 
https://github.com/Pinguwien/guestbook-backend/blob/master/src/main/java/de/codecentric/demo/guestbook/infrastructure/environment/spring/config/SecurityConfig.java

my related blogposts are in german, the one regarding communication and 
@async is available at 
https://blog.codecentric.de/2017/09/keycloak-und-spring-security-teil-3-kommunikation-via-keycloakresttemplate/

I have it on my list for months now to translate them to english, but... 
well, work work work work work ;-)

Hope that helps!

Best regards,
Dominik

Am 02.05.18 um 18:46 schrieb Matteo Salvetti:
> Hi all,
> 
> I'm using Spring Boot and Keycloak to develop a web-app. Then I wrote a
> scheduled task where I'm using the KeycloakRestTemplate to ask some data to
> another app, as you can see below:
> 
>      @Override
>      @Scheduled(cron="0 50 09 * * MON-FRI")
>      public void concludiCommessa() {
> 
>          try {
>              FDto[] ftts = new
> ObjectMapper().readValue(restTemplate.getForEntity(URI.create(MY_URL),
> String.class).getBody(), FDto[].class);
> 
>               ..............................
>              }
>          } catch (RestClientException | IOException e) {
>          }
>      }
> 
> If I run it on the server I have the following error:
> 
> 2018-04-18 09:50:00.067 ERROR 2503 --- [pool-8-thread-1]
> o.s.s.s.TaskUtils$LoggingErrorHandler    : Unexpected error occurred
> in scheduled task.
> 
> java.lang.IllegalStateException: Cannot set authorization header
> because there is no authenticated principal
>      at org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory.getKeycloakSecurityContext(KeycloakClientRequestFactory.java:70)
> ~[keycloak-spring-security-adapter-3.4.2.Final.jar:3.4.2.Final]
>      at org.keycloak.adapters.springsecurity.client.KeycloakClientRequestFactory.postProcessHttpRequest(KeycloakClientRequestFactory.java:55)
> ~[keycloak-spring-security-adapter-3.4.2.Final.jar:3.4.2.Final]
>      at org.springframework.http.client.HttpComponentsClientHttpRequestFactory.createRequest(HttpComponentsClientHttpRequestFactory.java:207)
> ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
>      at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.java:85)
> ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
>      at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:656)
> ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
>      at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:636)
> ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
>      at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:336)
> ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
>      at it.edile.service.api.ApiServiceImpl.concludiCommessa(ApiServiceImpl.java:287)
> ~[classes/:0.0.1-SNAPSHOT]
>      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
> ~[na:1.8.0_161]
>      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
> ~[na:1.8.0_161]
>      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
> ~[na:1.8.0_161]
>      at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
>      at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
> ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
>      at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
> ~[spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
>      at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)
> [spring-context-4.3.14.RELEASE.jar:4.3.14.RELEASE]
>      at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
> [na:1.8.0_161]
>      at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_161]
>      at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
> [na:1.8.0_161]
>      at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
> [na:1.8.0_161]
>      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
> [na:1.8.0_161]
>      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
> [na:1.8.0_161]
>      at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]
> 
> Why?
> 
> How can I pass a principal if I'm using an async task?
> 
> This is my security configuration:
> 
> @Autowired
> public void configureGlobal(AuthenticationManagerBuilder auth) {
>      auth.authenticationProvider(keycloakAuthenticationProvider());
> }
> 
> @Bean
> @Override
> protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
>      return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
> }
> 
> @Bean
> @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
> public KeycloakRestTemplate keycloakRestTemplate() {
>      return new KeycloakRestTemplate(keycloakClientRequestFactory);
> }
> 
> @Bean
> public KeycloakConfigResolver keycloakConfigResolver() {
>      return new KeycloakSpringBootConfigResolver();
> }
> 
>   This is my keycloak properties:
> 
> #######################################
> #             KEYCLOAK                #
> #######################################
> keycloak.realm=MY_REALM
> keycloak.auth-server-url=MY_URL/auth
> keycloak.ssl-required=external
> keycloak.resource=EdilGest
> keycloak.credentials.jwt.client-key-password=PWD
> keycloak.credentials.jwt.client-keystore-file=classpath:CLIENT.jks
> keycloak.credentials.jwt.client-keystore-password=PWD
> keycloak.use-resource-role-mappings=true
> keycloak.principal-attribute=preferred_username
> 
> I'm also trying to use the Service Account now, but it doesn't work at the
> moment... Reading here:
> https://www.keycloak.org/docs/latest/server_admin/index.html#_service_accounts
> 
> I have to send a request like:
> 
> POST /auth/realms/demo/protocol/openid-connect/token
>      Authorization: Basic cHJvZHVjdC1zYS1jbGllbnQ6cGFzc3dvcmQ=
>      Content-Type: application/x-www-form-urlencoded
> 
>      grant_type=client_credentials
> 
> to keycloak, but how can I send it using Spring? and how can I set the jks
> instead of client and secret?
> 
> 
> My security config
> 
> onfiguration
> @EnableWebSecurity
> @EnableGlobalMethodSecurity(prePostEnabled=true)
> @KeycloakConfiguration
> public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
> 
>      @Autowired
>      public KeycloakClientRequestFactory keycloakClientRequestFactory;
> 
>      @Override
>      protected void configure(HttpSecurity http) throws Exception {
>          super.configure(http);
> 
>          http
>              .httpBasic()
>              .disable();
> 
>          http
>          .authorizeRequests()
>              .antMatchers("/webjars/**").permitAll()
>              .antMatchers("/resources/**").permitAll()
>              .anyRequest().hasAuthority("......")
>          .and()
>          .logout()
>              .logoutUrl("/logout")
>              .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
>              .permitAll()
>              .logoutSuccessUrl(mux)
>              .invalidateHttpSession(true);
> 
>      }
> 
>      @Autowired
>      public void configureGlobal(AuthenticationManagerBuilder auth) {
>          auth.authenticationProvider(keycloakAuthenticationProvider());
>      }
> 
>      @Bean
>      @Override
>      protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
>          return new RegisterSessionAuthenticationStrategy(new
> SessionRegistryImpl());
>      }
> 
>      @Bean
>      @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
>      public KeycloakRestTemplate keycloakRestTemplate() {
>          return new KeycloakRestTemplate(keycloakClientRequestFactory);
>      }
> 
>      @Bean
>      public KeycloakConfigResolver keycloakConfigResolver() {
>          return new KeycloakSpringBootConfigResolver();
>      }
> 
>      @Bean
>      public FilterRegistrationBean
> keycloakAuthenticationProcessingFilterRegistrationBean(KeycloakAuthenticationProcessingFilter
> filter) {
>          FilterRegistrationBean registrationBean = new
> FilterRegistrationBean(filter);
>          registrationBean.setEnabled(false);
>          return registrationBean;
>      }
> 
>      @Bean
>      public FilterRegistrationBean
> keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter
> filter) {
>          FilterRegistrationBean registrationBean = new
> FilterRegistrationBean(filter);
>          registrationBean.setEnabled(false);
>          return registrationBean;
>      }
> 
>      @Override
>      public void configure(WebSecurity web) throws Exception {
>          web
>             .ignoring()
>             .antMatchers("/resources/**", "/static/**", "/css/**",
> "/js/**", "/images/**", "/webjars/**");
>      }
> 
> }
> 
> 
> If you want, please take a look here:
> https://stackoverflow.com/questions/49900124/can-i-pass-a-principal-with-rest-template-if-im-using-an-async-task-wiht-spring
> _______________________________________________
> keycloak-user mailing list
> keycloak-user at lists.jboss.org
> https://lists.jboss.org/mailman/listinfo/keycloak-user
> 


More information about the keycloak-user mailing list