我正在使用Spring Boot
和Keycloak
来开发网络应用。
然后我编写了一个计划任务,我使用KeycloakRestTemplate
向另一个应用程序询问一些数据,如下所示:
@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) {
}
}
如果我在服务器上运行它,则会出现以下错误:
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]
为什么?
如果我使用异步任务,如何传递校长?
修改 这是我的安全配置:
@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属性:
#######################################
# 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
我必须发送一个请求:
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而不是客户端和秘密?
编辑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: Cannot set authorization header because there is no authenticated principal
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();
我做错了什么?
答案 0 :(得分:0)
默认情况下,SecurityContextHolder使用THREAD_LOCAL策略,因此其他线程不会从其父级继承上下文。 查看JAVadoc的MODE_INHERITABLETHREADLOCAL,看它是否符合您的需求。
答案 1 :(得分:0)
您的方法是@Scheduled,它在与调用者线程不同的线程中执行。作为线程本地的SecurityContextHolder不会将安全上下文传播到计划的线程。一种方法是通过在@Configuration类的默认构造函数中添加此行,将SecurityContextHolder的默认策略从THREAD_LOCAL更改为MODE_INHERITABLETHREADLOCAL。
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
但是根据您的使用情况,您应该知道安全上下文可能包含您可能不希望传递的敏感信息。
答案 2 :(得分:0)
如果您想通过春天发送如下所示的请求
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令牌后,您对资源服务器的后续请求应包含标题
Authorization: Bearer <jwt bearer token>
在 @Xtreme Biker 关于主题Keycloak spring security client credential grant的帖子中,他提供了一些示例代码,可能是如何使用拦截器方法实现此目的。
更新更新: -
根据keycloak文档 - https://www.keycloak.org/docs/3.1/securing_apps/topics/oidc/java/java-adapter-config.html
在application.properties文件中设置应具有以下属性的jks。
keycloak.client-keystore-password=PWD
keycloak.client-keystore=classpath:CLIENT.jks
keycloak.client-key-password=PWD
并根据 - https://www.keycloak.org/docs/3.1/securing_apps/topics/oidc/java/spring-security-adapter.html,一旦您正确设置了jks,KeycloakRestTemplate
应该能够为您的请求添加适当的身份验证标头。
更新3: -
经过 - https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/java/spring-security-adapter.adoc后,我坚信KeycloakRestTemplate
应该能够将所需的jwt令牌添加到请求的授权标头中,因为它使用KeycloakClientRequestFactory
来获取令牌字符串来自KeycloakSecurityContext
。
请尝试添加本文档中建议的所有配置,例如所有过滤器bean配置
@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文件中提供所有必需的属性。
由于您正在拍摄来自计划任务的请求,这可能是asyc性质,因此您可能需要更改SecurityConfig
的构造函数中的策略,以便代替ThreadLocal
{{1使用SecurityContext
,它在生成childthreads时传递此信息。
InheritableThreadLocal
希望这可以帮助您解决问题。