Java >> Java Tutorial >  >> Tag >> Spring

Spring From the Trenches:Aufrufen einer gesicherten Methode aus einem geplanten Job

Nehmen wir an, wir haben eine von Spring unterstützte Anwendung implementiert und sie mithilfe der Methodensicherheitsausdrücke von Spring Security gesichert.

Unsere nächste Aufgabe besteht darin, einen geplanten Job zu implementieren, der die gesicherten Methoden verwendet. Genauer gesagt müssen wir einen geplanten Job implementieren, der eine Nachricht von unserer Serviceklasse erhält und die empfangene Nachricht in das Protokoll schreibt.

Fangen wir an.

Unser erster Versuch

Lassen Sie uns einen geplanten Job erstellen, der die gesicherte Methode aufruft, und herausfinden, was passiert, wenn der Job ausgeführt wird. Beginnen wir mit einem Blick auf die Serviceschicht unserer Beispielanwendung.

Die Dienstschicht

Die Methoden der gesicherten Dienstklasse werden im MessageService deklariert Schnittstelle. Es deklariert eine Methode namens getMessage() und gibt an, dass nur Benutzer, die eine Rolle ROLE_USER haben kann es aufrufen.

Der Quellcode des MessageService Die Benutzeroberfläche sieht wie folgt aus:

import org.springframework.security.access.prepost.PreAuthorize;

public interface MessageService {

    @PreAuthorize("hasRole('ROLE_USER')")
    public String getMessage();
}

Unsere Implementierung des MessageService Schnittstelle ist ziemlich einfach. Sein Quellcode sieht wie folgt aus:

import org.springframework.stereotype.Service;

@Service
public class HelloMessageService implements MessageService {

    @Override
    public String getMessage() {
        return "Hello World!";
    }
}

Lassen Sie uns weitermachen und einen geplanten Job erstellen, der die Methode getMessage() aufruft.

Geplanten Job erstellen

Wir können den geplanten Job erstellen, indem wir diesen Schritten folgen:

  1. Erstellen Sie einen Geplanten Job Klasse und kommentieren Sie sie mit @Component Anmerkung. Dadurch wird sichergestellt, dass unser geplanter Job während des Klassenpfad-Scans gefunden wird (solange wir ihn in ein gescanntes Paket einfügen).
  2. Fügen Sie der erstellten Klasse ein privates Logger-Feld hinzu und erstellen Sie einen Logger Objekt durch Aufrufen des statischen getLogger() Methode der LoggerFactory Klasse. Wir werden den Logger verwenden Objekt, um die Nachricht zu schreiben, die wir vom HelloMessageService erhalten dem Protokoll widersprechen.
  3. Fügen Sie einen privaten MessageService hinzu Feld zur erstellten Klasse.
  4. Fügen Sie der erstellten Klasse einen Konstruktor hinzu und kommentieren Sie ihn mit @Autowired Anmerkung. Dadurch wird sichergestellt, dass wir einen MessageService einfügen können Bean an den MessageService -Feld mithilfe der Konstruktorinjektion.
  5. Fügen Sie ein öffentliches run() hinzu Methode zur erstellten Klasse und kommentieren Sie sie mit @Scheduled Anmerkung. Setzen Sie den Wert seines cron-Attributs auf '${scheduling.job.cron}' . Das bedeutet, dass der Cron-Ausdruck aus einer Eigenschaftendatei gelesen wird und sein Wert der Wert von scheduling.job.cron ist Eigenschaft (Weitere Einzelheiten dazu finden Sie in diesem Blogbeitrag).
  6. Implementieren Sie run() Methode durch Aufrufen von getMessage() Methode des MessageService Schnittstelle. Schreiben Sie die empfangene Nachricht in das Protokoll.

Der Quellcode unseres geplanten Jobs sieht wie folgt aus:

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;

    @Autowired
    public ScheduledJob(MessageService messageService) {
        this.messageService = messageService;
    }

    @Scheduled(cron = "${scheduling.job.cron}")
    public void run() {
        String message = messageService.getMessage();
        LOGGER.debug("Received message: {}", message);
    }
}

Mal sehen, was passiert, wenn run() Methode des ScheduledJob Klasse wird aufgerufen.

Es funktioniert nicht

Wenn unser geplanter Job ausgeführt wird, wird die AuthenticationCredentialsNotFoundException wird geworfen und wir sehen den folgenden Stacktrace:

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 SecurityContext
	at 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)

Dieser Stacktrace ist eigentlich ziemlich hilfreich. Es teilt uns mit, dass die gesicherte Methode aufgrund einer Authentifizierung nicht aufgerufen werden konnte Objekt wurde im SecurityContext nicht gefunden .

