Create features/integrations

This commit is contained in:
Rob Winch
2021-09-21 14:53:38 -05:00
parent ab63ebbbee
commit ca2bc958df
10 changed files with 20 additions and 274 deletions

View File

@@ -0,0 +1,260 @@
[[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:
====
.Java
[source,java,role="primary"]
----
public void run() {
try {
SecurityContextHolder.setContext(securityContext);
delegate.run();
} finally {
SecurityContextHolder.clearContext();
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
fun 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.adoc#nsa-global-method-security[<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:
====
.Java
[source,java,role="primary"]
----
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable, context);
new Thread(wrappedRunnable).start();
----
.Kotlin
[source,kotlin,role="secondary"]
----
val originalRunnable = Runnable {
// invoke secured service
}
val context: SecurityContext = SecurityContextHolder.getContext()
val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable, context)
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:
====
.Java
[source,java,role="primary"]
----
Runnable originalRunnable = new Runnable() {
public void run() {
// invoke secured service
}
};
DelegatingSecurityContextRunnable wrappedRunnable =
new DelegatingSecurityContextRunnable(originalRunnable);
new Thread(wrappedRunnable).start();
----
.Kotlin
[source,kotlin,role="secondary"]
----
val originalRunnable = Runnable {
// invoke secured service
}
val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable)
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:
====
.Java
[source,java,role="primary"]
----
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);
----
.Kotlin
[source,kotlin,role="secondary"]
----
val context: SecurityContext = SecurityContextHolder.createEmptyContext()
val authentication: Authentication =
UsernamePasswordAuthenticationToken("user", "doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER"))
context.authentication = authentication
val delegateExecutor = SimpleAsyncTaskExecutor()
val executor = DelegatingSecurityContextExecutor(delegateExecutor, context)
val originalRunnable = Runnable {
// 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`.
====
.Java
[source,java,role="primary"]
----
@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);
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Autowired
lateinit var executor: Executor // becomes an instance of our DelegatingSecurityContextExecutor
fun submitRunnable() {
val originalRunnable = Runnable {
// 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:
====
.Java
[source,java,role="primary"]
----
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
----
.Kotlin
[source,kotlin,role="secondary"]
----
val delegateExecutor = SimpleAsyncTaskExecutor()
val executor = 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`

View File

@@ -0,0 +1,264 @@
[[crypto]]
= Spring Security Crypto Module
[[spring-security-crypto-introduction]]
== Introduction
The Spring Security Crypto module provides support for symmetric encryption, key generation, and password encoding.
The code is distributed as part of the core module but has no dependencies on any other Spring Security (or Spring) code.
[[spring-security-crypto-encryption]]
== Encryptors
The Encryptors class provides factory methods for constructing symmetric encryptors.
Using this class, you can create ByteEncryptors to encrypt data in raw byte[] form.
You can also construct TextEncryptors to encrypt text strings.
Encryptors are thread-safe.
[[spring-security-crypto-encryption-bytes]]
=== BytesEncryptor
Use the `Encryptors.stronger` factory method to construct a BytesEncryptor:
.BytesEncryptor
====
.Java
[source,java,role="primary"]
----
Encryptors.stronger("password", "salt");
----
.Kotlin
[source,kotlin,role="secondary"]
----
Encryptors.stronger("password", "salt")
----
====
The "stronger" encryption method creates an encryptor using 256 bit AES encryption with
Galois Counter Mode (GCM).
It derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2).
This method requires Java 6.
The password used to generate the SecretKey should be kept in a secure place and not be shared.
The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised.
A 16-byte random initialization vector is also applied so each encrypted message is unique.
The provided salt should be in hex-encoded String form, be random, and be at least 8 bytes in length.
Such a salt may be generated using a KeyGenerator:
.Generating a key
====
.Java
[source,java,role="primary"]
----
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
----
.Kotlin
[source,kotlin,role="secondary"]
----
val salt = KeyGenerators.string().generateKey() // generates a random 8-byte salt that is then hex-encoded
----
====
Users may also use the `standard` encryption method, which is 256-bit AES in Cipher Block Chaining (CBC) Mode.
This mode is not https://en.wikipedia.org/wiki/Authenticated_encryption[authenticated] and does not provide any
guarantees about the authenticity of the data.
For a more secure alternative, users should prefer `Encryptors.stronger`.
[[spring-security-crypto-encryption-text]]
=== TextEncryptor
Use the Encryptors.text factory method to construct a standard TextEncryptor:
.TextEncryptor
====
.Java
[source,java,role="primary"]
----
Encryptors.text("password", "salt");
----
.Kotlin
[source,kotlin,role="secondary"]
----
Encryptors.text("password", "salt")
----
====
A TextEncryptor uses a standard BytesEncryptor to encrypt text data.
Encrypted results are returned as hex-encoded strings for easy storage on the filesystem or in the database.
Use the Encryptors.queryableText factory method to construct a "queryable" TextEncryptor:
.Queryable TextEncryptor
====
.Java
[source,java,role="primary"]
----
Encryptors.queryableText("password", "salt");
----
.Kotlin
[source,kotlin,role="secondary"]
----
Encryptors.queryableText("password", "salt")
----
====
The difference between a queryable TextEncryptor and a standard TextEncryptor has to do with initialization vector (iv) handling.
The iv used in a queryable TextEncryptor#encrypt operation is shared, or constant, and is not randomly generated.
This means the same text encrypted multiple times will always produce the same encryption result.
This is less secure, but necessary for encrypted data that needs to be queried against.
An example of queryable encrypted text would be an OAuth apiKey.
[[spring-security-crypto-keygenerators]]
== Key Generators
The KeyGenerators class provides a number of convenience factory methods for constructing different types of key generators.
Using this class, you can create a BytesKeyGenerator to generate byte[] keys.
You can also construct a StringKeyGenerator to generate string keys.
KeyGenerators are thread-safe.
=== BytesKeyGenerator
Use the KeyGenerators.secureRandom factory methods to generate a BytesKeyGenerator backed by a SecureRandom instance:
.BytesKeyGenerator
====
.Java
[source,java,role="primary"]
----
BytesKeyGenerator generator = KeyGenerators.secureRandom();
byte[] key = generator.generateKey();
----
.Kotlin
[source,kotlin,role="secondary"]
----
val generator = KeyGenerators.secureRandom()
val key = generator.generateKey()
----
====
The default key length is 8 bytes.
There is also a KeyGenerators.secureRandom variant that provides control over the key length:
.KeyGenerators.secureRandom
====
.Java
[source,java,role="primary"]
----
KeyGenerators.secureRandom(16);
----
.Kotlin
[source,kotlin,role="secondary"]
----
KeyGenerators.secureRandom(16)
----
====
Use the KeyGenerators.shared factory method to construct a BytesKeyGenerator that always returns the same key on every invocation:
.KeyGenerators.shared
====
.Java
[source,java,role="primary"]
----
KeyGenerators.shared(16);
----
.Kotlin
[source,kotlin,role="secondary"]
----
KeyGenerators.shared(16)
----
====
=== StringKeyGenerator
Use the KeyGenerators.string factory method to construct a 8-byte, SecureRandom KeyGenerator that hex-encodes each key as a String:
.StringKeyGenerator
====
.Java
[source,java,role="primary"]
----
KeyGenerators.string();
----
.Kotlin
[source,kotlin,role="secondary"]
----
KeyGenerators.string()
----
====
[[spring-security-crypto-passwordencoders]]
== Password Encoding
The password package of the spring-security-crypto module provides support for encoding passwords.
`PasswordEncoder` is the central service interface and has the following signature:
[source,java]
----
public interface PasswordEncoder {
String encode(String rawPassword);
boolean matches(String rawPassword, String encodedPassword);
}
----
The matches method returns true if the rawPassword, once encoded, equals the encodedPassword.
This method is designed to support password-based authentication schemes.
The `BCryptPasswordEncoder` implementation uses the widely supported "bcrypt" algorithm to hash the passwords.
Bcrypt uses a random 16 byte salt value and is a deliberately slow algorithm, in order to hinder password crackers.
The amount of work it does can be tuned using the "strength" parameter which takes values from 4 to 31.
The higher the value, the more work has to be done to calculate the hash.
The default value is 10.
You can change this value in your deployed system without affecting existing passwords, as the value is also stored in the encoded hash.
.BCryptPasswordEncoder
====
.Java
[source,java,role="primary"]
----
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
[source,kotlin,role="secondary"]
----
// Create an encoder with strength 16
val encoder = BCryptPasswordEncoder(16)
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
The `Pbkdf2PasswordEncoder` implementation uses PBKDF2 algorithm to hash the passwords.
In order to defeat password cracking PBKDF2 is a deliberately slow algorithm and should be tuned to take about .5 seconds to verify a password on your system.
.Pbkdf2PasswordEncoder
====
.Java
[source,java,role="primary"]
----
// Create an encoder with all the defaults
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
val encoder = Pbkdf2PasswordEncoder()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====

View File

@@ -0,0 +1,70 @@
[[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:
====
.Java
[source,java,role="primary"]
----
@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension {
return SecurityEvaluationContextExtension()
}
----
====
In XML Configuration, this would look like:
[source,xml]
----
<bean class="org.springframework.security.data.repository.query.SecurityEvaluationContextExtension"/>
----
[[data-query]]
== Security Expressions within @Query
Now Spring Security can be used within your queries.
For example:
====
.Java
[source,java,role="primary"]
----
@Repository
public interface MessageRepository extends PagingAndSortingRepository<Message,Long> {
@Query("select m from Message m where m.to.id = ?#{ principal?.id }")
Page<Message> findInbox(Pageable pageable);
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Repository
interface MessageRepository : PagingAndSortingRepository<Message?, Long?> {
@Query("select m from Message m where m.to.id = ?#{ principal?.id }")
fun findInbox(pageable: Pageable?): Page<Message?>?
}
----
====
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.

View File

@@ -0,0 +1,13 @@
[[integrations]]
= Integrations
Spring Security provides integrations with numerous frameworks and APIs.
In this section, we discuss generic integrations that are not specific to Servlet or Reactive environments.
To see specific integrations, refer to the xref:servlet/integrations/index.adoc[Servlet] and xref:servlet/integrations/index.adoc[Reactive] Integrations sections.
// FIXME add link to reactive integrations
* xref:features/integrations/cryptography.adoc[Cryptography]
* xref:features/integrations/data.adoc[Spring Data]
* xref:features/integrations/concurrency.adoc[Java's Concurrency APIs]
* xref:features/integrations/jackson.adoc[Jackson]
* xref:features/integrations/localization.adoc[Localization]

View File

@@ -0,0 +1,47 @@
[[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]):
====
.Java
[source,java,role="primary"]
----
ObjectMapper mapper = new ObjectMapper();
ClassLoader loader = getClass().getClassLoader();
List<Module> modules = SecurityJackson2Modules.getModules(loader);
mapper.registerModules(modules);
// ... use ObjectMapper as normally ...
SecurityContext context = new SecurityContextImpl();
// ...
String json = mapper.writeValueAsString(context);
----
.Kotlin
[source,kotlin,role="secondary"]
----
val mapper = ObjectMapper()
val loader = javaClass.classLoader
val modules: MutableList<Module> = SecurityJackson2Modules.getModules(loader)
mapper.registerModules(modules)
// ... use ObjectMapper as normally ...
val context: SecurityContext = SecurityContextImpl()
// ...
val json: String = mapper.writeValueAsString(context)
----
====
[NOTE]
====
The following Spring Security modules provide Jackson support:
- spring-security-core (`CoreJackson2Module`)
- spring-security-web (`WebJackson2Module`, `WebServletJackson2Module`, `WebServerJackson2Module`)
- xref:servlet/oauth2/oauth2-client.adoc#oauth2client[ spring-security-oauth2-client] (`OAuth2ClientJackson2Module`)
- spring-security-cas (`CasJackson2Module`)
====

View File

@@ -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]
----
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:org/springframework/security/messages"/>
</bean>
----
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.