aspcms手机网站怎么做,哪里有焊接加工外发的,一个网站要多少钱,深圳出台鼓励芯片产业政策假设我们已经实现了一个Spring支持的应用程序#xff0c;并使用Spring Security的方法安全性表达式对其进行了保护 。 我们的下一个任务是使用安全方法实施计划作业。 更具体地说#xff0c;我们必须实现一个计划的作业#xff0c;该作业从我们的服务类中获取一条消息… 假设我们已经实现了一个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) {CollectionGrantedAuthority 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();CollectionGrantedAuthority 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