Die zwei häufigsten Lösungen für dieses Problem, die ich gesehen habe, sind:

  • Erstellen Sie eine separate Methode, die dasselbe tut wie die geschützte Methode, und ändern Sie den geplanten Job, um diese Methode zu verwenden. Diese Methode hat oft einen Javadoc-Kommentar, der besagt, dass nur der geplante Job diese Methode aufrufen kann. Diese Lösung hat zwei Probleme:1) sie verstopft die Codebasis und 2) irgendjemand wird diese Methode schließlich sowieso aufrufen (niemand liest wirklich Javadocs, es sei denn, er muss).
  • Entfernen Sie die Sicherheitsanmerkung der Methode aus der Methode, die vom geplanten Job aufgerufen wurde. Dies ist aus offensichtlichen Gründen eine wirklich schlechte Lösung. Hinweis: Diese Methode wurde aus gutem Grund gesichert!

Glücklicherweise gibt es auch eine dritte Möglichkeit, dieses Problem zu lösen. Beginnen wir damit, herauszufinden, wo der von unserem geplanten Job verwendete Sicherheitskontext gespeichert ist.

Woher kommt der Sicherheitskontext?

Die Lösung unseres Problems ist klar:

Wir müssen eine Authentifizierung erstellen -Objekt und fügen Sie es dem SecurityContext hinzu bevor die gesicherte Methode aufgerufen wird.

Bevor wir jedoch die notwendigen Änderungen an unserer Beispielanwendung vornehmen können, müssen wir verstehen, wo der SecurityContext Objekt gespeichert.

Wenn wir nichts anderes konfiguriert haben, wird der Sicherheitskontext in ThreadLocal gespeichert . Mit anderen Worten, jeder Thread hat seinen eigenen Sicherheitskontext. Das bedeutet, dass alle geplanten Jobs, die in demselben Thread ausgeführt werden, denselben Sicherheitskontext teilen.

Nehmen wir an, wir haben drei geplante Jobs. Diese Jobs heißen A , B , und C . Nehmen wir außerdem an, dass diese Jobs in alphabetischer Reihenfolge ausgeführt werden.

Wenn wir den Standard-Thread-Pool verwenden, der nur einen Thread hat, teilen sich alle Jobs denselben Sicherheitskontext. Wenn der Job B setzt die Authentifizierung dem Sicherheitskontext widersprechen, passieren die folgenden Dinge, wenn die geplanten Jobs ausgeführt werden:

  • Der Job A kann die gesicherte Methode nicht aufrufen, da sie vor Job B ausgeführt wird . Das bedeutet, dass eine Authentifizierung Objekt im Sicherheitskontext nicht gefunden.
  • Der Job B kann die gesicherte Methode aufrufen, da sie die Authentifizierung festlegt Objekt zum Sicherheitskontext, bevor versucht wird, die gesicherte Methode aufzurufen.
  • Der Job C kann die gesicherte Methode aufrufen, da sie nach Job B ausgeführt wird die die Authentifizierung festlegt dem Sicherheitskontext widersprechen.

Wenn wir einen Thread-Pool verwenden, der mehr als einen Thread hat, hat jeder Thread seinen eigenen Sicherheitskontext. Wenn der Job A setzt die Authentifizierung dem Sicherheitskontext widersprechen, werden alle Jobs, die im selben Thread ausgeführt werden, mit denselben Privilegien ausgeführt, solange sie nach Job A ausgeführt werden .

Lassen Sie uns jeden Job einzeln durchgehen:

  • Der Job A kann die gesicherte Methode aufrufen, da sie die Authentifizierung festlegt Objekt zum Sicherheitskontext, bevor versucht wird, die gesicherte Methode aufzurufen.
  • Der Job B kann die gesicherte Methode aufrufen, WENN sie im selben Thread wie Job A ausgeführt wird . Wenn der Job nicht im selben Thread ausgeführt wird, kann er die gesicherte Methode nicht aufrufen, da die Authentifizierung Objekt im Sicherheitskontext nicht gefunden.
  • Der Job C kann die gesicherte Methode aufrufen, WENN sie im selben Thread wie Job A ausgeführt wird . Wenn der Job nicht im selben Thread ausgeführt wird, kann er die gesicherte Methode nicht aufrufen, da die Authentifizierung Objekt im Sicherheitskontext nicht gefunden.

