[SPRING] keycloak을 사용하여 웹 페이지를 제공하는 클라이언트와 함께 예약 된 작업을 사용하려면 어떻게해야합니까?
SPRINGkeycloak을 사용하여 웹 페이지를 제공하는 클라이언트와 함께 예약 된 작업을 사용하려면 어떻게해야합니까?
나는 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
키 클로킹을 할 수는 있지만 어떻게 스프링을 사용하여 보낼 수 있습니까? 클라이언트와 비밀 대신 jks를 설정하려면 어떻게해야합니까?
EDIT 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();
내가 뭘 잘못하고 있죠?
해결법
-
==============================
1.기본적으로 SecurityContextHolder는 THREAD_LOCAL 전략을 사용하므로 다른 스레드는 부모로부터 컨텍스트를 상속받지 않습니다. MODE_INHERITABLETHREADLOCAL에 대한 Javadoc을보고 자신의 필요에 맞는 지 확인하십시오.
기본적으로 SecurityContextHolder는 THREAD_LOCAL 전략을 사용하므로 다른 스레드는 부모로부터 컨텍스트를 상속받지 않습니다. MODE_INHERITABLETHREADLOCAL에 대한 Javadoc을보고 자신의 필요에 맞는 지 확인하십시오.
-
==============================
2.귀하의 방법은 @ 일정이 호출자 스레드와 다른 스레드에서 실행됩니다. 스레드 로컬 인 SecurityContextHolder는 보안 컨텍스트를 예약 된 스레드에 전파하지 않습니다. 하나의 방법은 @Configuration 클래스의 기본 생성자에이 줄을 추가하여 SecurityContextHolder의 기본 전략을 THREAD_LOCAL에서 MODE_INHERITABLETHREADLOCAL로 변경하는 것입니다.
귀하의 방법은 @ 일정이 호출자 스레드와 다른 스레드에서 실행됩니다. 스레드 로컬 인 SecurityContextHolder는 보안 컨텍스트를 예약 된 스레드에 전파하지 않습니다. 하나의 방법은 @Configuration 클래스의 기본 생성자에이 줄을 추가하여 SecurityContextHolder의 기본 전략을 THREAD_LOCAL에서 MODE_INHERITABLETHREADLOCAL로 변경하는 것입니다.
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
그러나 유스 케이스에 따라 보안 컨텍스트에 전달하지 않을 수도있는 중요한 정보가 포함될 수 있음을 알아야합니다.
-
==============================
3.아래 봄과 같은 요청을 보내려면
아래 봄과 같은 요청을 보내려면
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을 실제 값으로 바꿉니다.
최신 정보:-
귀하의 질문에 언급 한 링크를 참조, 당신은 자체 jct에 의해 jwt 무기명 토큰 (클라이언트 액세스 토큰)을 생성 할 수 있습니다. jwt 토큰을 얻으면 리소스 서버에 대한 후속 요청에 헤더가 있어야합니다.
Authorization: Bearer <jwt bearer token>
@Xtreme Biker가 Keycloak 스프링 보안 클라이언트 자격증 명칭 부여에 관한 게시물에서 그는 인터셉터 접근법을 사용하여이를 달성 할 수있는 방법에 대한 몇 가지 샘플 코드를 제공했습니다.
추가 업데이트 : -
keycloak docs에 따르면 - https://www.keycloak.org/docs/3.1/securing_apps/topics/oidc/java/java-adapter-config.html
jks를 설정하려면 application.properties 파일의 특성을 따라야한다.
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 토큰은 KeycloakSecurityContext에서 토큰 문자열을 가져 오는 KeycloakClientRequestFactory를 사용하기 때문에 요청의 권한 헤더에 추가됩니다.
이 문서에 제안 된 모든 구성을 모든 필터 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 SecurityContext 대신 InheritableThreadLocal이 사용되며 childthreads를 생성 할 때이 정보를 전달합니다.
public SecurityConfig ( KeycloakClientRequestFactory keycloakClientRequestFactory ) { this.keycloakClientRequestFactory = keycloakClientRequestFactory ; // to use principal and authentication together with @ async SecurityContextHolder.setStrategyName ( SecurityContextHolder.MODE_INHERITABLETHREADLOCAL ) ; }
참고 - https://translate.google.co.in/translate?hl=ko&sl=de&u=https://blog.codecentric.de/2017/09/keycloak-und-spring-security-teil-3-kommunikation- via-keycloakresttemplate / & prev = 자세한 내용을 검색하십시오.
희망이 당신의 문제를 해결하는 방법을 당신을 도울 것입니다.
from https://stackoverflow.com/questions/49900124/how-can-i-use-a-scheduled-task-with-a-client-that-provides-also-web-page-using-k by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] 바람둥이에 봄 부트 전쟁 파일 : errorPageFilter는 TomcatEmbeddedServletContainerFactory에 캐스팅 될 수 없습니다. (0) | 2019.05.15 |
---|---|
[SPRING] JBoss (6.2.0 GA)에서 EJB Jar에서 잭슨 2 주석이 무시되었습니다. (0) | 2019.05.15 |
[SPRING] Spring 데이터 나머지 : 다른 객체의 키로 검색하는 방법? (0) | 2019.05.15 |
[SPRING] Spring REST 모의 컨텍스트 경로 (0) | 2019.05.15 |
[SPRING] 새 트랜잭션 범위 내에서 개체 업데이트 (0) | 2019.05.15 |