Merge branch '5.8.x' into 6.0.x

Closes gh-13406
This commit is contained in:
Rob Winch
2023-06-18 21:33:58 -05:00
116 changed files with 4826 additions and 3206 deletions

View File

@@ -67,26 +67,31 @@ Instead Spring Security introduces `DelegatingPasswordEncoder`, which solves all
You can easily construct an instance of `DelegatingPasswordEncoder` by using `PasswordEncoderFactories`:
.Create Default DelegatingPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val passwordEncoder: PasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
----
====
======
Alternatively, you can create your own custom instance:
.Create Custom DelegatingPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
String idForEncode = "bcrypt";
@@ -105,7 +110,8 @@ PasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(idForEncode, encoders);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val idForEncode = "bcrypt"
@@ -122,7 +128,7 @@ encoders["sha256"] = StandardPasswordEncoder()
val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, encoders)
----
====
======
[[authentication-password-storage-dpe-format]]
=== Password Storage Format
@@ -130,12 +136,10 @@ val passwordEncoder: PasswordEncoder = DelegatingPasswordEncoder(idForEncode, en
The general format for a password is:
.DelegatingPasswordEncoder Storage Format
====
[source,text,attrs="-attributes"]
----
{id}encodedPassword
----
====
`id` is an identifier that is used to look up which `PasswordEncoder` should be used and `encodedPassword` is the original encoded password for the selected `PasswordEncoder`.
The `id` must be at the beginning of the password, start with `{`, and end with `}`.
@@ -144,7 +148,6 @@ For example, the following might be a list of passwords encoded using different
All of the original passwords are `password`.
.DelegatingPasswordEncoder Encoded Passwords Example
====
[source,text,attrs="-attributes"]
----
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG // <1>
@@ -153,7 +156,6 @@ All of the original passwords are `password`.
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= // <4>
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 // <5>
----
====
<1> The first password has a `PasswordEncoder` id of `bcrypt` and an `encodedPassword` value of `$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG`.
When matching, it would delegate to `BCryptPasswordEncoder`
@@ -182,12 +184,10 @@ In the `DelegatingPasswordEncoder` we constructed earlier, that means that the r
The end result looks like the following example:
.DelegatingPasswordEncoder Encode Example
====
[source,text,attrs="-attributes"]
----
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
====
[[authentication-password-storage-dpe-matching]]
=== Password Matching
@@ -209,8 +209,10 @@ If you are putting together a demo or a sample, it is a bit cumbersome to take t
There are convenience mechanisms to make this easier, but this is still not intended for production.
.withDefaultPasswordEncoder Example
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary",attrs="-attributes"]
----
UserDetails user = User.withDefaultPasswordEncoder()
@@ -222,7 +224,8 @@ System.out.println(user.getPassword());
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary",attrs="-attributes"]
----
val user = User.withDefaultPasswordEncoder()
@@ -233,13 +236,15 @@ val user = User.withDefaultPasswordEncoder()
println(user.password)
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
====
======
If you are creating multiple users, you can also reuse the builder:
.withDefaultPasswordEncoder Reusing the Builder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
UserBuilder users = User.withDefaultPasswordEncoder();
@@ -255,7 +260,8 @@ UserDetails admin = users
.build();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val users = User.withDefaultPasswordEncoder()
@@ -270,7 +276,7 @@ val admin = users
.roles("USER", "ADMIN")
.build()
----
====
======
This does hash the password that is stored, but the passwords are still exposed in memory and in the compiled source code.
Therefore, it is still not considered secure for a production environment.
@@ -284,26 +290,22 @@ The easiest way to properly encode your password is to use the https://docs.spri
For example, the following example encodes the password of `password` for use with <<authentication-password-storage-dpe>>:
.Spring Boot CLI encodepassword Example
====
[source,attrs="-attributes"]
----
spring encodepassword password
{bcrypt}$2a$10$X5wFBtLrL/kHcmrOGGTrGufsBX8CJ0WpQpF3pgeuxBB/H73BK1DW6
----
====
[[authentication-password-storage-dpe-troubleshoot]]
=== Troubleshooting
The following error occurs when one of the passwords that are stored has no `id`, as described in <<authentication-password-storage-dpe-format>>.
====
----
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:196)
----
====
The easiest way to resolve it is to figure out how your passwords are currently being stored and explicitly provide the correct `PasswordEncoder`.
@@ -312,20 +314,16 @@ If you are migrating from Spring Security 4.2.x, you can revert to the previous
Alternatively, you can prefix all of your passwords with the correct `id` and continue to use `DelegatingPasswordEncoder`.
For example, if you are using BCrypt, you would migrate your password from something like:
====
----
$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
====
to
====
[source,attrs="-attributes"]
----
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
----
====
For a complete listing of the mappings, see the Javadoc for
https://docs.spring.io/spring-security/site/docs/5.0.x/api/org/springframework/security/crypto/factory/PasswordEncoderFactories.html[`PasswordEncoderFactories`].
@@ -340,8 +338,10 @@ The default implementation of `BCryptPasswordEncoder` uses strength 10 as mentio
tune and test the strength parameter on your own system so that it takes roughly 1 second to verify a password.
.BCryptPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with strength 16
@@ -350,7 +350,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with strength 16
@@ -358,7 +359,7 @@ val encoder = BCryptPasswordEncoder(16)
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-argon2]]
== Argon2PasswordEncoder
@@ -370,8 +371,10 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second
The current implementation of the `Argon2PasswordEncoder` requires BouncyCastle.
.Argon2PasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -380,7 +383,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -388,7 +392,7 @@ val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-pbkdf2]]
== Pbkdf2PasswordEncoder
@@ -399,8 +403,10 @@ Like other adaptive one-way functions, it should be tuned to take about 1 second
This algorithm is a good choice when FIPS certification is required.
.Pbkdf2PasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -409,7 +415,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -417,7 +424,7 @@ val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-scrypt]]
== SCryptPasswordEncoder
@@ -427,8 +434,10 @@ To defeat password cracking on custom hardware, scrypt is a deliberately slow al
Like other adaptive one-way functions, it should be tuned to take about 1 second to verify a password on your system.
.SCryptPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -437,7 +446,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -445,7 +455,7 @@ val encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======
[[authentication-password-storage-other]]
== Other ``PasswordEncoder``s
@@ -470,8 +480,10 @@ You should instead migrate to using `DelegatingPasswordEncoder` to support secur
====
.NoOpPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -480,14 +492,16 @@ public static NoOpPasswordEncoder passwordEncoder() {
}
----
.XML
XML::
+
[source,xml,role="secondary"]
----
<b:bean id="passwordEncoder"
class="org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method="getInstance"/>
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -495,7 +509,7 @@ fun passwordEncoder(): PasswordEncoder {
return NoOpPasswordEncoder.getInstance();
}
----
====
======
[NOTE]
====
@@ -513,36 +527,42 @@ You can configure Spring Security to provide this discovery endpoint.
For example, if the change password endpoint in your application is `/change-password`, then you can configure Spring Security like so:
.Default Change Password Endpoint
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
.passwordManagement(Customizer.withDefaults())
----
.XML
XML::
+
[source,xml,role="secondary"]
----
<sec:password-management/>
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
passwordManagement { }
}
----
====
======
Then, when a password manager navigates to `/.well-known/change-password` then Spring Security will redirect your endpoint, `/change-password`.
Or, if your endpoint is something other than `/change-password`, you can also specify that like so:
.Change Password Endpoint
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
http
@@ -551,13 +571,15 @@ http
)
----
.XML
XML::
+
[source,xml,role="secondary"]
----
<sec:password-management change-password-page="/update-password"/>
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
http {
@@ -566,6 +588,6 @@ http {
}
}
----
====
======
With the above configuration, when a password manager navigates to `/.well-known/change-password`, then Spring Security will redirect to `/update-password`.