Es ist klar, dass der beste Weg, dieses Problem zu lösen, darin besteht, sicherzustellen, dass jeder geplante Job unter Verwendung der erforderlichen Berechtigungen ausgeführt wird. Diese Lösung hat zwei Vorteile:

  • Wir können unsere Jobs in beliebiger Reihenfolge ausführen.
  • Wir müssen nicht sicherstellen, dass Jobs in einem "richtigen" Thread ausgeführt werden.

Lassen Sie uns herausfinden, wie wir dieses Problem lösen können, wenn unsere Anwendung Spring Security 3.1 verwendet.

Spring Security 3.1:Manuelle Arbeit erforderlich

Wenn unsere Anwendung Spring Security 3.1 verwendet, ist der einfachste Weg, unser Problem zu lösen,

  • Erstellen Sie eine Authentifizierung -Objekt und setzen Sie es auf den Sicherheitskontext, bevor unser Job versucht, die gesicherte Methode aufzurufen.
  • Entfernen Sie die Authentifizierung Objekt aus dem Sicherheitskontext, bevor der Job beendet ist.

Beginnen wir mit der Erstellung eines AuthenticationUtil Klasse, die die erforderlichen Methoden bereitstellt.

Erstellen der AuthenticationUtil-Klasse

Wir können das AuthenticationUtil erstellen Klasse, indem Sie diesen Schritten folgen:

  1. Erstellen Sie das AuthenticationUtil Klasse.
  2. Fügen Sie einen privaten Konstruktor AuthenticationUtil hinzu Klasse. Dadurch wird sichergestellt, dass die Klasse nicht instanziiert werden kann.
  3. Fügen Sie eine statische clearAuthentication() hinzu -Methode in die Klasse und implementieren Sie die Methode, indem Sie die folgenden Schritte ausführen:
    1. Rufen Sie den Sicherheitskontext ab Objekt durch Aufrufen des statischen getContext() Methode des SecurityContextHolder Klasse.
    2. Entfernen Sie die Authentifizierungsinformationen, indem Sie setContext() aufrufen Methode des SecurityContext Schnittstelle. Übergeben Sie null als Methodenparameter.
  4. Fügen Sie eine statische configureAuthentication() hinzu Methode zur Klasse. Diese Methode übernimmt die Rolle des Benutzers als Methodenparameter. Implementieren Sie diese Methode, indem Sie die folgenden Schritte ausführen:
    1. Erstellen Sie eine Sammlung von GrantedAuthority Objekte durch Aufrufen der statischen createAuthorityList() Methode der AuthorityUtils Klasse. Übergeben Sie die Benutzerrolle als Methodenparameter.
    2. Erstellen Sie ein neues UsernamePasswordAuthenticationToken Objekt und übergeben Sie die folgenden Objekte als Konstruktorargumente:
      1. Das erste Konstruktorargument ist der Prinzipal. Übergeben Sie den String 'user' als erstes Konstruktorargument.
      2. Das zweite Konstruktorargument sind die Anmeldeinformationen des Benutzers. Übergeben Sie die als Methodenparameter angegebene Rolle als zweites Konstruktorargument.
      3. Das dritte Konstruktorargument enthält die Berechtigungen des Benutzers. Übergeben Sie die erstellte Collection object als drittes Konstruktorargument.
    3. Rufen Sie den Sicherheitskontext ab Objekt durch Aufrufen des statischen getContext() Methode des SecurityContextHolder Klasse.
    4. Stellen Sie die erstellte Authentifizierung ein Objekt zum Sicherheitskontext, indem Sie setAuthentication() aufrufen Methode des SecurityContext Schnittstelle. Übergeben Sie das erstellte UsernamePasswordAuthenticationToken als Methodenparameter.

Der Quellcode des AuthenticationUtil Klasse sieht wie folgt aus:

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

Wir sind noch nicht fertig. Wir müssen noch einige Änderungen an unserem geplanten Job vornehmen. Lassen Sie uns herausfinden, wie wir diese Änderungen vornehmen können.

Ändern des geplanten Auftrags

Wir müssen zwei Änderungen am ScheduledJob vornehmen Klasse. Wir können diese Änderungen vornehmen, indem wir diesen Schritten folgen:

  1. Rufen Sie die statische configureAuthentication() auf Methode des AuthenticationUtil Klasse, wenn der Job gestartet wird, und übergeben Sie den String 'ROLE_USER' als Methodenparameter. Dadurch wird sichergestellt, dass unser geplanter Job dieselben Methoden ausführen kann wie ein normaler Benutzer mit der Rolle ROLE_USER .
  2. Rufen Sie die statische clearAuthentication() auf Methode des AuthenticationUtil Klasse, kurz bevor die Arbeit beendet ist. Dadurch wurden die Authentifizierungsinformationen aus dem Sicherheitskontext entfernt.

