日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

摆脱困境:从计划作业中调用安全方法

發布時間:2023/12/3 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 摆脱困境:从计划作业中调用安全方法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

假設我們已經實現了一個Spring支持的應用程序,并使用Spring Security的方法安全性表達式對其進行了保護 。

我們的下一個任務是使用安全方法實施計劃作業。 更具體地說,我們必須實現一個計劃的作業,該作業從我們的服務類中獲取一條消息,并將接收到的消息寫到日志中。

讓我們開始吧。

本博客文章中描述的計劃作業使用在特定于配置文件的配置文件中配置的cron表達式。 如果您不知道如何執行此操作,建議您閱讀我的博客文章,其中描述了如何使用帶有@Scheduled批注的特定于環境的cron表達式 。

我們的第一次嘗試

讓我們創建一個計劃的作業,該作業調用安全方法并找出執行作業時發生的情況。 讓我們先來看一下示例應用程序的服務層。

服務層

安全服務類的方法在MessageService接口中聲明。 它聲明了一個稱為getMessage()的方法,并指定只有具有角色ROLE_USER的用戶才能調用它。

MessageService接口的源代碼如下所示:

import org.springframework.security.access.prepost.PreAuthorize;public interface MessageService {@PreAuthorize("hasRole('ROLE_USER')")public String getMessage(); }

我們對MessageService接口的實現非常簡單。 其源代碼如下:

import org.springframework.stereotype.Service;@Service public class HelloMessageService implements MessageService {@Overridepublic String getMessage() {return "Hello World!";} }

讓我們繼續并創建調用getMessage()方法的計劃作業。

創建計劃的作業

