如何使用客户端使用keycloak提供网页? [英] How can I use a scheduled task with a client that provides also web-page using keycloak?

查看:1375
本文介绍了如何使用客户端使用keycloak提供网页?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Spring Boot Keycloak 来开发网络应用程序。
然后我编写了一个计划任务,我正在使用 KeycloakRestTemplate 向一个应用程序询问一些数据,如下所示:

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]

为什么?

如果我使用异步任务,如何传递本金?

How can I pass a principal if I'm using an async task?

编辑
这是我的安全配置:

EDIT 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();
}

编辑这是我的keycloak属性:

EDIT 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

编辑

我正在尝试使用服务帐户,但它现在不起作用......请在此处阅读: https ://www.keycloak.org/docs/latest/ server_admin / index.html #_service_accounts

I'm 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

我必须发送如下请求:

POST /auth/realms/demo/protocol/openid-connect/token
    Authorization: Basic cHJvZHVjdC1zYS1jbGllbnQ6cGFzc3dvcmQ=
    Content-Type: application/x-www-form-urlencoded

    grant_type=client_credentials

到keycloak,但我怎么能用Spring发送它?如何设置jks而不是客户端和秘密?

to keycloak, but how can I send it using Spring? and how can I set the jks instead of client and secret?

编辑2

我的安全配置

