From 310a50587c713cda1e2eab53436af7ef66a3acdc Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 10 Nov 2021 15:38:29 -0700 Subject: [PATCH] Port Missing Integration Docs Closes gh-10465 --- docs/modules/ROOT/nav.adoc | 4 + .../servlet/integrations/concurrency.adoc | 166 ++++++++++++++++++ .../ROOT/pages/servlet/integrations/data.adoc | 45 +++++ .../pages/servlet/integrations/jackson.adoc | 30 ++++ .../servlet/integrations/localization.adoc | 36 ++++ 5 files changed, 281 insertions(+) create mode 100644 docs/modules/ROOT/pages/servlet/integrations/concurrency.adoc create mode 100644 docs/modules/ROOT/pages/servlet/integrations/data.adoc create mode 100644 docs/modules/ROOT/pages/servlet/integrations/jackson.adoc create mode 100644 docs/modules/ROOT/pages/servlet/integrations/localization.adoc diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 6e5e30a003..b1f65c4d56 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -81,7 +81,11 @@ *** xref:servlet/exploits/http.adoc[] *** xref:servlet/exploits/firewall.adoc[] ** xref:servlet/integrations/index.adoc[Integrations] +*** xref:servlet/integrations/concurrency.adoc[Concurrency] +*** xref:servlet/integrations/jackson.adoc[Jackson] +*** xref:servlet/integrations/localization.adoc[Localization] *** xref:servlet/integrations/servlet-api.adoc[Servlet APIs] +*** xref:servlet/integrations/data.adoc[Spring Data] *** xref:servlet/integrations/mvc.adoc[Spring MVC] *** xref:servlet/integrations/websocket.adoc[WebSocket] *** xref:servlet/integrations/cors.adoc[Spring's CORS Support] diff --git a/docs/modules/ROOT/pages/servlet/integrations/concurrency.adoc b/docs/modules/ROOT/pages/servlet/integrations/concurrency.adoc new file mode 100644 index 0000000000..3d8036baf8 --- /dev/null +++ b/docs/modules/ROOT/pages/servlet/integrations/concurrency.adoc @@ -0,0 +1,166 @@ +[[concurrency]] += Concurrency Support + +In most environments, Security is stored on a per `Thread` basis. +This means that when work is done on a new `Thread`, the `SecurityContext` is lost. +Spring Security provides some infrastructure to help make this much easier for users. +Spring Security provides low level abstractions for working with Spring Security in multi-threaded environments. +In fact, this is what Spring Security builds on to integration with xref:servlet/integrations/servlet-api.adoc#servletapi-start-runnable[`AsyncContext.start(Runnable)`] and xref:servlet/integrations/mvc.adoc#mvc-async[Spring MVC Async Integration]. + +== DelegatingSecurityContextRunnable + +One of the most fundamental building blocks within Spring Security's concurrency support is the `DelegatingSecurityContextRunnable`. +It wraps a delegate `Runnable` in order to initialize the `SecurityContextHolder` with a specified `SecurityContext` for the delegate. +It then invokes the delegate Runnable ensuring to clear the `SecurityContextHolder` afterwards. +The `DelegatingSecurityContextRunnable` looks something like this: + +[source,java] +---- +public void run() { +try { + SecurityContextHolder.setContext(securityContext); + delegate.run(); +} finally { + SecurityContextHolder.clearContext(); +} +} +---- + +While very simple, it makes it seamless to transfer the SecurityContext from one Thread to another. +This is important since, in most cases, the SecurityContextHolder acts on a per Thread basis. +For example, you might have used Spring Security's xref:servlet/appendix/namespace/method-security.adoc#nsa-global-method-security[``] support to secure one of your services. +You can now easily transfer the `SecurityContext` of the current `Thread` to the `Thread` that invokes the secured service. +An example of how you might do this can be found below: + +[source,java] +---- +Runnable originalRunnable = new Runnable() { +public void run() { + // invoke secured service +} +}; + +SecurityContext context = SecurityContextHolder.getContext(); +DelegatingSecurityContextRunnable wrappedRunnable = + new DelegatingSecurityContextRunnable(originalRunnable, context); + +new Thread(wrappedRunnable).start(); +---- + +The code above performs the following steps: + +* Creates a `Runnable` that will be invoking our secured service. +Notice that it is not aware of Spring Security +* Obtains the `SecurityContext` that we wish to use from the `SecurityContextHolder` and initializes the `DelegatingSecurityContextRunnable` +* Use the `DelegatingSecurityContextRunnable` to create a Thread +* Start the Thread we created + +Since it is quite common to create a `DelegatingSecurityContextRunnable` with the `SecurityContext` from the `SecurityContextHolder` there is a shortcut constructor for it. +The following code is the same as the code above: + + +[source,java] +---- +Runnable originalRunnable = new Runnable() { +public void run() { + // invoke secured service +} +}; + +DelegatingSecurityContextRunnable wrappedRunnable = + new DelegatingSecurityContextRunnable(originalRunnable); + +new Thread(wrappedRunnable).start(); +---- + +The code we have is simple to use, but it still requires knowledge that we are using Spring Security. +In the next section we will take a look at how we can utilize `DelegatingSecurityContextExecutor` to hide the fact that we are using Spring Security. + +== DelegatingSecurityContextExecutor + +In the previous section we found that it was easy to use the `DelegatingSecurityContextRunnable`, but it was not ideal since we had to be aware of Spring Security in order to use it. +Let's take a look at how `DelegatingSecurityContextExecutor` can shield our code from any knowledge that we are using Spring Security. + +The design of `DelegatingSecurityContextExecutor` is very similar to that of `DelegatingSecurityContextRunnable` except it accepts a delegate `Executor` instead of a delegate `Runnable`. +You can see an example of how it might be used below: + + +[source,java] +---- +SecurityContext context = SecurityContextHolder.createEmptyContext(); +Authentication authentication = + new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER")); +context.setAuthentication(authentication); + +SimpleAsyncTaskExecutor delegateExecutor = + new SimpleAsyncTaskExecutor(); +DelegatingSecurityContextExecutor executor = + new DelegatingSecurityContextExecutor(delegateExecutor, context); + +Runnable originalRunnable = new Runnable() { +public void run() { + // invoke secured service +} +}; + +executor.execute(originalRunnable); +---- + +The code performs the following steps: + +* Creates the `SecurityContext` to be used for our `DelegatingSecurityContextExecutor`. +Note that in this example we simply create the `SecurityContext` by hand. +However, it does not matter where or how we get the `SecurityContext` (i.e. we could obtain it from the `SecurityContextHolder` if we wanted). +* Creates a delegateExecutor that is in charge of executing submitted ``Runnable``s +* Finally we create a `DelegatingSecurityContextExecutor` which is in charge of wrapping any Runnable that is passed into the execute method with a `DelegatingSecurityContextRunnable`. +It then passes the wrapped Runnable to the delegateExecutor. +In this instance, the same `SecurityContext` will be used for every Runnable submitted to our `DelegatingSecurityContextExecutor`. +This is nice if we are running background tasks that need to be run by a user with elevated privileges. +* At this point you may be asking yourself "How does this shield my code of any knowledge of Spring Security?" Instead of creating the `SecurityContext` and the `DelegatingSecurityContextExecutor` in our own code, we can inject an already initialized instance of `DelegatingSecurityContextExecutor`. + +[source,java] +---- +@Autowired +private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor + +public void submitRunnable() { +Runnable originalRunnable = new Runnable() { + public void run() { + // invoke secured service + } +}; +executor.execute(originalRunnable); +} +---- + +Now our code is unaware that the `SecurityContext` is being propagated to the `Thread`, then the `originalRunnable` is run, and then the `SecurityContextHolder` is cleared out. +In this example, the same user is being used to run each thread. +What if we wanted to use the user from `SecurityContextHolder` at the time we invoked `executor.execute(Runnable)` (i.e. the currently logged in user) to process ``originalRunnable``? +This can be done by removing the `SecurityContext` argument from our `DelegatingSecurityContextExecutor` constructor. +For example: + + +[source,java] +---- +SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); +DelegatingSecurityContextExecutor executor = + new DelegatingSecurityContextExecutor(delegateExecutor); +---- + +Now anytime `executor.execute(Runnable)` is executed the `SecurityContext` is first obtained by the `SecurityContextHolder` and then that `SecurityContext` is used to create our `DelegatingSecurityContextRunnable`. +This means that we are running our `Runnable` with the same user that was used to invoke the `executor.execute(Runnable)` code. + +== Spring Security Concurrency Classes + +Refer to the Javadoc for additional integrations with both the Java concurrent APIs and the Spring Task abstractions. +They are quite self-explanatory once you understand the previous code. + +* `DelegatingSecurityContextCallable` +* `DelegatingSecurityContextExecutor` +* `DelegatingSecurityContextExecutorService` +* `DelegatingSecurityContextRunnable` +* `DelegatingSecurityContextScheduledExecutorService` +* `DelegatingSecurityContextSchedulingTaskExecutor` +* `DelegatingSecurityContextAsyncTaskExecutor` +* `DelegatingSecurityContextTaskExecutor` +* `DelegatingSecurityContextTaskScheduler` diff --git a/docs/modules/ROOT/pages/servlet/integrations/data.adoc b/docs/modules/ROOT/pages/servlet/integrations/data.adoc new file mode 100644 index 0000000000..35f89ec78c --- /dev/null +++ b/docs/modules/ROOT/pages/servlet/integrations/data.adoc @@ -0,0 +1,45 @@ +[[data]] += Spring Data Integration + +Spring Security provides Spring Data integration that allows referring to the current user within your queries. +It is not only useful but necessary to include the user in the queries to support paged results since filtering the results afterwards would not scale. + +[[data-configuration]] +== Spring Data & Spring Security Configuration + +To use this support, add `org.springframework.security:spring-security-data` dependency and provide a bean of type `SecurityEvaluationContextExtension`. +In Java Configuration, this would look like: + +[source,java] +---- +@Bean +public SecurityEvaluationContextExtension securityEvaluationContextExtension() { + return new SecurityEvaluationContextExtension(); +} +---- + +In XML Configuration, this would look like: + +[source,xml] +---- + +---- + +[[data-query]] +== Security Expressions within @Query + +Now Spring Security can be used within your queries. +For example: + +[source,java] +---- +@Repository +public interface MessageRepository extends PagingAndSortingRepository { + @Query("select m from Message m where m.to.id = ?#{ principal?.id }") + Page findInbox(Pageable pageable); +} +---- + +This checks to see if the `Authentication.getPrincipal().getId()` is equal to the recipient of the `Message`. +Note that this example assumes you have customized the principal to be an Object that has an id property. +By exposing the `SecurityEvaluationContextExtension` bean, all of the xref:servlet/authorization/expression-based.adoc#common-expressions[Common Security Expressions] are available within the Query. diff --git a/docs/modules/ROOT/pages/servlet/integrations/jackson.adoc b/docs/modules/ROOT/pages/servlet/integrations/jackson.adoc new file mode 100644 index 0000000000..ee17f37c69 --- /dev/null +++ b/docs/modules/ROOT/pages/servlet/integrations/jackson.adoc @@ -0,0 +1,30 @@ +[[jackson]] += Jackson Support + +Spring Security provides Jackson support for persisting Spring Security related classes. +This can improve the performance of serializing Spring Security related classes when working with distributed sessions (i.e. session replication, Spring Session, etc). + +To use it, register the `SecurityJackson2Modules.getModules(ClassLoader)` with `ObjectMapper` (https://github.com/FasterXML/jackson-databind[jackson-databind]): + +[source,java] +---- +ObjectMapper mapper = new ObjectMapper(); +ClassLoader loader = getClass().getClassLoader(); +List modules = SecurityJackson2Modules.getModules(loader); +mapper.registerModules(modules); + +// ... use ObjectMapper as normally ... +SecurityContext context = new SecurityContextImpl(); +// ... +String json = mapper.writeValueAsString(context); +---- + +[NOTE] +==== +The following Spring Security modules provide Jackson support: + +- spring-security-core (`CoreJackson2Module`) +- spring-security-web (`WebJackson2Module`, `WebServletJackson2Module`, `WebServerJackson2Module`) +- <> (`OAuth2ClientJackson2Module`) +- spring-security-cas (`CasJackson2Module`) +==== diff --git a/docs/modules/ROOT/pages/servlet/integrations/localization.adoc b/docs/modules/ROOT/pages/servlet/integrations/localization.adoc new file mode 100644 index 0000000000..e1fc22b9a2 --- /dev/null +++ b/docs/modules/ROOT/pages/servlet/integrations/localization.adoc @@ -0,0 +1,36 @@ +[[localization]] += Localization +Spring Security supports localization of exception messages that end users are likely to see. +If your application is designed for English-speaking users, you don't need to do anything as by default all Security messages are in English. +If you need to support other locales, everything you need to know is contained in this section. + +All exception messages can be localized, including messages related to authentication failures and access being denied (authorization failures). +Exceptions and logging messages that are focused on developers or system deplopers (including incorrect attributes, interface contract violations, using incorrect constructors, startup time validation, debug-level logging) are not localized and instead are hard-coded in English within Spring Security's code. + +Shipping in the `spring-security-core-xx.jar` you will find an `org.springframework.security` package that in turn contains a `messages.properties` file, as well as localized versions for some common languages. +This should be referred to by your `ApplicationContext`, as Spring Security classes implement Spring's `MessageSourceAware` interface and expect the message resolver to be dependency injected at application context startup time. +Usually all you need to do is register a bean inside your application context to refer to the messages. +An example is shown below: + +[source,xml] +---- + + + +---- + +The `messages.properties` is named in accordance with standard resource bundles and represents the default language supported by Spring Security messages. +This default file is in English. + +If you wish to customize the `messages.properties` file, or support other languages, you should copy the file, rename it accordingly, and register it inside the above bean definition. +There are not a large number of message keys inside this file, so localization should not be considered a major initiative. +If you do perform localization of this file, please consider sharing your work with the community by logging a JIRA task and attaching your appropriately-named localized version of `messages.properties`. + +Spring Security relies on Spring's localization support in order to actually lookup the appropriate message. +In order for this to work, you have to make sure that the locale from the incoming request is stored in Spring's `org.springframework.context.i18n.LocaleContextHolder`. +Spring MVC's `DispatcherServlet` does this for your application automatically, but since Spring Security's filters are invoked before this, the `LocaleContextHolder` needs to be set up to contain the correct `Locale` before the filters are called. +You can either do this in a filter yourself (which must come before the Spring Security filters in `web.xml`) or you can use Spring's `RequestContextFilter`. +Please refer to the Spring Framework documentation for further details on using localization with Spring. + +The "contacts" sample application is set up to use localized messages.