我們可以按照以下步驟創建計劃的作業:

  • 創建一個ScheduledJob類,并使用@Component注釋對其進行注釋。 這樣可以確保在類路徑掃描期間找到我們的計劃作業(只要將其放入要掃描的程序包中)。
  • 將私有的Logger字段添加到創建的類中,并通過調用LoggerFactory類的靜態getLogger()方法來創建Logger對象。 我們將使用Logger對象將從HelloMessageService對象收到的消息寫入日志。
  • 將私有MessageService字段添加到創建的類。
  • 將一個構造函數添加到創建的類中,并使用@Autowired注釋對其進行注釋。 這確保了我們可以使用構造函數注入將MessageService bean注入MessageService字段。
  • 向創建的類添加一個公共run()方法,并使用@Scheduled批注對其進行批注。 將其cron屬性的值設置為'$ {scheduling.job.cron}' 。 這意味著cron表達式是從屬性文件中讀取的,其值是schedule.job.cron屬性的值( 有關此內容的更多信息,請參閱此博客文章 )。
  • 通過調用MessageService接口的getMessage()方法來實現run()方法。 將收到的消息寫入日志。
  • 我們計劃的作業的源代碼如下所示:

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

    讓我們看看調用ScheduledJob類的run()方法時會發生什么。

    它不起作用

    當執行我們的計劃作業時,將拋出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)

    該堆棧跟蹤實際上非常有用。 它告訴我們安全方法無法調用,因為從SecurityContext中找不到Authentication對象。

    我看到的兩個最常見的解決方案是:

    • 創建一個與受保護的方法具有相同功能的單獨的方法,然后修改計劃的作業以使用此方法。 此方法通常具有Javadoc注釋,該注釋指出只有計劃的作業才能調用此方法。 這種解決方案有兩個問題:1)它會使代碼庫混亂,并且2)最終無論如何都會有人調用該方法(除非有必要,否則沒人真正閱讀Javadocs)。
    • 從計劃作業調用的方法中刪除方法安全注釋。 由于明顯的原因,這是一個非常糟糕的解決方案。 提示:該方法的安全是有充分理由的!

    幸運的是,還有第三種方法可以解決此問題。 讓我們開始查找計劃作業使用的安全上下文的存儲位置。

    安全上下文從何而來?

    我們的問題的解決方案很明確:我們必須創建一個Authentication對象,然后將其添加到SecurityContext中,然后調用安全方法。

    但是,在對示例應用程序進行必要的修改之前,我們必須了解SecurityContext對象的存儲位置。

    如果未進行其他配置,則將安全上下文存儲到ThreadLocal 。 換句話說,每個線程都有其自己的安全上下文。 這意味著在同一線程中執行的所有計劃作業均共享相同的安全上下文。

    假設我們有三個預定的作業。 這些作業稱為A , B和C。 另外,我們假設這些作業是按字母順序執行的。

    如果我們使用只有一個線程的默認線程池,則所有作業共享相同的安全上下文。 如果作業B將身份驗證對象設置為安全上下文,則執行計劃的作業時會發生以下情況:

    • 作業A無法調用安全方法,因為它在作業B之前執行。 這意味著從安全上下文中找不到身份驗證對象。
    • 作業B可以調用安全方法,因為作業B在嘗試調用安全方法之前將Authentication對象設置為安全上下文。
    • 作業C可以調用安全方法,因為它是在將身份驗證對象設置為安全上下文的作業B之后執行的。

    如果我們使用一個具有多個線程的線程池,則每個線程都有其自己的安全上下文。 如果作業A將Authentication對象設置為安全上下文,則在同一線程中執行的所有作業都將使用相同的特權執行,只要它們在作業A之后執行即可。

    讓我們一步一步地完成每一項工作:

    • 作業A可以調用安全方法,因為它在嘗試調用安全方法之前將Authentication對象設置為安全上下文。
    • 如果作業B 與作業A在同一線程中執行,則作業B可以調用安全方法。 如果作業不在同一線程中執行,則無法調用安全方法,因為無法從安全上下文中找到Authentication對象。
    • 如果作業C 與作業A在同一線程中執行,則作業C可以調用安全方法。 如果作業不在同一線程中執行,則無法調用安全方法,因為無法從安全上下文中找到Authentication對象。

    顯然,解決此問題的最佳方法是確保使用所需的特權執行每個計劃的作業。 該解決方案有兩個好處:

    • 我們可以以任何順序執行工作。
    • 我們不必確保作業在“正確的”線程中執行。

    讓我們找出當我們的應用程序使用Spring Security 3.1時如何解決這個問題。

    Spring Security 3.1:需要手動工作

    如果我們的應用程序使用Spring Security 3.1,則解決問題的最簡單方法是

    • 在我們的工作嘗試調用安全方法之前,創建一個Authentication對象并將其設置為安全上下文。
    • 在作業完成之前,從安全上下文中刪除身份驗證對象。

    讓我們從創建提供所需方法的AuthenticationUtil類開始。

    創建AuthenticationUtil類

    我們可以按照以下步驟創建AuthenticationUtil類:

  • 創建AuthenticationUtil類。
  • 向AuthenticationUtil類添加一個私有構造函數。 這樣可以確保無法實例化該類。
  • 將靜態clearAuthentication()方法添加到該類,并按照以下步驟實現該方法:
  • 通過調用SecurityContextHolder類的靜態getContext()方法來獲取SecurityContext對象。
  • 通過調用SecurityContext接口的setContext()方法刪除身份驗證信息。 將null作為方法參數傳遞。
  • 將靜態configureAuthentication()方法添加到該類。 此方法將用戶的角色作為方法參數。 通過執行以下步驟來實現此方法:
  • 通過調用AuthorityUtils類的靜態createAuthorityList()方法來創建GrantedAuthority對象的集合 。 將用戶角色作為方法參數傳遞。
  • 創建一個新的UsernamePasswordAuthenticationToken對象,并將以下對象作為構造函數參數傳遞:
  • 第一個構造函數參數是主體。 將字符串“ user”作為第一個構造函數參數傳遞。
  • 第二個構造函數參數是用戶的憑據。 將作為方法參數給出的角色作為第二個構造函數參數傳遞。
  • 第三個構造函數參數包含用戶的權限。 將創建的Collection <GrantedAuthority>對象作為第三個構造函數參數傳遞。
  • 通過調用SecurityContextHolder類的靜態getContext()方法來獲取SecurityContext對象。
  • 通過調用SecurityContext接口的setAuthentication()方法將創建的Authentication對象設置為安全上下文。 將創建的UsernamePasswordAuthenticationToken作為方法參數傳遞。
  • 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);} }

    我們還沒有完成。 我們仍然必須對我們的預定工作進行一些修改。 讓我們找出如何進行這些修改。

    修改計劃的作業

    我們必須對ScheduledJob類進行兩次修改。 我們可以按照以下步驟進行修改:

  • 啟動作業時,調用AuthenticationUtil類的靜態configureAuthentication()方法,并將字符串 'ROLE_USER'作為方法參數傳遞。 這樣可以確保我們的計劃作業可以執行與具有ROLE_USER角色的普通用戶相同的方法。
  • 在作業完成之前,調用AuthenticationUtil類的靜態clearAuthentication()方法。 這從安全上下文中刪除了身份驗證信息。
  • 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();} }

    讓我們找出運行預定作業時會發生什么。

    運行計劃的作業

    調用作業時,以下消息將寫入日志:

    2013-12-17 20:41:33,019 DEBUG - ScheduledJob ? ? ? ? ? ?- Received message: Hello World!

    當我們的應用程序使用Spring Security 3.1時,一切都將正常運行。 我們的解決方案不是那么優雅,但可以。 該解決方案的明顯缺點是,我們必須記住在計劃的作業中調用AuthenticationUtil類的configureAuthentication()和clearAuthentication()方法。

    Spring Security 3.2解決了這個問題。 讓我們繼續前進,找出當我們的應用程序使用Spring Security 3.2時如何解決這個問題。

    Spring Security 3.2:幾乎就像魔術一樣!

    Spring Security 3.2具有全新的并發支持 ,這使我們可以將安全上下文從一個線程轉移到另一個線程。 讓我們找出如何配置應用程序上下文以使用Spring Security 3.2提供的功能。

    配置應用程序上下文

    因為我們要使用Spring Security 3.2的新并發支持,所以我們必須對應用程序上下文配置類進行以下更改( 原始配置在此博客文章中進行了描述 ):

  • 實現SchedulingConfigurer接口。 該接口可以通過使用@EnableScheduling批注進行批注的應用程序上下文配置類來實現,并且通常用于配置使用的TaskScheduler bean或以編程方式配置執行的任務。
  • 將私有createrSchedulerSecurityContext()方法添加到配置類。 此方法沒有方法參數,它返回一個SecurityContext對象。 通過執行以下步驟來實現此方法:
  • 通過調用SecurityContextHolder類的靜態createEmptyContext()方法來創建新的SecurityContext對象。
  • 通過調用AuthorityUtils類的靜態createAuthorityList()方法來創建GrantedAuthority對象的集合 。 將字符串 “ ROLE_USER”作為方法參數傳遞。
  • 創建一個新的UsernamePasswordAuthenticationToken對象,并將以下對象作為構造函數參數傳遞:
  • 第一個構造函數參數是主體。 將字符串 “ user”作為第一個構造函數參數傳遞。
  • 第二個構造函數參數是用戶的憑據。 將字符串 “ ROLE_USER”作為第二個構造函數參數傳遞。
  • 第三個構造函數參數包含用戶的權限。 將創建的Collection <GrantedAuthority>對象作為第三個構造函數參數傳遞。
  • 通過調用SecurityContext接口的setAuthentication()方法,將創建的UsernamePasswordAuthenticationToken對象設置為創建的安全上下文。
  • 將公共taskExecutor()方法添加到配置類中,并使用@Bean注釋對該方法進行注釋。 此方法沒有方法參數,并返回Executor對象。 通過執行以下步驟來實現此方法:
  • 通過調用Executors類的靜態newSingleThreadScheduledExecutor()方法來創建新的ScheduledExecutorService對象。 這將創建一個ScheduledExecutorService對象,該對象通過使用一個線程來運行所有作業。
  • 通過調用私有的createSchedulerSecurityContext()方法來獲取對SecurityContext對象的引用。
  • 創建一個新的DelegatingSecurityContextScheduledExecutorService對象,并將以下對象作為構造函數參數傳遞:
  • 第一個構造函數參數是ScheduledExecutorService對象。 該對象用于調用計劃的作業。 將創建的ScheduledExecutorService對象作為第一個構造函數參數傳遞。
  • 第二個構造函數參數是SecurityContext對象。 創建的DelegatingSecurityContextScheduledExecutorService對象確保每個調用的作業都使用此SecurityContext 。 將創建的SecurityContext對象作為第二個構造函數參數傳遞。
  • 返回創建的DelegatingSecurityContextScheduledExecutorService對象。
  • 實現SchedulingConfigurer接口的configureTasks()方法 。 此方法將ScheduledTaskRegistrar對象作為方法參數。 通過執行以下步驟來實現此方法:
  • 通過調用taskExecutor()方法創建一個新的Executor對象。
  • 通過調用ScheduledTaskRegistrar類的setScheduler()方法來設置使用的調度程序 ,并將Executor對象作為方法參數傳遞。
  • ExampleApplicationContext類的源代碼如下所示(相關部分已突出顯示):

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

    這就對了。 此配置確保每個計劃的作業都可以訪問由createSchedulerSecurityContext()方法創建的SecurityContext對象。 這意味著每個計劃的作業都可以調用安全的方法,這些方法可以由角色為“ ROLE_USER”的用戶調用。

    讓我們快速看一下我們的預定工作。

    那預定的工作呢?

    該解決方案的最好之處在于,我們不必對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);} }

    調用計劃的作業時,將以下行寫入日志:

    2013-12-17 21:12:14,012 DEBUG - ScheduledJob - Received message: Hello World!

    很酷 對?

    摘要

    現在,我們已經成功創建了可以調用安全方法的計劃作業。 本教程教會了我們三件事:

    • 我們了解到,通常SecurityContext對象存儲在ThreadLocal中 ,這意味著在同一線程中執行的所有計劃作業均共享相同的安全上下文
    • 我們了解到,如果我們的應用程序使用Spring Security 3.1,并且希望從計劃的作業中調用安全方法,那么最簡單的方法是在每個計劃的作業中配置使用的Authentication對象。
    • 我們學習了如何使用Spring Security 3.2的并發支持,以及如何將SecurityContext對象從一個線程轉移到另一個線程。

    您可以從Github( Spring Security 3.1和Spring Security 3.2 )獲得此博客文章的示例應用程序。

    注意: Spring Security 3.2示例的XML配置目前無法正常工作。 如果有時間,我會修復它。

    參考: 從工作槽中跳出來:從Petri Kainulainen博客上的JCG合作伙伴 Petri Kainulainen 調用預定作業中的安全方法 。

    翻譯自: https://www.javacodegeeks.com/2014/01/spring-from-the-trenches-invoking-a-secured-method-from-a-scheduled-job.html

    總結

    以上是生活随笔為你收集整理的摆脱困境:从计划作业中调用安全方法的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。