多维柔性作业调用_摆脱困境:从预定作业中调用安全方法
多維柔性作業(yè)調(diào)用
假設(shè)我們已經(jīng)實現(xiàn)了一個Spring支持的應(yīng)用程序,并使用Spring Security的方法安全性表達式對其進行了保護 。
我們的下一個任務(wù)是使用安全方法實施計劃作業(yè)。 更具體地說,我們必須實現(xiàn)一個計劃的作業(yè),該作業(yè)從我們的服務(wù)類中獲取一條消息,并將接收到的消息寫入日志。
讓我們開始吧。
本博客文章中描述的計劃作業(yè)使用在特定于配置文件的配置文件中配置的cron表達式。 如果您不知道如何執(zhí)行此操作,建議您閱讀我的博客文章,其中描述了如何使用帶有@Scheduled批注的特定于環(huán)境的cron表達式 。
我們的第一次嘗試
讓我們創(chuàng)建一個計劃的作業(yè),該作業(yè)調(diào)用受保護的方法并找出執(zhí)行作業(yè)時發(fā)生的情況。 讓我們先來看一下示例應(yīng)用程序的服務(wù)層。
服務(wù)層
安全服務(wù)類的方法在MessageService接口中聲明。 它聲明了一個稱為getMessage()的方法,并指定只有具有角色ROLE_USER的用戶才能調(diào)用它。
MessageService接口的源代碼如下所示:
import org.springframework.security.access.prepost.PreAuthorize;public interface MessageService {@PreAuthorize("hasRole('ROLE_USER')")public String getMessage(); }我們對MessageService接口的實現(xiàn)非常簡單。 其源代碼如下:
import org.springframework.stereotype.Service;@Service public class HelloMessageService implements MessageService {@Overridepublic String getMessage() {return "Hello World!";} }讓我們繼續(xù)并創(chuàng)建調(diào)用getMessage()方法的計劃作業(yè)。
創(chuàng)建計劃的作業(yè)
我們可以按照以下步驟創(chuàng)建計劃的作業(yè):
我們計劃的作業(yè)的源代碼如下:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;@Component public class ScheduledJob {private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);private final MessageService messageService;@Autowiredpublic ScheduledJob(MessageService messageService) {this.messageService = messageService;}@Scheduled(cron = "${scheduling.job.cron}")public void run() {String message = messageService.getMessage();LOGGER.debug("Received message: {}", message);} }讓我們看看調(diào)用ScheduledJob類的run()方法時會發(fā)生什么。
它不起作用
當執(zhí)行我們的計劃作業(yè)時,將拋出AuthenticationCredentialsNotFoundException ,并且我們看到以下堆棧跟蹤:
2013-12-10 19:45:19,001 ERROR - kUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task. org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContextat org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:339)at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:198)at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)at com.sun.proxy.$Proxy31.getMessage(Unknown Source)at net.petrikainulainen.spring.trenches.scheduling.job.ScheduledJobTwo.run(ScheduledJobTwo.java:26)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:601)at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:64)at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:53)at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81)at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)at java.util.concurrent.FutureTask.run(FutureTask.java:166)at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178)at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)at java.lang.Thread.run(Thread.java:722)該stacktrace實際上非常有用。 它告訴我們安全方法無法調(diào)用,因為從SecurityContext中找不到Authentication對象。
我看到的兩個最常見的解決方案是:
- 創(chuàng)建一個與受保護的方法具有相同功能的單獨方法,然后修改計劃的作業(yè)以使用此方法。 此方法通常具有Javadoc注釋,該注釋指出只有計劃的作業(yè)才能調(diào)用此方法。 這個解決方案有兩個問題:1)它會使代碼庫混亂,并且2)最終無論如何都將調(diào)用該方法(除非真正需要,否則沒人真正閱讀Javadocs)。
- 從計劃作業(yè)調(diào)用的方法中刪除方法安全注釋。 由于明顯的原因,這是一個非常糟糕的解決方案。 提示:該方法的安全是有充分理由的!
幸運的是,還有第三種方法可以解決此問題。 讓我們開始查找計劃作業(yè)使用的安全上下文的存儲位置。
安全上下文從何而來?
我們的問題的解決方案很明確:我們必須創(chuàng)建一個Authentication對象,然后在調(diào)用安全方法之前將其添加到SecurityContext中 。
但是,在對示例應(yīng)用程序進行必要的修改之前,我們必須了解SecurityContext對象的存儲位置。
如果未進行其他配置,則將安全上下文存儲到ThreadLocal 。 換句話說,每個線程都有其自己的安全上下文。 這意味著在同一線程中執(zhí)行的所有計劃作業(yè)均共享相同的安全上下文。
假設(shè)我們有三個預(yù)定的作業(yè)。 這些作業(yè)稱為A , B和C。 另外,我們假設(shè)這些作業(yè)是按字母順序執(zhí)行的。
如果我們使用只有一個線程的默認線程池,則所有作業(yè)共享相同的安全上下文。 如果作業(yè)B將身份驗證對象設(shè)置為安全上下文,則執(zhí)行計劃的作業(yè)時會發(fā)生以下情況:
- 作業(yè)A無法調(diào)用安全方法,因為它在作業(yè)B之前執(zhí)行。 這意味著從安全上下文中找不到身份驗證對象。
- 作業(yè)B可以調(diào)用安全方法,因為作業(yè)B在嘗試調(diào)用安全方法之前將Authentication對象設(shè)置為安全上下文。
- 作業(yè)C可以調(diào)用安全方法,因為它是在將身份驗證對象設(shè)置為安全上下文的作業(yè)B之后執(zhí)行的。
如果我們使用具有多個線程的線程池,則每個線程都有其自己的安全上下文。 如果作業(yè)A將Authentication對象設(shè)置為安全上下文,則在同一線程中執(zhí)行的所有作業(yè)都將使用相同的特權(quán)執(zhí)行,只要它們在作業(yè)A之后執(zhí)行即可。
讓我們一步一步地完成每一項工作:
- 作業(yè)A可以調(diào)用安全方法,因為作業(yè)A在嘗試調(diào)用安全方法之前將Authentication對象設(shè)置為安全上下文。
- 如果作業(yè)B 與作業(yè)A在同一線程中執(zhí)行,則作業(yè)B可以調(diào)用安全方法。 如果未在同一線程中執(zhí)行作業(yè),則無法調(diào)用安全方法,因為無法從安全上下文中找到Authentication對象。
- 如果作業(yè)C 與作業(yè)A在同一線程中執(zhí)行,則作業(yè)C可以調(diào)用安全方法。 如果未在同一線程中執(zhí)行作業(yè),則無法調(diào)用安全方法,因為無法從安全上下文中找到Authentication對象。
顯然,解決此問題的最佳方法是確保使用所需的特權(quán)執(zhí)行每個計劃的作業(yè)。 此解決方案有兩個好處:
- 我們可以按任何順序執(zhí)行工作。
- 我們不必確保作業(yè)在“正確的”線程中執(zhí)行。
讓我們找出當我們的應(yīng)用程序使用Spring Security 3.1時如何解決這個問題。
Spring Security 3.1:需要手動工作
如果我們的應(yīng)用程序使用Spring Security 3.1,則解決問題的最簡單方法是
- 在我們的工作嘗試調(diào)用安全方法之前,創(chuàng)建一個Authentication對象并將其設(shè)置為安全上下文。
- 在作業(yè)完成之前,從安全上下文中刪除身份驗證對象。
讓我們從創(chuàng)建提供所需方法的AuthenticationUtil類開始。
創(chuàng)建AuthenticationUtil類
我們可以按照以下步驟創(chuàng)建AuthenticationUtil類:
AuthenticationUtil類的源代碼如下所示:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder;import java.util.Collection;public final class AuthenticationUtil {//Ensures that this class cannot be instantiatedprivate AuthenticationUtil() {}public static void clearAuthentication() {SecurityContextHolder.getContext().setAuthentication(null);}public static void configureAuthentication(String role) {Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(role);Authentication authentication = new UsernamePasswordAuthenticationToken("user",role,authorities);SecurityContextHolder.getContext().setAuthentication(authentication);} }我們還沒有完成。 我們?nèi)匀槐仨殞ξ覀兊念A(yù)定工作進行一些修改。 讓我們找出如何進行這些修改。
修改計劃的作業(yè)
我們必須對ScheduledJob類進行兩次修改。 我們可以按照以下步驟進行修改:
ScheduledJob類的源代碼如下所示:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;@Component public class ScheduledJob {private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);private final MessageService messageService;@Autowiredpublic ScheduledJob(MessageService messageService) {this.messageService = messageService;}@Scheduled(cron = "${scheduling.job.cron}")public void run() {AuthenticationUtil.configureAuthentication("ROLE_USER");String message = messageService.getMessage();LOGGER.debug("Received message: {}", message);AuthenticationUtil.clearAuthentication();} }讓我們找出運行預(yù)定作業(yè)時會發(fā)生什么。
運行計劃的作業(yè)
調(diào)用作業(yè)時,以下消息將寫入日志:
2013-12-17 20:41:33,019 DEBUG - ScheduledJob ? ? ? ? ? ?- Received message: Hello World!當我們的應(yīng)用程序使用Spring Security 3.1時,一切都將正常運行。 我們的解決方案不是那么優(yōu)雅,但可以。 該解決方案的明顯缺點是,我們必須記住在計劃的作業(yè)中調(diào)用AuthenticationUtil類的configureAuthentication()和clearAuthentication()方法。
Spring Security 3.2解決了這個問題。 讓我們繼續(xù)前進,找出當我們的應(yīng)用程序使用Spring Security 3.2時如何解決這個問題。
Spring Security 3.2:幾乎就像魔術(shù)一樣!
Spring Security 3.2具有全新的并發(fā)支持 ,這使我們可以將安全上下文從一個線程轉(zhuǎn)移到另一個線程。 讓我們找出如何配置應(yīng)用程序上下文以使用Spring Security 3.2提供的功能。
配置應(yīng)用程序上下文
因為我們要使用Spring Security 3.2的新并發(fā)支持,所以我們必須對應(yīng)用程序上下文配置類進行以下更改( 原始配置在此博客文章中進行了描述 ):
ExampleApplicationContext類的源代碼如下所示(相關(guān)部分已突出顯示):
import org.springframework.context.annotation.*; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.io.ClassPathResource; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder;import java.util.Collection; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService;@Configuration @EnableScheduling @ComponentScan(basePackages = {"net.petrikainulainen.spring.trenches.scheduling" }) @Import(ExampleSecurityContext.class) @PropertySource("classpath:application.properties") public class ExampleApplicationContext implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.setScheduler(taskExecutor());}@Beanpublic Executor taskExecutor() {ScheduledExecutorService delegateExecutor = Executors.newSingleThreadScheduledExecutor();SecurityContext schedulerContext = createSchedulerSecurityContext();return new DelegatingSecurityContextScheduledExecutorService(delegateExecutor, schedulerContext);}private SecurityContext createSchedulerSecurityContext() {SecurityContext context = SecurityContextHolder.createEmptyContext();Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");Authentication authentication = new UsernamePasswordAuthenticationToken("user","ROLE_USER",authorities);context.setAuthentication(authentication);return context;}@Beanpublic PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();properties.setLocation(new ClassPathResource( "application.properties" ));properties.setIgnoreResourceNotFound(false);return properties;} }這就對了。 此配置確保每個計劃的作業(yè)都可以訪問由createSchedulerSecurityContext()方法創(chuàng)建的SecurityContext對象。 這意味著每個計劃的作業(yè)都可以調(diào)用安全的方法,這些方法可以由角色為“ ROLE_USER”的用戶調(diào)用。
讓我們快速看一下我們的預(yù)定工作。
那預(yù)定的工作呢?
該解決方案的最好之處在于,我們不必對ScheduledJob類進行任何更改。 其源代碼如下:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;@Component public class ScheduledJob {private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledJob.class);private final MessageService messageService;@Autowiredpublic ScheduledJob(MessageService messageService) {this.messageService = messageService;}@Scheduled(cron = "${scheduling.job.cron}")public void run() {String message = messageService.getMessage();LOGGER.debug("Received message: {}", message);} }調(diào)用計劃作業(yè)時,將以下行寫入日志:
2013-12-17 21:12:14,012 DEBUG - ScheduledJob - Received message: Hello World!很酷 對?
摘要
現(xiàn)在,我們已經(jīng)成功創(chuàng)建了可以調(diào)用安全方法的計劃作業(yè)。 本教程教會了我們?nèi)?#xff1a;
- 我們了解到,通常將SecurityContext對象存儲到ThreadLocal中 ,這意味著在同一線程中執(zhí)行的所有計劃作業(yè)均共享相同的安全上下文
- 我們了解到,如果我們的應(yīng)用程序使用Spring Security 3.1,并且希望從計劃的作業(yè)中調(diào)用安全方法,則最簡單的方法是在每個計劃的作業(yè)中配置使用的Authentication對象。
- 我們學(xué)習(xí)了如何使用Spring Security 3.2的并發(fā)支持,以及如何將SecurityContext對象從一個線程轉(zhuǎn)移到另一個線程。
您可以從Github( Spring Security 3.1和Spring Security 3.2 )獲得此博客文章的示例應(yīng)用程序。
注意: Spring Security 3.2示例的XML配置目前無法正常工作。 如果有時間,我會修復(fù)它。
翻譯自: https://www.javacodegeeks.com/2014/01/spring-from-the-trenches-invoking-a-secured-method-from-a-scheduled-job.html
多維柔性作業(yè)調(diào)用
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的多维柔性作业调用_摆脱困境:从预定作业中调用安全方法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Excel三种求和方式Excel求和的三
- 下一篇: TellDontAsk的扩展