View File

@@ -25,7 +25,6 @@ Assume that your bank's website provides a form that allows transferring money f
For example, the transfer form might look like:
.Transfer form
====
[source,html]
----
<form method="post"
@@ -40,12 +39,10 @@ For example, the transfer form might look like:
value="Transfer"/>
</form>
----
====
The corresponding HTTP request might look like:
.Transfer HTTP request
====
[source]
----
POST /transfer HTTP/1.1
@@ -55,13 +52,11 @@ Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876
----
====
Now pretend you authenticate to your bank's website and then, without logging out, visit an evil website.
The evil website contains an HTML page with the following form:
.Evil transfer form
====
[source,html]
----
<form method="post"
@@ -79,7 +74,6 @@ The evil website contains an HTML page with the following form:
value="Win Money!"/>
</form>
----
====
You like to win money, so you click on the submit button.
In the process, you have unintentionally transferred $100 to a malicious user.
@@ -134,7 +128,6 @@ Assume that the actual CSRF token is required to be in an HTTP parameter named `
Our application's transfer form would look like:
.Synchronizer Token Form
====
[source,html]
----
<form method="post"
@@ -152,7 +145,6 @@ Our application's transfer form would look like:
value="Transfer"/>
</form>
----
====
The form now contains a hidden input with the value of the CSRF token.
External sites cannot read the CSRF token since the same origin policy ensures the evil site cannot read the response.
@@ -160,7 +152,6 @@ External sites cannot read the CSRF token since the same origin policy ensures t
The corresponding HTTP request to transfer money would look like this:
.Synchronizer Token request
====
[source]
----
POST /transfer HTTP/1.1
@@ -170,7 +161,6 @@ Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
----
====
You will notice that the HTTP request now contains the `_csrf` parameter with a secure random value.
@@ -191,12 +181,10 @@ Spring Framework's https://docs.spring.io/spring-framework/docs/current/javadoc-
An example, of an HTTP response header with the `SameSite` attribute might look like:
.SameSite HTTP response
====
[source]
----
Set-Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly; SameSite=Lax
----
====
Valid values for the `SameSite` attribute are:
@@ -245,7 +233,6 @@ However, you must be very careful, as there are CSRF exploits that can impact JS
For example, a malicious user can create a http://blog.opensecurityresearch.com/2012/02/json-csrf-with-parameter-padding.html[CSRF with JSON by using the following form]:
.CSRF with JSON form
====
[source,html]
----
<form action="https://bank.example.com/transfer" method="post" enctype="text/plain">
@@ -254,13 +241,11 @@ For example, a malicious user can create a http://blog.opensecurityresearch.com/
value="Win Money!"/>
</form>
----
====
This produces the following JSON structure
.CSRF with JSON request
====
[source,javascript]
----
{ "amount": 100,
@@ -269,13 +254,11 @@ This produces the following JSON structure
"ignore_me": "=test"
}
----
====
If an application were not validating the `Content-Type` header, it would be exposed to this exploit.
Depending on the setup, a Spring MVC application that validates the Content-Type could still be exploited by updating the URL suffix to end with `.json`, as follows:
.CSRF with JSON Spring MVC form
====
[source,html]
----
<form action="https://bank.example.com/transfer.json" method="post" enctype="text/plain">
@@ -284,7 +267,6 @@ Depending on the setup, a Spring MVC application that validates the Content-Type
value="Win Money!"/>
</form>
----
====
[[csrf-when-stateless]]
=== CSRF and Stateless Browser Applications
@@ -394,7 +376,6 @@ Some applications can use a form parameter to override the HTTP method.
For example, the following form can treat the HTTP method as a `delete` rather than a `post`.
.CSRF Hidden HTTP Method Form
====
[source,html]
----
<form action="/process"
@@ -405,7 +386,6 @@ For example, the following form can treat the HTTP method as a `delete` rather t
value="delete"/>
</form>
----
====
Overriding the HTTP method occurs in a filter.

View File

@@ -24,7 +24,6 @@ Spring Security provides a default set of security related HTTP response headers
The default for Spring Security is to include the following headers:
.Default Security HTTP Response Headers
====
[source,http]
----
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
@@ -35,7 +34,6 @@ Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-XSS-Protection: 0
----
====
[NOTE]
====
@@ -65,14 +63,12 @@ If a user authenticates to view sensitive information and then logs out, we do n
The cache control headers that are sent by default are:
.Default Cache Control HTTP Response Headers
====
[source]
----
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
----
====
To be secure by default, Spring Security adds these headers by default.
However, if your application provides its own cache control headers, Spring Security backs out of the way.
@@ -105,12 +101,10 @@ A malicious user might create a http://webblaze.cs.berkeley.edu/papers/barth-cab
By default, Spring Security disables content sniffing by adding the following header to HTTP responses:
.nosniff HTTP Response Header
====
[source,http]
----
X-Content-Type-Options: nosniff
----
====
[[headers-hsts]]
== HTTP Strict Transport Security (HSTS)
@@ -140,12 +134,10 @@ For example, Spring Security's default behavior is to add the following header,
.Strict Transport Security HTTP Response Header
====
[source]
----
Strict-Transport-Security: max-age=31536000 ; includeSubDomains ; preload
----
====
The optional `includeSubDomains` directive instructs the browser that subdomains (such as `secure.mybank.example.com`) should also be treated as an HSTS domain.
@@ -193,12 +185,10 @@ While not perfect, the frame breaking code is the best you can do for the legacy
A more modern approach to address clickjacking is to use https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options[X-Frame-Options] header.
By default, Spring Security disables rendering pages within an iframe by using with the following header:
====
[source]
----
X-Frame-Options: DENY
----
====
[[headers-xss-protection]]
== X-XSS-Protection
@@ -213,12 +203,10 @@ The filter has been deprecated in major browsers, and https://cheatsheetseries.o
By default, Spring Security blocks the content by using the following header:
====
[source]
----
X-XSS-Protection: 0
----
====
[[headers-csp]]
@@ -250,12 +238,10 @@ A security policy contains a set of security policy directives, each responsible
For example, a web application can declare that it expects to load scripts from specific, trusted sources by including the following header in the response:
.Content Security Policy Example
====
[source]
----
Content-Security-Policy: script-src https://trustedscripts.example.com
----
====
An attempt to load a script from another source other than what is declared in the `script-src` directive is blocked by the user-agent.
Additionally, if the https://www.w3.org/TR/CSP2/#directive-report-uri[report-uri] directive is declared in the security policy, the violation will be reported by the user-agent to the declared URL.
@@ -263,12 +249,10 @@ Additionally, if the https://www.w3.org/TR/CSP2/#directive-report-uri[report-uri
For example, if a web application violates the declared security policy, the following response header instructs the user-agent to send violation reports to the URL specified in the policy's `report-uri` directive.
.Content Security Policy with report-uri
====
[source]
----
Content-Security-Policy: script-src https://trustedscripts.example.com; report-uri /csp-report-endpoint/
----
====
https://www.w3.org/TR/CSP2/#violation-reports[Violation reports] are standard JSON structures that can be captured either by the web application's own API or by a publicly hosted CSP violation reporting service, such as https://report-uri.io/.
@@ -279,12 +263,10 @@ When a policy is deemed effective, it can be enforced by using the `Content-Secu
Given the following response header, the policy declares that scripts can be loaded from one of two possible sources.
.Content Security Policy Report Only
====
[source]
----
Content-Security-Policy-Report-Only: script-src 'self' https://trustedscripts.example.com; report-uri /csp-report-endpoint/
----
====
If the site violates this policy, by attempting to load a script from `evil.example.com`, the user-agent sends a violation report to the declared URL specified by the `report-uri` directive but still lets the violating resource load.
@@ -311,12 +293,10 @@ page the user was on.
Spring Security's approach is to use the https://www.w3.org/TR/referrer-policy/[Referrer Policy] header, which provides different https://www.w3.org/TR/referrer-policy/#referrer-policies[policies]:
.Referrer Policy Example
====
[source]
----
Referrer-Policy: same-origin
----
====
The Referrer-Policy response header instructs the browser to let the destination knows the source where the user was previously.
@@ -331,12 +311,10 @@ See the relevant sections to see how to configure both xref:servlet/exploits/hea
https://wicg.github.io/feature-policy/[Feature Policy] is a mechanism that lets web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser.
.Feature Policy Example
====
[source]
----
Feature-Policy: geolocation 'self'
----
====
With Feature Policy, developers can opt-in to a set of "`policies`" for the browser to enforce on specific features used throughout your site.
These policies restrict what APIs the site can access or modify the browser's default behavior for certain features.
@@ -353,12 +331,10 @@ See the relevant sections to see how to configure both xref:servlet/exploits/hea
https://w3c.github.io/webappsec-permissions-policy/[Permissions Policy] is a mechanism that lets web developers selectively enable, disable, and modify the behavior of certain APIs and web features in the browser.
.Permissions Policy Example
====
[source]
----
Permissions-Policy: geolocation=(self)
----
====
With Permissions Policy, developers can opt-in to a set of "policies" for the browser to enforce on specific features used throughout your site.
These policies restrict what APIs the site can access or modify the browser's default behavior for certain features.
@@ -374,12 +350,10 @@ See the relevant sections to see how to configure both xref:servlet/exploits/hea
https://www.w3.org/TR/clear-site-data/[Clear Site Data] is a mechanism by which any browser-side data (cookies, local storage, and the like) can be removed when an HTTP response contains this header:
====
[source]
----
Clear-Site-Data: "cache", "cookies", "storage", "executionContexts"
----
====
This is a nice clean-up action to perform on logout.