Der Quellcode des ScheduledJob Klasse sieht wie folgt aus:

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;

    @Autowired
    public 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();
    }
}
Wie Derek betonte, sollten wir clearAuthentication() aufrufen Methode des AuthenticationUtil Klasse innerhalb eines endlich Block. Wenn wir dies nicht tun, könnte unser Sicherheitskontext in den Thread-Pool des Jobs gelangen, der mit anderen Jobs geteilt werden könnte.

Lassen Sie uns herausfinden, was passiert, wenn unser geplanter Job ausgeführt wird.

Ausführen des geplanten Auftrags

Wenn der Job aufgerufen wird, wird die folgende Nachricht in das Protokoll geschrieben:

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

Alles funktioniert perfekt, wenn unsere Anwendung Spring Security 3.1 verwendet. Unsere Lösung ist nicht so elegant, aber sie funktioniert. Der offensichtliche Nachteil dieser Lösung ist, dass wir daran denken müssen, configureAuthentication() aufzurufen und clearAuthentication() Methoden des AuthenticationUtil Klasse in unseren geplanten Jobs.

Spring Security 3.2 löst dieses Problem. Lassen Sie uns weitermachen und herausfinden, wie wir dieses Problem lösen können, wenn unsere Anwendung Spring Security 3.2 verwendet.

Spring Security 3.2:Es ist fast wie Magie!

Spring Security 3.2 hat eine brandneue Nebenläufigkeitsunterstützung, die uns die Möglichkeit gibt, den Sicherheitskontext von einem Thread auf einen anderen zu übertragen. Lassen Sie uns herausfinden, wie wir unseren Anwendungskontext so konfigurieren können, dass er die von Spring Security 3.2 bereitgestellten Funktionen verwendet.

Konfigurieren des Anwendungskontexts

Da wir die neue Nebenläufigkeitsunterstützung von Spring Security 3.2 nutzen möchten, müssen wir die folgenden Änderungen an unserer Anwendungskontext-Konfigurationsklasse vornehmen (die ursprüngliche Konfiguration wird in diesem Blogbeitrag beschrieben):

  1. Implementieren Sie den SchedulingConfigurer Schnittstelle. Diese Schnittstelle kann durch Anwendungskontext-Konfigurationsklassen implementiert werden, die mit @EnableScheduling annotiert sind Annotation, und wird oft verwendet, um den verwendeten TaskScheduler zu konfigurieren Bean oder konfigurieren Sie die ausgeführten Aufgaben programmgesteuert.
  2. Fügen Sie einen privaten createrSchedulerSecurityContext() hinzu Methode zur Konfigurationsklasse. Diese Methode hat keine Methodenparameter, sie gibt einen SecurityContext zurück Objekt. Implementieren Sie diese Methode, indem Sie die folgenden Schritte ausführen:
    1. Erstellen Sie einen neuen Sicherheitskontext Objekt durch Aufrufen des statischen createEmptyContext() Methode des SecurityContextHolder Klasse.
    2. Erstellen Sie eine Sammlung von GrantedAuthority Objekte durch Aufrufen der statischen createAuthorityList() Methode der AuthorityUtils Klasse. Übergeben Sie die Zeichenfolge 'ROLE_USER' als Methodenparameter.
    3. Erstellen Sie ein neues UsernamePasswordAuthenticationToken Objekt und übergeben Sie die folgenden Objekte als Konstruktorargumente:
      1. Das erste Konstruktorargument ist der Prinzipal. Übergeben Sie die Zeichenfolge 'user' als erstes Konstruktorargument.
      2. Das zweite Konstruktorargument sind die Anmeldeinformationen des Benutzers. Übergeben Sie die Zeichenfolge 'ROLE_USER' als zweites Konstruktorargument.
      3. Das dritte Konstruktorargument enthält die Berechtigungen des Benutzers. Übergeben Sie die erstellte Collection object als drittes Konstruktorargument.
    4. Setzen Sie das erstellte UsernamePasswordAuthenticationToken Objekt zum erstellten Sicherheitskontext hinzufügen, indem Sie setAuthentication() aufrufen Methode des SecurityContext Schnittstelle.
  3. Fügen Sie einen öffentlichen taskExecutor() hinzu -Methode in die Konfigurationsklasse und kommentieren Sie die Methode mit @Bean Anmerkung. Diese Methode hat keine Methodenparameter und gibt einen Executor zurück Objekt. Implementieren Sie diese Methode, indem Sie die folgenden Schritte ausführen:
    1. Erstellen Sie einen neuen ScheduledExecutorService durch Aufrufen des statischen newSingleThreadScheduledExecutor() Methode der Executors Klasse. Dadurch wird ein ScheduledExecutorService erstellt Objekt, das alle Jobs über einen einzigen Thread ausführt.
    2. Beziehen Sie eine Referenz auf den SecurityContext -Objekt durch Aufrufen des privaten createSchedulerSecurityContext() Methode.
    3. Erstellen Sie einen neuen DelegatingSecurityContextScheduledExecutorService Objekt und übergeben Sie die folgenden Objekte als Konstruktorargumente:
      1. Das erste Konstruktorargument ist ein ScheduledExecutorService Objekt. Dieses Objekt wird verwendet, um geplante Jobs aufzurufen. Übergeben Sie den erstellten ScheduledExecutorService Objekt als erstes Konstruktorargument.
      2. Das zweite Konstruktorargument ist ein SecurityContext Objekt. Der erstellte DelegatingSecurityContextScheduledExecutorService -Objekt stellt sicher, dass jeder aufgerufene Job diesen SecurityContext verwendet . Übergeben Sie den erstellten SecurityContext object als zweites Konstruktorargument.
    4. Gib den erstellten DelegatingSecurityContextScheduledExecutorService zurück Objekt.
  4. Implementieren Sie die configureTasks() Methode des SchedulingConfigurer Schnittstelle. Diese Methode nimmt einen ScheduledTaskRegistrar Objekt als Methodenparameter. Implementieren Sie diese Methode, indem Sie die folgenden Schritte ausführen:
    1. Erstellen Sie einen neuen Executor Objekt durch Aufrufen von taskExecutor() Methode.
    2. Legen Sie den verwendeten Scheduler fest, indem Sie setScheduler() aufrufen Methode des ScheduledTaskRegistrar Klasse und bestehe den Executor Objekt als Methodenparameter.

