Merge branch '5.8.x' into 6.0.x
Closes gh-13406
This commit is contained in:
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
====
|
||||
|
||||
Reference in New Issue
Block a user