View File

@@ -14,8 +14,10 @@ It wraps a delegate `Runnable` in order to initialize the `SecurityContextHolder
It then invokes the delegate Runnable ensuring to clear the `SecurityContextHolder` afterwards.
The `DelegatingSecurityContextRunnable` looks something like this:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
public void run() {
@@ -28,7 +30,8 @@ try {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
fun run() {
@@ -40,7 +43,7 @@ fun run() {
}
}
----
====
======
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.
@@ -48,8 +51,10 @@ For example, you might have used Spring Security's xref:servlet/appendix/namespa
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
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Runnable originalRunnable = new Runnable() {
@@ -65,7 +70,8 @@ DelegatingSecurityContextRunnable wrappedRunnable =
new Thread(wrappedRunnable).start();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val originalRunnable = Runnable {
@@ -76,7 +82,7 @@ val wrappedRunnable = DelegatingSecurityContextRunnable(originalRunnable, contex
Thread(wrappedRunnable).start()
----
====
======
The code above performs the following steps:
@@ -90,8 +96,10 @@ Since it is quite common to create a `DelegatingSecurityContextRunnable` with th
The following code is the same as the code above:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Runnable originalRunnable = new Runnable() {
@@ -106,7 +114,8 @@ DelegatingSecurityContextRunnable wrappedRunnable =
new Thread(wrappedRunnable).start();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val originalRunnable = Runnable {
@@ -117,7 +126,7 @@ 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.
@@ -131,8 +140,10 @@ The design of `DelegatingSecurityContextExecutor` is very similar to that of `De
You can see an example of how it might be used below:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
SecurityContext context = SecurityContextHolder.createEmptyContext();
@@ -154,7 +165,8 @@ public void run() {
executor.execute(originalRunnable);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val context: SecurityContext = SecurityContextHolder.createEmptyContext()
@@ -171,7 +183,7 @@ val originalRunnable = Runnable {
executor.execute(originalRunnable)
----
====
======
The code performs the following steps:
@@ -185,8 +197,10 @@ In this instance, the same `SecurityContext` will be used for every Runnable sub
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
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Autowired
@@ -202,7 +216,8 @@ executor.execute(originalRunnable);
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Autowired
@@ -215,7 +230,7 @@ fun submitRunnable() {
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.
@@ -224,8 +239,10 @@ This can be done by removing the `SecurityContext` argument from our `Delegating
For example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
@@ -233,13 +250,14 @@ DelegatingSecurityContextExecutor executor =
new DelegatingSecurityContextExecutor(delegateExecutor);
----
.Kotlin
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.

View File

@@ -23,19 +23,22 @@ Both `BytesEncryptor` and `TextEncryptor` are interfaces. `BytesEncryptor` has m
You can use the `Encryptors.stronger` factory method to construct a `BytesEncryptor`:
.BytesEncryptor
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Encryptors.stronger("password", "salt");
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
Encryptors.stronger("password", "salt")
----
====
======
The `stronger` encryption method creates an encryptor by using 256-bit AES encryption with
Galois Counter Mode (GCM).
@@ -49,19 +52,22 @@ The provided salt should be in hex-encoded String form, be random, and be at lea
You can generate such a salt by using a `KeyGenerator`:
.Generating a key
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val salt = KeyGenerators.string().generateKey() // generates a random 8-byte salt that is then hex-encoded
----
====
======
You can 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
@@ -73,19 +79,22 @@ For a more secure alternative, use `Encryptors.stronger`.
You can use the `Encryptors.text` factory method to construct a standard TextEncryptor:
.TextEncryptor
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
Encryptors.text("password", "salt");
----
.Kotlin
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 a database.
@@ -101,81 +110,92 @@ You can also construct a {security-api-url}org/springframework/security/crypto/k
You can use the `KeyGenerators.secureRandom` factory methods to generate a `BytesKeyGenerator` backed by a `SecureRandom` instance:
.BytesKeyGenerator
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
BytesKeyGenerator generator = KeyGenerators.secureRandom();
byte[] key = generator.generateKey();
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val generator = KeyGenerators.secureRandom()
val key = generator.generateKey()
----
====
======
The default key length is 8 bytes.
A `KeyGenerators.secureRandom` variant provides control over the key length:
.KeyGenerators.secureRandom
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
KeyGenerators.secureRandom(16);
----
.Kotlin
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
[tabs]
======
Java::
+
[source,java,role="primary"]
----
KeyGenerators.shared(16);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
KeyGenerators.shared(16)
----
====
======
=== StringKeyGenerator
You can use the `KeyGenerators.string` factory method to construct an 8-byte, `SecureRandom` `KeyGenerator` that hex-encodes each key as a `String`:
.StringKeyGenerator
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
KeyGenerators.string();
----
.Kotlin
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 {
@@ -188,7 +208,6 @@ public interface PasswordEncoder {
}
}
----
====
The `matches` method returns true if the `rawPassword`, once encoded, equals the `encodedPassword`.
This method is designed to support password-based authentication schemes.
@@ -202,8 +221,10 @@ You can change this value in your deployed system without affecting existing pas
The following example uses the `BCryptPasswordEncoder`:
.BCryptPasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@@ -213,7 +234,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@@ -222,7 +244,7 @@ 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.
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.
@@ -230,8 +252,10 @@ The following system uses the `Pbkdf2PasswordEncoder`:
.Pbkdf2PasswordEncoder
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
// Create an encoder with all the defaults
@@ -240,7 +264,8 @@ String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
// Create an encoder with all the defaults
@@ -248,4 +273,4 @@ val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8()
val result: String = encoder.encode("myPassword")
assertTrue(encoder.matches("myPassword", result))
----
====
======

View File

@@ -10,8 +10,10 @@ It is not only useful but necessary to include the user in the queries to suppor
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
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
@@ -20,7 +22,8 @@ public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
@@ -28,7 +31,7 @@ fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension {
return SecurityEvaluationContextExtension()
}
----
====
======
In XML Configuration, this would look like:
@@ -43,8 +46,10 @@ In XML Configuration, this would look like:
Now Spring Security can be used within your queries.
For example:
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Repository
@@ -54,7 +59,8 @@ public interface MessageRepository extends PagingAndSortingRepository<Message,Lo
}
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Repository
@@ -63,7 +69,7 @@ interface MessageRepository : PagingAndSortingRepository<Message?, Long?> {
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.

View File

@@ -6,8 +6,10 @@ This can improve the performance of serializing Spring Security related classes
To use it, register the `SecurityJackson2Modules.getModules(ClassLoader)` with `ObjectMapper` (https://github.com/FasterXML/jackson-databind[jackson-databind]):
====
.Java
[tabs]
======
Java::
+
[source,java,role="primary"]
----
ObjectMapper mapper = new ObjectMapper();
@@ -21,7 +23,8 @@ SecurityContext context = new SecurityContextImpl();
String json = mapper.writeValueAsString(context);
----
.Kotlin
Kotlin::
+
[source,kotlin,role="secondary"]
----
val mapper = ObjectMapper()
@@ -34,7 +37,7 @@ val context: SecurityContext = SecurityContextImpl()
// ...
val json: String = mapper.writeValueAsString(context)
----
====
======
[NOTE]
====