@Configuration
@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/**");
    }

}

编辑3
这里我尝试了...它不起作用..我有同样的错误: java.lang.IllegalStateException:无法设置授权头,因为没有经过身份验证的主体

KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("EdilGest.jks"), "EdilGest".toCharArray());

JWTClientCredentialsProvider jwtClientCredentialsProvider = new JWTClientCredentialsProvider();
jwtClientCredentialsProvider.setupKeyPair(keyStoreKeyFactory.getKeyPair("MyClient"));
String token = jwtClientCredentialsProvider.createSignedRequestToken("MyClient", "http://myKeycloak/auth/");

String data = "grant_type=client_credentials" ;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add(HttpHeaders.AUTHORIZATION, "Bearer " +token);

HttpEntity<String> requestEntity = new HttpEntity<String>(data, headers);
String ftt = keycloakRestTemplate.exchange(URI.create(MyUrl), HttpMethod.POST, requestEntity, String.class).getBody();

我做错了什么?

推荐答案

如果你想通过春天发送如下的请求

If you want to send a request like below through spring

POST /auth/realms/demo/protocol/openid-connect/token
    Authorization: Basic cHJvZHVjdC1zYS1jbGllbnQ6cGFzc3dvcmQ=
    Content-Type: application/x-www-form-urlencoded

    grant_type=client_credentials

你需要的是什么

RestTemplate template = new RestTemplate();
String uri = "https://host:port/auth/realms/demo/protocol/openid-connect/token";
String data = "grant_type=client_credentials" ;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add(HttpHeaders.AUTHORIZATION, "Basic " + Base64Utils.encodeToString("clientId:clientSecret".getBytes()));
HttpEntity<String> requestEntity = new HttpEntity<String>(data, headers);

ResponseEntity<JsonNode> result = template.exchange(uri, HttpMethod.POST, requestEntity, JsonNode.class);
JsonNode jn = result.getBody();
String access_token = jn.get("access_token").asText();

替换 clientId clientSecret 具有实际值。

更新: -

参考您在问题中提到的链接,您可以通过keycloak本身生成jwt bearer token(客户端访问令牌)。获得jwt令牌后,您对资源服务器的后续请求应包含标题

Referring to the link that you have mentioned in your question, you can generate the jwt bearer token (client access token) by keycloak itself. Once you get the jwt token, your subsequent requests to resource server should contain header

Authorization: Bearer <jwt bearer token>

@Xtreme Biker 关于主题Keycloak spring security客户端凭据授权,他提供了一些示例代码,可能为您提供了如何使用它拦截器方法。

In the post by @Xtreme Biker on topic Keycloak spring security client credential grant , he has provided some sample code for perhaps how you can achieve this using interceptor approach.

更新更新: -

根据keycloak docs - < a href =https://www.keycloak.org/docs/3.1/securing_apps/topics/oidc/java/java-adapter-config.html =nofollow noreferrer> https://www.keycloak.org /docs/3.1/securing_apps/topics/oidc/java/java-adapter-config.html

According to keycloak docs - https://www.keycloak.org/docs/3.1/securing_apps/topics/oidc/java/java-adapter-config.html

设置你应该拥有以下属性的jks application.properties文件。

to set the jks you should have following properties in your application.properties file.

keycloak.client-keystore-password=PWD
keycloak.client-keystore=classpath:CLIENT.jks
keycloak.client-key-password=PWD

并根据 - < a href =https://www.ke ycloak.org/docs/3.1/securing_apps/topics/oidc/java/spring-security-adapter.html\"rel =nofollow noreferrer> https://www.keycloak.org/docs/3.1/securing_apps/topics/oidc /java/spring-security-adapter.html ,一旦正确设置了jks, KeycloakRestTemplate 应该能够为您的请求添加适当的身份验证标头。

and according to - https://www.keycloak.org/docs/3.1/securing_apps/topics/oidc/java/spring-security-adapter.html , once you set the jks properly,KeycloakRestTemplate should be able to add proper authentication header to your request.

更新3: -

完成后 - https://github.com/keycloak/keycloak -documentation / blob / master / secured_apps / topics / oidc / java / spring-security-adapter.adoc ,我坚信 KeycloakRestTemplate 应该能够添加请求的授权标头所需的jwt标记,因为它使用 KeycloakClientRequestFactory KeycloakSecurityContext 获取标记字符串。

After going through - https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/java/spring-security-adapter.adoc , I firmly believe that KeycloakRestTemplate should be able to add the required jwt token to the authorization header of the request because it uses KeycloakClientRequestFactory which gets token string from KeycloakSecurityContext.

请尝试添加本文档中建议的所有配置,如所有过滤器bean配置

Please try to add all the configuration suggested in this doc like all the filter bean configurations

@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;
    }

    @Bean
    public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
            KeycloakAuthenticatedActionsFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
        KeycloakSecurityContextRequestFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

并确保在application.properties文件中提供所有必需的属性。

and ensure you provide all the required properties in the application.properties file.

因为您正在从预定任务中拍摄请求,这可能是asyc本质上所以您可能必须更改 SecurityConfig <的构造函数中的策略/ code>,这样代替 ThreadLocal SecurityContext 一个 InheritableThreadLocal ,它在生成childthreads时传递此信息。

Since you are shooting the request from scheduled task which may be asyc in nature so you may have to change the strategy in the constructor of your SecurityConfig, so that instead of a ThreadLocal SecurityContext an InheritableThreadLocal is used, which passes this information when spawning childthreads.

public SecurityConfig ( KeycloakClientRequestFactory keycloakClientRequestFactory ) {
         this.keycloakClientRequestFactory = keycloakClientRequestFactory ;

         // to use principal and authentication together with @ async
         SecurityContextHolder.setStrategyName ( SecurityContextHolder.MODE_INHERITABLETHREADLOCAL ) ;

     }

Ref - https://translate.google.co.in/translate?hl=en&sl=de&u=https://blog.codecentric.de/2017/ 09 / keycloak-und-spring-security-teil-3-kommunikation-via-keycloakresttemplate /& prev = search 了解更多详情。

Ref - https://translate.google.co.in/translate?hl=en&sl=de&u=https://blog.codecentric.de/2017/09/keycloak-und-spring-security-teil-3-kommunikation-via-keycloakresttemplate/&prev=search for more details.

希望这个将帮助您解决问题。

Hope this will help you some way in resolving your issue.

这篇关于如何使用客户端使用keycloak提供网页?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