Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
*** xref:features/integrations/concurrency.adoc[Java's Concurrency APIs]
*** xref:features/integrations/jackson.adoc[Jackson]
*** xref:features/integrations/localization.adoc[Localization]
*** xref:features/integrations/outside-request.adoc[Outside of a Request]
* xref:modules.adoc[Project Modules]
* xref:samples.adoc[Samples]
* xref:servlet/index.adoc[Servlet Applications]
Expand Down
195 changes: 195 additions & 0 deletions docs/modules/ROOT/pages/features/integrations/outside-request.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
[[outside-request]]
= Spring Security Outside of a Request

Much of the reference documentation assumes that Spring Security is protecting an HTTP request handled by a Servlet or Reactive application.
However, a significant amount of code runs without any request at all, such as:

* scheduled jobs (`@Scheduled`, Quartz)
* command-line runners (`CommandLineRunner`, `ApplicationRunner`)
* message listeners (JMS, Kafka, RabbitMQ, Spring Integration)
* background threads and `Executor` tasks
* plain unit tests that exercise secured services directly

This page describes the pieces that are useful in those contexts.
If you do have an HTTP request, start instead from the xref:servlet/index.adoc[Servlet] or xref:reactive/index.adoc[Reactive] sections.

[[outside-request-securitycontext]]
== The `SecurityContextHolder` and Threads

The central abstraction is the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[`SecurityContextHolder`], which stores the current `SecurityContext`.
By default it uses a `ThreadLocal`, so it works the same way with or without an HTTP request: whatever `Thread` you are running on, `SecurityContextHolder.getContext()` returns the `SecurityContext` associated with that thread.

This has two consequences that matter outside of a web request:

1. You can populate the context yourself before invoking secured code.
2. When you hand work to another thread, the context is lost unless you propagate it explicitly.

You can set and clear the context manually:

[tabs]
======
Java::
+

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of the Spring Security reference pulls code samples from compiled example sources (e.g. include::partial$...) rather than embedding inline Java/Kotlin blocks. Inlined snippets like the try/finally block here, the DelegatingSecurityContextRunnable block, and the @SpringBootTest test do not get compiled, so a future rename of SecurityContextHolder.createEmptyContext() or DelegatingSecurityContextRunnable will silently rot this page. Consider moving the samples into the docs module's example sources and using include:: with tag regions, as features/integrations/concurrency.adoc does.

[source,java,role="primary"]
----
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
try {
// invoke secured code
} finally {
SecurityContextHolder.clearContext();
}
----

Kotlin::
+
[source,kotlin,role="secondary"]
----
val context = SecurityContextHolder.createEmptyContext()
context.authentication = authentication
SecurityContextHolder.setContext(context)
try {
// invoke secured code
} finally {
SecurityContextHolder.clearContext()
}
----
======

[[outside-request-concurrency]]
== Propagating the Context to Other Threads

Because the context is thread-bound, work submitted to another thread (for example, a `ThreadPoolTaskExecutor`) does not see the caller's `SecurityContext`.
Spring Security provides delegating wrappers so that you do not have to manage this by hand.
For example, `DelegatingSecurityContextRunnable` and `DelegatingSecurityContextExecutor` capture the current context and restore it around the delegate:

[tabs]
======
Java::
+
[source,java,role="primary"]
----
Runnable task = new DelegatingSecurityContextRunnable(originalRunnable);
Executor executor = new DelegatingSecurityContextExecutor(delegateExecutor);

executor.execute(task);
----

Kotlin::
+
[source,kotlin,role="secondary"]
----
val task = DelegatingSecurityContextRunnable(originalRunnable)
val executor: Executor = DelegatingSecurityContextExecutor(delegateExecutor)

executor.execute(task)
----
======

See xref:features/integrations/concurrency.adoc[Concurrency Support] for the full set of wrappers.

[[outside-request-method-security]]
== Method Security

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MethodSecurityConfig sample is missing @Configuration:

@EnableMethodSecurity
public class MethodSecurityConfig {
}

@EnableMethodSecurity is a stereotype that imports configuration but the enclosing class still needs to be a Spring-managed bean to be picked up (either @Configuration, or picked up via component scan on another stereotype). As written, dropped into a plain class, the annotation has no effect and readers will quietly end up with no method security enabled. Please add @Configuration to both the Java and Kotlin snippets to match the rest of the Spring Security reference docs.


xref:servlet/authorization/method-security.adoc[Method security] does not require an HTTP request.
Once it is enabled, annotations such as `@PreAuthorize`, `@PostAuthorize` and `@PreFilter` are evaluated against the `Authentication` found in the `SecurityContextHolder`, regardless of what triggered the invocation:

[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}

@Service
public class MessageService {

@PreAuthorize("hasRole('ADMIN')")
public void process(String message) {
// ...
}
}
----

Kotlin::
+
[source,kotlin,role="secondary"]
----
@Configuration
@EnableMethodSecurity
class MethodSecurityConfig

@Service
class MessageService {

@PreAuthorize("hasRole('ADMIN')")
fun process(message: String) {
// ...
}
}
----
======

This is often the most convenient way to secure non-web code: the security decision travels with the method, so a scheduled job or message listener is protected without any request-scoped machinery.
When you invoke such a method from a background thread, remember to propagate the context as described in the previous section if the `Authentication` is not already available on that thread.

[[outside-request-testing]]
== Testing Without a Web Request

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The testing example bootstraps the full application context with @SpringBootTest, but the surrounding prose promises "plain unit tests that exercise secured services directly" (bullet in the intro). For a reader who truly has no web layer, a slice test or a plain @ExtendWith(SpringExtension.class) + narrow @ContextConfiguration is a much better fit and much faster. Consider either switching the snippet to a lighter setup or adding a sentence noting that @WithMockUser works with any Spring Test context, not only @SpringBootTest.

You can test secured services without starting `MockMvc` or an HTTP server.
Spring Security integrates with Spring Test through `@WithMockUser` and `@WithSecurityContext`, which set up a `SecurityContext` for the duration of a test.
These annotations work with any Spring Test context, so you can use a lightweight slice instead of booting the whole application:

[tabs]
======
Java::
+
[source,java,role="primary"]
----
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = MethodSecurityConfig.class)
class MessageServiceTests {

@Autowired
MessageService messageService;

@Test
@WithMockUser(roles = "ADMIN")
void adminCanProcess() {
messageService.process("hello");
}
}
----

Kotlin::
+
[source,kotlin,role="secondary"]
----
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [MethodSecurityConfig::class])
class MessageServiceTests {

@Autowired
lateinit var messageService: MessageService

@Test
@WithMockUser(roles = ["ADMIN"])
fun adminCanProcess() {
messageService.process("hello")
}
}
----
======

See xref:servlet/test/method.adoc#test-method[Testing Method Security] for `@WithMockUser`, `@WithUserDetails`, and using xref:servlet/test/method.adoc#test-method-withsecuritycontext[`@WithSecurityContext`] with a custom `Authentication`.

[[outside-request-reactive]]
== Reactive Environments

If you are on a reactive stack, `SecurityContextHolder` is not the idiomatic mechanism; Spring Security instead carries the context through Reactor's `Context` via xref:reactive/authorization/method.adoc[`ReactiveSecurityContextHolder`].
Refer to xref:reactive/index.adoc[Reactive Applications] for details.