Der Quellcode der Klasse ExampleApplicationContext sieht wie folgt aus (die relevanten Teile sind hervorgehoben):

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 {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
    }

    @Bean
    public 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;
    }

    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();

        properties.setLocation(new ClassPathResource( "application.properties" ));
        properties.setIgnoreResourceNotFound(false);

        return properties;
    }
}

Das ist es. Diese Konfiguration stellt sicher, dass jeder geplante Job Zugriff auf den SecurityContext hat Objekt, das von createSchedulerSecurityContext() erstellt wurde Methode. Das bedeutet, dass jeder geplante Job gesicherte Methoden aufrufen kann, die von einem Benutzer mit der Rolle „ROLE_USER“ aufgerufen werden können.

Werfen wir einen kurzen Blick auf unseren geplanten Job.

Was ist mit dem geplanten Job?

Das Beste an dieser Lösung ist, dass wir keine Änderungen am ScheduledJob vornehmen müssen Klasse. Sein Quellcode sieht wie folgt aus:

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;

    @Autowired
    public ScheduledJob(MessageService messageService) {
        this.messageService = messageService;
    }

    @Scheduled(cron = "${scheduling.job.cron}")
    public void run() {
        String message = messageService.getMessage();
        LOGGER.debug("Received message: {}", message);
    }
}

Wenn der geplante Job aufgerufen wird, wird die folgende Zeile in das Protokoll geschrieben:

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

Ziemlich cool. Richtig?

Zusammenfassung

Wir haben jetzt erfolgreich geplante Jobs erstellt, die eine gesicherte Methode aufrufen können. Dieses Tutorial hat uns drei Dinge gelehrt:

  • Wir haben gelernt, dass typischerweise der Sicherheitskontext Objekt wird in ThreadLocal gespeichert Das bedeutet, dass alle geplanten Jobs, die in demselben Thread ausgeführt werden, denselben Sicherheitskontext teilen
  • Wir haben gelernt, dass, wenn unsere Anwendung Spring Security 3.1 verwendet und wir eine gesicherte Methode von einem geplanten Job aufrufen möchten, der einfachste Weg, dies zu tun, darin besteht, die verwendete Authentifizierung zu konfigurieren Objekt in jedem geplanten Job.
  • Wir haben gelernt, wie wir die Nebenläufigkeitsunterstützung von Spring Security 3.2 nutzen und den SecurityContext übertragen können Objekt von einem Thread zum anderen.

Sie können die Beispielanwendungen dieses Blogbeitrags von Github (Spring Security 3.1 und Spring Security 3.2) herunterladen.

Hinweis: Die XML-Konfiguration des Spring Security 3.2-Beispiels funktioniert derzeit nicht. Ich werde es reparieren, wenn ich Zeit dafür habe.


Java-Tag