commit
8407c9ebee
|
@ -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]
|
||||
====
|
||||
|
|
|
@ -29,7 +29,6 @@ Alternatively, you can manually add the starter, as the following example shows:
|
|||
|
||||
|
||||
.pom.xml
|
||||
====
|
||||
[source,xml,subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
|
@ -40,13 +39,11 @@ Alternatively, you can manually add the starter, as the following example shows:
|
|||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
====
|
||||
|
||||
Since Spring Boot provides a Maven BOM to manage dependency versions, you do not need to specify a version.
|
||||
If you wish to override the Spring Security version, you can do so by providing a Maven property:
|
||||
|
||||
.pom.xml
|
||||
====
|
||||
[source,xml,subs="verbatim,attributes"]
|
||||
----
|
||||
<properties>
|
||||
|
@ -54,14 +51,12 @@ If you wish to override the Spring Security version, you can do so by providing
|
|||
<spring-security.version>{spring-security-version}</spring-security.version>
|
||||
</properties>
|
||||
----
|
||||
====
|
||||
|
||||
Since Spring Security makes breaking changes only in major releases, you can safely use a newer version of Spring Security with Spring Boot.
|
||||
However, at times, you may need to update the version of Spring Framework as well.
|
||||
You can do so by adding a Maven property:
|
||||
|
||||
.pom.xml
|
||||
====
|
||||
[source,xml,subs="verbatim,attributes"]
|
||||
----
|
||||
<properties>
|
||||
|
@ -69,7 +64,6 @@ You can do so by adding a Maven property:
|
|||
<spring.version>{spring-core-version}</spring.version>
|
||||
</properties>
|
||||
----
|
||||
====
|
||||
|
||||
If you use additional features (such as LDAP, OAuth 2, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies].
|
||||
|
||||
|
@ -79,7 +73,6 @@ If you use additional features (such as LDAP, OAuth 2, and others), you need to
|
|||
When you use Spring Security without Spring Boot, the preferred way is to use Spring Security's BOM to ensure that a consistent version of Spring Security is used throughout the entire project. The following example shows how to do so:
|
||||
|
||||
.pom.xml
|
||||
====
|
||||
[source,xml,ubs="verbatim,attributes"]
|
||||
----
|
||||
<dependencyManagement>
|
||||
|
@ -95,12 +88,10 @@ When you use Spring Security without Spring Boot, the preferred way is to use Sp
|
|||
</dependencies>
|
||||
</dependencyManagement>
|
||||
----
|
||||
====
|
||||
|
||||
A minimal Spring Security Maven set of dependencies typically looks like the following example:
|
||||
|
||||
.pom.xml
|
||||
====
|
||||
[source,xml,subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
|
@ -115,7 +106,6 @@ A minimal Spring Security Maven set of dependencies typically looks like the fol
|
|||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
====
|
||||
|
||||
If you use additional features (such as LDAP, OAuth 2, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies].
|
||||
|
||||
|
@ -124,7 +114,6 @@ Many users are likely to run afoul of the fact that Spring Security's transitive
|
|||
The easiest way to resolve this is to use the `spring-framework-bom` within the `<dependencyManagement>` section of your `pom.xml`:
|
||||
|
||||
.pom.xml
|
||||
====
|
||||
[source,xml,subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencyManagement>
|
||||
|
@ -140,7 +129,6 @@ The easiest way to resolve this is to use the `spring-framework-bom` within the
|
|||
</dependencies>
|
||||
</dependencyManagement>
|
||||
----
|
||||
====
|
||||
|
||||
The preceding example ensures that all the transitive dependencies of Spring Security use the Spring {spring-core-version} modules.
|
||||
|
||||
|
@ -157,7 +145,6 @@ All GA releases (that is, versions ending in .RELEASE) are deployed to Maven Cen
|
|||
If you use a SNAPSHOT version, you need to ensure that you have the Spring Snapshot repository defined:
|
||||
|
||||
.pom.xml
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
|
@ -169,12 +156,10 @@ If you use a SNAPSHOT version, you need to ensure that you have the Spring Snaps
|
|||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
====
|
||||
|
||||
If you use a milestone or release candidate version, you need to ensure that you have the Spring Milestone repository defined, as the following example shows:
|
||||
|
||||
.pom.xml
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
|
@ -186,7 +171,6 @@ If you use a milestone or release candidate version, you need to ensure that you
|
|||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
====
|
||||
|
||||
[[getting-gradle]]
|
||||
== Gradle
|
||||
|
@ -203,7 +187,6 @@ The simplest and preferred method to use the starter is to use https://docs.spri
|
|||
Alternatively, you can manually add the starter:
|
||||
|
||||
.build.gradle
|
||||
====
|
||||
[source,groovy]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
|
@ -211,32 +194,27 @@ dependencies {
|
|||
compile "org.springframework.boot:spring-boot-starter-security"
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Since Spring Boot provides a Maven BOM to manage dependency versions, you need not specify a version.
|
||||
If you wish to override the Spring Security version, you can do so by providing a Gradle property:
|
||||
|
||||
.build.gradle
|
||||
====
|
||||
[source,groovy]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
ext['spring-security.version']='{spring-security-version}'
|
||||
----
|
||||
====
|
||||
|
||||
Since Spring Security makes breaking changes only in major releases, you can safely use a newer version of Spring Security with Spring Boot.
|
||||
However, at times, you may need to update the version of Spring Framework as well.
|
||||
You can do so by adding a Gradle property:
|
||||
|
||||
.build.gradle
|
||||
====
|
||||
[source,groovy]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
ext['spring.version']='{spring-core-version}'
|
||||
----
|
||||
====
|
||||
|
||||
If you use additional features (such as LDAP, OAuth 2, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies].
|
||||
|
||||
|
@ -246,7 +224,6 @@ When you use Spring Security without Spring Boot, the preferred way is to use Sp
|
|||
You can do so by using the https://github.com/spring-gradle-plugins/dependency-management-plugin[Dependency Management Plugin]:
|
||||
|
||||
.build.gradle
|
||||
====
|
||||
[source,groovy]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
|
@ -260,12 +237,10 @@ dependencyManagement {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
A minimal Spring Security Maven set of dependencies typically looks like the following:
|
||||
|
||||
.build.gradle
|
||||
====
|
||||
[source,groovy]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
|
@ -274,7 +249,6 @@ dependencies {
|
|||
compile "org.springframework.security:spring-security-config"
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
If you use additional features (such as LDAP, OAuth 2, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies].
|
||||
|
||||
|
@ -284,7 +258,6 @@ The easiest way to resolve this is to use the `spring-framework-bom` within your
|
|||
You can do so by using the https://github.com/spring-gradle-plugins/dependency-management-plugin[Dependency Management Plugin]:
|
||||
|
||||
.build.gradle
|
||||
====
|
||||
[source,groovy]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
|
@ -298,7 +271,6 @@ dependencyManagement {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The preceding example ensures that all the transitive dependencies of Spring Security use the Spring {spring-core-version} modules.
|
||||
|
||||
|
@ -307,35 +279,29 @@ The preceding example ensures that all the transitive dependencies of Spring Sec
|
|||
All GA releases (that is, versions ending in .RELEASE) are deployed to Maven Central, so using the `mavenCentral()` repository is sufficient for GA releases. The following example shows how to do so:
|
||||
|
||||
.build.gradle
|
||||
====
|
||||
[source,groovy]
|
||||
----
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
If you use a SNAPSHOT version, you need to ensure that you have the Spring Snapshot repository defined:
|
||||
|
||||
.build.gradle
|
||||
====
|
||||
[source,groovy]
|
||||
----
|
||||
repositories {
|
||||
maven { url 'https://repo.spring.io/snapshot' }
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
If you use a milestone or release candidate version, you need to ensure that you have the Spring Milestone repository defined:
|
||||
|
||||
.build.gradle
|
||||
====
|
||||
[source,groovy]
|
||||
----
|
||||
repositories {
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
}
|
||||
----
|
||||
====
|
||||
|
|
|
@ -9,7 +9,6 @@ The Lambda DSL is present in Spring Security since version 5.2, and it allows HT
|
|||
You may have seen this style of configuration in the Spring Security documentation or samples.
|
||||
Let us take a look at how a lambda configuration of HTTP security compares to the previous configuration style.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
.Configuration using lambdas
|
||||
----
|
||||
|
@ -34,9 +33,7 @@ public class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
====
|
||||
[source,java]
|
||||
.Equivalent configuration without using lambdas
|
||||
----
|
||||
|
@ -61,7 +58,6 @@ public class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The Lambda DSL is the preferred way to configure Spring Security, the prior configuration style will not be valid in Spring Security 7 where the usage of the Lambda DSL will be required.
|
||||
This has been done mainly for a couple of reasons:
|
||||
|
@ -88,7 +84,6 @@ This is a shortcut for the lambda expression `it -> {}`.
|
|||
You may also configure WebFlux security using lambdas in a similar manner.
|
||||
Below is an example configuration using lambdas.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
.WebFlux configuration using lambdas
|
||||
----
|
||||
|
@ -113,7 +108,6 @@ public class SecurityConfig {
|
|||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
=== Goals of the Lambda DSL
|
||||
|
||||
|
|
|
@ -7,35 +7,41 @@ If you have already performed the xref:migration/index.adoc[initial migration st
|
|||
In 6.0, `@EnableReactiveMethodSecurity` defaults `useAuthorizationManager` to `true`.
|
||||
So, to complete migration, {security-api-url}org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurity.html[`@EnableReactiveMethodSecurity`] remove the `useAuthorizationManager` attribute:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity(useAuthorizationManager = true)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity(useAuthorizationManager = true)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
changes to:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
== Propagate ``AuthenticationServiceException``s
|
||||
|
||||
|
@ -44,8 +50,10 @@ Because ``AuthenticationServiceException``s represent a server-side error instea
|
|||
|
||||
So, if you opted into this behavior by setting `rethrowAuthenticationServiceException` too `true`, you can now remove it like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
AuthenticationFailureHandler bearerFailureHandler = new ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint);
|
||||
|
@ -54,7 +62,8 @@ AuthenticationFailureHandler basicFailureHandler = new ServerAuthenticationEntry
|
|||
basicFailureHandler.setRethrowAuthenticationServiceException(true);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val bearerFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint)
|
||||
|
@ -62,25 +71,28 @@ bearerFailureHandler.setRethrowAuthenticationServiceException(true)
|
|||
val basicFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(basicEntryPoint)
|
||||
basicFailureHandler.setRethrowAuthenticationServiceException(true)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
changes to:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
AuthenticationFailureHandler bearerFailureHandler = new ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint);
|
||||
AuthenticationFailureHandler basicFailureHandler = new ServerAuthenticationEntryPointFailureHandler(basicEntryPoint);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val bearerFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint)
|
||||
val basicFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(basicEntryPoint)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
|
|
@ -9,8 +9,10 @@ Because ``AuthenticationServiceException``s represent a server-side error instea
|
|||
|
||||
So, if you opted into this behavior by setting `rethrowAuthenticationServiceException` to `true`, you can now remove it like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
AuthenticationFilter authenticationFilter = new AuthenticationFilter(...);
|
||||
|
@ -19,7 +21,8 @@ handler.setRethrowAuthenticationServiceException(true);
|
|||
authenticationFilter.setAuthenticationFailureHandler(handler);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val authenticationFilter: AuthenticationFilter = AuthenticationFilter(...)
|
||||
|
@ -28,7 +31,8 @@ handler.setRethrowAuthenticationServiceException(true)
|
|||
authenticationFilter.setAuthenticationFailureHandler(handler)
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.AuthenticationFilter">
|
||||
|
@ -40,12 +44,14 @@ authenticationFilter.setAuthenticationFailureHandler(handler)
|
|||
<property name="rethrowAuthenticationServiceException" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
changes to:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
AuthenticationFilter authenticationFilter = new AuthenticationFilter(...);
|
||||
|
@ -53,7 +59,8 @@ AuthenticationEntryPointFailureHandler handler = new AuthenticationEntryPointFai
|
|||
authenticationFilter.setAuthenticationFailureHandler(handler);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val authenticationFilter: AuthenticationFilter = AuthenticationFilter(...)
|
||||
|
@ -61,7 +68,8 @@ val handler: AuthenticationEntryPointFailureHandler = AuthenticationEntryPointFa
|
|||
authenticationFilter.setAuthenticationFailureHandler(handler)
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<bean id="authenticationFilter" class="org.springframework.security.web.authentication.AuthenticationFilter">
|
||||
|
@ -73,7 +81,7 @@ authenticationFilter.setAuthenticationFailureHandler(handler)
|
|||
<!-- ... -->
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-opt-in-sha256-rememberme]]
|
||||
== Use SHA-256 in Remember Me
|
||||
|
@ -83,8 +91,10 @@ To complete the migration, any default values can be removed.
|
|||
|
||||
For example, if you opted in to the 6.0 default for `encodingAlgorithm` and `matchingAlgorithm` like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -108,7 +118,9 @@ public class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
.XML
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -122,12 +134,14 @@ public class SecurityConfig {
|
|||
<property name="encodingAlgorithm" value="SHA256"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
then the defaults can be removed:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -148,7 +162,9 @@ public class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
.XML
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -160,7 +176,7 @@ public class SecurityConfig {
|
|||
<property name="key" value="springRocks"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
== Default authorities for oauth2Login()
|
||||
|
||||
|
|
|
@ -13,23 +13,27 @@ So, to complete migration, remove any `websocket-message-broker@use-authorizatio
|
|||
|
||||
For example:
|
||||
|
||||
====
|
||||
.Xml
|
||||
[tabs]
|
||||
======
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<websocket-message-broker use-authorization-manager="true"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
changes to:
|
||||
|
||||
====
|
||||
.Xml
|
||||
[tabs]
|
||||
======
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<websocket-message-broker/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
There are no further migrations steps for Java or Kotlin for this feature.
|
||||
|
||||
|
@ -41,8 +45,10 @@ So, to complete migration, any defaults values can be removed.
|
|||
|
||||
For example, if you opted in to the 6.0 default for `filter-all-dispatcher-types` or `authorizeHttpRequests#filterAllDispatcherTypes` like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
|
@ -52,7 +58,8 @@ http
|
|||
)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -63,17 +70,20 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http use-authorization-manager="true" filter-all-dispatcher-types="true"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
then the defaults may be removed:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
|
@ -82,7 +92,8 @@ http
|
|||
)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -92,12 +103,13 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
|
|
@ -9,7 +9,6 @@ Using `@PreAuthorize` and `@PostAuthorize` annotations require additional hints
|
|||
|
||||
Let's take an example where you have a custom implementation of `UserDetails` class as follows and that implementation is returned by your `UserDetailsService`:
|
||||
|
||||
====
|
||||
.Custom Implementation of UserDetails
|
||||
[source,java]
|
||||
----
|
||||
|
@ -28,11 +27,9 @@ public class CustomUserDetails implements UserDetails {
|
|||
// constructors, getters and setters
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
And you want to use the `isAdmin()` method inside a `@PreAuthorize` annotation as follows:
|
||||
|
||||
====
|
||||
.Using isAdmin() to secure a method
|
||||
[source,java]
|
||||
----
|
||||
|
@ -41,7 +38,6 @@ public String hello() {
|
|||
return "Hello!";
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -68,7 +64,6 @@ In this example we are going to use {spring-framework-reference-url}core.html#co
|
|||
You might need to register all your classes that you want to use in your `@PreAuthorize` and `@PostAuthorize` annotations.
|
||||
====
|
||||
|
||||
====
|
||||
.Using @RegisterReflectionForBinding
|
||||
[source,java]
|
||||
----
|
||||
|
@ -78,6 +73,5 @@ public class MyConfiguration {
|
|||
//...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
And that's it, now you can run the native image of your application and it should work as expected.
|
||||
|
|
|
@ -11,7 +11,10 @@ This will:
|
|||
Often, you will want to also invalidate the session on logout.
|
||||
To achieve this, you can add the `WebSessionServerLogoutHandler` to your logout configuration, like so:
|
||||
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -28,7 +31,8 @@ SecurityWebFilterChain http(ServerHttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -47,3 +51,4 @@ fun http(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
|
|
@ -5,8 +5,10 @@ Similar to xref:servlet/authentication/x509.adoc#servlet-x509[Servlet X.509 auth
|
|||
|
||||
The following example shows a reactive x509 security configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -20,7 +22,8 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -33,14 +36,16 @@ fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used. The default principal extractor is `SubjectDnX509PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client. The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
|
||||
|
||||
The following example demonstrates how these defaults can be overridden:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -67,7 +72,8 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -89,7 +95,7 @@ fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In the previous example, a username is extracted from the OU field of a client certificate instead of CN, and account lookup using `ReactiveUserDetailsService` is not performed at all. Instead, if the provided certificate issued to an OU named "`Trusted Org Unit`", a request is authenticated.
|
||||
|
||||
|
|
|
@ -6,8 +6,10 @@ By default, Spring Security’s authorization will require all requests to be au
|
|||
The explicit configuration looks like:
|
||||
|
||||
.All Requests Require Authenticated User
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -22,7 +24,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -36,14 +39,16 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
We can configure Spring Security to have different rules by adding more rules in order of precedence.
|
||||
|
||||
.Multiple Authorize Requests Rules
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;
|
||||
|
@ -68,7 +73,8 @@ SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -88,7 +94,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
<1> There are multiple authorization rules specified.
|
||||
Each rule is considered in the order they were declared.
|
||||
|
|
|
@ -32,8 +32,10 @@ For earlier versions, please read about similar support with <<jc-enable-reactiv
|
|||
For example, the following would enable Spring Security's `@PreAuthorize` annotation:
|
||||
|
||||
.Method Security Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
|
||||
|
@ -41,15 +43,17 @@ public class MethodSecurityConfig {
|
|||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Adding an annotation to a method (on a class or interface) would then limit the access to that method accordingly.
|
||||
Spring Security's native annotation support defines a set of attributes for the method.
|
||||
These will be passed to the various method interceptors, like `AuthorizationManagerBeforeReactiveMethodInterceptor`, for it to make the actual decision:
|
||||
|
||||
.Method Security Annotation Usage
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public interface BankService {
|
||||
|
@ -63,7 +67,7 @@ public interface BankService {
|
|||
Mono<Account> post(Account account, Double amount);
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In this case `hasRole` refers to the method found in `SecurityExpressionRoot` that gets invoked by the SpEL evaluation engine.
|
||||
|
||||
|
@ -71,8 +75,10 @@ In this case `hasRole` refers to the method found in `SecurityExpressionRoot` th
|
|||
A bean like that might look something like this:
|
||||
|
||||
.Method Security Reactive Boolean Expression
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -80,20 +86,22 @@ public Function<Account, Mono<Boolean>> func() {
|
|||
return (account) -> Mono.defer(() -> Mono.just(account.getId().equals(12)));
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Customizing Authorization
|
||||
|
||||
Spring Security's `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` ship with rich expression-based support.
|
||||
|
||||
[[jc-reactive-method-security-custom-granted-authority-defaults]]
|
||||
|
||||
[[jc-reactive-method-security-custom-granted-authority-defaults]]
|
||||
Also, for role-based authorization, Spring Security adds a default `ROLE_` prefix, which is uses when evaluating expressions like `hasRole`.
|
||||
You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
|
||||
|
||||
.Custom MethodSecurityExpressionHandler
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -101,7 +109,7 @@ static GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
|||
return new GrantedAuthorityDefaults("MYPREFIX_");
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -124,8 +132,10 @@ If that authorization denies access, the value is not returned, and an `AccessDe
|
|||
To recreate what adding `@EnableReactiveMethodSecurity(useAuthorizationManager=true)` does by default, you would publish the following configuration:
|
||||
|
||||
.Full Pre-post Method Security Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -160,15 +170,17 @@ class MethodSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Notice that Spring Security's method security is built using Spring AOP.
|
||||
So, interceptors are invoked based on the order specified.
|
||||
This can be customized by calling `setOrder` on the interceptor instances like so:
|
||||
|
||||
.Publish Custom Advisor
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -179,13 +191,15 @@ Advisor postFilterAuthorizationMethodInterceptor() {
|
|||
return interceptor;
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You may want to only support `@PreAuthorize` in your application, in which case you can do the following:
|
||||
|
||||
.Only @PreAuthorize Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -202,7 +216,7 @@ class MethodSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Or, you may have a custom before-method `ReactiveAuthorizationManager` that you want to add to the list.
|
||||
|
||||
|
@ -211,9 +225,11 @@ In this case, you will need to tell Spring Security both the `ReactiveAuthorizat
|
|||
Thus, you can configure Spring Security to invoke your `ReactiveAuthorizationManager` in between `@PreAuthorize` and `@PostAuthorize` like so:
|
||||
|
||||
.Custom Before Advisor
|
||||
====
|
||||
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
|
||||
|
@ -230,7 +246,7 @@ class MethodSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -243,8 +259,10 @@ After-method authorization is generally concerned with analysing the return valu
|
|||
For example, you might have a method that confirms that the account requested actually belongs to the logged-in user like so:
|
||||
|
||||
.@PostAuthorize example
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public interface BankService {
|
||||
|
@ -254,7 +272,7 @@ public interface BankService {
|
|||
Mono<Account> readAccount(Long id);
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can supply your own `AuthorizationMethodInterceptor` to customize how access to the return value is evaluated.
|
||||
|
||||
|
@ -262,8 +280,10 @@ For example, if you have your own custom annotation, you can configure it like s
|
|||
|
||||
|
||||
.Custom After Advisor
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableReactiveMethodSecurity(useAuthorizationManager=true)
|
||||
|
@ -278,7 +298,7 @@ class MethodSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
and it will be invoked after the `@PostAuthorize` interceptor.
|
||||
|
||||
|
@ -291,8 +311,10 @@ When intercepting coroutines, only the first interceptor participates.
|
|||
If any other interceptors are present and come after Spring Security's method security interceptor, https://github.com/spring-projects/spring-framework/issues/22462[they will be skipped].
|
||||
====
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
|
@ -309,7 +331,8 @@ StepVerifier.create(messageByUsername)
|
|||
.verifyComplete();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
|
||||
|
@ -324,12 +347,14 @@ StepVerifier.create(messageByUsername)
|
|||
.expectNext("Hi user")
|
||||
.verifyComplete()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Where `this::findMessageByUsername` is defined as:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Mono<String> findMessageByUsername(String username) {
|
||||
|
@ -337,19 +362,22 @@ Mono<String> findMessageByUsername(String username) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
fun findMessageByUsername(username: String): Mono<String> {
|
||||
return Mono.just("Hi $username")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following minimal method security configures method security in reactive applications:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -371,7 +399,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -392,12 +421,14 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Consider the following class:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -409,7 +440,8 @@ public class HelloWorldMessageService {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -420,12 +452,14 @@ class HelloWorldMessageService {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Alternatively, the following class uses Kotlin coroutines:
|
||||
|
||||
====
|
||||
.Kotlin
|
||||
[tabs]
|
||||
======
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -437,7 +471,7 @@ class HelloWorldMessageService {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
Combined with our configuration above, `@PreAuthorize("hasRole('ADMIN')")` ensures that `findByMessage` is invoked only by a user with the `ADMIN` role.
|
||||
|
@ -447,8 +481,10 @@ This means that the expression must not block.
|
|||
|
||||
When integrating with xref:reactive/configuration/webflux.adoc#jc-webflux[WebFlux Security], the Reactor Context is automatically established by Spring Security according to the authenticated user:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -484,7 +520,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -516,6 +553,6 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can find a complete sample in {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method].
|
||||
|
|
|
@ -14,8 +14,10 @@ A few sample applications demonstrate the code:
|
|||
The following listing shows a minimal WebFlux Security configuration:
|
||||
|
||||
.Minimal WebFlux Security Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
-----
|
||||
@Configuration
|
||||
|
@ -34,7 +36,8 @@ public class HelloWebfluxSecurityConfig {
|
|||
}
|
||||
-----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
-----
|
||||
@Configuration
|
||||
|
@ -52,7 +55,7 @@ class HelloWebfluxSecurityConfig {
|
|||
}
|
||||
}
|
||||
-----
|
||||
====
|
||||
======
|
||||
|
||||
This configuration provides form and HTTP basic authentication, sets up authorization to require an authenticated user for accessing any page, sets up a default login page and a default logout page, sets up security related HTTP headers, adds CSRF protection, and more.
|
||||
|
||||
|
@ -61,8 +64,10 @@ This configuration provides form and HTTP basic authentication, sets up authoriz
|
|||
The following page shows an explicit version of the minimal WebFlux Security configuration:
|
||||
|
||||
.Explicit WebFlux Security Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
-----
|
||||
@Configuration
|
||||
|
@ -92,7 +97,8 @@ public class HelloWebfluxSecurityConfig {
|
|||
}
|
||||
-----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
-----
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
|
@ -123,7 +129,7 @@ class HelloWebfluxSecurityConfig {
|
|||
}
|
||||
}
|
||||
-----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Make sure that you import the `invoke` function in your Kotlin class, sometimes the IDE will not auto-import it causing compilation issues.
|
||||
|
@ -140,8 +146,10 @@ You can configure multiple `SecurityWebFilterChain` instances to separate config
|
|||
|
||||
For example, you can isolate configuration for URLs that start with `/api`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -179,7 +187,8 @@ static class MultiSecurityHttpConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
|
@ -219,13 +228,13 @@ open class MultiSecurityHttpConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
<1> Configure a `SecurityWebFilterChain` with an `@Order` to specify which `SecurityWebFilterChain` Spring Security should consider first
|
||||
<2> Use `PathPatternParserServerWebExchangeMatcher` to state that this `SecurityWebFilterChain` will only apply to URL paths that start with `/api/`
|
||||
<3> Specify the authentication mechanisms that will be used for `/api/**` endpoints
|
||||
<4> Create another instance of `SecurityWebFilterChain` with lower precedence to match all other URLs
|
||||
<5> Specify the authentication mechanisms that will be used for the rest of the application
|
||||
====
|
||||
|
||||
Spring Security selects one `SecurityWebFilterChain` `@Bean` for each request.
|
||||
It matches the requests in order by the `securityMatcher` definition.
|
||||
|
|
|
@ -35,8 +35,10 @@ These defaults come from https://docs.angularjs.org/api/ng/service/$http#cross-s
|
|||
You can configure `CookieServerCsrfTokenRepository` in Java Configuration:
|
||||
|
||||
.Store CSRF Token in a Cookie
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
-----
|
||||
@Bean
|
||||
|
@ -48,7 +50,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
|
|||
}
|
||||
-----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
-----
|
||||
@Bean
|
||||
|
@ -61,7 +64,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
-----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -78,8 +81,10 @@ However, you can disable CSRF protection if it xref:features/exploits/csrf.adoc#
|
|||
The Java configuration below will disable CSRF protection.
|
||||
|
||||
.Disable CSRF Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -91,7 +96,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
-----
|
||||
@Bean
|
||||
|
@ -104,7 +110,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
-----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-csrf-configure-request-handler]]
|
||||
==== Configure ServerCsrfTokenRequestHandler
|
||||
|
@ -117,8 +123,10 @@ As of 6.0, the default implementation is `XorServerCsrfTokenRequestAttributeHand
|
|||
If you wish to disable BREACH protection of the `CsrfToken` and revert to the 5.8 default, you can configure `ServerCsrfTokenRequestAttributeHandler` using the following Java configuration:
|
||||
|
||||
.Disable BREACH protection
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
-----
|
||||
@Bean
|
||||
|
@ -132,7 +140,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
|
|||
}
|
||||
-----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
-----
|
||||
@Bean
|
||||
|
@ -145,7 +154,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
-----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-csrf-include]]
|
||||
=== Include the CSRF Token
|
||||
|
@ -161,8 +170,10 @@ If your view technology does not provide a simple way to subscribe to the `Mono<
|
|||
The following example places the `CsrfToken` on the default attribute name (`_csrf`) used by Spring Security's <<webflux-csrf-include-form-auto,CsrfRequestDataValueProcessor>> to automatically include the CSRF token as a hidden input:
|
||||
|
||||
.`CsrfToken` as `@ModelAttribute`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ControllerAdvice
|
||||
|
@ -176,7 +187,8 @@ public class SecurityControllerAdvice {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ControllerAdvice
|
||||
|
@ -190,7 +202,7 @@ class SecurityControllerAdvice {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Fortunately, Thymeleaf provides <<webflux-csrf-include-form-auto,integration>> that works without any additional work.
|
||||
|
||||
|
@ -200,14 +212,12 @@ To post an HTML form, the CSRF token must be included in the form as a hidden in
|
|||
The following example shows what the rendered HTML might look like:
|
||||
|
||||
.CSRF Token HTML
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<input type="hidden"
|
||||
name="_csrf"
|
||||
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
|
||||
----
|
||||
====
|
||||
|
||||
Next, we discuss various ways of including the CSRF token in a form as a hidden input.
|
||||
|
||||
|
@ -227,7 +237,6 @@ If the <<webflux-csrf-include,other options>> for including the actual CSRF toke
|
|||
The following Thymeleaf sample assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`:
|
||||
|
||||
.CSRF Token in Form with Request Attribute
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<form th:action="@{/logout}"
|
||||
|
@ -239,7 +248,6 @@ The following Thymeleaf sample assumes that you <<webflux-csrf-include-subscribe
|
|||
th:value="${_csrf.token}"/>
|
||||
</form>
|
||||
----
|
||||
====
|
||||
|
||||
[[webflux-csrf-include-ajax]]
|
||||
==== Ajax and JSON Requests
|
||||
|
@ -261,7 +269,6 @@ An alternative pattern to <<webflux-csrf-include-form-auto,exposing the CSRF in
|
|||
The HTML might look something like this:
|
||||
|
||||
.CSRF meta tag HTML
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<html>
|
||||
|
@ -272,13 +279,11 @@ The HTML might look something like this:
|
|||
</head>
|
||||
<!-- ... -->
|
||||
----
|
||||
====
|
||||
|
||||
Once the meta tags contain the CSRF token, the JavaScript code can read the meta tags and include the CSRF token as a header.
|
||||
If you use jQuery, you could read the meta tags with the following code:
|
||||
|
||||
.AJAX send CSRF Token
|
||||
====
|
||||
[source,javascript]
|
||||
----
|
||||
$(function () {
|
||||
|
@ -289,13 +294,11 @@ $(function () {
|
|||
});
|
||||
});
|
||||
----
|
||||
====
|
||||
|
||||
The following sample assumes that you <<webflux-csrf-include-subscribe,expose>> the `CsrfToken` on an attribute named `_csrf`.
|
||||
The following example does this with Thymeleaf:
|
||||
|
||||
.CSRF meta tag JSP
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<html>
|
||||
|
@ -307,7 +310,6 @@ The following example does this with Thymeleaf:
|
|||
</head>
|
||||
<!-- ... -->
|
||||
----
|
||||
====
|
||||
|
||||
[[webflux-csrf-considerations]]
|
||||
== CSRF Considerations
|
||||
|
@ -339,8 +341,10 @@ For example, the following Java Configuration logs out when the `/logout` URL is
|
|||
// FIXME: This should be a link to log out documentation
|
||||
|
||||
.Log out with HTTP GET
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -352,7 +356,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -365,7 +370,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-considerations-csrf-timeouts]]
|
||||
|
@ -401,8 +406,10 @@ We have xref:features/exploits/csrf.adoc#csrf-considerations-multipart[already d
|
|||
In a WebFlux application, you can do so with the following configuration:
|
||||
|
||||
.Enable obtaining CSRF token from multipart/form-data
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -414,7 +421,8 @@ public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http)
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -427,7 +435,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-csrf-considerations-multipart-url]]
|
||||
==== Include CSRF Token in URL
|
||||
|
@ -437,14 +445,12 @@ Since the `CsrfToken` is exposed as an `ServerHttpRequest` <<webflux-csrf-includ
|
|||
An example with Thymeleaf is shown below:
|
||||
|
||||
.CSRF Token in Action
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<form method="post"
|
||||
th:action="@{/upload(${_csrf.parameterName}=${_csrf.token})}"
|
||||
enctype="multipart/form-data">
|
||||
----
|
||||
====
|
||||
|
||||
[[webflux-csrf-considerations-override-method]]
|
||||
=== HiddenHttpMethodFilter
|
||||
|
|
|
@ -16,8 +16,10 @@ For example, assume that you want the defaults but you wish to specify `SAMEORIG
|
|||
You can do so with the following configuration:
|
||||
|
||||
.Customize Default Security Headers
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -33,7 +35,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -48,14 +51,16 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If you do not want the defaults to be added and want explicit control over what should be used, you can disable the defaults:
|
||||
|
||||
|
||||
.Disable HTTP Security Response Headers
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -67,7 +72,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -80,7 +86,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-cache-control]]
|
||||
== Cache Control
|
||||
|
@ -96,8 +102,10 @@ You can find details on how to do so in the https://docs.spring.io/spring/docs/5
|
|||
If necessary, you can also disable Spring Security's cache control HTTP response headers.
|
||||
|
||||
.Cache Control Disabled
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -111,7 +119,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -126,7 +135,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-headers-content-type-options]]
|
||||
|
@ -135,8 +144,10 @@ By default, Spring Security includes xref:features/exploits/headers.adoc#headers
|
|||
However, you can disable it:
|
||||
|
||||
.Content Type Options Disabled
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -150,7 +161,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -165,7 +177,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-hsts]]
|
||||
== HTTP Strict Transport Security (HSTS)
|
||||
|
@ -174,8 +186,10 @@ However, you can customize the results explicitly.
|
|||
For example, the following example explicitly provides HSTS:
|
||||
|
||||
.Strict Transport Security
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -193,7 +207,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -210,7 +225,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-frame-options]]
|
||||
== X-Frame-Options
|
||||
|
@ -219,8 +234,10 @@ By default, Spring Security disables rendering within an iframe by using xref:fe
|
|||
You can customize frame options to use the same origin:
|
||||
|
||||
.X-Frame-Options: SAMEORIGIN
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -236,7 +253,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -251,7 +269,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-xss-protection]]
|
||||
== X-XSS-Protection
|
||||
|
@ -259,8 +277,10 @@ By default, Spring Security instructs browsers to disable the XSS Auditor by usi
|
|||
You can disable the `X-XSS-Protection` header entirely:
|
||||
|
||||
.X-XSS-Protection Customization
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -274,7 +294,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -289,13 +310,15 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can also change the header value:
|
||||
|
||||
.X-XSS-Protection Explicit header value
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -309,7 +332,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -324,7 +348,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-csp]]
|
||||
== Content Security Policy (CSP)
|
||||
|
@ -334,18 +358,18 @@ The web application author must declare the security policies to enforce and/or
|
|||
For example, consider the following security policy:
|
||||
|
||||
.Content Security Policy Example
|
||||
====
|
||||
[source,http]
|
||||
----
|
||||
Content-Security-Policy: script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/
|
||||
----
|
||||
====
|
||||
|
||||
Given the preceding policy, you can enable the CSP header:
|
||||
|
||||
.Content Security Policy
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -361,7 +385,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -376,13 +401,15 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
To enable the CSP `report-only` header, provide the following configuration:
|
||||
|
||||
.Content Security Policy Report Only
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -399,7 +426,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -415,7 +443,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-headers-referrer]]
|
||||
== Referrer Policy
|
||||
|
@ -424,8 +452,10 @@ By default, Spring Security does not add xref:features/exploits/headers.adoc#hea
|
|||
You can enable the Referrer Policy header using configuration as shown below:
|
||||
|
||||
.Referrer Policy Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -441,7 +471,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -456,7 +487,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-headers-feature]]
|
||||
|
@ -466,18 +497,18 @@ By default, Spring Security does not add xref:features/exploits/headers.adoc#hea
|
|||
Consider the following `Feature-Policy` header:
|
||||
|
||||
.Feature-Policy Example
|
||||
====
|
||||
[source]
|
||||
----
|
||||
Feature-Policy: geolocation 'self'
|
||||
----
|
||||
====
|
||||
|
||||
You can enable the preceding Feature Policy header:
|
||||
|
||||
.Feature-Policy Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -491,7 +522,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -504,7 +536,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-headers-permissions]]
|
||||
|
@ -514,18 +546,18 @@ By default, Spring Security does not add xref:features/exploits/headers.adoc#hea
|
|||
Consider the following `Permissions-Policy` header:
|
||||
|
||||
.Permissions-Policy Example
|
||||
====
|
||||
[source]
|
||||
----
|
||||
Permissions-Policy: geolocation=(self)
|
||||
----
|
||||
====
|
||||
|
||||
You can enable the preceding Permissions Policy header:
|
||||
|
||||
.Permissions-Policy Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -541,7 +573,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -556,7 +589,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-headers-clear-site-data]]
|
||||
|
@ -566,17 +599,17 @@ By default, Spring Security does not add xref:features/exploits/headers.adoc#hea
|
|||
Consider the following `Clear-Site-Data` header:
|
||||
|
||||
.Clear-Site-Data Example
|
||||
====
|
||||
----
|
||||
Clear-Site-Data: "cache", "cookies"
|
||||
----
|
||||
====
|
||||
|
||||
You can send the `Clear-Site-Data` header on logout:
|
||||
|
||||
.Clear-Site-Data Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -594,7 +627,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -612,4 +646,4 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -13,8 +13,10 @@ If a client makes a request using HTTP rather than HTTPS, you can configure Spri
|
|||
The following Java configuration redirects any HTTP requests to HTTPS:
|
||||
|
||||
.Redirect to HTTPS
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -26,7 +28,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -37,15 +40,17 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can wrap the configuration can be wrapped around an `if` statement to be turned on only in production.
|
||||
Alternatively, you can enable it by looking for a property about the request that happens only in production.
|
||||
For example, if the production environment adds a header named `X-Forwarded-Proto`, you should use the following Java Configuration:
|
||||
|
||||
.Redirect to HTTPS when X-Forwarded
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -59,7 +64,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -74,7 +80,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-hsts]]
|
||||
== Strict Transport Security
|
||||
|
|
|
@ -14,8 +14,10 @@ For your convenience, you can download a minimal Reactive Spring Boot + Spring S
|
|||
|
||||
You can add Spring Security to your Spring Boot project by adding `spring-boot-starter-security`.
|
||||
|
||||
====
|
||||
.Maven
|
||||
[tabs]
|
||||
======
|
||||
Maven::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<dependency>
|
||||
|
@ -24,12 +26,13 @@ You can add Spring Security to your Spring Boot project by adding `spring-boot-s
|
|||
</dependency>
|
||||
----
|
||||
|
||||
.Gradle
|
||||
Gradle::
|
||||
+
|
||||
[source,groovy,role="secondary"]
|
||||
----
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[servlet-hello-starting]]
|
||||
|
@ -38,10 +41,12 @@ You can add Spring Security to your Spring Boot project by adding `spring-boot-s
|
|||
You can now https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-running-with-the-maven-plugin[run the Spring Boot application] by using the Maven Plugin's `run` goal.
|
||||
The following example shows how to do so (and the beginning of the output from doing so):
|
||||
|
||||
.Running Spring Boot Application
|
||||
|
||||
====
|
||||
.Maven
|
||||
.Running Spring Boot Application
|
||||
[tabs]
|
||||
======
|
||||
Maven::
|
||||
+
|
||||
[source,bash,role="primary"]
|
||||
----
|
||||
$ ./mvnw spring-boot:run
|
||||
|
@ -53,7 +58,8 @@ Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
|
|||
...
|
||||
----
|
||||
|
||||
.Gradle
|
||||
Gradle::
|
||||
+
|
||||
[source,bash,role="secondary"]
|
||||
----
|
||||
$ ./gradlew bootRun
|
||||
|
@ -64,7 +70,7 @@ Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
|
|||
|
||||
...
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[authenticating]]
|
||||
== Authenticating
|
||||
|
|
|
@ -10,8 +10,10 @@ The easiest way to ensure that CORS is handled first is to use the `CorsWebFilte
|
|||
Users can integrate the `CorsWebFilter` with Spring Security by providing a `CorsConfigurationSource`.
|
||||
For example, the following will integrate CORS support within Spring Security:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -25,7 +27,8 @@ CorsConfigurationSource corsConfigurationSource() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -38,12 +41,14 @@ fun corsConfigurationSource(): CorsConfigurationSource {
|
|||
return source
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following will disable the CORS integration within Spring Security:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -55,7 +60,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -68,4 +74,4 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -17,8 +17,10 @@ When an `ObservationRegistry` bean is present, Spring Security creates traces fo
|
|||
|
||||
For example, consider a simple Boot application:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@SpringBootApplication
|
||||
|
@ -45,7 +47,8 @@ public class MyApplication {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@SpringBootApplication
|
||||
|
@ -72,20 +75,17 @@ class MyApplication {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
And a corresponding request:
|
||||
|
||||
====
|
||||
[source,bash]
|
||||
----
|
||||
?> http -a user:password :8080
|
||||
----
|
||||
====
|
||||
|
||||
Will produce the following output (indentation added for clarity):
|
||||
|
||||
====
|
||||
[source,bash]
|
||||
----
|
||||
START - name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@5dfdb78', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.00191856, duration(nanos)=1918560.0, startTimeNanos=101177265022745}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@121549e0']
|
||||
|
@ -101,15 +101,16 @@ START - name='http.server.requests', contextualName='null', error='null', lowCar
|
|||
STOP - name='spring.security.http.chains', contextualName='spring.security.http.chains.after', error='null', lowCardinalityKeyValues=[chain.size='14', filter.section='after'], highCardinalityKeyValues=[request.line='/'], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@40b25623', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.001693146, duration(nanos)=1693146.0, startTimeNanos=101178044824275}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@3b6cec2']
|
||||
STOP - name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@5dfdb78', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.784320641, duration(nanos)=7.84320641E8, startTimeNanos=101177265022745}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@121549e0']
|
||||
----
|
||||
====
|
||||
|
||||
[[webflux-observability-tracing-manual-configuration]]
|
||||
=== Manual Configuration
|
||||
|
||||
For a non-Spring Boot application, or to override the existing Boot configuration, you can publish your own `ObservationRegistry` and Spring Security will still pick it up.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@SpringBootApplication
|
||||
|
@ -138,7 +139,8 @@ public class MyApplication {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@SpringBootApplication
|
||||
|
@ -167,7 +169,8 @@ class MyApplication {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
<sec:http auto-config="true" observation-registry-ref="ref">
|
||||
|
@ -176,7 +179,7 @@ class MyApplication {
|
|||
|
||||
<!-- define and configure ObservationRegistry bean -->
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-observability-tracing-disable]]
|
||||
=== Disabling Observability
|
||||
|
@ -186,8 +189,10 @@ However, this may turn off observations for more than just Spring Security.
|
|||
|
||||
Instead, you can alter the provided `ObservationRegistry` with an `ObservationPredicate` like the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -197,7 +202,8 @@ ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -206,7 +212,7 @@ fun noSpringSecurityObservations(): ObservationRegistryCustomizer<ObservationReg
|
|||
(registry: ObservationRegistry) -> registry.observationConfig().observationPredicate(predicate)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
There is no facility for disabling observations with XML support.
|
||||
|
|
|
@ -14,8 +14,10 @@ The following example shows a minimal RSocket Security configuration:
|
|||
|
||||
You can find a minimal RSocket Security configuration below:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -34,7 +36,8 @@ public class HelloRSocketSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -51,7 +54,7 @@ open class HelloRSocketSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This configuration enables <<rsocket-authentication-simple,simple authentication>> and sets up <<rsocket-authorization,rsocket-authorization>> to require an authenticated user for any request.
|
||||
|
||||
|
@ -61,8 +64,10 @@ For Spring Security to work, we need to apply `SecuritySocketAcceptorInterceptor
|
|||
Doing so connects our `PayloadSocketAcceptorInterceptor` with the RSocket infrastructure.
|
||||
In a Spring Boot application, you can do this automatically by using `RSocketSecurityAutoConfiguration` with the following code:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -71,7 +76,8 @@ RSocketServerCustomizer springSecurityRSocketSecurity(SecuritySocketAcceptorInte
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -83,7 +89,7 @@ fun springSecurityRSocketSecurity(interceptor: SecuritySocketAcceptorInterceptor
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[rsocket-authentication]]
|
||||
== RSocket Authentication
|
||||
|
@ -123,8 +129,10 @@ See `RSocketSecurity.basicAuthentication(Customizer)` for setting it up.
|
|||
The RSocket receiver can decode the credentials by using `AuthenticationPayloadExchangeConverter`, which is automatically setup by using the `simpleAuthentication` portion of the DSL.
|
||||
The following example shows an explicit configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -140,7 +148,8 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -154,30 +163,35 @@ open fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInte
|
|||
return rsocket.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The RSocket sender can send credentials by using `SimpleAuthenticationEncoder`, which you can add to Spring's `RSocketStrategies`.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RSocketStrategies.Builder strategies = ...;
|
||||
strategies.encoder(new SimpleAuthenticationEncoder());
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
var strategies: RSocketStrategies.Builder = ...
|
||||
strategies.encoder(SimpleAuthenticationEncoder())
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can then use it to send a username and password to the receiver in the setup:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
MimeType authenticationMimeType =
|
||||
|
@ -189,7 +203,8 @@ Mono<RSocketRequester> requester = RSocketRequester.builder()
|
|||
.connectTcp(host, port);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val authenticationMimeType: MimeType =
|
||||
|
@ -200,12 +215,14 @@ val requester: Mono<RSocketRequester> = RSocketRequester.builder()
|
|||
.rsocketStrategies(strategies.build())
|
||||
.connectTcp(host, port)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Alternatively or additionally, a username and password can be sent in a request.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Mono<RSocketRequester> requester;
|
||||
|
@ -220,7 +237,8 @@ public Mono<AirportLocation> findRadar(String code) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.messaging.rsocket.retrieveMono
|
||||
|
@ -238,7 +256,7 @@ open fun findRadar(code: String): Mono<AirportLocation> {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[rsocket-authentication-jwt]]
|
||||
=== JWT
|
||||
|
@ -249,8 +267,10 @@ The support comes in the form of authenticating a JWT (determining that the JWT
|
|||
The RSocket receiver can decode the credentials by using `BearerPayloadExchangeConverter`, which is automatically setup by using the `jwt` portion of the DSL.
|
||||
The following listing shows an example configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -266,7 +286,8 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -280,13 +301,15 @@ fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorIntercept
|
|||
return rsocket.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The configuration above relies on the existence of a `ReactiveJwtDecoder` `@Bean` being present.
|
||||
An example of creating one from the issuer can be found below:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -296,7 +319,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -305,13 +329,15 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
.fromIssuerLocation("https://example.com/auth/realms/demo")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The RSocket sender does not need to do anything special to send the token, because the value is a simple `String`.
|
||||
The following example sends the token at setup time:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
MimeType authenticationMimeType =
|
||||
|
@ -322,7 +348,8 @@ Mono<RSocketRequester> requester = RSocketRequester.builder()
|
|||
.connectTcp(host, port);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val authenticationMimeType: MimeType =
|
||||
|
@ -333,12 +360,14 @@ val requester = RSocketRequester.builder()
|
|||
.setupMetadata(token, authenticationMimeType)
|
||||
.connectTcp(host, port)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Alternatively or additionally, you can send the token in a request:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
MimeType authenticationMimeType =
|
||||
|
@ -355,7 +384,8 @@ public Mono<AirportLocation> findRadar(String code) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val authenticationMimeType: MimeType =
|
||||
|
@ -371,7 +401,7 @@ open fun findRadar(code: String): Mono<AirportLocation> {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[rsocket-authorization]]
|
||||
== RSocket Authorization
|
||||
|
@ -380,8 +410,10 @@ RSocket authorization is performed with `AuthorizationPayloadInterceptor`, which
|
|||
You can use the DSL to set up authorization rules based upon the `PayloadExchange`.
|
||||
The following listing shows an example configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
rsocket
|
||||
|
@ -397,7 +429,9 @@ rsocket
|
|||
.anyExchange().permitAll() // <6>
|
||||
);
|
||||
----
|
||||
.Kotlin
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
rsocket
|
||||
|
@ -413,6 +447,7 @@ rsocket
|
|||
.anyExchange().permitAll()
|
||||
} // <6>
|
||||
----
|
||||
======
|
||||
<1> Setting up a connection requires the `ROLE_SETUP` authority.
|
||||
<2> If the route is `fetch.profile.me`, authorization only requires the user to be authenticated.
|
||||
<3> In this rule, we set up a custom matcher, where authorization requires the user to have the `ROLE_CUSTOM` authority.
|
||||
|
@ -424,7 +459,6 @@ A request is where the metadata is included.
|
|||
It would not include additional payloads.
|
||||
<6> This rule ensures that any exchange that does not already have a rule is allowed for anyone.
|
||||
In this example, it means that payloads that have no metadata also have no authorization rules.
|
||||
====
|
||||
|
||||
Note that authorization rules are performed in order.
|
||||
Only the first authorization rule that matches is invoked.
|
||||
|
|
|
@ -111,8 +111,10 @@ OPTIONAL. Space delimited, case sensitive list of ASCII string values that speci
|
|||
|
||||
The following example shows how to configure the `DefaultServerOAuth2AuthorizationRequestResolver` with a `Consumer<OAuth2AuthorizationRequest.Builder>` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -155,7 +157,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -196,7 +199,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
For the simple use case, where the additional request parameter is always the same for a specific provider, it may be added directly in the `authorization-uri` property.
|
||||
|
||||
|
@ -221,8 +224,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
|||
|
||||
The following example shows a variation of `authorizationRequestCustomizer()` from the preceding example, and instead overrides the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
|
||||
|
@ -232,7 +237,8 @@ private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomi
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
|
||||
|
@ -245,7 +251,7 @@ private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationReques
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
=== Storing the Authorization Request
|
||||
|
@ -260,8 +266,10 @@ The default implementation of `ServerAuthorizationRequestRepository` is `WebSess
|
|||
If you have a custom implementation of `ServerAuthorizationRequestRepository`, you may configure it as shown in the following example:
|
||||
|
||||
.ServerAuthorizationRequestRepository Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -280,7 +288,8 @@ public class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -299,7 +308,7 @@ class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Requesting an Access Token
|
||||
|
||||
|
@ -335,8 +344,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
|||
Whether you customize `WebClientReactiveAuthorizationCodeTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you’ll need to configure it as shown in the following example:
|
||||
|
||||
.Access Token Response Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -363,7 +374,8 @@ public class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -389,7 +401,7 @@ class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[oauth2Client-refresh-token-grant]]
|
||||
|
@ -433,8 +445,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
|||
Whether you customize `WebClientReactiveRefreshTokenTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you’ll need to configure it as shown in the following example:
|
||||
|
||||
.Access Token Response Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -451,7 +465,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
|||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -466,7 +481,7 @@ val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveO
|
|||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().refreshToken()` configures a `RefreshTokenReactiveOAuth2AuthorizedClientProvider`,
|
||||
|
@ -516,8 +531,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
|||
|
||||
Whether you customize `WebClientReactiveClientCredentialsTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -533,7 +550,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
|||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -547,7 +565,7 @@ val authorizedClientProvider: ReactiveOAuth2AuthorizedClientProvider = ReactiveO
|
|||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().clientCredentials()` configures a `ClientCredentialsReactiveOAuth2AuthorizedClientProvider`,
|
||||
|
@ -576,8 +594,10 @@ spring:
|
|||
|
||||
...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -599,7 +619,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -615,12 +636,14 @@ fun authorizedClientManager(
|
|||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -644,7 +667,8 @@ public class OAuth2ClientController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class OAuth2ClientController {
|
||||
|
@ -666,7 +690,7 @@ class OAuth2ClientController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`ServerWebExchange` is an OPTIONAL attribute.
|
||||
|
@ -713,8 +737,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
|||
|
||||
Whether you customize `WebClientReactivePasswordTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -731,7 +757,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
|||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val passwordTokenResponseClient: ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...
|
||||
|
@ -745,7 +772,7 @@ val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.bui
|
|||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`ReactiveOAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordReactiveOAuth2AuthorizedClientProvider`,
|
||||
|
@ -774,8 +801,10 @@ spring:
|
|||
|
||||
...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -819,7 +848,9 @@ private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttri
|
|||
};
|
||||
}
|
||||
----
|
||||
.Kotlin
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -858,12 +889,14 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<Mut
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -887,7 +920,8 @@ public class OAuth2ClientController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -909,7 +943,7 @@ class OAuth2ClientController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`ServerWebExchange` is an OPTIONAL attribute.
|
||||
|
@ -955,8 +989,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
|||
|
||||
Whether you customize `WebClientReactiveJwtBearerTokenResponseClient` or provide your own implementation of `ReactiveOAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -975,7 +1011,8 @@ ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
|
|||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -992,7 +1029,7 @@ val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.bui
|
|||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Using the Access Token
|
||||
|
||||
|
@ -1017,8 +1054,10 @@ spring:
|
|||
|
||||
...and the `OAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1043,7 +1082,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1060,12 +1100,14 @@ fun authorizedClientManager(
|
|||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RestController
|
||||
|
@ -1087,7 +1129,8 @@ public class OAuth2ResourceServerController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class OAuth2ResourceServerController {
|
||||
|
@ -1106,7 +1149,7 @@ class OAuth2ResourceServerController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`JwtBearerReactiveOAuth2AuthorizedClientProvider` resolves the `Jwt` assertion via `OAuth2AuthorizationContext.getPrincipal().getPrincipal()` by default, hence the use of `JwtAuthenticationToken` in the preceding example.
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
The `@RegisteredOAuth2AuthorizedClient` annotation provides the capability of resolving a method parameter to an argument value of type `OAuth2AuthorizedClient`.
|
||||
This is a convenient alternative compared to accessing the `OAuth2AuthorizedClient` using the `ReactiveOAuth2AuthorizedClientManager` or `ReactiveOAuth2AuthorizedClientService`.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -24,7 +26,8 @@ public class OAuth2ClientController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -37,7 +40,7 @@ class OAuth2ClientController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The `@RegisteredOAuth2AuthorizedClient` annotation is handled by `OAuth2AuthorizedClientArgumentResolver`, which directly uses a <<oauth2Client-authorized-manager-provider, ReactiveOAuth2AuthorizedClientManager>> and therefore inherits it's capabilities.
|
||||
|
||||
|
@ -58,8 +61,10 @@ It directly uses an <<oauth2Client-authorized-manager-provider, ReactiveOAuth2Au
|
|||
|
||||
The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -72,7 +77,8 @@ WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManage
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -83,7 +89,7 @@ fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): W
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Providing the Authorized Client
|
||||
|
||||
|
@ -91,8 +97,10 @@ The `ServerOAuth2AuthorizedClientExchangeFilterFunction` determines the client t
|
|||
|
||||
The following code shows how to set an `OAuth2AuthorizedClient` as a request attribute:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -110,7 +118,8 @@ public Mono<String> index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2Author
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -127,14 +136,16 @@ fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2Auth
|
|||
.thenReturn("index")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
<1> `oauth2AuthorizedClient()` is a `static` method in `ServerOAuth2AuthorizedClientExchangeFilterFunction`.
|
||||
|
||||
The following code shows how to set the `ClientRegistration.getRegistrationId()` as a request attribute:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -152,7 +163,8 @@ public Mono<String> index() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -169,7 +181,7 @@ fun index(): Mono<String> {
|
|||
.thenReturn("index")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
<1> `clientRegistrationId()` is a `static` method in `ServerOAuth2AuthorizedClientExchangeFilterFunction`.
|
||||
|
||||
|
||||
|
@ -181,8 +193,10 @@ If `setDefaultOAuth2AuthorizedClient(true)` is configured and the user has authe
|
|||
|
||||
The following code shows the specific configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -196,7 +210,8 @@ WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManage
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -208,7 +223,7 @@ fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): W
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[WARNING]
|
||||
It is recommended to be cautious with this feature since all HTTP requests will receive the access token.
|
||||
|
@ -217,8 +232,10 @@ Alternatively, if `setDefaultClientRegistrationId("okta")` is configured with a
|
|||
|
||||
The following code shows the specific configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -232,7 +249,8 @@ WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManage
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -244,7 +262,7 @@ fun webClient(authorizedClientManager: ReactiveOAuth2AuthorizedClientManager): W
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[WARNING]
|
||||
It is recommended to be cautious with this feature since all HTTP requests will receive the access token.
|
||||
|
|
|
@ -36,8 +36,10 @@ spring:
|
|||
|
||||
The following example shows how to configure `WebClientReactiveAuthorizationCodeTokenResponseClient`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
|
||||
|
@ -59,7 +61,8 @@ tokenResponseClient.addParametersConverter(
|
|||
new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val jwkResolver: Function<ClientRegistration, JWK> =
|
||||
|
@ -81,7 +84,7 @@ tokenResponseClient.addParametersConverter(
|
|||
NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
|
||||
)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
=== Authenticate using `client_secret_jwt`
|
||||
|
@ -105,8 +108,10 @@ spring:
|
|||
|
||||
The following example shows how to configure `WebClientReactiveClientCredentialsTokenResponseClient`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
|
||||
|
@ -127,7 +132,8 @@ tokenResponseClient.addParametersConverter(
|
|||
new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val jwkResolver = Function<ClientRegistration, JWK?> { clientRegistration: ClientRegistration ->
|
||||
|
@ -148,14 +154,16 @@ tokenResponseClient.addParametersConverter(
|
|||
NimbusJwtClientAuthenticationParametersConverter(jwkResolver)
|
||||
)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Customizing the JWT assertion
|
||||
|
||||
The JWT produced by `NimbusJwtClientAuthenticationParametersConverter` contains the `iss`, `sub`, `aud`, `jti`, `iat` and `exp` claims by default. You can customize the headers and/or claims by providing a `Consumer<NimbusJwtClientAuthenticationParametersConverter.JwtClientAuthenticationContext<T>>` to `setJwtClientAssertionCustomizer()`. The following example shows how to customize claims of the JWT:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Function<ClientRegistration, JWK> jwkResolver = ...
|
||||
|
@ -168,7 +176,8 @@ converter.setJwtClientAssertionCustomizer((context) -> {
|
|||
});
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val jwkResolver = ...
|
||||
|
@ -180,4 +189,4 @@ converter.setJwtClientAssertionCustomizer { context ->
|
|||
context.claims.claim("custom-claim", "claim-value")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -69,20 +69,23 @@ A `ClientRegistration` can be initially configured using discovery of an OpenID
|
|||
|
||||
`ClientRegistrations` provides convenience methods for configuring a `ClientRegistration` in this way, as can be seen in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
ClientRegistration clientRegistration =
|
||||
ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val clientRegistration = ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The above code will query in series `https://idp.example.com/issuer/.well-known/openid-configuration`, and then `https://idp.example.com/.well-known/openid-configuration/issuer`, and finally `https://idp.example.com/.well-known/oauth-authorization-server/issuer`, stopping at the first to return a 200 response.
|
||||
|
||||
|
@ -106,8 +109,10 @@ The auto-configuration also registers the `ReactiveClientRegistrationRepository`
|
|||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -125,7 +130,8 @@ public class OAuth2ClientController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -142,7 +148,7 @@ class OAuth2ClientController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2Client-authorized-client]]
|
||||
== OAuth2AuthorizedClient
|
||||
|
@ -163,8 +169,10 @@ From a developer perspective, the `ServerOAuth2AuthorizedClientRepository` or `R
|
|||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -183,7 +191,8 @@ public class OAuth2ClientController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -201,7 +210,7 @@ class OAuth2ClientController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Spring Boot 2.x auto-configuration registers an `ServerOAuth2AuthorizedClientRepository` and/or `ReactiveOAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`.
|
||||
|
@ -235,8 +244,10 @@ The `ReactiveOAuth2AuthorizedClientProviderBuilder` may be used to configure and
|
|||
|
||||
The following code shows an example of how to configure and build a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -261,7 +272,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -280,7 +292,7 @@ fun authorizedClientManager(
|
|||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
When an authorization attempt succeeds, the `DefaultReactiveOAuth2AuthorizedClientManager` will delegate to the `ReactiveOAuth2AuthorizationSuccessHandler`, which (by default) will save the `OAuth2AuthorizedClient` via the `ServerOAuth2AuthorizedClientRepository`.
|
||||
In the case of a re-authorization failure, eg. a refresh token is no longer valid, the previously saved `OAuth2AuthorizedClient` will be removed from the `ServerOAuth2AuthorizedClientRepository` via the `RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler`.
|
||||
|
@ -291,8 +303,10 @@ This can be useful when you need to supply a `ReactiveOAuth2AuthorizedClientProv
|
|||
|
||||
The following code shows an example of the `contextAttributesMapper`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -337,7 +351,8 @@ private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttri
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -376,7 +391,7 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, Mono<Mut
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The `DefaultReactiveOAuth2AuthorizedClientManager` is designed to be used *_within_* the context of a `ServerWebExchange`.
|
||||
When operating *_outside_* of a `ServerWebExchange` context, use `AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager` instead.
|
||||
|
@ -387,8 +402,10 @@ An OAuth 2.0 Client configured with the `client_credentials` grant type can be c
|
|||
|
||||
The following code shows an example of how to configure an `AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager` that provides support for the `client_credentials` grant type:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -410,7 +427,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -426,4 +444,4 @@ fun authorizedClientManager(
|
|||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -24,8 +24,10 @@ The `ServerHttpSecurity.oauth2Client()` DSL provides a number of configuration o
|
|||
The following code shows the complete configuration options provided by the `ServerHttpSecurity.oauth2Client()` DSL:
|
||||
|
||||
.OAuth2 Client Configuration Options
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -49,7 +51,8 @@ public class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -73,14 +76,16 @@ class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The `ReactiveOAuth2AuthorizedClientManager` is responsible for managing the authorization (or re-authorization) of an OAuth 2.0 Client, in collaboration with one or more `ReactiveOAuth2AuthorizedClientProvider`(s).
|
||||
|
||||
The following code shows an example of how to register a `ReactiveOAuth2AuthorizedClientManager` `@Bean` and associate it with a `ReactiveOAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials` and `password` authorization grant types:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -105,7 +110,8 @@ public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -124,4 +130,4 @@ fun authorizedClientManager(
|
|||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -23,8 +23,10 @@ These claims are normally represented by a JSON object that contains a collectio
|
|||
The following code shows the complete configuration options available for the `oauth2Login()` DSL:
|
||||
|
||||
.OAuth2 Login Configuration Options
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -53,7 +55,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -82,7 +85,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following sections go into more detail on each of the configuration options available:
|
||||
|
||||
|
@ -119,8 +122,10 @@ To override the default login page, configure the `exceptionHandling().authentic
|
|||
The following listing shows an example:
|
||||
|
||||
.OAuth2 Login Page Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -153,7 +158,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -187,7 +193,7 @@ class OAuth2LoginSecurityConfig {
|
|||
...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[IMPORTANT]
|
||||
You need to provide a `@Controller` with a `@RequestMapping("/login/oauth2")` that is capable of rendering the custom login page.
|
||||
|
@ -220,8 +226,10 @@ The default Authorization Response redirection endpoint is `+/login/oauth2/code/
|
|||
If you would like to customize the Authorization Response redirection endpoint, configure it as shown in the following example:
|
||||
|
||||
.Redirection Endpoint Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -240,7 +248,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -259,7 +268,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
|
@ -267,7 +276,10 @@ You also need to ensure the `ClientRegistration.redirectUri` matches the custom
|
|||
|
||||
The following listing shows an example:
|
||||
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",subs="-attributes"]
|
||||
----
|
||||
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
||||
|
@ -277,7 +289,8 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
|||
.build();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",subs="-attributes"]
|
||||
----
|
||||
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
||||
|
@ -286,6 +299,7 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
|||
.redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
|
||||
.build()
|
||||
----
|
||||
======
|
||||
====
|
||||
|
||||
|
||||
|
@ -322,8 +336,10 @@ The `GrantedAuthoritiesMapper` is given a list of granted authorities which cont
|
|||
Register a `GrantedAuthoritiesMapper` `@Bean` to have it automatically applied to the configuration, as shown in the following example:
|
||||
|
||||
.Granted Authorities Mapper Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -371,7 +387,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -408,7 +425,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2-login-advanced-map-authorities-reactiveoauth2userservice]]
|
||||
==== Delegation-based strategy with ReactiveOAuth2UserService
|
||||
|
@ -420,8 +437,10 @@ The `OAuth2UserRequest` (and `OidcUserRequest`) provides you access to the assoc
|
|||
The following example shows how to implement and configure a delegation-based strategy using an OpenID Connect 1.0 UserService:
|
||||
|
||||
.ReactiveOAuth2UserService Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -462,7 +481,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -501,7 +521,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-advanced-oauth2-user-service]]
|
||||
|
@ -518,8 +538,10 @@ If you need to customize the pre-processing of the UserInfo Request and/or the p
|
|||
|
||||
Whether you customize `DefaultReactiveOAuth2UserService` or provide your own implementation of `ReactiveOAuth2UserService`, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -542,7 +564,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -564,7 +587,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-advanced-oidc-user-service]]
|
||||
|
@ -578,8 +601,10 @@ If you need to customize the pre-processing of the UserInfo Request and/or the p
|
|||
|
||||
Whether you customize `OidcReactiveOAuth2UserService` or provide your own implementation of `ReactiveOAuth2UserService` for OpenID Connect 1.0 Provider's, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -602,7 +627,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -624,7 +650,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-advanced-idtoken-verify]]
|
||||
|
@ -641,8 +667,10 @@ The JWS algorithm resolver is a `Function` that accepts a `ClientRegistration` a
|
|||
|
||||
The following code shows how to configure the `OidcIdTokenDecoderFactory` `@Bean` to default to `MacAlgorithm.HS256` for all `ClientRegistration`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -653,7 +681,8 @@ public ReactiveJwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -663,7 +692,7 @@ fun idTokenDecoderFactory(): ReactiveJwtDecoderFactory<ClientRegistration> {
|
|||
return idTokenDecoderFactory
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
For MAC based algorithms such as `HS256`, `HS384` or `HS512`, the `client-secret` corresponding to the `client-id` is used as the symmetric key for signature verification.
|
||||
|
@ -699,8 +728,10 @@ spring:
|
|||
|
||||
...and the `OidcClientInitiatedServerLogoutSuccessHandler`, which implements RP-Initiated Logout, may be configured as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -737,7 +768,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",subs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -772,7 +804,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
NOTE: `OidcClientInitiatedServerLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder.
|
||||
If used, the application's base URL, like `https://app.example.org`, will replace it at request time.
|
||||
|
|
|
@ -252,8 +252,10 @@ If you need to override the auto-configuration based on your specific requiremen
|
|||
|
||||
The following example shows how to register a `ReactiveClientRegistrationRepository` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -283,7 +285,8 @@ public class OAuth2LoginConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -312,7 +315,7 @@ class OAuth2LoginConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-register-securitywebfilterchain-bean]]
|
||||
|
@ -321,8 +324,10 @@ class OAuth2LoginConfig {
|
|||
The following example shows how to register a `SecurityWebFilterChain` `@Bean` with `@EnableWebFluxSecurity` and enable OAuth 2.0 login through `serverHttpSecurity.oauth2Login()`:
|
||||
|
||||
.OAuth2 Login Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -342,7 +347,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -362,7 +368,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-completely-override-autoconfiguration]]
|
||||
|
@ -371,8 +377,10 @@ class OAuth2LoginSecurityConfig {
|
|||
The following example shows how to completely override the auto-configuration by registering a `ReactiveClientRegistrationRepository` `@Bean` and a `SecurityWebFilterChain` `@Bean`.
|
||||
|
||||
.Overriding the auto-configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -414,7 +422,8 @@ public class OAuth2LoginConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -456,7 +465,7 @@ class OAuth2LoginConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[webflux-oauth2-login-javaconfig-wo-boot]]
|
||||
|
@ -465,8 +474,10 @@ class OAuth2LoginConfig {
|
|||
If you are not able to use Spring Boot 2.x and would like to configure one of the pre-defined providers in `CommonOAuth2Provider` (for example, Google), apply the following configuration:
|
||||
|
||||
.OAuth2 Login Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -510,7 +521,8 @@ public class OAuth2LoginConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -556,4 +568,4 @@ class OAuth2LoginConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -10,8 +10,10 @@ For example, you may have a need to read the bearer token from a custom header.
|
|||
To do so, you can wire an instance of `ServerBearerTokenAuthenticationConverter` into the DSL:
|
||||
|
||||
.Custom Bearer Token Header
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
ServerBearerTokenAuthenticationConverter converter = new ServerBearerTokenAuthenticationConverter();
|
||||
|
@ -22,7 +24,8 @@ http
|
|||
);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val converter = ServerBearerTokenAuthenticationConverter()
|
||||
|
@ -33,15 +36,17 @@ return http {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
== Bearer Token Propagation
|
||||
|
||||
Now that you have a bearer token, you can pass that to downstream services.
|
||||
This is possible with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServerBearerExchangeFilterFunction.html[ServerBearerExchangeFilterFunction]`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -52,7 +57,8 @@ public WebClient rest() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -62,13 +68,15 @@ fun rest(): WebClient {
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
When the `WebClient` shown in the preceding example performs requests, Spring Security looks up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential.
|
||||
Then, it propagates that token in the `Authorization` header -- for example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
this.rest.get()
|
||||
|
@ -77,7 +85,8 @@ this.rest.get()
|
|||
.bodyToMono(String.class)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
this.rest.get()
|
||||
|
@ -85,14 +94,16 @@ this.rest.get()
|
|||
.retrieve()
|
||||
.bodyToMono<String>()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The prececing example invokes the `https://other-service.example.com/endpoint`, adding the bearer token `Authorization` header for you.
|
||||
|
||||
In places where you need to override this behavior, you can supply the header yourself:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
this.rest.get()
|
||||
|
@ -102,7 +113,8 @@ this.rest.get()
|
|||
.bodyToMono(String.class)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
rest.get()
|
||||
|
@ -111,7 +123,7 @@ rest.get()
|
|||
.retrieve()
|
||||
.bodyToMono<String>()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In this case, the filter falls back and forwards the request onto the rest of the web filter chain.
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ First, include the needed dependencies. Second, indicate the location of the aut
|
|||
|
||||
In a Spring Boot application, you need to specify which authorization server to use:
|
||||
|
||||
====
|
||||
[source,yml]
|
||||
----
|
||||
spring:
|
||||
|
@ -26,7 +25,6 @@ spring:
|
|||
jwt:
|
||||
issuer-uri: https://idp.example.com/issuer
|
||||
----
|
||||
====
|
||||
|
||||
Where `https://idp.example.com/issuer` is the value contained in the `iss` claim for JWT tokens that the authorization server issues.
|
||||
This resource server uses this property to further self-configure, discover the authorization server's public keys, and subsequently validate incoming JWTs.
|
||||
|
@ -58,13 +56,11 @@ If the authorization server is down when Resource Server queries it (given appro
|
|||
|
||||
Once the application is started up, Resource Server tries to process any request that contains an `Authorization: Bearer` header:
|
||||
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
GET / HTTP/1.1
|
||||
Authorization: Bearer some-token-value # Resource Server will process this
|
||||
----
|
||||
====
|
||||
|
||||
So long as this scheme is indicated, Resource Server tries to process the request according to the Bearer Token specification.
|
||||
|
||||
|
@ -91,7 +87,6 @@ From here, consider jumping to:
|
|||
|
||||
If the authorization server does not support any configuration endpoints, or if Resource Server must be able to start up independently from the authorization server, you can supply `jwk-set-uri` as well:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -102,7 +97,6 @@ spring:
|
|||
issuer-uri: https://idp.example.com
|
||||
jwk-set-uri: https://idp.example.com/.well-known/jwks.json
|
||||
----
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -125,8 +119,10 @@ Spring Boot generates two `@Bean` objects on Resource Server's behalf.
|
|||
The first bean is a `SecurityWebFilterChain` that configures the application as a resource server. When including `spring-security-oauth2-jose`, this `SecurityWebFilterChain` looks like:
|
||||
|
||||
.Resource Server SecurityWebFilterChain
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -140,7 +136,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -155,15 +152,17 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If the application does not expose a `SecurityWebFilterChain` bean, Spring Boot exposes the default one (shown in the preceding listing).
|
||||
|
||||
To replace it, expose the `@Bean` within the application:
|
||||
|
||||
.Replacing SecurityWebFilterChain
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -180,7 +179,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -196,7 +196,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The preceding configuration requires the scope of `message:read` for any URL that starts with `/messages/`.
|
||||
|
||||
|
@ -205,8 +205,10 @@ Methods on the `oauth2ResourceServer` DSL also override or replace auto configur
|
|||
For example, the second `@Bean` Spring Boot creates is a `ReactiveJwtDecoder`, which decodes `String` tokens into validated instances of `Jwt`:
|
||||
|
||||
.ReactiveJwtDecoder
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -215,7 +217,8 @@ public ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -223,7 +226,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
return ReactiveJwtDecoders.fromIssuerLocation(issuerUri)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -238,8 +241,10 @@ Its configuration can be overridden by using `jwkSetUri()` or replaced by using
|
|||
|
||||
You can configure an authorization server's JWK Set URI <<webflux-oauth2resourceserver-jwt-jwkseturi,as a configuration property>> or supply it in the DSL:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -257,7 +262,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -274,7 +280,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Using `jwkSetUri()` takes precedence over any configuration property.
|
||||
|
||||
|
@ -283,8 +289,10 @@ Using `jwkSetUri()` takes precedence over any configuration property.
|
|||
|
||||
`decoder()` is more powerful than `jwkSetUri()`, because it completely replaces any Spring Boot auto-configuration of `JwtDecoder`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -302,7 +310,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -319,7 +328,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This is handy when you need deeper configuration, such as <<webflux-oauth2resourceserver-jwt-validation,validation>>.
|
||||
|
||||
|
@ -329,8 +338,10 @@ This is handy when you need deeper configuration, such as <<webflux-oauth2resour
|
|||
Alternately, exposing a `ReactiveJwtDecoder` `@Bean` has the same effect as `decoder()`:
|
||||
You can construct one with a `jwkSetUri` like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -339,7 +350,8 @@ public ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -347,12 +359,14 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
or you can use the issuer and have `NimbusReactiveJwtDecoder` look up the `jwkSetUri` when `build()` is invoked, like the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -361,7 +375,8 @@ public ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -369,12 +384,14 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
return NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Or, if the defaults work for you, you can also use `JwtDecoders`, which does the above in addition to configuring the decoder's validator:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -383,7 +400,8 @@ public ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -391,7 +409,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
return ReactiveJwtDecoders.fromIssuerLocation(issuer)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-decoder-algorithm]]
|
||||
== Configuring Trusted Algorithms
|
||||
|
@ -405,7 +423,6 @@ You can customize this behavior with <<webflux-oauth2resourceserver-jwt-boot-alg
|
|||
|
||||
The simplest way to set the algorithm is as a property:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -416,15 +433,16 @@ spring:
|
|||
jws-algorithm: RS512
|
||||
jwk-set-uri: https://idp.example.org/.well-known/jwks.json
|
||||
----
|
||||
====
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-decoder-builder]]
|
||||
=== Customizing Trusted Algorithms by Using a Builder
|
||||
|
||||
For greater power, though, we can use a builder that ships with `NimbusReactiveJwtDecoder`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -434,7 +452,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -443,12 +462,14 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
.jwsAlgorithm(RS512).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Calling `jwsAlgorithm` more than once configures `NimbusReactiveJwtDecoder` to trust more than one algorithm:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -458,7 +479,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -467,12 +489,14 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Alternately, you can call `jwsAlgorithms`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -485,7 +509,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -498,7 +523,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-decoder-public-key]]
|
||||
=== Trusting a Single Asymmetric Key
|
||||
|
@ -511,7 +536,6 @@ The public key can be provided with <<webflux-oauth2resourceserver-jwt-decoder-p
|
|||
|
||||
You can specify a key with Spring Boot:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -521,13 +545,14 @@ spring:
|
|||
jwt:
|
||||
public-key-location: classpath:my-key.pub
|
||||
----
|
||||
====
|
||||
|
||||
Alternately, to allow for a more sophisticated lookup, you can post-process the `RsaKeyConversionServicePostProcessor`:
|
||||
|
||||
.BeanFactoryPostProcessor
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -538,7 +563,8 @@ BeanFactoryPostProcessor conversionServiceCustomizer() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -549,42 +575,45 @@ fun conversionServiceCustomizer(): BeanFactoryPostProcessor {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Specify your key's location:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
key.location: hfds://my-key.pub
|
||||
----
|
||||
====
|
||||
|
||||
Then autowire the value:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Value("${key.location}")
|
||||
RSAPublicKey key;
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Value("\${key.location}")
|
||||
val key: RSAPublicKey? = null
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-decoder-public-key-builder]]
|
||||
==== Using a Builder
|
||||
|
||||
To wire an `RSAPublicKey` directly, use the appropriate `NimbusReactiveJwtDecoder` builder:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -593,7 +622,8 @@ public ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -601,7 +631,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
return NimbusReactiveJwtDecoder.withPublicKey(key).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-decoder-secret-key]]
|
||||
=== Trusting a Single Symmetric Key
|
||||
|
@ -609,8 +639,10 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
You can also use a single symmetric key.
|
||||
You can load in your `SecretKey` and use the appropriate `NimbusReactiveJwtDecoder` builder:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -619,7 +651,8 @@ public ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -627,26 +660,26 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
return NimbusReactiveJwtDecoder.withSecretKey(this.key).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-authorization]]
|
||||
=== Configuring Authorization
|
||||
|
||||
A JWT that is issued from an OAuth 2.0 Authorization Server typically has either a `scope` or an `scp` attribute, indicating the scopes (or authorities) it has been granted -- for example:
|
||||
|
||||
====
|
||||
[source,json]
|
||||
----
|
||||
{ ..., "scope" : "messages contacts"}
|
||||
----
|
||||
====
|
||||
|
||||
When this is the case, Resource Server tries to coerce these scopes into a list of granted authorities, prefixing each scope with the string, `SCOPE_`.
|
||||
|
||||
This means that, to protect an endpoint or method with a scope derived from a JWT, the corresponding expressions should include this prefix:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -662,7 +695,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -679,25 +713,28 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can do something similar with method security:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
public Flux<Message> getMessages(...) {}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
fun getMessages(): Flux<Message> { }
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-authorization-extraction]]
|
||||
==== Extracting Authorities Manually
|
||||
|
@ -708,8 +745,10 @@ At other times, the resource server may need to adapt the attribute or a composi
|
|||
|
||||
To this end, the DSL exposes `jwtAuthenticationConverter()`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -735,7 +774,8 @@ Converter<Jwt, Mono<AbstractAuthenticationToken>> grantedAuthoritiesExtractor()
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -758,15 +798,17 @@ fun grantedAuthoritiesExtractor(): Converter<Jwt, Mono<AbstractAuthenticationTok
|
|||
return ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
`jwtAuthenticationConverter()` is responsible for converting a `Jwt` into an `Authentication`.
|
||||
As part of its configuration, we can supply a subsidiary converter to go from `Jwt` to a `Collection` of granted authorities.
|
||||
|
||||
That final converter might be something like the following `GrantedAuthoritiesExtractor`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
static class GrantedAuthoritiesExtractor
|
||||
|
@ -784,7 +826,8 @@ static class GrantedAuthoritiesExtractor
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
internal class GrantedAuthoritiesExtractor : Converter<Jwt, Collection<GrantedAuthority>> {
|
||||
|
@ -797,12 +840,14 @@ internal class GrantedAuthoritiesExtractor : Converter<Jwt, Collection<GrantedAu
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
For more flexibility, the DSL supports entirely replacing the converter with any class that implements `Converter<Jwt, Mono<AbstractAuthenticationToken>>`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
static class CustomAuthenticationConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
|
||||
|
@ -812,7 +857,8 @@ static class CustomAuthenticationConverter implements Converter<Jwt, Mono<Abstra
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
internal class CustomAuthenticationConverter : Converter<Jwt, Mono<AbstractAuthenticationToken>> {
|
||||
|
@ -821,7 +867,7 @@ internal class CustomAuthenticationConverter : Converter<Jwt, Mono<AbstractAuthe
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-jwt-validation]]
|
||||
=== Configuring Validation
|
||||
|
@ -840,8 +886,10 @@ This can cause some implementation heartburn, as the number of collaborating ser
|
|||
|
||||
Resource Server uses `JwtTimestampValidator` to verify a token's validity window, and you can configure it with a `clockSkew` to alleviate the clock drift problem:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -859,7 +907,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -872,7 +921,7 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -884,8 +933,10 @@ By default, Resource Server configures a clock skew of 60 seconds.
|
|||
|
||||
You can Add a check for the `aud` claim with the `OAuth2TokenValidator` API:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
|
||||
|
@ -901,7 +952,8 @@ public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class AudienceValidator : OAuth2TokenValidator<Jwt> {
|
||||
|
@ -915,12 +967,14 @@ class AudienceValidator : OAuth2TokenValidator<Jwt> {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Then, to add into a resource server, you can specifying the `ReactiveJwtDecoder` instance:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -938,7 +992,8 @@ ReactiveJwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -951,4 +1006,4 @@ fun jwtDecoder(): ReactiveJwtDecoder {
|
|||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -17,8 +17,10 @@ In each case, two things need to be done and trade-offs are associated with how
|
|||
|
||||
One way to differentiate tenants is by the issuer claim. Since the issuer claim accompanies signed JWTs, you can do so with the `JwtIssuerReactiveAuthenticationManagerResolver`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
JwtIssuerReactiveAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerReactiveAuthenticationManagerResolver
|
||||
|
@ -33,7 +35,8 @@ http
|
|||
);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val customAuthenticationManagerResolver = JwtIssuerReactiveAuthenticationManagerResolver("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo")
|
||||
|
@ -47,7 +50,7 @@ return http {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This is nice because the issuer endpoints are loaded lazily.
|
||||
In fact, the corresponding `JwtReactiveAuthenticationManager` is instantiated only when the first request with the corresponding issuer is sent.
|
||||
|
@ -58,8 +61,10 @@ This allows for an application startup that is independent from those authorizat
|
|||
You may not want to restart the application each time a new tenant is added.
|
||||
In this case, you can configure the `JwtIssuerReactiveAuthenticationManagerResolver` with a repository of `ReactiveAuthenticationManager` instances, which you can edit at runtime:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
private Mono<ReactiveAuthenticationManager> addManager(
|
||||
|
@ -85,7 +90,8 @@ http
|
|||
);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
private fun addManager(
|
||||
|
@ -108,7 +114,7 @@ return http {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In this case, you construct `JwtIssuerReactiveAuthenticationManagerResolver` with a strategy for obtaining the `ReactiveAuthenticationManager` given to the issuer.
|
||||
This approach lets us add and remove elements from the repository (shown as a `Map` in the preceding snippet) at runtime.
|
||||
|
|
|
@ -23,7 +23,6 @@ When using https://spring.io/projects/spring-boot[Spring Boot], configuring an a
|
|||
|
||||
You can specify where the introspection endpoint is:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -35,7 +34,6 @@ spring:
|
|||
client-id: client
|
||||
client-secret: secret
|
||||
----
|
||||
====
|
||||
|
||||
Where `https://idp.example.com/introspect` is the introspection endpoint hosted by your authorization server and `client-id` and `client-secret` are the credentials needed to hit that endpoint.
|
||||
|
||||
|
@ -56,13 +54,11 @@ This startup process is quite a bit simpler than for JWTs, since no endpoints ne
|
|||
|
||||
Once the application has started, Resource Server tries to process any request containing an `Authorization: Bearer` header:
|
||||
|
||||
====
|
||||
[source,http]
|
||||
----
|
||||
GET / HTTP/1.1
|
||||
Authorization: Bearer some-token-value # Resource Server will process this
|
||||
----
|
||||
====
|
||||
|
||||
So long as this scheme is indicated, Resource Server tries to process the request according to the Bearer Token specification.
|
||||
|
||||
|
@ -87,8 +83,10 @@ Once a token is authenticated, an instance of `BearerTokenAuthentication` is set
|
|||
|
||||
This means that it is available in `@Controller` methods when you use `@EnableWebFlux` in your configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
|
@ -97,7 +95,8 @@ public Mono<String> foo(BearerTokenAuthentication authentication) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
|
@ -105,12 +104,14 @@ fun foo(authentication: BearerTokenAuthentication): Mono<String> {
|
|||
return Mono.just(authentication.tokenAttributes["sub"].toString() + " is the subject")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Since `BearerTokenAuthentication` holds an `OAuth2AuthenticatedPrincipal`, that also means that it's available to controller methods, too:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
|
@ -119,7 +120,8 @@ public Mono<String> foo(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal pr
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
|
@ -127,7 +129,7 @@ fun foo(@AuthenticationPrincipal principal: OAuth2AuthenticatedPrincipal): Mono<
|
|||
return Mono.just(principal.getAttribute<Any>("sub").toString() + " is the subject")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Looking Up Attributes with SpEL
|
||||
|
||||
|
@ -135,8 +137,10 @@ You can access attributes with the Spring Expression Language (SpEL).
|
|||
|
||||
For example, if you use `@EnableReactiveMethodSecurity` so that you can use `@PreAuthorize` annotations, you can do:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PreAuthorize("principal?.attributes['sub'] = 'foo'")
|
||||
|
@ -145,7 +149,8 @@ public Mono<String> forFoosEyesOnly() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreAuthorize("principal.attributes['sub'] = 'foo'")
|
||||
|
@ -153,7 +158,7 @@ fun forFoosEyesOnly(): Mono<String> {
|
|||
return Mono.just("foo")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-opaque-sansboot]]
|
||||
== Overriding or Replacing Boot Auto Configuration
|
||||
|
@ -163,8 +168,10 @@ Spring Boot generates two `@Bean` instances for Resource Server.
|
|||
The first is a `SecurityWebFilterChain` that configures the application as a resource server.
|
||||
When you use an Opaque Token, this `SecurityWebFilterChain` looks like:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -178,7 +185,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -193,15 +201,17 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If the application does not expose a `SecurityWebFilterChain` bean, Spring Boot exposes the default bean (shown in the preceding listing).
|
||||
|
||||
You can replace it by exposing the bean within the application:
|
||||
|
||||
.Replacing SecurityWebFilterChain
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -224,7 +234,8 @@ public class MyCustomSecurityConfiguration {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -242,7 +253,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The preceding example requires the scope of `message:read` for any URL that starts with `/messages/`.
|
||||
|
||||
|
@ -250,8 +261,10 @@ Methods on the `oauth2ResourceServer` DSL also override or replace auto configur
|
|||
|
||||
For example, the second `@Bean` Spring Boot creates is a `ReactiveOpaqueTokenIntrospector`, which decodes `String` tokens into validated instances of `OAuth2AuthenticatedPrincipal`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -260,7 +273,8 @@ public ReactiveOpaqueTokenIntrospector introspector() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -268,7 +282,7 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
|
|||
return NimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If the application does not expose a `ReactiveOpaqueTokenIntrospector` bean, Spring Boot exposes the default one (shown in the preceding listing).
|
||||
|
||||
|
@ -279,8 +293,10 @@ You can override its configuration by using `introspectionUri()` and `introspect
|
|||
|
||||
You can configure an authorization server's Introspection URI <<webflux-oauth2resourceserver-opaque-introspectionuri,as a configuration property>>, or you can supply in the DSL:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -303,7 +319,8 @@ public class DirectlyConfiguredIntrospectionUri {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -321,7 +338,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Using `introspectionUri()` takes precedence over any configuration property.
|
||||
|
||||
|
@ -330,8 +347,10 @@ Using `introspectionUri()` takes precedence over any configuration property.
|
|||
|
||||
`introspector()` is more powerful than `introspectionUri()`. It completely replaces any Boot auto-configuration of `ReactiveOpaqueTokenIntrospector`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -353,7 +372,8 @@ public class DirectlyConfiguredIntrospector {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -370,7 +390,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This is handy when deeper configuration, such as <<webflux-oauth2resourceserver-opaque-authorization-extraction,authority mapping>>or <<webflux-oauth2resourceserver-opaque-jwt-introspector,JWT revocation>>, is necessary.
|
||||
|
||||
|
@ -379,8 +399,10 @@ This is handy when deeper configuration, such as <<webflux-oauth2resourceserver-
|
|||
|
||||
Or, exposing a `ReactiveOpaqueTokenIntrospector` `@Bean` has the same effect as `introspector()`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -389,7 +411,8 @@ public ReactiveOpaqueTokenIntrospector introspector() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -397,26 +420,26 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
|
|||
return NimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-opaque-authorization]]
|
||||
== Configuring Authorization
|
||||
|
||||
An OAuth 2.0 Introspection endpoint typically returns a `scope` attribute, indicating the scopes (or authorities) it has been granted -- for example:
|
||||
|
||||
====
|
||||
[source,json]
|
||||
----
|
||||
{ ..., "scope" : "messages contacts"}
|
||||
----
|
||||
====
|
||||
|
||||
When this is the case, Resource Server tries to coerce these scopes into a list of granted authorities, prefixing each scope with a string: `SCOPE_`.
|
||||
|
||||
This means that, to protect an endpoint or method with a scope derived from an Opaque Token, the corresponding expressions should include this prefix:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -436,7 +459,8 @@ public class MappedAuthorities {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -453,25 +477,28 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can do something similar with method security:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
public Flux<Message> getMessages(...) {}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
fun getMessages(): Flux<Message> { }
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-opaque-authorization-extraction]]
|
||||
=== Extracting Authorities Manually
|
||||
|
@ -492,8 +519,10 @@ If the introspection response were as the preceding example shows, Resource Serv
|
|||
|
||||
You can customize behavior by using a custom `ReactiveOpaqueTokenIntrospector` that looks at the attribute set and converts in its own way:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class CustomAuthoritiesOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
|
||||
|
@ -515,7 +544,8 @@ public class CustomAuthoritiesOpaqueTokenIntrospector implements ReactiveOpaqueT
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class CustomAuthoritiesOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
|
@ -535,12 +565,14 @@ class CustomAuthoritiesOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Thereafter, you can configure this custom introspector by exposing it as a `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -549,7 +581,8 @@ public ReactiveOpaqueTokenIntrospector introspector() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -557,7 +590,7 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
|
|||
return CustomAuthoritiesOpaqueTokenIntrospector()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-opaque-jwt-introspector]]
|
||||
== Using Introspection with JWTs
|
||||
|
@ -569,7 +602,6 @@ So, suppose you need to check with the authorization server on each request, in
|
|||
|
||||
Even though you are using the JWT format for the token, your validation method is introspection, meaning you would want to do:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -581,7 +613,6 @@ spring:
|
|||
client-id: client
|
||||
client-secret: secret
|
||||
----
|
||||
====
|
||||
|
||||
In this case, the resulting `Authentication` would be `BearerTokenAuthentication`.
|
||||
Any attributes in the corresponding `OAuth2AuthenticatedPrincipal` would be whatever was returned by the introspection endpoint.
|
||||
|
@ -591,8 +622,10 @@ Now what?
|
|||
|
||||
In this case, you can create a custom `ReactiveOpaqueTokenIntrospector` that still hits the endpoint but then updates the returned principal to have the JWTs claims as the attributes:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class JwtOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
|
||||
|
@ -618,7 +651,8 @@ public class JwtOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospect
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class JwtOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
|
@ -641,12 +675,14 @@ class JwtOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Thereafter, you can configure this custom introspector by exposing it as a `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -655,7 +691,8 @@ public ReactiveOpaqueTokenIntrospector introspector() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -663,7 +700,7 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
|
|||
return JwtOpaqueTokenIntrospector()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[webflux-oauth2resourceserver-opaque-userinfo]]
|
||||
== Calling a `/userinfo` Endpoint
|
||||
|
@ -679,8 +716,10 @@ The implementation in the next listing does three things:
|
|||
* Looks up the appropriate client registration associated with the `/userinfo` endpoint.
|
||||
* Invokes and returns the response from the `/userinfo` endpoint.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
|
||||
|
@ -709,7 +748,8 @@ public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntro
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
|
@ -732,13 +772,15 @@ class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If you aren't using `spring-security-oauth2-client`, it's still quite simple.
|
||||
You will simply need to invoke the `/userinfo` with your own instance of `WebClient`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospector {
|
||||
|
@ -754,7 +796,8 @@ public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntro
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
||||
|
@ -767,12 +810,14 @@ class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Either way, having created your `ReactiveOpaqueTokenIntrospector`, you should publish it as a `@Bean` to override the defaults:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -781,7 +826,8 @@ ReactiveOpaqueTokenIntrospector introspector() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -789,4 +835,4 @@ fun introspector(): ReactiveOpaqueTokenIntrospector {
|
|||
return UserInfoOpaqueTokenIntrospector()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
For example, we can test our example from xref:reactive/authorization/method.adoc#jc-erms[EnableReactiveMethodSecurity] by using the same setup and annotations that we used in xref:servlet/test/method.adoc#test-method[Testing Method Security].
|
||||
The following minimal sample shows what we can do:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
|
@ -39,7 +41,8 @@ public class HelloWorldMessageServiceTests {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ExtendWith(SpringExtension.class)
|
||||
|
@ -72,4 +75,4 @@ class HelloWorldMessageServiceTests {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
After xref:reactive/test/web/setup.adoc[applying the Spring Security support to `WebTestClient`], we can use either annotations or `mutateWith` support -- for example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
|
||||
|
@ -64,7 +66,8 @@ public void messageWhenMutateWithMockAdminThenOk() throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.test.web.reactive.server.expectBody
|
||||
|
@ -111,6 +114,6 @@ fun messageWhenMutateWithMockAdminThenOk() {
|
|||
.expectBody<String>().isEqualTo("Hello World!")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In addition to `mockUser()`, Spring Security ships with several other convenience mutators for things like xref:reactive/test/web/csrf.adoc[CSRF] and xref:reactive/test/web/oauth2.adoc[OAuth 2.0].
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
Spring Security also provides support for CSRF testing with `WebTestClient` -- for example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
|
||||
|
@ -16,7 +18,8 @@ this.rest
|
|||
...
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf
|
||||
|
@ -28,4 +31,4 @@ this.rest
|
|||
.uri("/login")
|
||||
...
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,8 +2,10 @@
|
|||
|
||||
The basic setup looks like this:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
|
||||
|
@ -31,7 +33,8 @@ public class HelloWebfluxMethodApplicationTests {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity
|
||||
|
@ -58,4 +61,4 @@ class HelloWebfluxMethodApplicationTests {
|
|||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -11,7 +11,6 @@ You can use these as a guideline for defining the schema for the database you ar
|
|||
The standard JDBC implementation of the `UserDetailsService` (`JdbcDaoImpl`) requires tables to load the password, account status (enabled or disabled) and a list of authorities (roles) for the user.
|
||||
You can use these as a guideline for defining the schema for the database you use.
|
||||
|
||||
====
|
||||
[source]
|
||||
----
|
||||
|
||||
|
@ -28,13 +27,11 @@ create table authorities (
|
|||
);
|
||||
create unique index ix_auth_username on authorities (username,authority);
|
||||
----
|
||||
====
|
||||
|
||||
=== For Oracle database
|
||||
|
||||
The following listing shows the Oracle variant of the schema creation commands:
|
||||
|
||||
====
|
||||
[source]
|
||||
----
|
||||
CREATE TABLE USERS (
|
||||
|
@ -51,14 +48,12 @@ CREATE TABLE AUTHORITIES (
|
|||
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_UNIQUE UNIQUE (USERNAME, AUTHORITY);
|
||||
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_FK1 FOREIGN KEY (USERNAME) REFERENCES USERS (USERNAME) ENABLE;
|
||||
----
|
||||
====
|
||||
|
||||
=== Group Authorities
|
||||
Spring Security 2.0 introduced support for group authorities in `JdbcDaoImpl`.
|
||||
The table structure if groups are enabled is as follows.
|
||||
You need to adjust the following schema to match the database dialect you use:
|
||||
|
||||
====
|
||||
[source]
|
||||
----
|
||||
|
||||
|
@ -80,7 +75,6 @@ create table group_members (
|
|||
constraint fk_group_members_group foreign key(group_id) references groups(id)
|
||||
);
|
||||
----
|
||||
====
|
||||
|
||||
Remember that these tables are required only if you us the provided JDBC `UserDetailsService` implementation.
|
||||
If you write your own or choose to implement `AuthenticationProvider` without a `UserDetailsService`, you have complete freedom over how you store the data, as long as the interface contract is satisfied.
|
||||
|
@ -91,7 +85,6 @@ This table is used to store the data used by the more secure <<remember-me-persi
|
|||
If you use `JdbcTokenRepositoryImpl` either directly or through the namespace, you need this table.
|
||||
Remember to adjust this schema to match the database dialect you use:
|
||||
|
||||
====
|
||||
[source]
|
||||
----
|
||||
|
||||
|
@ -103,7 +96,6 @@ create table persistent_logins (
|
|||
);
|
||||
|
||||
----
|
||||
====
|
||||
|
||||
[[dbschema-acl]]
|
||||
== ACL Schema
|
||||
|
@ -127,7 +119,6 @@ These schemas are also demonstrated in the following sections.
|
|||
=== HyperSQL
|
||||
The default schema works with the embedded HSQLDB database that is used in unit tests within the framework.
|
||||
|
||||
====
|
||||
[source,ddl]
|
||||
----
|
||||
create table acl_sid(
|
||||
|
@ -170,7 +161,6 @@ create table acl_entry(
|
|||
constraint foreign_fk_5 foreign key(sid) references acl_sid(id)
|
||||
);
|
||||
----
|
||||
====
|
||||
|
||||
=== PostgreSQL
|
||||
|
||||
|
@ -179,7 +169,6 @@ For PostgreSQL, you have to set the `classIdentityQuery` and `sidIdentityQuery`
|
|||
* `select currval(pg_get_serial_sequence('acl_class', 'id'))`
|
||||
* `select currval(pg_get_serial_sequence('acl_sid', 'id'))`
|
||||
|
||||
====
|
||||
[source,ddl]
|
||||
----
|
||||
create table acl_sid(
|
||||
|
@ -222,11 +211,9 @@ create table acl_entry(
|
|||
constraint foreign_fk_5 foreign key(sid) references acl_sid(id)
|
||||
);
|
||||
----
|
||||
====
|
||||
|
||||
=== MySQL and MariaDB
|
||||
|
||||
====
|
||||
[source,ddl]
|
||||
----
|
||||
CREATE TABLE acl_sid (
|
||||
|
@ -269,11 +256,9 @@ CREATE TABLE acl_entry (
|
|||
CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id)
|
||||
) ENGINE=InnoDB;
|
||||
----
|
||||
====
|
||||
|
||||
=== Microsoft SQL Server
|
||||
|
||||
====
|
||||
[source,ddl]
|
||||
----
|
||||
CREATE TABLE acl_sid (
|
||||
|
@ -316,11 +301,9 @@ CREATE TABLE acl_entry (
|
|||
CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id)
|
||||
);
|
||||
----
|
||||
====
|
||||
|
||||
=== Oracle Database
|
||||
|
||||
====
|
||||
[source,ddl]
|
||||
----
|
||||
CREATE TABLE ACL_SID (
|
||||
|
@ -386,14 +369,12 @@ BEGIN
|
|||
SELECT ACL_ENTRY_SQ.NEXTVAL INTO :NEW.ID FROM DUAL;
|
||||
END;
|
||||
----
|
||||
====
|
||||
|
||||
[[dbschema-oauth2-client]]
|
||||
== OAuth 2.0 Client Schema
|
||||
The JDBC implementation of xref:servlet/oauth2/client/core.adoc#oauth2Client-authorized-repo-service[ `OAuth2AuthorizedClientService`] (`JdbcOAuth2AuthorizedClientService`) requires a table for persisting `OAuth2AuthorizedClient` instances.
|
||||
You will need to adjust this schema to match the database dialect you use.
|
||||
|
||||
====
|
||||
[source,ddl]
|
||||
----
|
||||
CREATE TABLE oauth2_authorized_client (
|
||||
|
@ -410,4 +391,3 @@ CREATE TABLE oauth2_authorized_client (
|
|||
PRIMARY KEY (client_registration_id, principal_name)
|
||||
);
|
||||
----
|
||||
====
|
||||
|
|
|
@ -150,7 +150,6 @@ From Spring Security 2.0.1 onwards, when you use namespace-based configuration,
|
|||
|
||||
This is a debug level message which occurs the first time an anonymous user attempts to access a protected resource.
|
||||
|
||||
====
|
||||
[source]
|
||||
----
|
||||
DEBUG [ExceptionTranslationFilter] - Access is denied (user is anonymous); redirecting to authentication entry point
|
||||
|
@ -158,7 +157,6 @@ org.springframework.security.AccessDeniedException: Access is denied
|
|||
at org.springframework.security.vote.AffirmativeBased.decide(AffirmativeBased.java:68)
|
||||
at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:262)
|
||||
----
|
||||
====
|
||||
|
||||
It is normal and shouldn't be anything to worry about.
|
||||
|
||||
|
@ -176,7 +174,6 @@ Note that SSL requests are never cached.
|
|||
|
||||
The following listing shows another debug-level message that occurs the first time an anonymous user attempts to access a protected resource. However, this listing shows what happens when you do not have an `AnonymousAuthenticationFilter` in your filter chain configuration:
|
||||
|
||||
====
|
||||
[source]
|
||||
----
|
||||
DEBUG [ExceptionTranslationFilter] - Authentication exception occurred; redirecting to authentication entry point
|
||||
|
@ -185,7 +182,6 @@ org.springframework.security.AuthenticationCredentialsNotFoundException:
|
|||
at org.springframework.security.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:342)
|
||||
at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:254)
|
||||
----
|
||||
====
|
||||
|
||||
It is normal and is not something to worry about.
|
||||
|
||||
|
@ -202,8 +198,10 @@ This differs from one company to another, so you have to find it out yourself.
|
|||
Before adding a Spring Security LDAP configuration to an application, you should write a simple test by using standard Java LDAP code (without Spring Security involved) and make sure you can get that to work first.
|
||||
For example, to authenticate a user, you could use the following code:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
|
||||
|
@ -222,7 +220,8 @@ public void ldapAuthenticationIsSuccessful() throws Exception {
|
|||
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Test
|
||||
|
@ -236,7 +235,7 @@ fun ldapAuthenticationIsSuccessful() {
|
|||
val ctx = InitialLdapContext(env, null)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Session Management
|
||||
|
||||
|
@ -301,14 +300,12 @@ It is essential to make sure that the Spring Security session registry is notifi
|
|||
Without it, the session information is not removed from the registry.
|
||||
The following example adds a listener in a `web.xml` file:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<listener>
|
||||
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
|
||||
</listener>
|
||||
----
|
||||
====
|
||||
|
||||
[[appendix-faq-unwanted-session-creation]]
|
||||
=== Spring Security creates a session somewhere, even though I have configured it not to, by setting the create-session attribute to never. What is wrong?
|
||||
|
@ -420,7 +417,6 @@ Any that are marked as "`optional`" in the Spring Security `pom.xml` files have
|
|||
|
||||
If you use Maven, you need to add the following to your `pom.xml` file dependencies:
|
||||
|
||||
====
|
||||
[source]
|
||||
----
|
||||
|
||||
|
@ -438,7 +434,6 @@ If you use Maven, you need to add the following to your `pom.xml` file dependenc
|
|||
</dependency>
|
||||
|
||||
----
|
||||
====
|
||||
|
||||
The other required jars should be pulled in transitively.
|
||||
|
||||
|
@ -531,8 +526,10 @@ To load the data from an alternative source, you must use an explicitly declared
|
|||
You cannot use the namespace.
|
||||
You would then implement `FilterInvocationSecurityMetadataSource` to load the data as you please for a particular `FilterInvocation`. The `FilterInvocation` object contains the `HttpServletRequest`, so you can obtain the URL or any other relevant information on which to base your decision, based on what the list of returned attributes contains. A basic outline would look something like the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
|
||||
|
@ -561,7 +558,8 @@ You would then implement `FilterInvocationSecurityMetadataSource` to load the da
|
|||
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class MyFilterSecurityMetadataSource : FilterInvocationSecurityMetadataSource {
|
||||
|
@ -584,7 +582,7 @@ class MyFilterSecurityMetadataSource : FilterInvocationSecurityMetadataSource {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
For more information, look at the code for `DefaultFilterInvocationSecurityMetadataSource`.
|
||||
|
||||
|
@ -597,8 +595,10 @@ The `DefaultLdapAuthoritiesPopulator` loads the user authorities from the LDAP d
|
|||
|
||||
To use JDBC instead, you can implement the interface yourself, by using whatever SQL is appropriate for your schema:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
|
||||
|
@ -624,7 +624,8 @@ public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator {
|
|||
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class MyAuthoritiesPopulator : LdapAuthoritiesPopulator {
|
||||
|
@ -644,7 +645,7 @@ class MyAuthoritiesPopulator : LdapAuthoritiesPopulator {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You would then add a bean of this type to your application context and inject it into the `LdapAuthenticationProvider`. This is covered in the section on configuring LDAP by using explicit Spring beans in the LDAP chapter of the reference manual.
|
||||
Note that you cannot use the namespace for configuration in this case.
|
||||
|
@ -661,8 +662,10 @@ You can find more information in the https://docs.spring.io/spring/docs/3.0.x/sp
|
|||
Normally, you would add the functionality you require to the `postProcessBeforeInitialization` method of `BeanPostProcessor`. Suppose that you want to customize the `AuthenticationDetailsSource` used by the `UsernamePasswordAuthenticationFilter` (created by the `form-login` element). You want to extract a particular header called `CUSTOM_HEADER` from the request and use it while authenticating the user.
|
||||
The processor class would look like the following listing:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class CustomBeanPostProcessor implements BeanPostProcessor {
|
||||
|
@ -686,7 +689,8 @@ public class CustomBeanPostProcessor implements BeanPostProcessor {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class CustomBeanPostProcessor : BeanPostProcessor {
|
||||
|
@ -704,7 +708,7 @@ class CustomBeanPostProcessor : BeanPostProcessor {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You would then register this bean in your application context.
|
||||
Spring automatically invoke it on the beans defined in the application context.
|
||||
|
|
|
@ -28,8 +28,10 @@ In this case, the `Filter` typically writes the `HttpServletResponse`.
|
|||
The power of the `Filter` comes from the `FilterChain` that is passed into it.
|
||||
|
||||
.`FilterChain` Usage Example
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
|
||||
|
@ -39,7 +41,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
||||
|
@ -48,7 +51,7 @@ fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterCh
|
|||
// do something after the rest of the application
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Since a `Filter` impacts only downstream `Filter` instances and the `Servlet`, the order in which each `Filter` is invoked is extremely important.
|
||||
|
||||
|
@ -69,8 +72,10 @@ image::{figures}/delegatingfilterproxy.png[]
|
|||
The following listing shows pseudo code of `DelegatingFilterProxy`:
|
||||
|
||||
.`DelegatingFilterProxy` Pseudo Code
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
|
||||
|
@ -79,7 +84,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
||||
|
@ -87,7 +93,7 @@ fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterCh
|
|||
delegate.doFilter(request, response) // <2>
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
<1> Lazily get Filter that was registered as a Spring Bean.
|
||||
For the example in <<servlet-delegatingfilterproxy-figure>> `delegate` is an instance of __Bean Filter~0~__.
|
||||
<2> Delegate work to the Spring Bean.
|
||||
|
@ -229,7 +235,6 @@ If the application does not throw an `AccessDeniedException` or an `Authenticati
|
|||
|
||||
The pseudocode for `ExceptionTranslationFilter` looks something like this:
|
||||
|
||||
====
|
||||
.ExceptionTranslationFilter pseudocode
|
||||
[source,java]
|
||||
----
|
||||
|
@ -247,7 +252,6 @@ try {
|
|||
This means that if another part of the application, (<<servlet-authorization-filtersecurityinterceptor,`FilterSecurityInterceptor`>> or method security) throws an `AuthenticationException` or `AccessDeniedException` it is caught and handled here.
|
||||
<2> If the user is not authenticated or it is an `AuthenticationException`, __Start Authentication__.
|
||||
<3> Otherwise, __Access Denied__
|
||||
====
|
||||
|
||||
[[savedrequests]]
|
||||
== Saving Requests Between Authentication
|
||||
|
@ -277,8 +281,10 @@ Or you may want to shut off this feature since you always want to redirect the u
|
|||
To do that, you can use {security-api-url}org/springframework/security/web/savedrequest/NullRequestCache.html[the `NullRequestCache` implementation].
|
||||
|
||||
.Prevent the Request From Being Saved
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -293,7 +299,8 @@ SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -308,7 +315,8 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http auto-config="true">
|
||||
|
@ -318,7 +326,7 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
|
|||
|
||||
<b:bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[requestcacheawarefilter]]
|
||||
|
|
|
@ -32,7 +32,6 @@ There is a corresponding `AnonymousAuthenticationProvider`, which is chained int
|
|||
Finally, an `AnonymousAuthenticationFilter` is chained after the normal authentication mechanisms and automatically adds an `AnonymousAuthenticationToken` to the `SecurityContextHolder` if there is no existing `Authentication` held there.
|
||||
The filter and authentication provider is defined as follows:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="anonymousAuthFilter"
|
||||
|
@ -46,7 +45,6 @@ The filter and authentication provider is defined as follows:
|
|||
<property name="key" value="foobar"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
@ -66,7 +64,6 @@ The same syntax is used after the equals sign for the `userMap` property of `InM
|
|||
|
||||
As explained earlier, the benefit of anonymous authentication is that all URI patterns can have security applied to them, as the following example shows:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="filterSecurityInterceptor"
|
||||
|
@ -84,7 +81,6 @@ As explained earlier, the benefit of anonymous authentication is that all URI pa
|
|||
</property>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
[[anonymous-auth-trust-resolver]]
|
||||
== AuthenticationTrustResolver
|
||||
|
@ -107,8 +103,10 @@ https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc
|
|||
|
||||
This means that a construct like this one:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -121,7 +119,8 @@ public String method(Authentication authentication) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -133,7 +132,7 @@ fun method(authentication: Authentication?): String {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
will always return "not anonymous", even for anonymous requests.
|
||||
The reason is that Spring MVC resolves the parameter using `HttpServletRequest#getPrincipal`, which is `null` when the request is anonymous.
|
||||
|
@ -141,8 +140,10 @@ The reason is that Spring MVC resolves the parameter using `HttpServletRequest#g
|
|||
If you'd like to obtain the `Authentication` in anonymous requests, use `@CurrentSecurityContext` instead:
|
||||
|
||||
.Use CurrentSecurityContext for Anonymous requests
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -151,11 +152,12 @@ public String method(@CurrentSecurityContext SecurityContext context) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
fun method(@CurrentSecurityContext context : SecurityContext) : String =
|
||||
context!!.authentication!!.name
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -32,7 +32,10 @@ The simplest way to indicate a user is authenticated is to set the `SecurityCont
|
|||
|
||||
.Setting `SecurityContextHolder`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext(); // <1>
|
||||
|
@ -43,7 +46,8 @@ context.setAuthentication(authentication);
|
|||
SecurityContextHolder.setContext(context); // <3>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val context: SecurityContext = SecurityContextHolder.createEmptyContext() // <1>
|
||||
|
@ -52,6 +56,7 @@ context.authentication = authentication
|
|||
|
||||
SecurityContextHolder.setContext(context) // <3>
|
||||
----
|
||||
======
|
||||
|
||||
<1> We start by creating an empty `SecurityContext`.
|
||||
You should create a new `SecurityContext` instance instead of using `SecurityContextHolder.getContext().setAuthentication(authentication)` to avoid race conditions across multiple threads.
|
||||
|
@ -66,8 +71,10 @@ Spring Security uses this information for xref:servlet/authorization/index.adoc#
|
|||
To obtain information about the authenticated principal, access the `SecurityContextHolder`.
|
||||
|
||||
.Access Currently Authenticated User
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
|
@ -77,7 +84,8 @@ Object principal = authentication.getPrincipal();
|
|||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val context = SecurityContextHolder.getContext()
|
||||
|
@ -86,7 +94,7 @@ val username = authentication.name
|
|||
val principal = authentication.principal
|
||||
val authorities = authentication.authorities
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
// FIXME: Add links to and relevant description of HttpServletRequest.getRemoteUser() and @CurrentSecurityContext @AuthenticationPrincipal
|
||||
|
||||
|
|
|
@ -340,8 +340,10 @@ Now that Spring Security obtains PGTs, you can use them to create proxy tickets
|
|||
The CAS xref:samples.adoc#samples[sample application] contains a working example in the `ProxyTicketSampleServlet`.
|
||||
Example code can be found below:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
|
@ -360,7 +362,8 @@ String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
protected fun doGet(request: HttpServletRequest, response: HttpServletResponse?) {
|
||||
|
@ -376,7 +379,7 @@ protected fun doGet(request: HttpServletRequest, response: HttpServletResponse?)
|
|||
val proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[cas-pt]]
|
||||
=== Proxy Ticket Authentication
|
||||
|
|
|
@ -6,8 +6,10 @@ For each authentication that succeeds or fails, a `AuthenticationSuccessEvent` o
|
|||
To listen for these events, you must first publish an `AuthenticationEventPublisher`.
|
||||
Spring Security's `DefaultAuthenticationEventPublisher` works fine for this purpose:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -17,7 +19,8 @@ public AuthenticationEventPublisher authenticationEventPublisher
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -26,12 +29,14 @@ fun authenticationEventPublisher
|
|||
return DefaultAuthenticationEventPublisher(applicationEventPublisher)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Then you can use Spring's `@EventListener` support:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -48,7 +53,8 @@ public class AuthenticationEvents {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -64,7 +70,7 @@ class AuthenticationEvents {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
While similar to `AuthenticationSuccessHandler` and `AuthenticationFailureHandler`, these are nice in that they can be used independently from the servlet API.
|
||||
|
||||
|
@ -89,8 +95,10 @@ The publisher does an exact `Exception` match, which means that sub-classes of t
|
|||
|
||||
To that end, you may want to supply additional mappings to the publisher through the `setAdditionalExceptionMappings` method:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -106,7 +114,8 @@ public AuthenticationEventPublisher authenticationEventPublisher
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -119,14 +128,16 @@ fun authenticationEventPublisher
|
|||
return authenticationEventPublisher
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
== Default Event
|
||||
|
||||
You can also supply a catch-all event to fire in the case of any `AuthenticationException`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -140,7 +151,8 @@ public AuthenticationEventPublisher authenticationEventPublisher
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -151,4 +163,4 @@ fun authenticationEventPublisher
|
|||
return authenticationEventPublisher
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -68,7 +68,6 @@ While the Spring configuration for `InMemoryConfiguration` can be more verbose t
|
|||
The next example provides a configuration of `DefaultJaasAuthenticationProvider` that uses `InMemoryConfiguration`.
|
||||
Note that custom implementations of `Configuration` can easily be injected into `DefaultJaasAuthenticationProvider` as well.
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="jaasAuthProvider"
|
||||
|
@ -107,7 +106,6 @@ class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticatio
|
|||
</property>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
[[jaas-jaasauthenticationprovider]]
|
||||
|
@ -118,19 +116,16 @@ The `JaasAuthenticationProvider` then uses the default `Configuration` to create
|
|||
|
||||
Assume that we have a JAAS login configuration file, `/WEB-INF/login.conf`, with the following contents:
|
||||
|
||||
====
|
||||
[source,txt]
|
||||
----
|
||||
JAASTest {
|
||||
sample.SampleLoginModule required;
|
||||
};
|
||||
----
|
||||
====
|
||||
|
||||
Like all Spring Security beans, the `JaasAuthenticationProvider` is configured through the application context.
|
||||
The following definitions would correspond to the above JAAS login configuration file:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
|
||||
|
@ -153,19 +148,16 @@ class="org.springframework.security.authentication.jaas.JaasAuthenticationProvid
|
|||
</property>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
[[jaas-apiprovision]]
|
||||
== Running as a Subject
|
||||
If configured, the `JaasApiIntegrationFilter` tries to run as the `Subject` on the `JaasAuthenticationToken`.
|
||||
This means that the `Subject` can be accessed using:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Subject subject = Subject.getSubject(AccessController.getContext());
|
||||
----
|
||||
====
|
||||
|
||||
You can configure this integration by using the xref:servlet/appendix/namespace/http.adoc#nsa-http-jaas-api-provision[jaas-api-provision] attribute.
|
||||
This feature is useful when integrating with legacy or external API's that rely on the JAAS Subject being populated.
|
||||
|
|
|
@ -48,15 +48,18 @@ Thus, only <<permit-logout-endpoints,custom logout endpoints>> that you create y
|
|||
For example, if you want to simply change the URI that Spring Security is matching, you can do so in the `logout` DSL in following way:
|
||||
|
||||
.Custom Logout Uri
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
.logout((logout) -> logout.logoutUrl("/my/logout/uri"))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -66,12 +69,13 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<logout logout-url="/my/logout/uri"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
and no authorization changes are necessary since it simply adjusts the `LogoutFilter`.
|
||||
|
||||
|
@ -82,8 +86,10 @@ This is because Spring MVC processes your request after Spring Security does.
|
|||
You can do this using `authorizeHttpRequests` or `<intercept-url>` like so:
|
||||
|
||||
.Custom Logout Endpoint
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
|
@ -94,7 +100,8 @@ http
|
|||
.logout((logout) -> logout.logoutSuccessUrl("/my/success/endpoint"))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -107,7 +114,8 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -115,7 +123,7 @@ http {
|
|||
<logout logout-success-url="/my/success/endpoint"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In this example, you tell the `LogoutFilter` to redirect to `/my/success/endpoint` when it is done.
|
||||
And, you explicitly permit the `/my/success/endpoint` endpoint in xref:servlet/authorization/authorize-http-requests.adoc[the `AuthorizationFilter`].
|
||||
|
@ -124,8 +132,10 @@ Specifying it twice can be cumbersome, though.
|
|||
If you are using Java configuration, you can instead set the `permitAll` property in the logout DSL like so:
|
||||
|
||||
.Permitting Custom Logout Endpoints
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
|
@ -138,7 +148,8 @@ http
|
|||
)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http
|
||||
|
@ -150,7 +161,7 @@ http
|
|||
permitAll = true
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
which will add all logout URIs to the permit list for you.
|
||||
|
||||
|
@ -160,8 +171,10 @@ which will add all logout URIs to the permit list for you.
|
|||
If you are using Java configuration, you can add clean up actions of your own by calling the `addLogoutHandler` method in the `logout` DSL, like so:
|
||||
|
||||
.Custom Logout Handler
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
|
||||
|
@ -169,7 +182,8 @@ http
|
|||
.logout((logout) -> logout.addLogoutHandler(cookies))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -178,7 +192,7 @@ http {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Because {security-api-url}org/springframework/security/web/authentication/logout/LogoutHandler.html[``LogoutHandler``]s are for the purposes of cleanup, they should not throw exceptions.
|
||||
|
@ -194,15 +208,18 @@ For example, you can configure the {security-api-url}org/springframework/securit
|
|||
[[delete-cookies]]
|
||||
Or you can instead set the appropriate configuration value like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
.logout((logout) -> logout.deleteCookies("our-custom-cookie"))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -212,14 +229,15 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
<logout delete-cookies="our-custom-cookie"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Specifying that the `JSESSIONID` cookie is not necessary since {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] removes it by virtue of invalidating the session.
|
||||
|
@ -233,8 +251,10 @@ This is a handy and secure way to ensure that everything, including the session
|
|||
You can add configure Spring Security to write the `Clear-Site-Data` header on logout like so:
|
||||
|
||||
.Using Clear-Site-Data
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
|
||||
|
@ -242,7 +262,8 @@ http
|
|||
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter())
|
||||
|
@ -252,15 +273,17 @@ http {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You give the `ClearSiteDataHeaderWriter` constructor the list of things that you want to be cleared out.
|
||||
|
||||
The above configuration clears out all site data, but you can also configure it to remove just cookies like so:
|
||||
|
||||
.Using Clear-Site-Data to Clear Cookies
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directives.COOKIES));
|
||||
|
@ -268,7 +291,8 @@ http
|
|||
.logout((logout) -> logout.addLogoutHandler(clearSiteData))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directives.COOKIES))
|
||||
|
@ -278,7 +302,7 @@ http {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[customizing-logout-success]]
|
||||
== Customizing Logout Success
|
||||
|
@ -290,15 +314,18 @@ For example, instead of redirecting, you may want to only return a status code.
|
|||
In this case, you can provide a success handler instance, like so:
|
||||
|
||||
.Using Clear-Site-Data to Clear Cookies
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
.logout((logout) -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -308,7 +335,8 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<bean name="mySuccessHandlerBean" class="org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler"/>
|
||||
|
@ -316,7 +344,7 @@ http {
|
|||
<logout success-handler-ref="mySuccessHandlerBean"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
Since {security-api-url}org/springframework/security/web/authentication/logout/LogoutSuccessHandler.html[`LogoutSuccessHandler`] is a functional interface, you can provide a custom one as a lambda.
|
||||
|
@ -332,8 +360,10 @@ In fact, it is often simpler to <<add-logout-handler, register a custom `LogoutH
|
|||
That said, if you find yourself in a circumstance where a custom logout endpoint is needed, like the following one:
|
||||
|
||||
.Custom Logout Endpoint
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PostMapping("/my/logout")
|
||||
|
@ -343,7 +373,8 @@ public String performLogout() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PostMapping("/my/logout")
|
||||
|
@ -352,14 +383,16 @@ fun performLogout(): String {
|
|||
return "redirect:/home"
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
then you will need to have that endpoint invoke Spring Security's {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[`SecurityContextLogoutHandler`] to ensure a secure and complete logout.
|
||||
Something like the following is needed at a minimum:
|
||||
|
||||
.Custom Logout Endpoint
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
|
||||
|
@ -372,7 +405,8 @@ public String performLogout(Authentication authentication, HttpServletRequest re
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val logoutHandler = SecurityContextLogoutHandler()
|
||||
|
@ -384,7 +418,7 @@ fun performLogout(val authentication: Authentication, val request: HttpServletRe
|
|||
return "redirect:/home"
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Such will clear out the {security-api-url}/org/springframework/security/core/context/SecurityContextHolderStrategy.html[`SecurityContextHolderStrategy`] and {security-api-url}/org/springframework/security/web/context/SecurityContextRepository.html[`SecurityContextRepository`] as needed.
|
||||
|
||||
|
|
|
@ -60,9 +60,11 @@ However, as soon as any servlet based configuration is provided, HTTP Basic must
|
|||
The following example shows a minimal, explicit configuration:
|
||||
|
||||
.Explicit HTTP Basic Configuration
|
||||
====
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
|
@ -73,8 +75,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
.XML
|
||||
----
|
||||
<http>
|
||||
<!-- ... -->
|
||||
|
@ -82,8 +85,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
</http>
|
||||
----
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
|
@ -94,4 +98,4 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
return http.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -25,22 +25,22 @@ This is a value the server generates.
|
|||
Spring Security's nonce adopts the following format:
|
||||
|
||||
.Digest Syntax
|
||||
====
|
||||
[source,txt]
|
||||
----
|
||||
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
|
||||
expirationTime: The date and time when the nonce expires, expressed in milliseconds
|
||||
key: A private key to prevent modification of the nonce token
|
||||
----
|
||||
====
|
||||
|
||||
You need to ensure that you xref:features/authentication/password-storage.adoc#authentication-password-storage-configuration[configure] insecure plain text xref:features/authentication/password-storage.adoc#authentication-password-storage[Password Storage] using `NoOpPasswordEncoder`.
|
||||
(See the {security-api-url}org/springframework/security/crypto/password/NoOpPasswordEncoder.html[`NoOpPasswordEncoder`] class in the Javadoc.)
|
||||
The following provides an example of configuring Digest Authentication with Java Configuration:
|
||||
|
||||
.Digest Authentication
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Autowired
|
||||
|
@ -68,7 +68,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<b:bean id="digestFilter"
|
||||
|
@ -88,4 +89,4 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
<custom-filter ref="userFilter" position="DIGEST_AUTH_FILTER"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -67,8 +67,10 @@ However, as soon as any servlet-based configuration is provided, form based logi
|
|||
The following example shows a minimal, explicit Java configuration:
|
||||
|
||||
.Form Login
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
|
@ -78,7 +80,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -87,7 +90,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
|
@ -97,7 +101,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In the preceding configuration, Spring Security renders a default login page.
|
||||
Most production applications require a custom login form.
|
||||
|
@ -106,8 +110,10 @@ Most production applications require a custom login form.
|
|||
The following configuration demonstrates how to provide a custom login form.
|
||||
|
||||
.Custom Login Form Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
|
@ -120,7 +126,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -130,7 +137,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
|
@ -143,16 +151,14 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-authentication-form-custom-html]]
|
||||
When the login page is specified in the Spring Security configuration, you are responsible for rendering the page.
|
||||
// FIXME: default login page rendered by Spring Security
|
||||
The following https://www.thymeleaf.org/[Thymeleaf] template produces an HTML login form that complies with a login page of `/login`.:
|
||||
|
||||
.Login Form
|
||||
====
|
||||
.src/main/resources/templates/login.html
|
||||
.Login Form - src/main/resources/templates/login.html
|
||||
[source,xml]
|
||||
----
|
||||
<!DOCTYPE html>
|
||||
|
@ -178,7 +184,6 @@ The following https://www.thymeleaf.org/[Thymeleaf] template produces an HTML lo
|
|||
</body>
|
||||
</html>
|
||||
----
|
||||
====
|
||||
|
||||
There are a few key points about the default HTML form:
|
||||
|
||||
|
@ -197,8 +202,10 @@ If you use Spring MVC, you need a controller that maps `GET /login` to the login
|
|||
The following example shows a minimal `LoginController`:
|
||||
|
||||
.LoginController
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -210,7 +217,8 @@ class LoginController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -221,4 +229,4 @@ class LoginController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -8,8 +8,10 @@ Spring Security's `InMemoryUserDetailsManager` implements xref:servlet/authentic
|
|||
In the following sample, we use xref:features/authentication/password-storage.adoc#authentication-password-storage-boot-cli[Spring Boot CLI] to encode a password value of `password` and get the encoded password of `+{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW+`:
|
||||
|
||||
.InMemoryUserDetailsManager Java Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -28,7 +30,8 @@ public UserDetailsService users() {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
<user-service>
|
||||
|
@ -41,7 +44,8 @@ public UserDetailsService users() {
|
|||
</user-service>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -59,7 +63,7 @@ fun users(): UserDetailsService {
|
|||
return InMemoryUserDetailsManager(user, admin)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The preceding samples store the passwords in a secure format but leave a lot to be desired in terms of a getting started experience.
|
||||
|
||||
|
@ -68,8 +72,10 @@ However, it does not protect against obtaining the password by decompiling the s
|
|||
For this reason, `User.withDefaultPasswordEncoder` should only be used for "`getting started`" and is not intended for production.
|
||||
|
||||
.InMemoryUserDetailsManager with User.withDefaultPasswordEncoder
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -90,7 +96,8 @@ public UserDetailsService users() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -110,13 +117,12 @@ fun users(): UserDetailsService {
|
|||
return InMemoryUserDetailsManager(user, admin)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
There is no simple way to use `User.withDefaultPasswordEncoder` with XML-based configuration.
|
||||
For demos or just getting started, you can choose to prefix the password with `+{noop}+` to indicate xref:features/authentication/password-storage.adoc#authentication-password-storage-dpe-format[no encoding should be used]:
|
||||
|
||||
.<user-service> `+{noop}+` XML Configuration
|
||||
====
|
||||
[source,xml,attrs="-attributes"]
|
||||
----
|
||||
<user-service>
|
||||
|
@ -128,4 +134,3 @@ For demos or just getting started, you can choose to prefix the password with `+
|
|||
authorities="ROLE_USER,ROLE_ADMIN" />
|
||||
</user-service>
|
||||
----
|
||||
====
|
||||
|
|
|
@ -29,7 +29,6 @@ The default schema is also exposed as a classpath resource named `org/springfram
|
|||
====
|
||||
|
||||
.Default User Schema
|
||||
====
|
||||
[source,sql]
|
||||
----
|
||||
create table users(
|
||||
|
@ -45,12 +44,10 @@ create table authorities (
|
|||
);
|
||||
create unique index ix_auth_username on authorities (username,authority);
|
||||
----
|
||||
====
|
||||
|
||||
Oracle is a popular database choice but requires a slightly different schema:
|
||||
|
||||
.Default User Schema for Oracle Databases
|
||||
====
|
||||
[source,sql]
|
||||
----
|
||||
CREATE TABLE USERS (
|
||||
|
@ -67,7 +64,6 @@ CREATE TABLE AUTHORITIES (
|
|||
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_UNIQUE UNIQUE (USERNAME, AUTHORITY);
|
||||
ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_FK1 FOREIGN KEY (USERNAME) REFERENCES USERS (USERNAME) ENABLE;
|
||||
----
|
||||
====
|
||||
|
||||
[[servlet-authentication-jdbc-schema-group]]
|
||||
=== Group Schema
|
||||
|
@ -75,7 +71,6 @@ ALTER TABLE AUTHORITIES ADD CONSTRAINT AUTHORITIES_FK1 FOREIGN KEY (USERNAME) RE
|
|||
If your application uses groups, you need to provide the groups schema:
|
||||
|
||||
.Default Group Schema
|
||||
====
|
||||
[source,sql]
|
||||
----
|
||||
create table groups (
|
||||
|
@ -96,7 +91,6 @@ create table group_members (
|
|||
constraint fk_group_members_group foreign key(group_id) references groups(id)
|
||||
);
|
||||
----
|
||||
====
|
||||
|
||||
[[servlet-authentication-jdbc-datasource]]
|
||||
== Setting up a DataSource
|
||||
|
@ -105,8 +99,10 @@ Before we configure `JdbcUserDetailsManager`, we must create a `DataSource`.
|
|||
In our example, we set up an https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#jdbc-embedded-database-support[embedded DataSource] that is initialized with the <<servlet-authentication-jdbc-schema,default user schema>>.
|
||||
|
||||
.Embedded Data Source
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -118,7 +114,8 @@ DataSource dataSource() {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<jdbc:embedded-database>
|
||||
|
@ -126,7 +123,8 @@ DataSource dataSource() {
|
|||
</jdbc:embedded-database>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -137,7 +135,7 @@ fun dataSource(): DataSource {
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In a production environment, you want to ensure that you set up a connection to an external database.
|
||||
|
||||
|
@ -148,9 +146,11 @@ In this sample, we use xref:features/authentication/password-storage.adoc#authen
|
|||
See the xref:features/authentication/password-storage.adoc#authentication-password-storage[PasswordEncoder] section for more details about how to store passwords.
|
||||
|
||||
.JdbcUserDetailsManager
|
||||
====
|
||||
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -172,7 +172,8 @@ UserDetailsManager users(DataSource dataSource) {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
<jdbc-user-service>
|
||||
|
@ -185,7 +186,8 @@ UserDetailsManager users(DataSource dataSource) {
|
|||
</jdbc-user-service>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -206,4 +208,4 @@ fun users(dataSource: DataSource): UserDetailsManager {
|
|||
return users
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -45,7 +45,6 @@ Spring Security supports using either:
|
|||
In the following samples, we expose `users.ldif` as a classpath resource to initialize the embedded LDAP server with two users, `user` and `admin`, both of which have a password of `password`:
|
||||
|
||||
.users.ldif
|
||||
====
|
||||
[source,ldif]
|
||||
----
|
||||
dn: ou=groups,dc=springframework,dc=org
|
||||
|
@ -91,7 +90,6 @@ objectclass: groupOfNames
|
|||
cn: admin
|
||||
uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
|
||||
----
|
||||
====
|
||||
|
||||
[[servlet-authentication-ldap-unboundid]]
|
||||
=== Embedded UnboundID Server
|
||||
|
@ -99,8 +97,10 @@ uniqueMember: uid=admin,ou=people,dc=springframework,dc=org
|
|||
If you wish to use https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundID], specify the following dependencies:
|
||||
|
||||
.UnboundID Dependencies
|
||||
====
|
||||
.Maven
|
||||
[tabs]
|
||||
======
|
||||
Maven::
|
||||
+
|
||||
[source,xml,role="primary",subs="verbatim,attributes"]
|
||||
----
|
||||
<dependency>
|
||||
|
@ -111,21 +111,24 @@ If you wish to use https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundID], spe
|
|||
</dependency>
|
||||
----
|
||||
|
||||
.Gradle
|
||||
Gradle::
|
||||
+
|
||||
[source,groovy,role="secondary",subs="verbatim,attributes"]
|
||||
----
|
||||
depenendencies {
|
||||
runtimeOnly "com.unboundid:unboundid-ldapsdk:{unboundid-ldapsdk-version}"
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can then configure the Embedded LDAP Server using an `EmbeddedLdapServerContextSourceFactoryBean`.
|
||||
This will instruct Spring Security to start an in-memory LDAP server:
|
||||
|
||||
.Embedded LDAP Server Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -134,7 +137,8 @@ public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -142,14 +146,16 @@ fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
|
|||
return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Alternatively, you can manually configure the Embedded LDAP Server.
|
||||
If you choose this approach, you will be responsible for managing the lifecycle of the Embedded LDAP Server.
|
||||
|
||||
.Explicit Embedded LDAP Server Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -159,7 +165,8 @@ UnboundIdContainer ldapContainer() {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<b:bean class="org.springframework.security.ldap.server.UnboundIdContainer"
|
||||
|
@ -167,7 +174,8 @@ UnboundIdContainer ldapContainer() {
|
|||
c:ldif="classpath:users.ldif"/>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -175,7 +183,7 @@ fun ldapContainer(): UnboundIdContainer {
|
|||
return UnboundIdContainer("dc=springframework,dc=org","classpath:users.ldif")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-authentication-ldap-apacheds]]
|
||||
=== Embedded ApacheDS Server
|
||||
|
@ -190,8 +198,10 @@ Once a stable release of ApacheDS 2.x is available, we will consider updating.
|
|||
If you wish to use https://directory.apache.org/apacheds/[Apache DS], specify the following dependencies:
|
||||
|
||||
.ApacheDS Dependencies
|
||||
====
|
||||
.Maven
|
||||
[tabs]
|
||||
======
|
||||
Maven::
|
||||
+
|
||||
[source,xml,role="primary",subs="+attributes"]
|
||||
----
|
||||
<dependency>
|
||||
|
@ -208,7 +218,8 @@ If you wish to use https://directory.apache.org/apacheds/[Apache DS], specify th
|
|||
</dependency>
|
||||
----
|
||||
|
||||
.Gradle
|
||||
Gradle::
|
||||
+
|
||||
[source,groovy,role="secondary",subs="+attributes"]
|
||||
----
|
||||
depenendencies {
|
||||
|
@ -216,13 +227,15 @@ depenendencies {
|
|||
runtimeOnly "org.apache.directory.server:apacheds-server-jndi:{apacheds-core-version}"
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can then configure the Embedded LDAP Server:
|
||||
|
||||
.Embedded LDAP Server Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -232,7 +245,8 @@ ApacheDSContainer ldapContainer() {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<b:bean class="org.springframework.security.ldap.server.ApacheDSContainer"
|
||||
|
@ -240,7 +254,8 @@ ApacheDSContainer ldapContainer() {
|
|||
c:ldif="classpath:users.ldif"/>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -248,7 +263,7 @@ fun ldapContainer(): ApacheDSContainer {
|
|||
return ApacheDSContainer("dc=springframework,dc=org", "classpath:users.ldif")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-authentication-ldap-contextsource]]
|
||||
== LDAP ContextSource
|
||||
|
@ -258,8 +273,10 @@ To do so, create an LDAP `ContextSource` (which is the equivalent of a JDBC `Dat
|
|||
If you have already configured an `EmbeddedLdapServerContextSourceFactoryBean`, Spring Security will create an LDAP `ContextSource` that points to the embedded LDAP server.
|
||||
|
||||
.LDAP Context Source with Embedded LDAP Server
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -271,7 +288,8 @@ public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -281,13 +299,15 @@ fun contextSourceFactoryBean(): EmbeddedLdapServerContextSourceFactoryBean {
|
|||
return contextSourceFactoryBean
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Alternatively, you can explicitly configure the LDAP `ContextSource` to connect to the supplied LDAP server:
|
||||
|
||||
.LDAP Context Source
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
ContextSource contextSource(UnboundIdContainer container) {
|
||||
|
@ -295,21 +315,23 @@ ContextSource contextSource(UnboundIdContainer container) {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<ldap-server
|
||||
url="ldap://localhost:53389/dc=springframework,dc=org" />
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
fun contextSource(container: UnboundIdContainer): ContextSource {
|
||||
return DefaultSpringSecurityContextSource("ldap://localhost:53389/dc=springframework,dc=org")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-authentication-ldap-authentication]]
|
||||
== Authentication
|
||||
|
@ -337,8 +359,10 @@ The advantage to using bind authentication is that the user's secrets (the passw
|
|||
The following example shows bind authentication configuration:
|
||||
|
||||
.Bind Authentication
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -349,14 +373,16 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
<ldap-authentication-provider
|
||||
user-dn-pattern="uid={0},ou=people"/>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -366,15 +392,17 @@ fun authenticationManager(contextSource: BaseLdapPathContextSource): Authenticat
|
|||
return factory.createAuthenticationManager()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The preceding simple example would obtain the DN for the user by substituting the user login name in the supplied pattern and attempting to bind as that user with the login password.
|
||||
This is OK if all your users are stored under a single node in the directory.
|
||||
If, instead, you wish to configure an LDAP search filter to locate the user, you could use the following:
|
||||
|
||||
.Bind Authentication with Search Filter
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -386,7 +414,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
<ldap-authentication-provider
|
||||
|
@ -394,7 +423,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou
|
|||
user-search-base="ou=people"/>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -405,7 +435,7 @@ fun authenticationManager(contextSource: BaseLdapPathContextSource): Authenticat
|
|||
return factory.createAuthenticationManager()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If used with the `ContextSource` <<servlet-authentication-ldap-contextsource,definition shown earlier>>, this would perform a search under the DN `ou=people,dc=springframework,dc=org` by using `+(uid={0})+` as a filter.
|
||||
Again, the user login name is substituted for the parameter in the filter name, so it searches for an entry with the `uid` attribute equal to the user name.
|
||||
|
@ -419,8 +449,10 @@ This can either be done by retrieving the value of the password attribute and ch
|
|||
An LDAP compare cannot be done when the password is properly hashed with a random salt.
|
||||
|
||||
.Minimal Password Compare Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -432,7 +464,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
<ldap-authentication-provider
|
||||
|
@ -441,7 +474,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou
|
|||
</ldap-authentication-provider>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -453,13 +487,15 @@ fun authenticationManager(contextSource: BaseLdapPathContextSource?): Authentica
|
|||
return factory.createAuthenticationManager()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following example shows a more advanced configuration with some customizations:
|
||||
|
||||
.Password Compare Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -472,7 +508,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
<ldap-authentication-provider
|
||||
|
@ -485,7 +522,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou
|
|||
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -498,7 +536,7 @@ fun authenticationManager(contextSource: BaseLdapPathContextSource): Authenticat
|
|||
return factory.createAuthenticationManager()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
<1> Specify the password attribute as `pwd`.
|
||||
|
||||
|
@ -508,8 +546,10 @@ Spring Security's `LdapAuthoritiesPopulator` is used to determine what authoriti
|
|||
The following example shows how configure `LdapAuthoritiesPopulator`:
|
||||
|
||||
.LdapAuthoritiesPopulator Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -530,7 +570,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
<ldap-authentication-provider
|
||||
|
@ -538,7 +579,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou
|
|||
group-search-filter="member={0}"/>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -559,7 +601,7 @@ fun authenticationManager(
|
|||
return factory.createAuthenticationManager()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
== Active Directory
|
||||
|
||||
|
@ -579,8 +621,10 @@ This is not currently supported, but hopefully will be in a future version.
|
|||
The following example configures Active Directory:
|
||||
|
||||
.Example Active Directory Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -589,7 +633,8 @@ ActiveDirectoryLdapAuthenticationProvider authenticationProvider() {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<bean id="authenticationProvider"
|
||||
|
@ -599,7 +644,8 @@ ActiveDirectoryLdapAuthenticationProvider authenticationProvider() {
|
|||
</bean>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -607,4 +653,4 @@ fun authenticationProvider(): ActiveDirectoryLdapAuthenticationProvider {
|
|||
return ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -13,8 +13,10 @@ This is only used if the `AuthenticationManagerBuilder` has not been populated a
|
|||
====
|
||||
|
||||
.Custom UserDetailsService Bean
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -23,18 +25,20 @@ CustomUserDetailsService customUserDetailsService() {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
<b:bean class="example.CustomUserDetailsService"/>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun customUserDetailsService() = CustomUserDetailsService()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
// FIXME: Add CustomUserDetails example with links to @AuthenticationPrincipal
|
||||
|
|
|
@ -25,7 +25,6 @@ Location: /login
|
|||
The user submits their username and password.
|
||||
|
||||
.Username and Password Submitted
|
||||
====
|
||||
[source,http]
|
||||
----
|
||||
POST /login HTTP/1.1
|
||||
|
@ -34,31 +33,26 @@ Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
|
|||
|
||||
username=user&password=password&_csrf=35942e65-a172-4cd4-a1d4-d16a51147b3e
|
||||
----
|
||||
====
|
||||
|
||||
Upon authenticating the user, the user is associated to a new session id to prevent xref:servlet/authentication/session-management.adoc#ns-session-fixation[session fixation attacks].
|
||||
|
||||
.Authenticated User is Associated to New Session
|
||||
====
|
||||
[source,http]
|
||||
----
|
||||
HTTP/1.1 302 Found
|
||||
Location: /
|
||||
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=Lax
|
||||
----
|
||||
====
|
||||
|
||||
Subsequent requests include the session cookie which is used to authenticate the user for the remainder of the session.
|
||||
|
||||
.Authenticated Session Provided as Credentials
|
||||
====
|
||||
[source,http]
|
||||
----
|
||||
GET / HTTP/1.1
|
||||
Host: example.com
|
||||
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
[[securitycontextrepository]]
|
||||
|
@ -93,8 +87,10 @@ When the error dispatch is made, there is no `SecurityContext` established.
|
|||
This means that the error page cannot use the `SecurityContext` for authorization or displaying the current user unless the `SecurityContext` is persisted somehow.
|
||||
|
||||
.Use RequestAttributeSecurityContextRepository
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
|
@ -107,7 +103,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http security-context-repository-ref="contextRepository">
|
||||
|
@ -116,7 +113,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
<b:bean name="contextRepository"
|
||||
class="org.springframework.security.web.context.RequestAttributeSecurityContextRepository" />
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[delegatingsecuritycontextrepository]]
|
||||
=== DelegatingSecurityContextRepository
|
||||
|
@ -126,8 +123,10 @@ The {security-api-url}org/springframework/security/web/context/DelegatingSecurit
|
|||
The most useful arrangement for this is configured with the following example, which allows the use of both xref:requestattributesecuritycontextrepository[`RequestAttributeSecurityContextRepository`] and xref:httpsecuritycontextrepository[`HttpSessionSecurityContextRepository`] simultaneously.
|
||||
|
||||
.Configure DelegatingSecurityContextRepository
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -144,7 +143,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -162,7 +162,8 @@ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http security-context-repository-ref="contextRepository">
|
||||
|
@ -178,7 +179,7 @@ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
</constructor-arg>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
|
|
@ -26,8 +26,10 @@ This class checks the current contents of the security context and, if it is emp
|
|||
Subclasses override the following methods to obtain this information.
|
||||
|
||||
.Override AbstractPreAuthenticatedProcessingFilter
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);
|
||||
|
@ -35,14 +37,15 @@ protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest reques
|
|||
protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
protected abstract fun getPreAuthenticatedPrincipal(request: HttpServletRequest): Any?
|
||||
|
||||
protected abstract fun getPreAuthenticatedCredentials(request: HttpServletRequest): Any?
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
After calling these, the filter creates a `PreAuthenticatedAuthenticationToken` that contains the returned data and submits it for authentication.
|
||||
|
@ -70,14 +73,12 @@ The pre-authenticated provider has little more to do than load the `UserDetails`
|
|||
It does this by delegating to an `AuthenticationUserDetailsService`.
|
||||
The latter is similar to the standard `UserDetailsService` but takes an `Authentication` object rather than just user name:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface AuthenticationUserDetailsService {
|
||||
UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
This interface may also have other uses, but, with pre-authentication, it allows access to the authorities that were packaged in the `Authentication` object, as we saw in the previous section.
|
||||
The `PreAuthenticatedGrantedAuthoritiesUserDetailsService` class does this.
|
||||
|
@ -111,7 +112,6 @@ If an attacker is able to forge the headers in their original request without th
|
|||
==== Siteminder Example Configuration
|
||||
The following example shows a typical configuration that uses this filter:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<security:http>
|
||||
|
@ -137,7 +137,6 @@ The following example shows a typical configuration that uses this filter:
|
|||
<security:authentication-provider ref="preauthAuthProvider" />
|
||||
</security:authentication-manager>
|
||||
----
|
||||
====
|
||||
|
||||
We've assumed here that the xref:servlet/configuration/xml-namespace.adoc#ns-config[security namespace] is being used for configuration.
|
||||
It's also assumed that you have added a `UserDetailsService` (called "userDetailsService") to your configuration to load the user's roles.
|
||||
|
|
|
@ -16,7 +16,6 @@ If you use an authentication provider that does not use a `UserDetailsService` (
|
|||
This approach uses hashing to achieve a useful remember-me strategy.
|
||||
In essence, a cookie is sent to the browser upon successful interactive authentication, with the cookie being composed as follows:
|
||||
|
||||
====
|
||||
[source,txt]
|
||||
----
|
||||
base64(username + ":" + expirationTime + ":" + algorithmName + ":"
|
||||
|
@ -28,7 +27,6 @@ expirationTime: The date and time when the remember-me token expires, express
|
|||
key: A private key to prevent modification of the remember-me token
|
||||
algorithmName: The algorithm used to generate and to verify the remember-me token signature
|
||||
----
|
||||
====
|
||||
|
||||
The remember-me token is valid only for the period specified and only if the username, password, and key do not change.
|
||||
Notably, this has a potential security issue, in that a captured remember-me token is usable from any user agent until such time as the token expires.
|
||||
|
@ -39,7 +37,6 @@ Alternatively, remember-me services should not be used at all.
|
|||
|
||||
If you are familiar with the topics discussed in the chapter on xref:servlet/configuration/xml-namespace.adoc#ns-config[namespace configuration], you can enable remember-me authentication by adding the `<remember-me>` element:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -47,7 +44,6 @@ If you are familiar with the topics discussed in the chapter on xref:servlet/con
|
|||
<remember-me key="myAppKey"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
The `UserDetailsService` is normally selected automatically.
|
||||
If you have more than one in your application context, you need to specify which one should be used with the `user-service-ref` attribute, where the value is the name of your `UserDetailsService` bean.
|
||||
|
@ -58,7 +54,6 @@ This approach is based on the article titled http://jaspan.com/improved_persiste
|
|||
There is a discussion on this in the comments section of this article.)
|
||||
To use the this approach with namespace configuration, supply a datasource reference:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -66,11 +61,9 @@ To use the this approach with namespace configuration, supply a datasource refer
|
|||
<remember-me data-source-ref="someDataSource"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
The database should contain a `persistent_logins` table, created by using the following SQL (or equivalent):
|
||||
|
||||
====
|
||||
[source,ddl]
|
||||
----
|
||||
create table persistent_logins (username varchar(64) not null,
|
||||
|
@ -78,7 +71,6 @@ create table persistent_logins (username varchar(64) not null,
|
|||
token varchar(64) not null,
|
||||
last_used timestamp not null)
|
||||
----
|
||||
====
|
||||
|
||||
[[remember-me-impls]]
|
||||
== Remember-Me Interfaces and Implementations
|
||||
|
@ -87,7 +79,6 @@ It is also used within `BasicAuthenticationFilter`.
|
|||
The hooks invoke a concrete `RememberMeServices` at the appropriate times.
|
||||
The following listing shows the interface:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);
|
||||
|
@ -97,7 +88,6 @@ void loginFail(HttpServletRequest request, HttpServletResponse response);
|
|||
void loginSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication successfulAuthentication);
|
||||
----
|
||||
====
|
||||
|
||||
See the Javadoc for {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] for a fuller discussion on what the methods do, although note that, at this stage, `AbstractAuthenticationProcessingFilter` calls only the `loginFail()` and `loginSuccess()` methods.
|
||||
The `autoLogin()` method is called by `RememberMeAuthenticationFilter` whenever the `SecurityContextHolder` does not contain an `Authentication`.
|
||||
|
@ -120,8 +110,10 @@ If no `algorithmName` is present, the default matching algorithm will be used, w
|
|||
You can specify different algorithms for signature encoding and for signature matching, this allows users to safely upgrade to a different encoding algorithm while still able to verify old ones if there is no `algorithmName` present.
|
||||
To do that you can specify your customized `TokenBasedRememberMeServices` as a Bean and use it in the configuration.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -144,7 +136,9 @@ RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
|
|||
return rememberMe;
|
||||
}
|
||||
----
|
||||
.XML
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -159,11 +153,10 @@ RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
|
|||
<property name="encodingAlgorithm" value="SHA256"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following beans are required in an application context to enable remember-me services:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="rememberMeFilter" class=
|
||||
|
@ -183,7 +176,6 @@ The following beans are required in an application context to enable remember-me
|
|||
<property name="key" value="springRocks"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
Remember to add your `RememberMeServices` implementation to your `UsernamePasswordAuthenticationFilter.setRememberMeServices()` property, include the `RememberMeAuthenticationProvider` in your `AuthenticationManager.setProviders()` list, and add `RememberMeAuthenticationFilter` into your `FilterChainProxy` (typically immediately after your `UsernamePasswordAuthenticationFilter`).
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ Because Spring Security provides a number of helper classes that automatically c
|
|||
== Configuration
|
||||
Spring Security provices a `RunAsManager` interface:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Authentication buildRunAs(Authentication authentication, Object object,
|
||||
|
@ -24,7 +23,6 @@ boolean supports(ConfigAttribute attribute);
|
|||
|
||||
boolean supports(Class clazz);
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
The first method returns the `Authentication` object that should replace the existing `Authentication` object for the duration of the method invocation.
|
||||
|
@ -46,7 +44,6 @@ It accepts as valid any `RunAsUserToken` presented.
|
|||
To ensure malicious code does not create a `RunAsUserToken` and present it for guaranteed acceptance by the `RunAsImplAuthenticationProvider`, the hash of a key is stored in all generated tokens.
|
||||
The `RunAsManagerImpl` and `RunAsImplAuthenticationProvider` is created in the bean context with the same key:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="runAsManager"
|
||||
|
@ -59,7 +56,6 @@ The `RunAsManagerImpl` and `RunAsImplAuthenticationProvider` is created in the b
|
|||
<property name="key" value="my_run_as_password"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
By using the same key, each `RunAsUserToken` can be validated because it was created by an approved `RunAsManagerImpl`.
|
||||
The `RunAsUserToken` is immutable after creation, for security reasons.
|
||||
|
|
|
@ -87,8 +87,10 @@ First, you need to create an implementation of `SecurityContextRepository` or us
|
|||
|
||||
[[customizing-the-securitycontextrepository]]
|
||||
.Customizing the `SecurityContextRepository`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -103,7 +105,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -119,7 +122,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http security-context-repository-ref="repo">
|
||||
|
@ -127,7 +131,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
</http>
|
||||
<bean name="repo" class="com.example.MyCustomSecurityContextRepository" />
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -144,8 +148,10 @@ In some cases, for example, you might be authenticating a user manually instead
|
|||
You can use a custom filters or a {spring-framework-reference-url}/web.html#mvc-controller[Spring MVC controller] endpoint to do that.
|
||||
If you want to save the authentication between requests, in the `HttpSession`, for example, you have to do so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
private SecurityContextRepository securityContextRepository =
|
||||
|
@ -170,7 +176,7 @@ class LoginRequest {
|
|||
// getters and setters
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
<1> Add the `SecurityContextRepository` to the controller
|
||||
<2> Inject the `HttpServletRequest` and `HttpServletResponse` to be able to save the `SecurityContext`
|
||||
|
@ -196,8 +202,10 @@ Some authentication mechanisms like xref:servlet/authentication/passwords/basic.
|
|||
|
||||
If you do not wish to create sessions, you can use `SessionCreationPolicy.STATELESS`, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -211,7 +219,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -226,19 +235,20 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http create-session="stateless">
|
||||
<!-- ... -->
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The above configuration is <<customizing-where-authentication-is-stored, configuring the `SecurityContextRepository`>> to use a `NullSecurityContextRepository` and is also xref:servlet/architecture.adoc#requestcache-prevent-saved-request[preventing the request from being saved in the session].
|
||||
|
||||
[[never-policy-session-still-created]]
|
||||
|
||||
[[never-policy-session-still-created]]
|
||||
If you are using `SessionCreationPolicy.NEVER`, you might notice that the application is still creating a `HttpSession`.
|
||||
In most cases, this happens because the xref:servlet/architecture.adoc#savedrequests[request is saved in the session] for the authenticated resource to re-request after authentication is successful.
|
||||
To avoid that, please refer to xref:servlet/architecture.adoc#requestcache-prevent-saved-request[how to prevent the request of being saved] section.
|
||||
|
@ -252,8 +262,10 @@ If, for some reason, you are using a stateless authentication mechanism, but you
|
|||
For the xref:servlet/authentication/passwords/basic.adoc[HTTP Basic], you can add xref:servlet/configuration/java.adoc#post-processing-configured-objects[a `ObjectPostProcessor`] that changes the `SecurityContextRepository` used by the `BasicAuthenticationFilter`:
|
||||
|
||||
.Store HTTP Basic authentication in the `HttpSession`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -273,7 +285,7 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
|
|||
return http.build();
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The above also applies to others authentication mechanisms, like xref:servlet/oauth2/resource-server/index.adoc[Bearer Token Authentication].
|
||||
|
||||
|
@ -302,8 +314,10 @@ In summary, when `requireExplicitSave` is `true`, Spring Security sets up xref:s
|
|||
If you wish to place constraints on a single user's ability to log in to your application, Spring Security supports this out of the box with the following simple additions.
|
||||
First, you need to add the following listener to your configuration to keep Spring Security updated about session lifecycle events:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -312,7 +326,8 @@ public HttpSessionEventPublisher httpSessionEventPublisher() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -321,7 +336,8 @@ open fun httpSessionEventPublisher(): HttpSessionEventPublisher {
|
|||
}
|
||||
----
|
||||
|
||||
.web.xml
|
||||
web.xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<listener>
|
||||
|
@ -330,12 +346,14 @@ open fun httpSessionEventPublisher(): HttpSessionEventPublisher {
|
|||
</listener-class>
|
||||
</listener>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Then add the following lines to your security configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -348,7 +366,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -364,7 +383,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -374,15 +394,17 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
</session-management>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
This will prevent a user from logging in multiple times - a second login will cause the first to be invalidated.
|
||||
|
||||
Using Spring Boot, you can test the above configuration scenario the following way:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
|
@ -412,14 +434,16 @@ public class MaximumSessionsTests {
|
|||
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can try it using the {gh-samples-url}/servlet/spring-boot/java/session-management/maximum-sessions[Maximum Sessions sample].
|
||||
|
||||
It is also common that you would prefer to prevent a second login, in which case you can use:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -433,7 +457,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -450,7 +475,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -459,7 +485,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
</session-management>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
The second login will then be rejected.
|
||||
|
@ -469,8 +495,10 @@ If instead you want to use an error page, you can add the attribute `session-aut
|
|||
|
||||
Using Spring Boot, you can test the above configuration the following way:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
|
@ -501,7 +529,7 @@ public class MaximumSessionsPreventLoginTests {
|
|||
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If you are using a customized authentication filter for form-based login, then you have to configure concurrent session control support explicitly.
|
||||
You can try it using the {gh-samples-url}/servlet/spring-boot/java/session-management/maximum-sessions-prevent-login[Maximum Sessions Prevent Login sample].
|
||||
|
@ -513,8 +541,10 @@ That said, Spring Security can detect when a session has expired and take specif
|
|||
For example, you may want to redirect to a specific endpoint when a user makes a request with an already-expired session.
|
||||
This is achieved through the `invalidSessionUrl` in `HttpSecurity`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -527,7 +557,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -541,7 +572,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -549,7 +581,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
<session-management invalid-session-url="/invalidSession" />
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Note that if you use this mechanism to detect session timeouts, it may falsely report an error if the user logs out and then logs back in without closing the browser.
|
||||
This is because the session cookie is not cleared when you invalidate the session and will be resubmitted even if the user has logged out.
|
||||
|
@ -560,8 +592,10 @@ If that is your case, you might want to <<clearing-session-cookie-on-logout,conf
|
|||
The `invalidSessionUrl` is a convenience method for setting the `InvalidSessionStrategy` using the {security-api-url}/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.html[`SimpleRedirectInvalidSessionStrategy` implementation].
|
||||
If you want to customize the behavior, you can implement the {security-api-url}/org/springframework/security/web/session/InvalidSessionStrategy.html[`InvalidSessionStrategy`] interface and configure it using the `invalidSessionStrategy` method:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -574,7 +608,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -588,7 +623,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -597,15 +633,17 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
<bean name="myCustomInvalidSessionStrategy" class="com.example.MyCustomInvalidSessionStrategy" />
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[clearing-session-cookie-on-logout]]
|
||||
== Clearing Session Cookies on Logout
|
||||
|
||||
You can explicitly delete the JSESSIONID cookie on logging out, for example by using the https://w3c.github.io/webappsec-clear-site-data/[`Clear-Site-Data` header] in the logout handler:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -618,7 +656,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -632,7 +671,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -650,14 +690,16 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
</b:bean>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This has the advantage of being container agnostic and will work with any container that supports the `Clear-Site-Data` header.
|
||||
|
||||
As an alternative, you can also use the following syntax in the logout handler:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -670,7 +712,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -684,14 +727,15 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
<logout delete-cookies="JSESSIONID" />
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Unfortunately, this cannot be guaranteed to work with every servlet container, so you need to test it in your environment.
|
||||
|
||||
|
@ -701,14 +745,12 @@ If you run your application behind a proxy, you may also be able to remove the s
|
|||
For example, by using Apache HTTPD's `mod_headers`, the following directive deletes the `JSESSIONID` cookie by expiring it in the response to a logout request (assuming the application is deployed under the `/tutorial` path):
|
||||
=====
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<LocationMatch "/tutorial/logout">
|
||||
Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT"
|
||||
</LocationMatch>
|
||||
----
|
||||
====
|
||||
|
||||
More details on the xref:servlet/exploits/headers.adoc#servlet-headers-clear-site-data[Clear Site Data] and xref:servlet/authentication/logout.adoc[Logout sections].
|
||||
|
||||
|
@ -737,8 +779,10 @@ This is the default in Servlet 3.0 or older containers.
|
|||
|
||||
You can configure the session fixation protection by doing:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -753,7 +797,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -769,14 +814,15 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
<session-management session-fixation-protection="newSession" />
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
When session fixation protection occurs, it results in a `SessionFixationProtectionEvent` being published in the application context.
|
||||
If you use `changeSessionId`, this protection will __also__ result in any ``jakarta.servlet.http.HttpSessionIdListener``s being notified, so use caution if your code listens for both events.
|
||||
|
@ -790,8 +836,10 @@ You can also set the session fixation protection to `none` to disable it, but th
|
|||
|
||||
Consider the following block of code:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
|
||||
|
@ -802,7 +850,7 @@ SecurityContext context = SecurityContextHolder.createEmptyContext(); <1>
|
|||
context.setAuthentication(authentication); <2>
|
||||
SecurityContextHolder.setContext(context); <3>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
1. Creates an empty `SecurityContext` instance by accessing the `SecurityContextHolder` statically.
|
||||
2. Sets the `Authentication` object in the `SecurityContext` instance.
|
||||
|
@ -817,8 +865,10 @@ By default, they will still look up the strategy from `SecurityContextHolder`.
|
|||
These changes are largely internal, but they present the opportunity for applications to autowire the `SecurityContextHolderStrategy` instead of accessing the `SecurityContext` statically.
|
||||
To do so, you should change the code to the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class SomeClass {
|
||||
|
@ -837,7 +887,7 @@ public class SomeClass {
|
|||
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
1. Creates an empty `SecurityContext` instance using the configured `SecurityContextHolderStrategy`.
|
||||
2. Sets the `Authentication` object in the `SecurityContext` instance.
|
||||
|
@ -850,8 +900,10 @@ public class SomeClass {
|
|||
At times, it can be valuable to eagerly create sessions.
|
||||
This can be done by using the {security-api-url}org/springframework/security/web/session/ForceEagerSessionCreationFilter.html[`ForceEagerSessionCreationFilter`] which can be configured using:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -864,7 +916,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -878,14 +931,15 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http create-session="ALWAYS">
|
||||
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ You should get this working before trying it out with Spring Security.
|
|||
Enabling X.509 client authentication is very straightforward.
|
||||
To do so, add the `<x509/>` element to your http security namespace configuration:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -29,7 +28,6 @@ To do so, add the `<x509/>` element to your http security namespace configuratio
|
|||
<x509 subject-principal-regex="CN=(.*?)," user-service-ref="userService"/>;
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
The element has two optional attributes:
|
||||
|
||||
|
@ -60,7 +58,6 @@ You can install these in your browser to enable SSL client authentication.
|
|||
|
||||
To run tomcat with SSL support, drop the `server.jks` file into the tomcat `conf` directory and add the following connector to the `server.xml` file:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" scheme="https" secure="true"
|
||||
|
@ -71,7 +68,6 @@ To run tomcat with SSL support, drop the `server.jks` file into the tomcat `conf
|
|||
truststoreType="JKS" truststorePass="password"
|
||||
/>
|
||||
----
|
||||
====
|
||||
|
||||
`clientAuth` can also be set to `want` if you still want SSL connections to succeed even if the client does not provide a certificate.
|
||||
Clients that do not present a certificate cannot access any objects secured by Spring Security unless you use a non-X.509 authentication mechanism, such as form authentication.
|
||||
|
|
|
@ -143,8 +143,10 @@ We do not intend to support non-long identifiers in Spring Security's ACL module
|
|||
|
||||
The following fragment of code shows how to create an `Acl` or modify an existing `Acl`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Prepare the information we'd like in our access control entry (ACE)
|
||||
|
@ -165,7 +167,8 @@ acl.insertAce(acl.getEntries().length, p, sid, true);
|
|||
aclService.updateAcl(acl);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val oi: ObjectIdentity = ObjectIdentityImpl(Foo::class.java, 44)
|
||||
|
@ -184,7 +187,7 @@ aclService.createAcl(oi)
|
|||
acl!!.insertAce(acl.entries.size, p, sid, true)
|
||||
aclService.updateAcl(acl)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In the preceding example, we retrieve the ACL associated with the `Foo` domain object with identifier number 44.
|
||||
We then add an ACE so that a principal named "`Samantha`" can "`administer`" the object.
|
||||
|
|
|
@ -14,14 +14,12 @@ The `GrantedAuthority` objects are inserted into the `Authentication` object by
|
|||
|
||||
The `GrantedAuthority` interface has only one method:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
|
||||
String getAuthority();
|
||||
|
||||
----
|
||||
====
|
||||
|
||||
This method is used by an
|
||||
`AuthorizationManager` instance to obtain a precise `String` representation of the `GrantedAuthority`.
|
||||
|
@ -46,8 +44,10 @@ You can customize this with `GrantedAuthorityDefaults`.
|
|||
You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
|
||||
|
||||
.Custom MethodSecurityExpressionHandler
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -56,7 +56,8 @@ static GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
companion object {
|
||||
|
@ -67,14 +68,15 @@ companion object {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
|
||||
<constructor-arg value="MYPREFIX_"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -95,7 +97,6 @@ Applications that customize an `AccessDecisionManager` or `AccessDecisionVoter`
|
|||
``AuthorizationManager``s are called by Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[request-based], xref:servlet/authorization/method-security.adoc[method-based], and xref:servlet/integrations/websocket.adoc[message-based] authorization components and are responsible for making final access control decisions.
|
||||
The `AuthorizationManager` interface contains two methods:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);
|
||||
|
@ -105,7 +106,6 @@ default AuthorizationDecision verify(Supplier<Authentication> authentication, Ob
|
|||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The ``AuthorizationManager``'s `check` method is passed all the relevant information it needs in order to make an authorization decision.
|
||||
In particular, passing the secure `Object` enables those arguments contained in the actual secure object invocation to be inspected.
|
||||
|
@ -167,8 +167,10 @@ In some cases, like migrating an older application, it may be desirable to intro
|
|||
To call an existing `AccessDecisionManager`, you can do:
|
||||
|
||||
.Adapting an AccessDecisionManager
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -194,15 +196,17 @@ public class AccessDecisionManagerAuthorizationManagerAdapter implements Authori
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
And then wire it into your `SecurityFilterChain`.
|
||||
|
||||
Or to only call an `AccessDecisionVoter`, you can do:
|
||||
|
||||
.Adapting an AccessDecisionVoter
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -224,7 +228,7 @@ public class AccessDecisionVoterAuthorizationManagerAdapter implements Authoriza
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
And then wire it into your `SecurityFilterChain`.
|
||||
|
||||
|
@ -241,8 +245,10 @@ An extended version of Spring Security's `RoleVoter`, `RoleHierarchyVoter`, is c
|
|||
A typical configuration might look like this:
|
||||
|
||||
.Hierarchical Roles Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -262,7 +268,8 @@ static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHiera
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
<bean id="roleHierarchy"
|
||||
|
@ -282,7 +289,7 @@ static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHiera
|
|||
<property ref="roleHierarchy"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`RoleHierarchy` bean configuration is not yet ported over to `@EnableMethodSecurity`.
|
||||
|
@ -344,7 +351,6 @@ The `AccessDecisionManager` then decides whether or not to throw an `AccessDenie
|
|||
|
||||
The `AccessDecisionVoter` interface has three methods:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);
|
||||
|
@ -353,7 +359,6 @@ boolean supports(ConfigAttribute attribute);
|
|||
|
||||
boolean supports(Class clazz);
|
||||
----
|
||||
====
|
||||
|
||||
Concrete implementations return an `int`, with possible values being reflected in the `AccessDecisionVoter` static fields named `ACCESS_ABSTAIN`, `ACCESS_DENIED` and `ACCESS_GRANTED`.
|
||||
A voting implementation returns `ACCESS_ABSTAIN` if it has no opinion on an authorization decision.
|
||||
|
|
|
@ -12,8 +12,10 @@ That said, any time you use xref:servlet/configuration/java.adoc#jc-httpsecurity
|
|||
Whenever you have an `HttpSecurity` instance, you should at least do:
|
||||
|
||||
.Use authorizeHttpRequests
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
|
@ -22,7 +24,8 @@ http
|
|||
)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -32,14 +35,15 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This tells Spring Security that any endpoint in your application requires that the security context at a minimum be authenticated in order to allow it.
|
||||
|
||||
|
@ -86,8 +90,10 @@ This means that the `REQUEST` dispatch needs authorization, but also ``FORWARD``
|
|||
For example, {spring-framework-reference-url}web.html#spring-web[Spring MVC] can `FORWARD` the request to a view resolver that renders a Thymeleaf template, like so:
|
||||
|
||||
.Sample Forwarding Spring MVC Controller
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -99,7 +105,8 @@ public class MyController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -110,7 +117,7 @@ class MyController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In this case, authorization happens twice; once for authorizing `/endpoint` and once for forwarding to Thymeleaf to render the "endpoint" template.
|
||||
|
||||
|
@ -120,8 +127,10 @@ Another example of this principle is {spring-boot-reference-url}web.html#web.ser
|
|||
If the container catches an exception, say like the following:
|
||||
|
||||
.Sample Erroring Spring MVC Controller
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -133,7 +142,8 @@ public class MyController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -144,7 +154,7 @@ class MyController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
then Boot will dispatch it to the `ERROR` dispatch.
|
||||
|
||||
|
@ -167,8 +177,10 @@ You can configure Spring Security to have different rules by adding more rules i
|
|||
If you want to require that `/endpoint` only be accessible by end users with the `USER` authority, then you can do:
|
||||
|
||||
.Authorize an Endpoint
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -184,7 +196,8 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -199,7 +212,8 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -207,7 +221,7 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
|
|||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
As you can see, the declaration can be broken up in to pattern/rule pairs.
|
||||
|
||||
|
@ -220,8 +234,10 @@ Spring Security supports several patterns and several rules; you can also progra
|
|||
Once authorized, you can test it using xref:servlet/test/method.adoc#test-method-withmockuser[Security's test support] in the following way:
|
||||
|
||||
.Test Endpoint Authorization
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@WithMockUser(authorities="USER")
|
||||
|
@ -244,7 +260,7 @@ void anyWhenUnauthenticatedThenUnauthorized() {
|
|||
.andExpect(status().isUnauthorized())
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[match-requests]]
|
||||
== Matching Requests
|
||||
|
@ -267,8 +283,10 @@ Let's say that you instead of wanting to match the `/endpoint` endpoint, you wan
|
|||
In that case, you can do something like the following:
|
||||
|
||||
.Match with Ant
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
|
@ -278,7 +296,8 @@ http
|
|||
)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -289,7 +308,8 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -297,15 +317,17 @@ http {
|
|||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The way to read this is "if the request is `/resource` or some subdirectory, require the `USER` authority; otherwise, only require authentication"
|
||||
|
||||
You can also extract path values from the request, as seen below:
|
||||
|
||||
.Authorize and Extract
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
|
@ -315,7 +337,8 @@ http
|
|||
)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -326,7 +349,8 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -334,13 +358,15 @@ http {
|
|||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Once authorized, you can test it using xref:servlet/test/method.adoc#test-method-withmockuser[Security's test support] in the following way:
|
||||
|
||||
.Test Directory Authorization
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@WithMockUser(authorities="USER")
|
||||
|
@ -363,7 +389,7 @@ void anyWhenUnauthenticatedThenUnauthorized() {
|
|||
.andExpect(status().isUnauthorized())
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Spring Security only matches paths.
|
||||
|
@ -378,8 +404,10 @@ For example, consider a path that contains the username and the rule that all us
|
|||
You can use {security-api-url}org/springframework/security/web/util/matcher/RegexRequestMatcher.html[`RegexRequestMatcher`] to respect this rule, like so:
|
||||
|
||||
.Match with Regex
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
|
@ -389,7 +417,8 @@ http
|
|||
)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -400,7 +429,8 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -408,7 +438,7 @@ http {
|
|||
<intercept-url pattern="/**" access="denyAll"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[match-by-httpmethod]]
|
||||
=== Matching By Http Method
|
||||
|
@ -419,8 +449,10 @@ One place where this is handy is when authorizing by permissions granted, like b
|
|||
To require all ``GET``s to have the `read` permission and all ``POST``s to have the `write` permission, you can do something like this:
|
||||
|
||||
.Match by HTTP Method
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
|
@ -431,7 +463,8 @@ http
|
|||
)
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -443,7 +476,8 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -452,7 +486,7 @@ http {
|
|||
<intercept-url pattern="/**" access="denyAll"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
These authorization rules should read as: "if the request is a GET, then require `read` permission; else, if the request is a POST, then require `write` permission; else, deny the request"
|
||||
|
||||
|
@ -462,8 +496,10 @@ Denying the request by default is a healthy security practice since it turns the
|
|||
Once authorized, you can test it using xref:servlet/test/method.adoc#test-method-withmockuser[Security's test support] in the following way:
|
||||
|
||||
.Test Http Method Authorization
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@WithMockUser(authorities="read")
|
||||
|
@ -494,7 +530,7 @@ void postWhenNoWriteAuthorityThenForbidden() {
|
|||
.andExpect(status().isForbidden());
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[match-by-dispatcher-type]]
|
||||
=== Matching By Dispatcher Type
|
||||
|
@ -575,8 +611,10 @@ However, if you want to extract values from the request, you will need to have a
|
|||
Once authorized, you can test it using xref:servlet/test/method.adoc#test-method-withmockuser[Security's test support] in the following way:
|
||||
|
||||
.Test Custom Authorization
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@WithMockUser(authorities="print")
|
||||
|
@ -593,7 +631,7 @@ void printWhenNoPrintAuthorityThenForbidden() {
|
|||
.andExpect(status().isForbidden());
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[authorize-requests]]
|
||||
== Authorizing Requests
|
||||
|
@ -613,8 +651,10 @@ As a quick summary, here are the authorization rules built into the DSL:
|
|||
Having now learned the patterns, rules, and how they can be paired together, you should be able to understand what is going on in this more complex example:
|
||||
|
||||
.Authorize Requests
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static jakarta.servlet.DispatcherType.*;
|
||||
|
@ -638,7 +678,7 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
|
|||
return http.build();
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
<1> There are multiple authorization rules specified.
|
||||
Each rule is considered in the order they were declared.
|
||||
<2> Dispatches `FORWARD` and `ERROR` are permitted to allow {spring-framework-reference-url}web.html#spring-web[Spring MVC] to render views and Spring Boot to render errors
|
||||
|
@ -685,8 +725,10 @@ And here is a brief look at the most common fields:
|
|||
Having now learned the patterns, rules, and how they can be paired together, you should be able to understand what is going on in this more complex example:
|
||||
|
||||
.Authorize Requests Using SpEL
|
||||
====
|
||||
.Xml
|
||||
[tabs]
|
||||
======
|
||||
Xml::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -696,7 +738,7 @@ Having now learned the patterns, rules, and how they can be paired together, you
|
|||
<intercept-url pattern="/**" access="denyAll"/> <4>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
<1> We specified a URL patters that any user can access.
|
||||
Specifically, any user can access a request if the URL starts with "/static/".
|
||||
<2> Any URL that starts with "/admin/" will be restricted to users who have the role "ROLE_ADMIN".
|
||||
|
@ -714,8 +756,10 @@ Additionally, Spring Security provides a mechanism for discovering path paramete
|
|||
For example, you can access a path parameter in your SpEL expression in the following way:
|
||||
|
||||
.Authorize Request using SpEL path variable
|
||||
====
|
||||
.Xml
|
||||
[tabs]
|
||||
======
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -723,7 +767,7 @@ For example, you can access a path parameter in your SpEL expression in the foll
|
|||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This expression refers to the path variable after `/resource/` and requires that it is equal to `Authentication#getName`.
|
||||
|
||||
|
@ -734,8 +778,10 @@ If you want to configure Spring Security to use a separate service for authoriza
|
|||
First, your `AuthorizationManager` may look something like this:
|
||||
|
||||
.Open Policy Agent Authorization Manager
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -746,13 +792,15 @@ public final class OpenPolicyAgentAuthorizationManager implements AuthorizationM
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Then, you can wire it into Spring Security in the following way:
|
||||
|
||||
.Any Request Goes to Remote Service
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -766,7 +814,7 @@ SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthoriza
|
|||
return http.build();
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[favor-permitall]]
|
||||
=== Favor `permitAll` over `ignoring`
|
||||
|
@ -818,8 +866,10 @@ You can override the default when you declare a `SecurityFilterChain`.
|
|||
Instead of using {security-api-url}org/springframework/security/config/annotation/web/builders/HttpSecurity.html#authorizeRequests()[`authorizeRequests`], use `authorizeHttpRequests`, like so:
|
||||
|
||||
.Use authorizeHttpRequests
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -833,7 +883,7 @@ SecurityFilterChain web(HttpSecurity http) throws AuthenticationException {
|
|||
return http.build();
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This improves on `authorizeRequests` in a number of ways:
|
||||
|
||||
|
@ -852,37 +902,43 @@ For Java configuration, {security-api-url}org/springframework/security/web/acces
|
|||
|
||||
To use `WebExpressionAuthorizationManager`, you can construct one with the expression you are trying to migrate, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
.requestMatchers("/test/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
.requestMatchers("/test/**").access(WebExpressionAuthorizationManager("hasRole('ADMIN') && hasRole('USER')"))
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If you are referring to a bean in your expression like so: `@webSecurity.check(authentication, request)`, it's recommended that you instead call the bean directly, which will look something like the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
.requestMatchers("/test/**").access((authentication, context) ->
|
||||
new AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
.requestMatchers("/test/**").access((authentication, context): AuthorizationManager<RequestAuthorizationContext> ->
|
||||
AuthorizationDecision(webSecurity.check(authentication.get(), context.getRequest())))
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
For complex instructions that include bean references as well as other expressions, it is recommended that you change those to implement `AuthorizationManager` and refer to them by calling `.access(AuthorizationManager)`.
|
||||
|
||||
|
@ -896,8 +952,10 @@ We use `securityMatchers` to determine if xref:servlet/configuration/java.adoc#j
|
|||
The same way, we can use `requestMatchers` to determine the authorization rules that we should apply to a given request.
|
||||
Look at the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -918,7 +976,9 @@ public class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
.Kotlin
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -940,7 +1000,7 @@ open class SecurityConfig {
|
|||
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
<1> Configure `HttpSecurity` to only be applied to URLs that start with `/api/`
|
||||
<2> Allow access to URLs that start with `/user/` to users with the `USER` role
|
||||
|
@ -952,8 +1012,10 @@ You can read more about the Spring MVC integration xref:servlet/integrations/mvc
|
|||
|
||||
If you want to use a specific `RequestMatcher`, just pass an implementation to the `securityMatcher` and/or `requestMatcher` methods:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; <1>
|
||||
|
@ -986,7 +1048,9 @@ public class MyCustomRequestMatcher implements RequestMatcher {
|
|||
}
|
||||
}
|
||||
----
|
||||
.Kotlin
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher <1>
|
||||
|
@ -1012,7 +1076,7 @@ open class SecurityConfig {
|
|||
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
<1> Import the static factory methods from `AntPathRequestMatcher` and `RegexRequestMatcher` to create `RequestMatcher` instances.
|
||||
<2> Configure `HttpSecurity` to only be applied to URLs that start with `/api/`, using `AntPathRequestMatcher`
|
||||
|
|
|
@ -9,8 +9,10 @@ To listen for these events, you must first publish an `AuthorizationEventPublish
|
|||
Spring Security's `SpringAuthorizationEventPublisher` will probably do fine.
|
||||
It comes publishes authorization events using Spring's `ApplicationEventPublisher`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -20,7 +22,8 @@ public AuthorizationEventPublisher authorizationEventPublisher
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -29,12 +32,14 @@ fun authorizationEventPublisher
|
|||
return SpringAuthorizationEventPublisher(applicationEventPublisher)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Then, you can use Spring's `@EventListener` support:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -47,7 +52,8 @@ public class AuthenticationEvents {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -59,7 +65,7 @@ class AuthenticationEvents {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[authorization-granted-events]]
|
||||
== Authorization Granted Events
|
||||
|
@ -71,8 +77,10 @@ In fact, publishing these events will likely require some business logic on your
|
|||
You can create your own event publisher that filters success events.
|
||||
For example, the following publisher only publishes authorization grants where `ROLE_ADMIN` was required:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -117,7 +125,8 @@ public class MyAuthorizationEventPublisher implements AuthorizationEventPublishe
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -157,4 +166,4 @@ class MyAuthorizationEventPublisher(val publisher: ApplicationEventPublisher,
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -19,7 +19,6 @@ The first step is to create our Spring Security Java Configuration.
|
|||
The configuration creates a Servlet Filter known as the `springSecurityFilterChain`, which is responsible for all the security (protecting the application URLs, validating submitted username and passwords, redirecting to the log in form, and so on) within your application.
|
||||
The following example shows the most basic example of a Spring Security Java Configuration:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -40,7 +39,6 @@ public class WebSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
This configuration is not complex or extensive, but it does a lot:
|
||||
|
||||
|
@ -78,7 +76,6 @@ The way in which we use `AbstractSecurityWebApplicationInitializer` differs depe
|
|||
|
||||
If you are not using Spring or Spring MVC, you need to pass the `WebSecurityConfig` to the superclass to ensure the configuration is picked up:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.security.web.context.*;
|
||||
|
@ -91,7 +88,6 @@ public class SecurityWebApplicationInitializer
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The `SecurityWebApplicationInitializer`:
|
||||
|
||||
|
@ -106,7 +102,6 @@ If we use the previous configuration, we would get an error.
|
|||
Instead, we should register Spring Security with the existing `ApplicationContext`.
|
||||
For example, if we use Spring MVC, our `SecurityWebApplicationInitializer` could look something like the following:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
import org.springframework.security.web.context.*;
|
||||
|
@ -116,14 +111,12 @@ public class SecurityWebApplicationInitializer
|
|||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
This onlys register the `springSecurityFilterChain` for every URL in your application.
|
||||
After that, we need to ensure that `WebSecurityConfig` was loaded in our existing `ApplicationInitializer`.
|
||||
For example, if we use Spring MVC it is added in the `getRootConfigClasses()`:
|
||||
|
||||
[[message-web-application-inititializer-java]]
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class MvcWebApplicationInitializer extends
|
||||
|
@ -137,7 +130,6 @@ public class MvcWebApplicationInitializer extends
|
|||
// ... other overrides ...
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[jc-httpsecurity]]
|
||||
== HttpSecurity
|
||||
|
@ -148,7 +140,6 @@ How does Spring Security know we want to support form-based authentication?
|
|||
Actually, there is a configuration class (called `SecurityFilterChain`) that is being invoked behind the scenes.
|
||||
It is configured with the following default implementation:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
|
@ -162,7 +153,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
return http.build();
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The default configuration (shown in the preceding example):
|
||||
|
||||
|
@ -172,7 +162,6 @@ The default configuration (shown in the preceding example):
|
|||
|
||||
Note that this configuration is parallels the XML Namespace configuration:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -181,7 +170,6 @@ Note that this configuration is parallels the XML Namespace configuration:
|
|||
<http-basic />
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
== Multiple HttpSecurity Instances
|
||||
|
||||
|
@ -189,7 +177,6 @@ We can configure multiple `HttpSecurity` instances just as we can have multiple
|
|||
The key is to register multiple `SecurityFilterChain` ``@Bean``s.
|
||||
The following example has a different configuration for URL's that start with `/api/`.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -234,14 +221,12 @@ public class MultiHttpSecurityConfig {
|
|||
<4> Create another instance of `SecurityFilterChain`.
|
||||
If the URL does not start with `/api/`, this configuration is used.
|
||||
This configuration is considered after `apiFilterChain`, since it has an `@Order` value after `1` (no `@Order` defaults to last).
|
||||
====
|
||||
|
||||
[[jc-custom-dsls]]
|
||||
== Custom DSLs
|
||||
|
||||
You can provide your own custom DSLs in Spring Security:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
|
||||
|
@ -274,7 +259,6 @@ public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurit
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -283,7 +267,6 @@ This is actually how methods like `HttpSecurity.authorizeRequests()` are impleme
|
|||
|
||||
You can then use the custom DSL:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -300,7 +283,6 @@ public class Config {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The code is invoked in the following order:
|
||||
|
||||
|
@ -312,16 +294,13 @@ If you want, you can have `HttpSecurity` add `MyCustomDsl` by default by using `
|
|||
For example, you can create a resource on the classpath named `META-INF/spring.factories` with the following contents:
|
||||
|
||||
.META-INF/spring.factories
|
||||
====
|
||||
[source]
|
||||
----
|
||||
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyCustomDsl
|
||||
----
|
||||
====
|
||||
|
||||
You can also explicit disable the default:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -336,7 +315,6 @@ public class Config {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[post-processing-configured-objects]]
|
||||
== Post Processing Configured Objects
|
||||
|
@ -349,7 +327,6 @@ While there are good reasons to not directly expose every property, users may st
|
|||
To address this issue, Spring Security introduces the concept of an `ObjectPostProcessor`, which can be used to modify or replace many of the `Object` instances created by the Java Configuration.
|
||||
For example, to configure the `filterSecurityPublishAuthorizationSuccess` property on `FilterSecurityInterceptor`, you can use the following:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
|
@ -368,4 +345,3 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
return http.build();
|
||||
}
|
||||
----
|
||||
====
|
||||
|
|
|
@ -17,7 +17,6 @@ How does Spring Security know we want to support form-based authentication?
|
|||
There is a configuration class (called `SecurityFilterChain`) that is being invoked behind the scenes.
|
||||
It is configured with the following default implementation:
|
||||
|
||||
====
|
||||
[source,kotlin]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -34,7 +33,6 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
return http.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
Make sure that import the `invoke` function in your class, sometimes the IDE will not auto-import it causing compilation issues.
|
||||
|
@ -47,7 +45,6 @@ The default configuration (shown in the preceding listing):
|
|||
|
||||
Note that this configuration is parallels the XML namespace configuration:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -56,7 +53,6 @@ Note that this configuration is parallels the XML namespace configuration:
|
|||
<http-basic />
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
== Multiple HttpSecurity Instances
|
||||
|
||||
|
@ -64,7 +60,6 @@ We can configure multiple `HttpSecurity` instances, just as we can have multiple
|
|||
The key is to register multiple `SecurityFilterChain` ``@Bean``s.
|
||||
The following example has a different configuration for URL's that start with `/api/`:
|
||||
|
||||
====
|
||||
[source,kotlin]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -113,4 +108,3 @@ class MultiHttpSecurityConfig {
|
|||
<4> Create another instance of `SecurityFilterChain`.
|
||||
If the URL does not start with `/api/`, this configuration is used.
|
||||
This configuration is considered after `apiFilterChain`, since it has an `@Order` value after `1` (no `@Order` defaults to last).
|
||||
====
|
||||
|
|
|
@ -10,12 +10,10 @@ You can use a namespace element to more concisely configure an individual bean o
|
|||
A simple element can conceal the fact that multiple beans and processing steps are being added to the application context.
|
||||
For example, adding the following element from the `security` namespace to an application context starts up an embedded LDAP server for testing use within the application:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<security:ldap-server />
|
||||
----
|
||||
====
|
||||
|
||||
This is much simpler than wiring up the equivalent Apache Directory Server beans.
|
||||
The most common alternative configuration requirements are supported by attributes on the `ldap-server` element, and the user is isolated from worrying about which beans they need to create and what the bean property names are.
|
||||
|
@ -26,7 +24,6 @@ We recommend that you try the https://spring.io/tools/sts[Spring Tool Suite], as
|
|||
To start using the `security` namespace in your application context, add the `spring-security-config` jar to your classpath.
|
||||
Then, all you need to do is add the schema declaration to your application context file:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
|
@ -39,13 +36,11 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans
|
|||
...
|
||||
</beans>
|
||||
----
|
||||
====
|
||||
|
||||
In many of the examples you can see (and in the sample applications), we often use `security` (rather than `beans`) as the default namespace, which means we can omit the prefix on all the security namespace elements, making the content easier to read.
|
||||
You may also want to do this if you have your application context divided up into separate files and have most of your security configuration in one of them.
|
||||
Your security application context file would then start like this:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<beans:beans xmlns="http://www.springframework.org/schema/security"
|
||||
|
@ -58,7 +53,6 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans
|
|||
...
|
||||
</beans:beans>
|
||||
----
|
||||
====
|
||||
|
||||
We assume this syntax is being used from now on in this chapter.
|
||||
|
||||
|
@ -95,7 +89,6 @@ In later sections, we introduce more advanced namespace configuration options.
|
|||
=== web.xml Configuration
|
||||
The first thing you need to do is add the following filter declaration to your `web.xml` file:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<filter>
|
||||
|
@ -108,7 +101,6 @@ The first thing you need to do is add the following filter declaration to your `
|
|||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
----
|
||||
====
|
||||
|
||||
`DelegatingFilterProxy` is a Spring Framework class that delegates to a filter implementation that is defined as a Spring bean in your application context.
|
||||
In this case, the bean is named `springSecurityFilterChain`, which is an internal infrastructure bean created by the namespace to handle web security.
|
||||
|
@ -121,7 +113,6 @@ Web security services are configured by the `<http>` element.
|
|||
=== A Minimal <http> Configuration
|
||||
To enable web security, you need the following configuration:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -130,7 +121,6 @@ To enable web security, you need the following configuration:
|
|||
<logout />
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
That listing says that we want:
|
||||
|
||||
|
@ -159,7 +149,6 @@ You can also add a `method` attribute to limit the match to a particular HTTP me
|
|||
|
||||
To add users, you can define a set of test data directly in the namespace:
|
||||
|
||||
====
|
||||
[source,xml,attrs="-attributes"]
|
||||
----
|
||||
<authentication-manager>
|
||||
|
@ -174,12 +163,10 @@ To add users, you can define a set of test data directly in the namespace:
|
|||
</authentication-provider>
|
||||
</authentication-manager>
|
||||
----
|
||||
====
|
||||
|
||||
The preceding listing shows an example of a secure way to store the same passwords.
|
||||
The password is prefixed with `+{bcrypt}+` to instruct `DelegatingPasswordEncoder`, which supports any configured `PasswordEncoder` for matching, that the passwords are hashed using BCrypt:
|
||||
|
||||
====
|
||||
[source,xml,attrs="-attributes"]
|
||||
----
|
||||
<authentication-manager>
|
||||
|
@ -195,7 +182,6 @@ The password is prefixed with `+{bcrypt}+` to instruct `DelegatingPasswordEncode
|
|||
</authentication-provider>
|
||||
</authentication-manager>
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
[subs="quotes"]
|
||||
|
@ -225,7 +211,6 @@ This is the URL to which the user is taken after successfully logging in. it def
|
|||
You can also configure things so that the user _always_ ends up at this page (regardless of whether the login was "`on-demand`" or they explicitly chose to log in) by setting the `always-use-default-target` attribute to `true`.
|
||||
This is useful if your application always requires that the user starts at a "`home`" page, for example:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http pattern="/login.htm*" security="none"/>
|
||||
|
@ -235,7 +220,6 @@ This is useful if your application always requires that the user starts at a "`h
|
|||
always-use-default-target='true' />
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
For even more control over the destination, you can use the `authentication-success-handler-ref` attribute as an alternative to `default-target-url`.
|
||||
The referenced bean should be an instance of `AuthenticationSuccessHandler`.
|
||||
|
@ -357,7 +341,6 @@ The filters, aliases, and namespace elements and attributes that create the filt
|
|||
|
||||
You can add your own filter to the stack by using the `custom-filter` element and one of these names to specify the position at which your filter should appear:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -366,7 +349,6 @@ You can add your own filter to the stack by using the `custom-filter` element an
|
|||
|
||||
<beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter"/>
|
||||
----
|
||||
====
|
||||
|
||||
You can also use the `after` or `before` attributes if you want your filter to be inserted before or after another filter in the stack.
|
||||
You can use `FIRST` and `LAST` with the `position` attribute to indicate that you want your filter to appear before or after the entire stack, respectively.
|
||||
|
@ -407,22 +389,18 @@ If you need to use a more complicated access control strategy, you can set an al
|
|||
|
||||
For method security, you do so by setting the `access-decision-manager-ref` attribute on `global-method-security` to the `id` of the appropriate `AccessDecisionManager` bean in the application context:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<global-method-security access-decision-manager-ref="myAccessDecisionManagerBean">
|
||||
...
|
||||
</global-method-security>
|
||||
----
|
||||
====
|
||||
|
||||
The syntax for web security is the same, but the attribute is on the `http` element:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http access-decision-manager-ref="myAccessDecisionManagerBean">
|
||||
...
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
|
|
@ -9,8 +9,10 @@ You can specify the default configuration explicitly using the following:
|
|||
|
||||
[[csrf-configuration]]
|
||||
.Configure CSRF Protection
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -27,7 +29,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -47,7 +50,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -55,7 +59,7 @@ class SecurityConfig {
|
|||
<csrf/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
To learn more about CSRF protection for your application, consider the following use cases:
|
||||
|
||||
|
@ -132,8 +136,10 @@ You can specify the default configuration explicitly using the following configu
|
|||
|
||||
[[csrf-token-repository-httpsession-configuration]]
|
||||
.Configure `HttpSessionCsrfTokenRepository`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -152,7 +158,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -174,7 +181,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -184,7 +192,7 @@ class SecurityConfig {
|
|||
<b:bean id="tokenRepository"
|
||||
class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[csrf-token-repository-cookie]]
|
||||
=== Using the `CookieCsrfTokenRepository`
|
||||
|
@ -203,8 +211,10 @@ You can configure the `CookieCsrfTokenRepository` using the following configurat
|
|||
|
||||
[[csrf-token-repository-cookie-configuration]]
|
||||
.Configure `CookieCsrfTokenRepository`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -223,7 +233,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -245,7 +256,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -256,7 +268,7 @@ class SecurityConfig {
|
|||
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository"
|
||||
p:cookieHttpOnly="false"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -274,8 +286,10 @@ Once you've implemented the `CsrfTokenRepository` interface, you can configure S
|
|||
|
||||
[[csrf-token-repository-custom-configuration]]
|
||||
.Configure Custom `CsrfTokenRepository`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -294,7 +308,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -316,7 +331,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -326,7 +342,7 @@ class SecurityConfig {
|
|||
<b:bean id="tokenRepository"
|
||||
class="example.CustomCsrfTokenRepository"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[csrf-token-request-handler]]
|
||||
== Handling the `CsrfToken`
|
||||
|
@ -362,8 +378,10 @@ You can specify the default configuration explicitly using the following configu
|
|||
|
||||
[[csrf-token-request-handler-breach-configuration]]
|
||||
.Configure BREACH protection
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -382,7 +400,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -404,7 +423,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -414,7 +434,7 @@ class SecurityConfig {
|
|||
<b:bean id="requestHandler"
|
||||
class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[csrf-token-request-handler-plain]]
|
||||
=== Using the `CsrfTokenRequestAttributeHandler`
|
||||
|
@ -433,8 +453,10 @@ This implementation also resolves the token value from the request as either a r
|
|||
The primary use of `CsrfTokenRequestAttributeHandler` is to opt-out of BREACH protection of the `CsrfToken`, which can be configured using the following configuration:
|
||||
|
||||
.Opt-out of BREACH protection
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -453,7 +475,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -475,7 +498,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -485,7 +509,7 @@ class SecurityConfig {
|
|||
<b:bean id="requestHandler"
|
||||
class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[csrf-token-request-handler-custom]]
|
||||
=== Customizing the `CsrfTokenRequestHandler`
|
||||
|
@ -503,8 +527,10 @@ Once you've implemented the `CsrfTokenRequestHandler` interface, you can configu
|
|||
|
||||
[[csrf-token-request-handler-custom-configuration]]
|
||||
.Configure Custom `CsrfTokenRequestHandler`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -523,7 +549,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -545,7 +572,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -555,7 +583,7 @@ class SecurityConfig {
|
|||
<b:bean id="requestHandler"
|
||||
class="example.CustomCsrfTokenRequestHandler"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[deferred-csrf-token]]
|
||||
== Deferred Loading of the `CsrfToken`
|
||||
|
@ -575,8 +603,10 @@ In the event that you want to opt-out of deferred tokens and cause the `CsrfToke
|
|||
|
||||
[[deferred-csrf-token-opt-out-configuration]]
|
||||
.Opt-out of Deferred CSRF Tokens
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -598,7 +628,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -623,7 +654,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -637,7 +669,7 @@ class SecurityConfig {
|
|||
</b:property>
|
||||
</b:bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -665,14 +697,12 @@ To submit an HTML form, the CSRF token must be included in the form as a hidden
|
|||
For example, the rendered HTML might look like:
|
||||
|
||||
.CSRF Token in HTML Form
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<input type="hidden"
|
||||
name="_csrf"
|
||||
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
|
||||
----
|
||||
====
|
||||
|
||||
The following view technologies automatically include the actual CSRF token in a form that has an unsafe HTTP method, such as a POST:
|
||||
|
||||
|
@ -685,7 +715,6 @@ If these options are not available, you can take advantage of the fact that the
|
|||
The following example does this with a JSP:
|
||||
|
||||
.CSRF Token in HTML Form with Request Attribute
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<c:url var="logoutUrl" value="/logout"/>
|
||||
|
@ -698,7 +727,6 @@ The following example does this with a JSP:
|
|||
value="${_csrf.token}"/>
|
||||
</form>
|
||||
----
|
||||
====
|
||||
|
||||
[[csrf-integration-javascript]]
|
||||
=== JavaScript Applications
|
||||
|
@ -743,8 +771,10 @@ In order to easily integrate a single-page application with Spring Security, the
|
|||
|
||||
[[csrf-integration-javascript-spa-configuration]]
|
||||
.Configure CSRF for Single-Page Application
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -811,7 +841,8 @@ final class CsrfCookieFilter extends OncePerRequestFilter {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -878,7 +909,8 @@ class CsrfCookieFilter : OncePerRequestFilter() {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -896,7 +928,7 @@ class CsrfCookieFilter : OncePerRequestFilter() {
|
|||
<b:bean id="csrfCookieFilter"
|
||||
class="example.CsrfCookieFilter"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
<1> Configure `CookieCsrfTokenRepository` with `HttpOnly` set to `false` so the cookie can be read by the JavaScript application.
|
||||
<2> Configure a custom `CsrfTokenRequestHandler` that resolves the CSRF token based on whether it is an HTTP request header (`X-XSRF-TOKEN`) or request parameter (`_csrf`).
|
||||
|
@ -909,7 +941,6 @@ For multi-page applications where JavaScript is loaded on each page, an alternat
|
|||
The HTML might look something like this:
|
||||
|
||||
.CSRF Token in HTML Meta Tag
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<html>
|
||||
|
@ -921,13 +952,11 @@ The HTML might look something like this:
|
|||
<!-- ... -->
|
||||
</html>
|
||||
----
|
||||
====
|
||||
|
||||
In order to include the CSRF token in the request, you can take advantage of the fact that the `CsrfToken` is exposed as an <<csrf-token-request-handler,`HttpServletRequest` attribute named `_csrf`>>.
|
||||
The following example does this with a JSP:
|
||||
|
||||
.CSRF Token in HTML Meta Tag with Request Attribute
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<html>
|
||||
|
@ -940,13 +969,11 @@ The following example does this with a JSP:
|
|||
<!-- ... -->
|
||||
</html>
|
||||
----
|
||||
====
|
||||
|
||||
Once the meta tags contain the CSRF token, the JavaScript code can read the meta tags and include the CSRF token as a header.
|
||||
If you use jQuery, you can do this with the following code:
|
||||
|
||||
.Include CSRF Token in AJAX Request
|
||||
====
|
||||
[source,javascript]
|
||||
----
|
||||
$(function () {
|
||||
|
@ -957,7 +984,6 @@ $(function () {
|
|||
});
|
||||
});
|
||||
----
|
||||
====
|
||||
|
||||
[[csrf-integration-javascript-other]]
|
||||
==== Other JavaScript Applications
|
||||
|
@ -969,8 +995,10 @@ The following is an example of `@ControllerAdvice` that applies to all controlle
|
|||
|
||||
[[controller-advice]]
|
||||
.CSRF Token in HTTP Response Header
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@ControllerAdvice
|
||||
|
@ -984,7 +1012,8 @@ public class CsrfControllerAdvice {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@ControllerAdvice
|
||||
|
@ -997,7 +1026,7 @@ class CsrfControllerAdvice {
|
|||
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -1038,8 +1067,10 @@ The following is an example of the `/csrf` endpoint that makes use of the xref:s
|
|||
|
||||
[[csrf-endpoint]]
|
||||
.The `/csrf` endpoint
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RestController
|
||||
|
@ -1053,7 +1084,8 @@ public class CsrfController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@RestController
|
||||
|
@ -1066,7 +1098,7 @@ class CsrfController {
|
|||
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -1091,8 +1123,10 @@ For example, you can configure a custom access denied page using the following c
|
|||
|
||||
[[csrf-access-denied-handler-configuration]]
|
||||
.Configure `AccessDeniedHandler`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1111,7 +1145,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -1133,7 +1168,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -1141,7 +1177,7 @@ class SecurityConfig {
|
|||
<access-denied-handler error-page="/access-denied"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[csrf-testing]]
|
||||
== CSRF Testing
|
||||
|
@ -1150,8 +1186,10 @@ You can use Spring Security's xref:servlet/test/mockmvc/setup.adoc[testing suppo
|
|||
|
||||
[[csrf-testing-example]]
|
||||
.Test CSRF Protection
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
|
||||
|
@ -1194,7 +1232,8 @@ public class CsrfTests {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*
|
||||
|
@ -1236,7 +1275,7 @@ class CsrfTests {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[disable-csrf]]
|
||||
== Disable CSRF Protection
|
||||
|
@ -1248,8 +1287,10 @@ You can also consider whether only certain endpoints do not require CSRF protect
|
|||
|
||||
[[disable-csrf-ignoring-configuration]]
|
||||
.Ignoring Requests
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1268,7 +1309,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -1290,7 +1332,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -1309,14 +1352,16 @@ class SecurityConfig {
|
|||
</b:constructor-arg>
|
||||
</b:bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If you need to disable CSRF protection, you can do so using the following configuration:
|
||||
|
||||
[[disable-csrf-configuration]]
|
||||
.Disable CSRF
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1333,7 +1378,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -1355,7 +1401,8 @@ class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -1363,7 +1410,7 @@ class SecurityConfig {
|
|||
<csrf disabled="true"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[csrf-considerations]]
|
||||
== CSRF Considerations
|
||||
|
@ -1394,8 +1441,10 @@ However, remember that this is generally not recommended.
|
|||
For example, the following logs out when the `/logout` URL is requested with any HTTP method:
|
||||
|
||||
.Log Out with Any HTTP Method
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1414,7 +1463,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
@ -1435,7 +1485,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
See the xref:servlet/authentication/logout.adoc[Logout] chapter for more information.
|
||||
|
||||
|
@ -1479,8 +1529,10 @@ However, only authorized users can submit a file that is processed by your appli
|
|||
In general, this is the recommended approach because the temporary file upload should have a negligible impact on most servers.
|
||||
|
||||
.Configure `MultipartFilter`
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
|
||||
|
@ -1492,7 +1544,8 @@ public class SecurityApplicationInitializer extends AbstractSecurityWebApplicati
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer() {
|
||||
|
@ -1502,7 +1555,8 @@ class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<filter>
|
||||
|
@ -1522,7 +1576,7 @@ class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer
|
|||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -1537,14 +1591,12 @@ Since the `CsrfToken` is exposed as an <<csrf-token-request-handler,`HttpServlet
|
|||
The following example does this with a JSP:
|
||||
|
||||
.CSRF Token in Action
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<form method="post"
|
||||
action="./upload?${_csrf.parameterName}=${_csrf.token}"
|
||||
enctype="multipart/form-data">
|
||||
----
|
||||
====
|
||||
|
||||
[[csrf-considerations-override-method]]
|
||||
=== HiddenHttpMethodFilter
|
||||
|
|
|
@ -46,8 +46,10 @@ However, it is important that you do so knowing that this can open your applicat
|
|||
For example, if you wish to use Spring MVC's matrix variables, you could use the following configuration:
|
||||
|
||||
.Allow Matrix Variables
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -58,7 +60,8 @@ public StrictHttpFirewall httpFirewall() {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<b:bean id="httpFirewall"
|
||||
|
@ -68,7 +71,8 @@ public StrictHttpFirewall httpFirewall() {
|
|||
<http-firewall ref="httpFirewall"/>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -78,7 +82,7 @@ fun httpFirewall(): StrictHttpFirewall {
|
|||
return firewall
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
To protect against https://www.owasp.org/index.php/Cross_Site_Tracing[Cross Site Tracing (XST)] and https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)[HTTP Verb Tampering], the `StrictHttpFirewall` provides an allowed list of valid HTTP methods that are allowed.
|
||||
The default valid methods are `DELETE`, `GET`, `HEAD`, `OPTIONS`, `PATCH`, `POST`, and `PUT`.
|
||||
|
@ -87,8 +91,10 @@ The following example allows only HTTP `GET` and `POST` methods:
|
|||
|
||||
|
||||
.Allow Only GET & POST
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -99,7 +105,8 @@ public StrictHttpFirewall httpFirewall() {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<b:bean id="httpFirewall"
|
||||
|
@ -109,7 +116,8 @@ public StrictHttpFirewall httpFirewall() {
|
|||
<http-firewall ref="httpFirewall"/>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -119,7 +127,7 @@ fun httpFirewall(): StrictHttpFirewall {
|
|||
return firewall
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -132,8 +140,8 @@ See https://jira.spring.io/browse/SPR-16851[SPR_16851] for an issue that request
|
|||
If you must allow any HTTP method (not recommended), you can use `StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)`.
|
||||
Doing so entirely disables validation of the HTTP method.
|
||||
|
||||
[[servlet-httpfirewall-headers-parameters]]
|
||||
|
||||
[[servlet-httpfirewall-headers-parameters]]
|
||||
`StrictHttpFirewall` also checks header names and values and parameter names.
|
||||
It requires that each character have a defined code point and not be a control character.
|
||||
|
||||
|
@ -151,8 +159,10 @@ Parameter values can be also controlled with `setAllowedParameterValues(Predicat
|
|||
For example, to switch off this check, you can wire your `StrictHttpFirewall` with `Predicate` instances that always return `true`:
|
||||
|
||||
.Allow Any Header Name, Header Value, and Parameter Name
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -165,7 +175,8 @@ public StrictHttpFirewall httpFirewall() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -177,7 +188,7 @@ fun httpFirewall(): StrictHttpFirewall {
|
|||
return firewall
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Alternatively, there might be a specific value that you need to allow.
|
||||
|
||||
|
@ -187,8 +198,10 @@ Due to this fact, some application servers parse this value into two separate ch
|
|||
You can address this with the `setAllowedHeaderValues` method:
|
||||
|
||||
.Allow Certain User Agents
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -201,7 +214,8 @@ public StrictHttpFirewall httpFirewall() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -213,13 +227,15 @@ fun httpFirewall(): StrictHttpFirewall {
|
|||
return firewall
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In the case of header values, you may instead consider parsing them as UTF-8 at verification time:
|
||||
|
||||
.Parse Headers As UTF-8
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
firewall.setAllowedHeaderValues((header) -> {
|
||||
|
@ -228,7 +244,8 @@ firewall.setAllowedHeaderValues((header) -> {
|
|||
});
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
firewall.setAllowedHeaderValues {
|
||||
|
@ -236,4 +253,4 @@ firewall.setAllowedHeaderValues {
|
|||
return allowed.matcher(parsed).matches()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -16,8 +16,10 @@ For example, assume that you want the defaults but you wish to specify `SAMEORIG
|
|||
You can do so with the following configuration:
|
||||
|
||||
.Customize Default Security Headers
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -38,7 +40,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -50,7 +53,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -70,7 +74,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If you do not want the defaults to be added and want explicit control over what should be used, you can disable the defaults.
|
||||
The next code listing shows how to do so.
|
||||
|
@ -78,8 +82,10 @@ The next code listing shows how to do so.
|
|||
If you use Spring Security's configuration, the following adds only xref:features/exploits/headers.adoc#headers-cache-control[Cache Control]:
|
||||
|
||||
.Customize Cache Control Headers
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -100,7 +106,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -112,7 +119,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -133,13 +141,15 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If necessary, you can disable all of the HTTP Security response headers with the following configuration:
|
||||
|
||||
.Disable All HTTP Security Headers
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -156,7 +166,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -166,7 +177,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -184,7 +196,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-cache-control]]
|
||||
== Cache Control
|
||||
|
@ -200,8 +212,10 @@ You can find details on how to do this in the https://docs.spring.io/spring/docs
|
|||
If necessary, you can also disable Spring Security's cache control HTTP response headers.
|
||||
|
||||
.Cache Control Disabled
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -220,7 +234,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -232,7 +247,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -252,7 +268,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-content-type-options]]
|
||||
== Content Type Options
|
||||
|
@ -261,8 +277,10 @@ Spring Security includes xref:features/exploits/headers.adoc#headers-content-typ
|
|||
However, you can disable it:
|
||||
|
||||
.Content Type Options Disabled
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -281,7 +299,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -293,7 +312,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -313,7 +333,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-hsts]]
|
||||
== HTTP Strict Transport Security (HSTS)
|
||||
|
@ -323,8 +343,10 @@ However, you can explicitly customize the results.
|
|||
The following example explicitly provides HSTS:
|
||||
|
||||
.Strict Transport Security
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -347,7 +369,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -362,7 +385,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -384,7 +408,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-hpkp]]
|
||||
== HTTP Public Key Pinning (HPKP)
|
||||
|
@ -393,8 +417,10 @@ Spring Security provides servlet support for xref:features/exploits/headers.adoc
|
|||
You can enable HPKP headers with the following configuration:
|
||||
|
||||
.HTTP Public Key Pinning
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -416,7 +442,9 @@ public class WebSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
.XML
|
||||
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -435,7 +463,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -458,7 +487,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-frame-options]]
|
||||
== X-Frame-Options
|
||||
|
@ -468,8 +497,10 @@ By default, Spring Security instructs browsers to block reflected XSS attacks by
|
|||
For example, the following configuration specifies that Spring Security should no longer instruct browsers to block the content:
|
||||
|
||||
.X-Frame-Options: SAMEORIGIN
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -490,7 +521,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -504,7 +536,8 @@ public class WebSecurityConfig {
|
|||
----
|
||||
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -524,7 +557,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-xss-protection]]
|
||||
== X-XSS-Protection
|
||||
|
@ -535,8 +568,10 @@ For example, the following configuration specifies that Spring Security instruct
|
|||
and block the content:
|
||||
|
||||
.X-XSS-Protection Customization
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -557,7 +592,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -569,7 +605,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -590,7 +627,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-csp]]
|
||||
== Content Security Policy (CSP)
|
||||
|
@ -601,18 +638,18 @@ The web application author must declare the security policy (or policies) to enf
|
|||
Consider the following security policy:
|
||||
|
||||
.Content Security Policy Example
|
||||
====
|
||||
[source,http]
|
||||
----
|
||||
Content-Security-Policy: script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/
|
||||
----
|
||||
====
|
||||
|
||||
Given the preceding security policy, you can enable the CSP header:
|
||||
|
||||
.Content Security Policy
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -633,7 +670,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -646,7 +684,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -667,13 +706,15 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
To enable the CSP `report-only` header, provide the following configuration:
|
||||
|
||||
.Content Security Policy Report Only
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -695,7 +736,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -709,7 +751,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -731,7 +774,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-referrer]]
|
||||
== Referrer Policy
|
||||
|
@ -740,8 +783,10 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-referre
|
|||
You can enable the Referrer Policy header by using the configuration:
|
||||
|
||||
.Referrer Policy
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -762,7 +807,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -774,7 +820,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -795,7 +842,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-feature]]
|
||||
== Feature Policy
|
||||
|
@ -804,18 +851,18 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-feature
|
|||
Consider the following `Feature-Policy` header:
|
||||
|
||||
.Feature-Policy Example
|
||||
====
|
||||
[source]
|
||||
----
|
||||
Feature-Policy: geolocation 'self'
|
||||
----
|
||||
====
|
||||
|
||||
You can enable the preceding feature policy header by using the following configuration:
|
||||
|
||||
.Feature-Policy
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -834,7 +881,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -846,7 +894,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -865,7 +914,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-permissions]]
|
||||
== Permissions Policy
|
||||
|
@ -874,18 +923,18 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-permiss
|
|||
Consider the following `Permissions-Policy` header:
|
||||
|
||||
.Permissions-Policy Example
|
||||
====
|
||||
[source]
|
||||
----
|
||||
Permissions-Policy: geolocation=(self)
|
||||
----
|
||||
====
|
||||
|
||||
You can enable the preceding permissions policy header using the following configuration:
|
||||
|
||||
.Permissions-Policy
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -906,7 +955,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -918,7 +968,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -939,7 +990,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-clear-site-data]]
|
||||
== Clear Site Data
|
||||
|
@ -948,17 +999,17 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-clear-s
|
|||
Consider the following Clear-Site-Data header:
|
||||
|
||||
.Clear-Site-Data Example
|
||||
====
|
||||
----
|
||||
Clear-Site-Data: "cache", "cookies"
|
||||
----
|
||||
====
|
||||
|
||||
You can send the preceding header on log out with the following configuration:
|
||||
|
||||
.Clear-Site-Data
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -977,7 +1028,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -996,7 +1048,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-custom]]
|
||||
== Custom Headers
|
||||
|
@ -1016,8 +1068,10 @@ X-Custom-Security-Header: header-value
|
|||
Given the preceding header, you could add the headers to the response by using the following configuration:
|
||||
|
||||
.StaticHeadersWriter
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1036,7 +1090,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -1048,7 +1103,8 @@ public class WebSecurityConfig {
|
|||
</http>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1067,7 +1123,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-headers-writer]]
|
||||
=== Headers Writer
|
||||
|
@ -1077,8 +1133,10 @@ The next example use a custom instance of `XFrameOptionsHeaderWriter`.
|
|||
If you wanted to explicitly configure <<servlet-headers-frame-options>>, you could do so with the following configuration:
|
||||
|
||||
.Headers Writer
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1097,7 +1155,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -1115,7 +1174,8 @@ See https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsi
|
|||
c:frameOptionsMode="SAMEORIGIN"/>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1134,7 +1194,7 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[headers-delegatingrequestmatcherheaderwriter]]
|
||||
=== DelegatingRequestMatcherHeaderWriter
|
||||
|
@ -1146,8 +1206,10 @@ You could use the `DelegatingRequestMatcherHeaderWriter` to do so.
|
|||
The following configuration example uses `DelegatingRequestMatcherHeaderWriter`:
|
||||
|
||||
.DelegatingRequestMatcherHeaderWriter Java Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1170,7 +1232,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.XML
|
||||
XML::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -1195,7 +1258,8 @@ public class WebSecurityConfig {
|
|||
</beans:bean>
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1218,4 +1282,4 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -13,8 +13,10 @@ If a client makes a request using HTTP rather than HTTPS, you can configure Spri
|
|||
For example, the following Java or Kotlin configuration redirects any HTTP requests to HTTPS:
|
||||
|
||||
.Redirect to HTTPS
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -33,7 +35,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -52,12 +55,11 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following XML configuration redirects all HTTP requests to HTTPS
|
||||
|
||||
.Redirect to HTTPS with XML Configuration
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -65,7 +67,6 @@ The following XML configuration redirects all HTTP requests to HTTPS
|
|||
...
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
[[servlet-hsts]]
|
||||
|
|
|
@ -21,8 +21,10 @@ With Spring Security <<servlet-hello-dependencies,on the classpath>>, you can no
|
|||
The following snippet shows some of the output that indicates that Spring Security is enabled in your application:
|
||||
|
||||
.Running Spring Boot Application
|
||||
====
|
||||
.Maven
|
||||
[tabs]
|
||||
======
|
||||
Maven::
|
||||
+
|
||||
[source,bash,role="primary"]
|
||||
----
|
||||
$ ./mvnw spring-boot:run
|
||||
|
@ -34,7 +36,8 @@ Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
|
|||
...
|
||||
----
|
||||
|
||||
.Gradle
|
||||
Gradle::
|
||||
+
|
||||
[source,bash,role="secondary"]
|
||||
----
|
||||
$ ./gradlew :bootRun
|
||||
|
@ -46,7 +49,8 @@ Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
|
|||
...
|
||||
----
|
||||
|
||||
.Jar
|
||||
Jar::
|
||||
+
|
||||
[source,bash,role="secondary"]
|
||||
----
|
||||
$ java -jar target/myapplication-0.0.1.jar
|
||||
|
@ -57,20 +61,18 @@ Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336
|
|||
|
||||
...
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Now that you have it running, you might try hitting an endpoint to see what happens.
|
||||
If you hit an endpoint without credentials like so:
|
||||
|
||||
.Querying a Secured Boot Application
|
||||
====
|
||||
[source,bash]
|
||||
----
|
||||
$ curl -i http://localhost:8080/some/path
|
||||
HTTP/1.1 401
|
||||
...
|
||||
----
|
||||
====
|
||||
|
||||
then Spring Security denies access with a `401 Unauthorized`.
|
||||
|
||||
|
@ -80,14 +82,12 @@ If you provide the same URL in a browser, it will redirect to a default login pa
|
|||
And if you hit an endpoint with credentials (found in the console output) as follows:
|
||||
|
||||
.Querying with Credentials
|
||||
====
|
||||
[source,bash]
|
||||
----
|
||||
$ curl -i -u user:8e557245-73e2-4286-969a-ff57fe326336 http://localhost:8080/some/path
|
||||
HTTP/1.1 404
|
||||
...
|
||||
----
|
||||
====
|
||||
|
||||
then Spring Boot will service the request, returning a `404 Not Found` in this case since `/some/path` doesn't exist.
|
||||
|
||||
|
@ -122,7 +122,6 @@ It can be helpful to understand how Spring Boot is coordinating with Spring Secu
|
|||
Taking a look at {spring-boot-api-url}org/springframework/boot/autoconfigure/security/servlet/SecurityAutoConfiguration.html[Boot's security auto configuration], it does the following (simplified for illustration):
|
||||
|
||||
.Spring Boot Security Auto Configuration
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@EnableWebSecurity <1>
|
||||
|
@ -143,7 +142,6 @@ public class DefaultSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
1. Adds the `@EnableWebSecurity` annotation. (Among other things, this publishes xref:servlet/architecture.adoc#servlet-securityfilterchain[Spring Security's default `Filter` chain] as a `@Bean`)
|
||||
2. Publishes a xref:servlet/authentication/passwords/user-details-service.adoc[`UserDetailsService`] `@Bean` with a username of `user` and a randomly generated password that is logged to the console
|
||||
3. Publishes an xref:servlet/authentication/events.adoc[`AuthenticationEventPublisher`] `@Bean` for publishing authentication events
|
||||
|
|
|
@ -14,7 +14,6 @@ It wraps a delegate `Runnable` to initialize the `SecurityContextHolder` with a
|
|||
It then invokes the delegate `Runnable`, ensuring to clear the `SecurityContextHolder` afterwards.
|
||||
The `DelegatingSecurityContextRunnable` looks something like this:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public void run() {
|
||||
|
@ -26,7 +25,6 @@ try {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
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.
|
||||
|
@ -34,7 +32,6 @@ For example, you might have used Spring Security's xref:servlet/appendix/namespa
|
|||
You can now transfer the `SecurityContext` of the current `Thread` to the `Thread` that invokes the secured service.
|
||||
The following example show how you might do so:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Runnable originalRunnable = new Runnable() {
|
||||
|
@ -49,7 +46,6 @@ DelegatingSecurityContextRunnable wrappedRunnable =
|
|||
|
||||
new Thread(wrappedRunnable).start();
|
||||
----
|
||||
====
|
||||
|
||||
The preceding code:
|
||||
|
||||
|
@ -63,7 +59,6 @@ Since it is common to create a `DelegatingSecurityContextRunnable` with the `Sec
|
|||
The following code has the same effect as the preceding code:
|
||||
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Runnable originalRunnable = new Runnable() {
|
||||
|
@ -77,7 +72,6 @@ DelegatingSecurityContextRunnable wrappedRunnable =
|
|||
|
||||
new Thread(wrappedRunnable).start();
|
||||
----
|
||||
====
|
||||
|
||||
The code we have is simple to use, but it still requires knowledge that we are using Spring Security.
|
||||
In the next section we will take a look at how we can utilize `DelegatingSecurityContextExecutor` to hide the fact that we are using Spring Security.
|
||||
|
@ -90,7 +84,6 @@ Now we look at how `DelegatingSecurityContextExecutor` can shield our code from
|
|||
The design of `DelegatingSecurityContextExecutor` is similar to that of `DelegatingSecurityContextRunnable`, except that it accepts a delegate `Executor` instead of a delegate `Runnable`.
|
||||
The following example shows how to use it:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
|
@ -111,7 +104,6 @@ public void run() {
|
|||
|
||||
executor.execute(originalRunnable);
|
||||
----
|
||||
====
|
||||
|
||||
This code:
|
||||
|
||||
|
@ -126,7 +118,6 @@ This is nice if we run background tasks that need to be run by a user with eleva
|
|||
|
||||
Consider the following example:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Autowired
|
||||
|
@ -141,21 +132,18 @@ Runnable originalRunnable = new Runnable() {
|
|||
executor.execute(originalRunnable);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Now our code is unaware that the `SecurityContext` is being propagated to the `Thread`, the `originalRunnable` is run, and 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` (that is, the currently logged in-user) at the time we invoked `executor.execute(Runnable)` to process `originalRunnable`?
|
||||
You can do so by removing the `SecurityContext` argument from our `DelegatingSecurityContextExecutor` constructor:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor();
|
||||
DelegatingSecurityContextExecutor executor =
|
||||
new DelegatingSecurityContextExecutor(delegateExecutor);
|
||||
----
|
||||
====
|
||||
|
||||
Now, any time `executor.execute(Runnable)` is run, 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.
|
||||
|
|
|
@ -8,8 +8,10 @@ If the request does not contain any cookies and Spring Security is first, the re
|
|||
The easiest way to ensure that CORS is handled first is to use the `CorsFilter`.
|
||||
Users can integrate the `CorsFilter` with Spring Security by providing a `CorsConfigurationSource` that uses the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -37,7 +39,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -64,11 +67,10 @@ open class WebSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following listing does the same thing in XML:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -79,12 +81,13 @@ The following listing does the same thing in XML:
|
|||
...
|
||||
</b:bean>
|
||||
----
|
||||
====
|
||||
|
||||
If you use Spring MVC's CORS support, you can omit specifying the `CorsConfigurationSource` and Spring Security uses the CORS configuration provided to Spring MVC:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -103,7 +106,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -121,11 +125,10 @@ open class WebSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following listing does the same thing in XML:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -134,4 +137,3 @@ The following listing does the same thing in XML:
|
|||
...
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
|
|
@ -9,8 +9,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`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -19,7 +21,8 @@ public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -27,24 +30,24 @@ 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 you can use Spring Security within your queries:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Repository
|
||||
|
@ -54,7 +57,8 @@ public interface MessageRepository extends PagingAndSortingRepository<Message,Lo
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Repository
|
||||
|
@ -63,7 +67,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,7 +6,6 @@ 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]):
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
@ -19,7 +18,6 @@ SecurityContext context = new SecurityContextImpl();
|
|||
// ...
|
||||
String json = mapper.writeValueAsString(context);
|
||||
----
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
|
|
@ -6,12 +6,10 @@ Spring Security has its own taglib, which provides basic support for accessing s
|
|||
== Declaring the Taglib
|
||||
To use any of the tags, you must have the security taglib declared in your JSP:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
|
||||
----
|
||||
====
|
||||
|
||||
[[taglibs-authorize]]
|
||||
== The authorize Tag
|
||||
|
@ -27,7 +25,6 @@ The first approach uses a xref:servlet/authorization/authorize-http-requests.ado
|
|||
The expression evaluation is delegated to the `SecurityExpressionHandler<FilterInvocation>` defined in the application context (you should have web expressions enabled in your `<http>` namespace configuration to make sure this service is available).
|
||||
So, for example, you might have:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<sec:authorize access="hasRole('supervisor')">
|
||||
|
@ -36,11 +33,9 @@ This content will only be visible to users who have the "supervisor" authority i
|
|||
|
||||
</sec:authorize>
|
||||
----
|
||||
====
|
||||
|
||||
When used in conjunction with Spring Security's `PermissionEvaluator`, the tag can also be used to check permissions:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<sec:authorize access="hasPermission(#domain,'read') or hasPermission(#domain,'write')">
|
||||
|
@ -49,14 +44,12 @@ This content will only be visible to users who have read or write permission to
|
|||
|
||||
</sec:authorize>
|
||||
----
|
||||
====
|
||||
|
||||
A common requirement is to show only a particular link, assuming the user is actually allowed to click it.
|
||||
How can we determine in advance whether something is allowed? This tag can also operate in an alternative mode that lets you define a particular URL as an attribute.
|
||||
If the user is allowed to invoke that URL, the tag body is evaluated. Otherwise, it is skipped.
|
||||
So you might have something like:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<sec:authorize url="/admin">
|
||||
|
@ -65,7 +58,6 @@ This content will only be visible to users who are authorized to send requests t
|
|||
|
||||
</sec:authorize>
|
||||
----
|
||||
====
|
||||
|
||||
To use this tag, you must also have an instance of `WebInvocationPrivilegeEvaluator` in your application context.
|
||||
If you are using the namespace, one is automatically registered.
|
||||
|
@ -111,7 +103,6 @@ Instead, use the <<taglibs-authorize>>.
|
|||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<sec:accesscontrollist hasPermission="1,2" domainObject="${someObject}">
|
||||
|
@ -120,7 +111,6 @@ The following listing shows an example:
|
|||
|
||||
</sec:accesscontrollist>
|
||||
----
|
||||
====
|
||||
|
||||
The permissions are passed to the `PermissionFactory` defined in the application context, converting them to ACL `Permission` instances, so they may be any format that is supported by the factory. They do not have to be integers. They could be strings such as `READ` or `WRITE`.
|
||||
If no `PermissionFactory` is found, an instance of `DefaultPermissionFactory` is used.
|
||||
|
@ -141,7 +131,6 @@ Do NOT place this tag within a Spring `<form:form></form:form>` block.
|
|||
Spring Security handles Spring forms automatically.
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<form method="post" action="/do/something">
|
||||
|
@ -151,7 +140,6 @@ The following listing shows an example:
|
|||
...
|
||||
</form>
|
||||
----
|
||||
====
|
||||
|
||||
[[taglibs-csrfmeta]]
|
||||
== The csrfMetaTags Tag
|
||||
|
@ -163,7 +151,6 @@ Once you use this tag, you can access the form field name, header name, and toke
|
|||
JQuery is used in this example to make the task easier.
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<!DOCTYPE html>
|
||||
|
@ -219,6 +206,5 @@ The following listing shows an example:
|
|||
</body>
|
||||
</html>
|
||||
----
|
||||
====
|
||||
|
||||
If CSRF protection is not enabled, `csrfMetaTags` outputs nothing.
|
||||
|
|
|
@ -12,7 +12,6 @@ Your `ApplicationContext` should refer to this, as Spring Security classes imple
|
|||
Usually, all you need to do is register a bean inside your application context to refer to the messages.
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<bean id="messageSource"
|
||||
|
@ -20,7 +19,6 @@ The following listing shows an example:
|
|||
<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.
|
||||
|
|
|
@ -32,7 +32,6 @@ This is necessary because Spring Security's `MvcRequestMatcher` expects a `Handl
|
|||
|
||||
For a `web.xml` file, this means that you should place your configuration in the `DispatcherServlet.xml`:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<listener>
|
||||
|
@ -60,12 +59,13 @@ For a `web.xml` file, this means that you should place your configuration in the
|
|||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
----
|
||||
====
|
||||
|
||||
The following `WebSecurityConfiguration` in placed in the `ApplicationContext` of the `DispatcherServlet`.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class SecurityInitializer extends
|
||||
|
@ -89,7 +89,8 @@ public class SecurityInitializer extends
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class SecurityInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
|
||||
|
@ -109,7 +110,7 @@ class SecurityInitializer : AbstractAnnotationConfigDispatcherServletInitializer
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -122,8 +123,10 @@ This is known as https://en.wikipedia.org/wiki/Defense_in_depth_(computing)[Defe
|
|||
|
||||
Consider a controller that is mapped as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RequestMapping("/admin")
|
||||
|
@ -132,7 +135,8 @@ public String admin() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@RequestMapping("/admin")
|
||||
|
@ -140,12 +144,14 @@ fun admin(): String {
|
|||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
To restrict access to this controller method to admin users, you can provide authorization rules by matching on the `HttpServletRequest` with the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -158,7 +164,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -171,18 +178,16 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
return http.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following listing does the same thing in XML:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
With either configuration, the `/admin` URL requires the authenticated user to be an admin user.
|
||||
However, depending on our Spring MVC configuration, the `/admin.html` URL also maps to our `admin()` method.
|
||||
|
@ -196,8 +201,10 @@ Therefore, it will protect the same URLs that Spring MVC will match on by using
|
|||
|
||||
One common requirement when using Spring MVC is to specify the servlet path property, for that you can use the `MvcRequestMatcher.Builder` to create multiple `MvcRequestMatcher` instances that share the same servlet path:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -212,7 +219,8 @@ public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospe
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -227,18 +235,16 @@ open fun filterChain(http: HttpSecurity, introspector: HandlerMappingIntrospecto
|
|||
return http.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The following XML has the same effect:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http request-matcher="mvc">
|
||||
<intercept-url pattern="/admin" access="hasRole('ADMIN')"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
[[mvc-authentication-principal]]
|
||||
== @AuthenticationPrincipal
|
||||
|
@ -247,7 +253,6 @@ Spring Security provides `AuthenticationPrincipalArgumentResolver`, which can au
|
|||
By using `@EnableWebSecurity`, you automatically have this added to your Spring MVC configuration.
|
||||
If you use XML-based configuration, you must add this yourself:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<mvc:annotation-driven>
|
||||
|
@ -256,14 +261,15 @@ If you use XML-based configuration, you must add this yourself:
|
|||
</mvc:argument-resolvers>
|
||||
</mvc:annotation-driven>
|
||||
----
|
||||
====
|
||||
|
||||
Once you have properly configured `AuthenticationPrincipalArgumentResolver`, you can entirely decouple from Spring Security in your Spring MVC layer.
|
||||
|
||||
Consider a situation where a custom `UserDetailsService` returns an `Object` that implements `UserDetails` and your own `CustomUser` `Object`. The `CustomUser` of the currently authenticated user could be accessed by using the following code:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RequestMapping("/messages/inbox")
|
||||
|
@ -276,7 +282,8 @@ public ModelAndView findMessagesForUser() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@RequestMapping("/messages/inbox")
|
||||
|
@ -287,12 +294,14 @@ open fun findMessagesForUser(): ModelAndView {
|
|||
// .. find messages for this user and return them ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
As of Spring Security 3.2, we can resolve the argument more directly by adding an annotation:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
|
@ -306,7 +315,8 @@ public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser cust
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@RequestMapping("/messages/inbox")
|
||||
|
@ -315,14 +325,16 @@ open fun findMessagesForUser(@AuthenticationPrincipal customUser: CustomUser?):
|
|||
// .. find messages for this user and return them ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Sometimes, you may need to transform the principal in some way.
|
||||
For example, if `CustomUser` needed to be final, it could not be extended.
|
||||
In this situation, the `UserDetailsService` might return an `Object` that implements `UserDetails` and provides a method named `getCustomUser` to access `CustomUser`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class CustomUserUserDetails extends User {
|
||||
|
@ -333,7 +345,8 @@ public class CustomUserUserDetails extends User {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class CustomUserUserDetails(
|
||||
|
@ -345,12 +358,14 @@ class CustomUserUserDetails(
|
|||
val customUser: CustomUser? = null
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
We could then access the `CustomUser` by using a https://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html[SpEL expression] that uses `Authentication.getPrincipal()` as the root object:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
|
@ -364,7 +379,8 @@ public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "c
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
|
@ -377,13 +393,15 @@ open fun findMessagesForUser(@AuthenticationPrincipal(expression = "customUser")
|
|||
// .. find messages for this user and return them ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
We can also refer to beans in our SpEL expressions.
|
||||
For example, we could use the following if we were using JPA to manage our users and if we wanted to modify and save a property on the current user:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
|
@ -401,7 +419,8 @@ public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntity
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
|
@ -420,7 +439,7 @@ open fun updateName(
|
|||
// ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
We can further remove our dependency on Spring Security by making `@AuthenticationPrincipal` a meta-annotation on our own annotation.
|
||||
The next example demonstrates how we could do so on an annotation named `@CurrentUser`.
|
||||
|
@ -431,8 +450,10 @@ To remove the dependency on Spring Security, it is the consuming application tha
|
|||
This step is not strictly required but assists in isolating your dependency to Spring Security to a more central location.
|
||||
====
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Target({ElementType.PARAMETER, ElementType.TYPE})
|
||||
|
@ -442,7 +463,8 @@ This step is not strictly required but assists in isolating your dependency to S
|
|||
public @interface CurrentUser {}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
|
||||
|
@ -451,13 +473,15 @@ public @interface CurrentUser {}
|
|||
@AuthenticationPrincipal
|
||||
annotation class CurrentUser
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
We have isolated our dependency on Spring Security to a single file.
|
||||
Now that `@CurrentUser` has been specified, we can use it to signal to resolve our `CustomUser` of the currently authenticated user:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RequestMapping("/messages/inbox")
|
||||
|
@ -467,7 +491,8 @@ public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@RequestMapping("/messages/inbox")
|
||||
|
@ -476,7 +501,7 @@ open fun findMessagesForUser(@CurrentUser customUser: CustomUser?): ModelAndView
|
|||
// .. find messages for this user and return them ...
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[mvc-async]]
|
||||
|
@ -486,8 +511,10 @@ Spring Web MVC 3.2+ has excellent support for https://docs.spring.io/spring/docs
|
|||
With no additional configuration, Spring Security automatically sets up the `SecurityContext` to the `Thread` that invokes a `Callable` returned by your controllers.
|
||||
For example, the following method automatically has its `Callable` invoked with the `SecurityContext` that was available when the `Callable` was created:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RequestMapping(method=RequestMethod.POST)
|
||||
|
@ -502,7 +529,8 @@ return new Callable<String>() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@RequestMapping(method = [RequestMethod.POST])
|
||||
|
@ -513,10 +541,10 @@ open fun processUpload(file: MultipartFile?): Callable<String> {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
.Associating SecurityContext to Callable's
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
More technically speaking, Spring Security integrates with `WebAsyncManager`.
|
||||
|
@ -537,7 +565,6 @@ Spring Security integrates with Spring MVC to add CSRF protection.
|
|||
Spring Security automatically xref:servlet/exploits/csrf.adoc#csrf-integration-form[include the CSRF Token] within forms that use the https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/view.html#view-jsp-formtaglib-formtag[Spring MVC form tag].
|
||||
Consider the following JSP:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
|
||||
|
@ -561,11 +588,9 @@ Consider the following JSP:
|
|||
</html>
|
||||
</jsp:root>
|
||||
----
|
||||
====
|
||||
|
||||
The preceding example output HTMLs that is similar to the following:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<!-- ... -->
|
||||
|
@ -577,7 +602,6 @@ The preceding example output HTMLs that is similar to the following:
|
|||
|
||||
<!-- ... -->
|
||||
----
|
||||
====
|
||||
|
||||
[[mvc-csrf-resolver]]
|
||||
=== Resolving the CsrfToken
|
||||
|
@ -588,8 +612,10 @@ If you use XML-based configuration, you must add this yourself.
|
|||
|
||||
Once `CsrfTokenArgumentResolver` is properly configured, you can expose the `CsrfToken` to your static HTML based application:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RestController
|
||||
|
@ -602,7 +628,8 @@ public class CsrfController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@RestController
|
||||
|
@ -613,7 +640,7 @@ class CsrfController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
It is important to keep the `CsrfToken` a secret from other domains.
|
||||
This means that, if you use https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS[Cross Origin Sharing (CORS)], you should *NOT* expose the `CsrfToken` to any external domains.
|
||||
|
|
|
@ -17,8 +17,10 @@ When an `ObservationRegistry` bean is present, Spring Security creates traces fo
|
|||
|
||||
For example, consider a simple Boot application:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@SpringBootApplication
|
||||
|
@ -45,7 +47,8 @@ public class MyApplication {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@SpringBootApplication
|
||||
|
@ -72,20 +75,17 @@ class MyApplication {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
And a corresponding request:
|
||||
|
||||
====
|
||||
[source,bash]
|
||||
----
|
||||
?> http -a user:password :8080
|
||||
----
|
||||
====
|
||||
|
||||
Will produce the following output (indentation added for clarity):
|
||||
|
||||
====
|
||||
[source,bash]
|
||||
----
|
||||
START - name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@687e16d1', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.001779024, duration(nanos)=1779024.0, startTimeNanos=91695917264958}']
|
||||
|
@ -106,15 +106,16 @@ START - name='http.server.requests', contextualName='null', error='null', lowCar
|
|||
STOP - name='spring.security.http.chains', contextualName='spring.security.http.chains.after', error='null', lowCardinalityKeyValues=[chain.position='17', chain.size='17', current.filter.name='DisableEncodeUrlFilter', filter.section='after'], highCardinalityKeyValues=[request.line='GET /'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@47af8207', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.007689382, duration(nanos)=7689382.0, startTimeNanos=91697152857268}']
|
||||
STOP - name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[request.line='GET /'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@687e16d1', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=1.245858319, duration(nanos)=1.245858319E9, startTimeNanos=91695917264958}']
|
||||
----
|
||||
====
|
||||
|
||||
[[observability-tracing-manual-configuration]]
|
||||
=== Manual Configuration
|
||||
|
||||
For a non-Spring Boot application, or to override the existing Boot configuration, you can publish your own `ObservationRegistry` and Spring Security will still pick it up.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@SpringBootApplication
|
||||
|
@ -143,7 +144,8 @@ public class MyApplication {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@SpringBootApplication
|
||||
|
@ -172,7 +174,8 @@ class MyApplication {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
<sec:http auto-config="true" observation-registry-ref="ref">
|
||||
|
@ -181,7 +184,7 @@ class MyApplication {
|
|||
|
||||
<!-- define and configure ObservationRegistry bean -->
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[observability-tracing-disable]]
|
||||
==== Disabling Observability
|
||||
|
@ -191,8 +194,10 @@ However, this may turn off observations for more than just Spring Security.
|
|||
|
||||
Instead, you can alter the provided `ObservationRegistry` with an `ObservationPredicate` like the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -202,7 +207,8 @@ ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -211,7 +217,7 @@ fun noSpringSecurityObservations(): ObservationRegistryCustomizer<ObservationReg
|
|||
(registry: ObservationRegistry) -> registry.observationConfig().observationPredicate(predicate)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
There is no facility for disabling observations with XML support.
|
||||
|
|
|
@ -25,8 +25,10 @@ For example, you might have created a custom `UserDetailsService` that returns a
|
|||
You could obtain this information with the following:
|
||||
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Authentication auth = httpServletRequest.getUserPrincipal();
|
||||
|
@ -37,7 +39,8 @@ String firstName = userDetails.getFirstName();
|
|||
String lastName = userDetails.getLastName();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val auth: Authentication = httpServletRequest.getUserPrincipal()
|
||||
|
@ -47,7 +50,7 @@ val userDetails: MyCustomUserDetails = auth.principal as MyCustomUserDetails
|
|||
val firstName: String = userDetails.firstName
|
||||
val lastName: String = userDetails.lastName
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -61,19 +64,22 @@ https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#
|
|||
Typically, users should not pass the `ROLE_` prefix to this method, since it is added automatically.
|
||||
For example, if you want to determine if the current user has the authority "ROLE_ADMIN", you could use the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
boolean isAdmin = httpServletRequest.isUserInRole("ADMIN");
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val isAdmin: Boolean = httpServletRequest.isUserInRole("ADMIN")
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This might be useful to determine if certain UI components should be displayed.
|
||||
For example, you might display admin links only if the current user is an admin.
|
||||
|
@ -94,8 +100,10 @@ If they are not authenticated, the configured `AuthenticationEntryPoint` is used
|
|||
You can use the https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#login%28java.lang.String,%20java.lang.String%29[`HttpServletRequest.login(String,String)`] method to authenticate the user with the current `AuthenticationManager`.
|
||||
For example, the following would attempt to authenticate with a username of `user` and a password of `password`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
try {
|
||||
|
@ -105,7 +113,8 @@ httpServletRequest.login("user","password");
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
try {
|
||||
|
@ -114,7 +123,7 @@ try {
|
|||
// fail to authenticate
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -136,8 +145,10 @@ The https://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html#start%2
|
|||
By using Spring Security's concurrency support, Spring Security overrides `AsyncContext.start(Runnable)` to ensure that the current `SecurityContext` is used when processing the Runnable.
|
||||
The following example outputs the current user's Authentication:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
final AsyncContext async = httpServletRequest.startAsync();
|
||||
|
@ -156,7 +167,8 @@ async.start(new Runnable() {
|
|||
});
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val async: AsyncContext = httpServletRequest.startAsync()
|
||||
|
@ -172,7 +184,7 @@ async.start {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servletapi-async]]
|
||||
=== Async Servlet Support
|
||||
|
@ -180,7 +192,6 @@ If you use Java-based configuration, you are ready to go.
|
|||
If you use XML configuration, a few updates are necessary.
|
||||
The first step is to ensure that you have updated your `web.xml` file to use at least the 3.0 schema:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
|
@ -190,11 +201,9 @@ version="3.0">
|
|||
|
||||
</web-app>
|
||||
----
|
||||
====
|
||||
|
||||
Next, you need to ensure that your `springSecurityFilterChain` is set up for processing asynchronous requests:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<filter>
|
||||
|
@ -211,7 +220,6 @@ Next, you need to ensure that your `springSecurityFilterChain` is set up for pro
|
|||
<dispatcher>ASYNC</dispatcher>
|
||||
</filter-mapping>
|
||||
----
|
||||
====
|
||||
|
||||
Now Spring Security ensures that your `SecurityContext` is propagated on asynchronous requests, too.
|
||||
|
||||
|
@ -221,8 +229,10 @@ Prior to Spring Security 3.2, the `SecurityContext` from the `SecurityContextHol
|
|||
This can cause issues in an asynchronous environment.
|
||||
Consider the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
httpServletRequest.startAsync();
|
||||
|
@ -242,7 +252,8 @@ new Thread("AsyncThread") {
|
|||
}.start();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
httpServletRequest.startAsync()
|
||||
|
@ -260,7 +271,7 @@ object : Thread("AsyncThread") {
|
|||
}
|
||||
}.start()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The issue is that this `Thread` is not known to Spring Security, so the `SecurityContext` is not propagated to it.
|
||||
This means that, when we commit the `HttpServletResponse`, there is no `SecurityContext`.
|
||||
|
|
|
@ -30,8 +30,10 @@ In Spring Security 5.8, this support has been refreshed to use the `Authorizatio
|
|||
To configure authorization using Java Configuration, simply include the `@EnableWebSocketSecurity` annotation and publish an `AuthorizationManager<Message<?>>` bean or in xref:servlet/appendix/namespace/websocket.adoc#nsa-websocket-security[XML] use the `use-authorization-manager` attribute.
|
||||
One way to do this is by using the `AuthorizationManagerMessageMatcherRegistry` to specify endpoint patterns like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -48,7 +50,8 @@ public class WebSocketSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -62,25 +65,28 @@ open class WebSocketSecurityConfig { // <1> <2>
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<websocket-message-broker use-authorization-manager="true"> <1> <2>
|
||||
<intercept-message pattern="/user/**" access="hasRole('USER')"/> <3>
|
||||
</websocket-message-broker>
|
||||
----
|
||||
======
|
||||
<1> Any inbound CONNECT message requires a valid CSRF token to enforce the <<websocket-sameorigin,Same Origin Policy>>.
|
||||
<2> The `SecurityContextHolder` is populated with the user within the `simpUser` header attribute for any inbound request.
|
||||
<3> Our messages require the proper authorization. Specifically, any inbound message that starts with `/user/` will require `ROLE_USER`. You can find additional details on authorization in <<websocket-authorization>>
|
||||
====
|
||||
|
||||
=== Custom Authorization
|
||||
|
||||
When using `AuthorizationManager`, customization is quite simple.
|
||||
For example, you can publish an `AuthorizationManager` that requires that all messages have a role of "USER" using `AuthorityAuthorizationManager`, as seen below:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -94,7 +100,8 @@ public class WebSocketSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -107,19 +114,22 @@ open class WebSocketSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<bean id="authorizationManager" class="org.example.MyAuthorizationManager"/>
|
||||
|
||||
<websocket-message-broker authorization-manager-ref="myAuthorizationManager"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
There are several ways to further match messages, as can be seen in a more advanced example below:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -140,7 +150,8 @@ public class WebSocketSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -159,7 +170,8 @@ open class WebSocketSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
<websocket-message-broker use-authorization-manager="true">
|
||||
|
@ -182,7 +194,7 @@ open class WebSocketSecurityConfig {
|
|||
<intercept-message pattern="/**" access="denyAll" /> <!--6-->
|
||||
</websocket-message-broker>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This will ensure that:
|
||||
|
||||
|
@ -284,19 +296,19 @@ Instead, we must include the token in the Stomp headers.
|
|||
Applications can xref:servlet/exploits/csrf.adoc#csrf-integration[obtain a CSRF token] by accessing the request attribute named `_csrf`.
|
||||
For example, the following allows accessing the `CsrfToken` in a JSP:
|
||||
|
||||
====
|
||||
[source,javascript]
|
||||
----
|
||||
var headerName = "${_csrf.headerName}";
|
||||
var token = "${_csrf.token}";
|
||||
----
|
||||
====
|
||||
|
||||
If you use static HTML, you can expose the `CsrfToken` on a REST endpoint.
|
||||
For example, the following would expose the `CsrfToken` on the `/csrf` URL:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RestController
|
||||
|
@ -309,7 +321,8 @@ public class CsrfController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@RestController
|
||||
|
@ -320,13 +333,12 @@ class CsrfController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The JavaScript can make a REST call to the endpoint and use the response to populate the `headerName` and the token.
|
||||
|
||||
We can now include the token in our Stomp client:
|
||||
|
||||
====
|
||||
[source,javascript]
|
||||
----
|
||||
...
|
||||
|
@ -337,7 +349,6 @@ stompClient.connect(headers, function(frame) {
|
|||
|
||||
})
|
||||
----
|
||||
====
|
||||
|
||||
[[websocket-sameorigin-disable]]
|
||||
=== Disable CSRF within WebSockets
|
||||
|
@ -345,8 +356,10 @@ NOTE: At this point, CSRF is not configurable when using `@EnableWebSocketSecuri
|
|||
|
||||
To disable CSRF, instead of using `@EnableWebSocketSecurity`, you can use XML support or add the Spring Security components yourself, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -368,7 +381,8 @@ public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -389,20 +403,23 @@ open class WebSocketSecurityConfig : WebSocketMessageBrokerConfigurer {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<websocket-message-broker use-authorization-manager="true" same-origin-disabled="true">
|
||||
<intercept-message pattern="/**" access="authenticated"/>
|
||||
</websocket-message-broker>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
On the other hand, if you are using the <<legacy-websocket-configuration,legacy `AbstractSecurityWebSocketMessageBrokerConfigurer`>> and you want to allow other domains to access your site, you can disable Spring Security's protection.
|
||||
For example, in Java Configuration you can use the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -417,7 +434,8 @@ public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBro
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -430,7 +448,7 @@ open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfi
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[websocket-expression-handler]]
|
||||
=== Custom Expression Handler
|
||||
|
@ -482,7 +500,6 @@ To allow SockJS frame-based transports to work, we need to configure Spring Secu
|
|||
You can customize `X-Frame-Options` with the xref:servlet/appendix/namespace/http.adoc#nsa-frame-options[frame-options] element.
|
||||
For example, the following instructs Spring Security to use `X-Frame-Options: SAMEORIGIN`, which allows iframes within the same domain:
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -494,12 +511,13 @@ For example, the following instructs Spring Security to use `X-Frame-Options: SA
|
|||
</headers>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
Similarly, you can customize frame options to use the same origin within Java Configuration by using the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -520,7 +538,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -540,7 +559,7 @@ open class WebSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[websocket-sockjs-csrf]]
|
||||
=== SockJS & Relaxing CSRF
|
||||
|
@ -559,8 +578,10 @@ We can easily achieve this by providing a CSRF `RequestMatcher`.
|
|||
Our Java configuration makes this easy.
|
||||
For example, if our stomp endpoint is `/chat`, we can disable CSRF protection only for URLs that start with `/chat/` by using the following configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -588,7 +609,8 @@ public class WebSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -613,11 +635,10 @@ open class WebSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If we use XML-based configuration, we can use thexref:servlet/appendix/namespace/http.adoc#nsa-csrf-request-matcher-ref[csrf@request-matcher-ref].
|
||||
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http ...>
|
||||
|
@ -649,8 +670,10 @@ If we use XML-based configuration, we can use thexref:servlet/appendix/namespace
|
|||
Before Spring Security 5.8, the way to configure messaging authorization using Java Configuration, was to extend the `AbstractSecurityWebSocketMessageBrokerConfigurer` and configure the `MessageSecurityMetadataSourceRegistry`.
|
||||
For example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -664,7 +687,8 @@ public class WebSocketSecurityConfig
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -674,7 +698,7 @@ open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfi
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This will ensure that:
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ The default implementation `DefaultOAuth2AuthorizationRequestResolver` matches o
|
|||
|
||||
Consider the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
====
|
||||
[source,yaml,attrs="-attributes"]
|
||||
----
|
||||
spring:
|
||||
|
@ -48,7 +47,6 @@ spring:
|
|||
authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize
|
||||
token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
|
||||
----
|
||||
====
|
||||
|
||||
Given the preceding properties, a request with the base path `/oauth2/authorization/okta` initiates the Authorization Request redirect by the `OAuth2AuthorizationRequestRedirectFilter` and ultimately starts the Authorization Code grant flow.
|
||||
|
||||
|
@ -60,7 +58,6 @@ which also initiates the Authorization Request redirect by the `OAuth2Authorizat
|
|||
|
||||
If the OAuth 2.0 Client is a https://tools.ietf.org/html/rfc6749#section-2.1[Public Client], configure the OAuth 2.0 Client registration as follows:
|
||||
|
||||
====
|
||||
[source,yaml,attrs="-attributes"]
|
||||
----
|
||||
spring:
|
||||
|
@ -75,7 +72,6 @@ spring:
|
|||
redirect-uri: "{baseUrl}/authorized/okta"
|
||||
...
|
||||
----
|
||||
====
|
||||
|
||||
Public Clients are supported by using https://tools.ietf.org/html/rfc7636[Proof Key for Code Exchange] (PKCE).
|
||||
If the client is running in an untrusted environment (such as a native application or web browser-based application) and is therefore incapable of maintaining the confidentiality of its credentials, PKCE is automatically used when the following conditions are true:
|
||||
|
@ -91,7 +87,6 @@ The `DefaultOAuth2AuthorizationRequestResolver` also supports `URI` template var
|
|||
|
||||
The following configuration uses all the supported `URI` template variables:
|
||||
|
||||
====
|
||||
[source,yaml,attrs="-attributes"]
|
||||
----
|
||||
spring:
|
||||
|
@ -104,7 +99,6 @@ spring:
|
|||
redirect-uri: "{baseScheme}://{baseHost}{basePort}{basePath}/authorized/{registrationId}"
|
||||
...
|
||||
----
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -128,8 +122,10 @@ The `prompt` parameter is optional. Space delimited, case sensitive list of ASCI
|
|||
|
||||
The following example shows how to configure the `DefaultOAuth2AuthorizationRequestResolver` with a `Consumer<OAuth2AuthorizationRequest.Builder>` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -174,7 +170,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -216,13 +213,12 @@ class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
For the simple use case where the additional request parameter is always the same for a specific provider, you can add it directly in the `authorization-uri` property.
|
||||
|
||||
For example, if the value for the request parameter `prompt` is always `consent` for the provider `okta`, you can configure it as follows:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -233,7 +229,6 @@ spring:
|
|||
okta:
|
||||
authorization-uri: https://dev-1234.oktapreview.com/oauth2/v1/authorize?prompt=consent
|
||||
----
|
||||
====
|
||||
|
||||
The preceding example shows the common use case of adding a custom parameter on top of the standard parameters.
|
||||
Alternatively, if your requirements are more advanced, you can take full control in building the Authorization Request URI by overriding the `OAuth2AuthorizationRequest.authorizationRequestUri` property.
|
||||
|
@ -245,8 +240,10 @@ Alternatively, if your requirements are more advanced, you can take full control
|
|||
|
||||
The following example shows a variation of `authorizationRequestCustomizer()` from the preceding example and instead overrides the `OAuth2AuthorizationRequest.authorizationRequestUri` property:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer() {
|
||||
|
@ -256,7 +253,8 @@ private Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomi
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationRequest.Builder> {
|
||||
|
@ -269,7 +267,7 @@ private fun authorizationRequestCustomizer(): Consumer<OAuth2AuthorizationReques
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
=== Storing the Authorization Request
|
||||
|
@ -286,8 +284,10 @@ The default implementation of `AuthorizationRequestRepository` is `HttpSessionOA
|
|||
If you have a custom implementation of `AuthorizationRequestRepository`, you can configure it as follows:
|
||||
|
||||
.AuthorizationRequestRepository Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -317,7 +317,8 @@ public class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -338,7 +339,8 @@ class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -347,7 +349,7 @@ class OAuth2ClientSecurityConfig {
|
|||
</oauth2-client>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Requesting an Access Token
|
||||
|
||||
|
@ -384,8 +386,10 @@ The custom `Converter` must return a valid `RequestEntity` representation of an
|
|||
On the other end, if you need to customize the post-handling of the Token Response, you need to provide `DefaultAuthorizationCodeTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
|
||||
The default `RestOperations` is configured as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
|
@ -395,7 +399,8 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
|||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val restTemplate = RestTemplate(listOf(
|
||||
|
@ -404,7 +409,7 @@ val restTemplate = RestTemplate(listOf(
|
|||
|
||||
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -420,8 +425,10 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error
|
|||
Whether you customize `DefaultAuthorizationCodeTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you need to configure it as follows:
|
||||
|
||||
.Access Token Response Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -442,7 +449,8 @@ public class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -463,7 +471,8 @@ class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -472,7 +481,7 @@ class OAuth2ClientSecurityConfig {
|
|||
</oauth2-client>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[oauth2Client-refresh-token-grant]]
|
||||
|
@ -520,8 +529,10 @@ The custom `Converter` must return a valid `RequestEntity` representation of an
|
|||
On the other end, if you need to customize the post-handling of the Token Response, you need to provide `DefaultRefreshTokenTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
|
||||
The default `RestOperations` is configured as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
|
@ -531,7 +542,8 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
|||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val restTemplate = RestTemplate(listOf(
|
||||
|
@ -540,7 +552,7 @@ val restTemplate = RestTemplate(listOf(
|
|||
|
||||
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -555,8 +567,10 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error
|
|||
|
||||
Whether you customize `DefaultRefreshTokenTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you need to configure it as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -573,7 +587,8 @@ OAuth2AuthorizedClientProvider authorizedClientProvider =
|
|||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -588,7 +603,7 @@ val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
|||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -643,8 +658,10 @@ The custom `Converter` must return a valid `RequestEntity` representation of an
|
|||
On the other end, if you need to customize the post-handling of the Token Response, you need to provide `DefaultClientCredentialsTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
|
||||
The default `RestOperations` is configured as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
|
@ -654,7 +671,8 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
|||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val restTemplate = RestTemplate(listOf(
|
||||
|
@ -663,7 +681,7 @@ val restTemplate = RestTemplate(listOf(
|
|||
|
||||
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -678,8 +696,10 @@ It uses an `OAuth2ErrorHttpMessageConverter` to convert the OAuth 2.0 Error para
|
|||
|
||||
Whether you customize `DefaultClientCredentialsTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you need to configure it as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -695,7 +715,8 @@ OAuth2AuthorizedClientProvider authorizedClientProvider =
|
|||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -709,7 +730,7 @@ val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
|||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -721,7 +742,6 @@ which is an implementation of an `OAuth2AuthorizedClientProvider` for the Client
|
|||
|
||||
Consider the following Spring Boot 2.x properties for an OAuth 2.0 Client registration:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -738,12 +758,13 @@ spring:
|
|||
okta:
|
||||
token-uri: https://dev-1234.oktapreview.com/oauth2/v1/token
|
||||
----
|
||||
====
|
||||
|
||||
Further consider the following `OAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -765,7 +786,8 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -781,12 +803,14 @@ fun authorizedClientManager(
|
|||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Given the preceding properties and bean, you can obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -818,7 +842,8 @@ public class OAuth2ClientController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class OAuth2ClientController {
|
||||
|
@ -846,7 +871,7 @@ class OAuth2ClientController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -897,8 +922,10 @@ The custom `Converter` must return a valid `RequestEntity` representation of an
|
|||
On the other end, if you need to customize the post-handling of the Token Response, you need to provide `DefaultPasswordTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
|
||||
The default `RestOperations` is configured as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
|
@ -908,7 +935,8 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
|||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val restTemplate = RestTemplate(listOf(
|
||||
|
@ -917,7 +945,7 @@ val restTemplate = RestTemplate(listOf(
|
|||
|
||||
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -932,8 +960,10 @@ It uses an `OAuth2ErrorHttpMessageConverter` to convert the OAuth 2.0 Error para
|
|||
|
||||
Whether you customize `DefaultPasswordTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you need to configure it as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -950,7 +980,8 @@ OAuth2AuthorizedClientProvider authorizedClientProvider =
|
|||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val passwordTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> = ...
|
||||
|
@ -964,7 +995,7 @@ val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
|||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -995,8 +1026,10 @@ spring:
|
|||
|
||||
Further consider the `OAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1039,7 +1072,9 @@ private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesM
|
|||
};
|
||||
}
|
||||
----
|
||||
.Kotlin
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1077,12 +1112,14 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, MutableM
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Given the preceding properties and bean, you can obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -1114,7 +1151,8 @@ public class OAuth2ClientController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -1142,7 +1180,7 @@ class OAuth2ClientController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -1189,8 +1227,10 @@ If you prefer to only add additional parameters, you can provide `JwtBearerGrant
|
|||
On the other end, if you need to customize the post-handling of the Token Response, you will need to provide `DefaultJwtBearerTokenResponseClient.setRestOperations()` with a custom configured `RestOperations`.
|
||||
The default `RestOperations` is configured as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
|
@ -1200,7 +1240,8 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
|||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val restTemplate = RestTemplate(listOf(
|
||||
|
@ -1209,7 +1250,7 @@ val restTemplate = RestTemplate(listOf(
|
|||
|
||||
restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -1224,8 +1265,10 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error
|
|||
|
||||
Whether you customize `DefaultJwtBearerTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -1244,7 +1287,8 @@ OAuth2AuthorizedClientProvider authorizedClientProvider =
|
|||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
// Customize
|
||||
|
@ -1261,7 +1305,7 @@ val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
|
|||
|
||||
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Using the Access Token
|
||||
|
||||
|
@ -1286,8 +1330,10 @@ spring:
|
|||
|
||||
...and the `OAuth2AuthorizedClientManager` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1312,7 +1358,8 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1329,12 +1376,14 @@ fun authorizedClientManager(
|
|||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You may obtain the `OAuth2AccessToken` as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@RestController
|
||||
|
@ -1357,7 +1406,8 @@ public class OAuth2ResourceServerController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class OAuth2ResourceServerController {
|
||||
|
@ -1378,7 +1428,7 @@ class OAuth2ResourceServerController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
`JwtBearerOAuth2AuthorizedClientProvider` resolves the `Jwt` assertion via `OAuth2AuthorizationContext.getPrincipal().getPrincipal()` by default, hence the use of `JwtAuthenticationToken` in the preceding example.
|
||||
|
|
|
@ -11,8 +11,10 @@ The `@RegisteredOAuth2AuthorizedClient` annotation provides the ability to resol
|
|||
This is a convenient alternative compared to accessing the `OAuth2AuthorizedClient` by using the `OAuth2AuthorizedClientManager` or `OAuth2AuthorizedClientService`.
|
||||
The following example shows how to use `@RegisteredOAuth2AuthorizedClient`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -29,7 +31,8 @@ public class OAuth2ClientController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -44,7 +47,7 @@ class OAuth2ClientController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The `@RegisteredOAuth2AuthorizedClient` annotation is handled by `OAuth2AuthorizedClientArgumentResolver`, which directly uses an xref:servlet/oauth2/client/core.adoc#oauth2Client-authorized-manager-provider[`OAuth2AuthorizedClientManager`] and, therefore, inherits its capabilities.
|
||||
|
||||
|
@ -65,8 +68,10 @@ It directly uses an xref:servlet/oauth2/client/core.adoc#oauth2Client-authorized
|
|||
|
||||
The following code shows an example of how to configure `WebClient` with OAuth 2.0 Client support:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -79,7 +84,8 @@ WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -90,7 +96,7 @@ fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClien
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Providing the Authorized Client
|
||||
|
||||
|
@ -98,8 +104,10 @@ The `ServletOAuth2AuthorizedClientExchangeFilterFunction` determines the client
|
|||
|
||||
The following code shows how to set an `OAuth2AuthorizedClient` as a request attribute:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -120,7 +128,8 @@ public String index(@RegisteredOAuth2AuthorizedClient("okta") OAuth2AuthorizedCl
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -139,13 +148,15 @@ fun index(@RegisteredOAuth2AuthorizedClient("okta") authorizedClient: OAuth2Auth
|
|||
return "index"
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `oauth2AuthorizedClient()` is a `static` method in `ServletOAuth2AuthorizedClientExchangeFilterFunction`.
|
||||
====
|
||||
|
||||
The following code shows how to set the `ClientRegistration.getRegistrationId()` as a request attribute:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -166,7 +177,8 @@ public String index() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
|
@ -186,8 +198,8 @@ fun index(): String {
|
|||
return "index"
|
||||
}
|
||||
----
|
||||
======
|
||||
<1> `clientRegistrationId()` is a `static` method in `ServletOAuth2AuthorizedClientExchangeFilterFunction`.
|
||||
====
|
||||
|
||||
|
||||
=== Defaulting the Authorized Client
|
||||
|
@ -198,8 +210,10 @@ If `setDefaultOAuth2AuthorizedClient(true)` is configured and the user has authe
|
|||
|
||||
The following code shows the specific configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -213,7 +227,8 @@ WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -225,7 +240,7 @@ fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClien
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
|
@ -236,8 +251,10 @@ Alternatively, if `setDefaultClientRegistrationId("okta")` is configured with a
|
|||
|
||||
The following code shows the specific configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -251,7 +268,8 @@ WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -263,7 +281,7 @@ fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClien
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
|
|
|
@ -36,8 +36,10 @@ spring:
|
|||
|
||||
The following example shows how to configure `DefaultAuthorizationCodeTokenResponseClient`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
|
||||
|
@ -63,7 +65,8 @@ DefaultAuthorizationCodeTokenResponseClient tokenResponseClient =
|
|||
tokenResponseClient.setRequestEntityConverter(requestEntityConverter);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val jwkResolver: Function<ClientRegistration, JWK> =
|
||||
|
@ -88,7 +91,7 @@ requestEntityConverter.addParametersConverter(
|
|||
val tokenResponseClient = DefaultAuthorizationCodeTokenResponseClient()
|
||||
tokenResponseClient.setRequestEntityConverter(requestEntityConverter)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
=== Authenticate using `client_secret_jwt`
|
||||
|
@ -112,8 +115,10 @@ spring:
|
|||
|
||||
The following example shows how to configure `DefaultClientCredentialsTokenResponseClient`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
|
||||
|
@ -138,7 +143,8 @@ DefaultClientCredentialsTokenResponseClient tokenResponseClient =
|
|||
tokenResponseClient.setRequestEntityConverter(requestEntityConverter);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val jwkResolver = Function<ClientRegistration, JWK?> { clientRegistration: ClientRegistration ->
|
||||
|
@ -162,14 +168,16 @@ requestEntityConverter.addParametersConverter(
|
|||
val tokenResponseClient = DefaultClientCredentialsTokenResponseClient()
|
||||
tokenResponseClient.setRequestEntityConverter(requestEntityConverter)
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Customizing the JWT assertion
|
||||
|
||||
The JWT produced by `NimbusJwtClientAuthenticationParametersConverter` contains the `iss`, `sub`, `aud`, `jti`, `iat` and `exp` claims by default. You can customize the headers and/or claims by providing a `Consumer<NimbusJwtClientAuthenticationParametersConverter.JwtClientAuthenticationContext<T>>` to `setJwtClientAssertionCustomizer()`. The following example shows how to customize claims of the JWT:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Function<ClientRegistration, JWK> jwkResolver = ...
|
||||
|
@ -182,7 +190,8 @@ converter.setJwtClientAssertionCustomizer((context) -> {
|
|||
});
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val jwkResolver = ...
|
||||
|
@ -194,4 +203,4 @@ converter.setJwtClientAssertionCustomizer { context ->
|
|||
context.claims.claim("custom-claim", "claim-value")
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -12,7 +12,6 @@ A `ClientRegistration` object holds information, such as client id, client secre
|
|||
|
||||
`ClientRegistration` and its properties are defined as follows:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public final class ClientRegistration {
|
||||
|
@ -66,26 +65,28 @@ This information is available only if the Spring Boot 2.x property `spring.secur
|
|||
<15> `(userInfoEndpoint)authenticationMethod`: The authentication method used when sending the access token to the UserInfo Endpoint.
|
||||
The supported values are *header*, *form*, and *query*.
|
||||
<16> `userNameAttributeName`: The name of the attribute returned in the UserInfo Response that references the Name or Identifier of the end-user.
|
||||
====
|
||||
|
||||
You can initially configure a `ClientRegistration` by using discovery of an OpenID Connect Provider's https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig[Configuration endpoint] or an Authorization Server's https://tools.ietf.org/html/rfc8414#section-3[Metadata endpoint].
|
||||
|
||||
`ClientRegistrations` provides convenience methods for configuring a `ClientRegistration` in this way, as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
ClientRegistration clientRegistration =
|
||||
ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val clientRegistration = ClientRegistrations.fromIssuerLocation("https://idp.example.com/issuer").build()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The preceding code queries, in series, `https://idp.example.com/issuer/.well-known/openid-configuration`, `https://idp.example.com/.well-known/openid-configuration/issuer`, and `https://idp.example.com/.well-known/oauth-authorization-server/issuer`, stopping at the first to return a 200 response.
|
||||
|
||||
|
@ -113,8 +114,10 @@ The auto-configuration also registers the `ClientRegistrationRepository` as a `@
|
|||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -135,7 +138,8 @@ public class OAuth2ClientController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -155,7 +159,7 @@ class OAuth2ClientController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2Client-authorized-client]]
|
||||
== OAuth2AuthorizedClient
|
||||
|
@ -175,8 +179,10 @@ From a developer perspective, the `OAuth2AuthorizedClientRepository` or `OAuth2A
|
|||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -199,7 +205,8 @@ public class OAuth2ClientController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -220,7 +227,7 @@ class OAuth2ClientController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -258,8 +265,10 @@ You can use `OAuth2AuthorizedClientProviderBuilder` to configure and build the d
|
|||
|
||||
The following code shows an example of how to configure and build an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials`, and `password` authorization grant types:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -284,7 +293,8 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -303,7 +313,7 @@ fun authorizedClientManager(
|
|||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
When an authorization attempt succeeds, the `DefaultOAuth2AuthorizedClientManager` delegates to the `OAuth2AuthorizationSuccessHandler`, which (by default) saves the `OAuth2AuthorizedClient` through the `OAuth2AuthorizedClientRepository`.
|
||||
In the case of a re-authorization failure (for example, a refresh token is no longer valid), the previously saved `OAuth2AuthorizedClient` is removed from the `OAuth2AuthorizedClientRepository` through the `RemoveAuthorizedClientOAuth2AuthorizationFailureHandler`.
|
||||
|
@ -314,8 +324,10 @@ This can be useful when you need to supply an `OAuth2AuthorizedClientProvider` w
|
|||
|
||||
The following code shows an example of the `contextAttributesMapper`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -359,7 +371,8 @@ private Function<OAuth2AuthorizeRequest, Map<String, Object>> contextAttributesM
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -397,7 +410,7 @@ private fun contextAttributesMapper(): Function<OAuth2AuthorizeRequest, MutableM
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The `DefaultOAuth2AuthorizedClientManager` is designed to be used _within_ the context of a `HttpServletRequest`.
|
||||
When operating _outside_ of a `HttpServletRequest` context, use `AuthorizedClientServiceOAuth2AuthorizedClientManager` instead.
|
||||
|
@ -408,8 +421,10 @@ An OAuth 2.0 Client configured with the `client_credentials` grant type can be c
|
|||
|
||||
The following code shows an example of how to configure an `AuthorizedClientServiceOAuth2AuthorizedClientManager` that provides support for the `client_credentials` grant type:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -431,7 +446,8 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -447,4 +463,4 @@ fun authorizedClientManager(
|
|||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -25,8 +25,10 @@ In addition, `HttpSecurity.oauth2Client().authorizationCodeGrant()` enables the
|
|||
The following code shows the complete configuration options provided by the `HttpSecurity.oauth2Client()` DSL:
|
||||
|
||||
.OAuth2 Client Configuration Options
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -51,7 +53,8 @@ public class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -76,14 +79,13 @@ class OAuth2ClientSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In addition to the `HttpSecurity.oauth2Client()` DSL, XML configuration is also supported.
|
||||
|
||||
The following code shows the complete configuration options available in the xref:servlet/appendix/namespace/http.adoc#nsa-oauth2-client[ security namespace]:
|
||||
|
||||
.OAuth2 Client XML Configuration Options
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -97,14 +99,15 @@ The following code shows the complete configuration options available in the xre
|
|||
</oauth2-client>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
The `OAuth2AuthorizedClientManager` is responsible for managing the authorization (or re-authorization) of an OAuth 2.0 Client, in collaboration with one or more `OAuth2AuthorizedClientProvider`(s).
|
||||
|
||||
The following code shows an example of how to register an `OAuth2AuthorizedClientManager` `@Bean` and associate it with an `OAuth2AuthorizedClientProvider` composite that provides support for the `authorization_code`, `refresh_token`, `client_credentials`, and `password` authorization grant types:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -129,7 +132,8 @@ public OAuth2AuthorizedClientManager authorizedClientManager(
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -148,4 +152,4 @@ fun authorizedClientManager(
|
|||
return authorizedClientManager
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -9,8 +9,10 @@ For example, `oauth2Login().authorizationEndpoint()` allows configuring the _Aut
|
|||
The following code shows an example:
|
||||
|
||||
.Advanced OAuth2 Login Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -39,7 +41,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -68,7 +71,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The main goal of the `oauth2Login()` DSL was to closely align with the naming, as defined in the specifications.
|
||||
|
||||
|
@ -92,8 +95,10 @@ These claims are normally represented by a JSON object that contains a collectio
|
|||
The following code shows the complete configuration options available for the `oauth2Login()` DSL:
|
||||
|
||||
.OAuth2 Login Configuration Options
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -130,7 +135,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -167,14 +173,13 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In addition to the `oauth2Login()` DSL, XML configuration is also supported.
|
||||
|
||||
The following code shows the complete configuration options available in the xref:servlet/appendix/namespace/http.adoc#nsa-oauth2-login[ security namespace]:
|
||||
|
||||
.OAuth2 Login XML Configuration Options
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<http>
|
||||
|
@ -194,7 +199,6 @@ The following code shows the complete configuration options available in the xre
|
|||
jwt-decoder-factory-ref="jwtDecoderFactory"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
|
||||
The following sections go into more detail on each of the configuration options available:
|
||||
|
||||
|
@ -223,20 +227,20 @@ The link's destination for each OAuth Client defaults to the following:
|
|||
|
||||
The following line shows an example:
|
||||
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<a href="/oauth2/authorization/google">Google</a>
|
||||
----
|
||||
====
|
||||
|
||||
To override the default login page, configure `oauth2Login().loginPage()` and (optionally) `oauth2Login().authorizationEndpoint().baseUri()`.
|
||||
|
||||
The following listing shows an example:
|
||||
|
||||
.OAuth2 Login Page Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -259,7 +263,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -281,7 +286,8 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -290,7 +296,7 @@ class OAuth2LoginSecurityConfig {
|
|||
/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
|
@ -303,12 +309,10 @@ As noted earlier, configuring `oauth2Login().authorizationEndpoint().baseUri()`
|
|||
However, if you choose to customize it, ensure the link to each OAuth Client matches the `authorizationEndpoint().baseUri()`.
|
||||
|
||||
The following line shows an example:
|
||||
====
|
||||
[source,html]
|
||||
----
|
||||
<a href="/login/oauth2/authorization/google">Google</a>
|
||||
----
|
||||
====
|
||||
=====
|
||||
|
||||
|
||||
|
@ -328,8 +332,10 @@ The default Authorization Response `baseUri` (redirection endpoint) is `*/login/
|
|||
If you would like to customize the Authorization Response `baseUri`, configure it as follows:
|
||||
|
||||
.Redirection Endpoint Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -350,7 +356,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -371,7 +378,8 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -380,7 +388,7 @@ class OAuth2LoginSecurityConfig {
|
|||
/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[IMPORTANT]
|
||||
=====
|
||||
|
@ -388,8 +396,10 @@ You also need to ensure the `ClientRegistration.redirectUri` matches the custom
|
|||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",subs="-attributes"]
|
||||
----
|
||||
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
||||
|
@ -399,7 +409,8 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
|||
.build();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",subs="-attributes"]
|
||||
----
|
||||
return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
||||
|
@ -408,7 +419,7 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google")
|
|||
.redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}")
|
||||
.build()
|
||||
----
|
||||
====
|
||||
======
|
||||
=====
|
||||
|
||||
|
||||
|
@ -445,8 +456,10 @@ The `GrantedAuthoritiesMapper` is given a list of granted authorities which cont
|
|||
Provide an implementation of `GrantedAuthoritiesMapper` and configure it, as follows:
|
||||
|
||||
.Granted Authorities Mapper Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -496,7 +509,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -536,7 +550,8 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -545,13 +560,15 @@ class OAuth2LoginSecurityConfig {
|
|||
/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Alternatively, you can register a `GrantedAuthoritiesMapper` `@Bean` to have it automatically applied to the configuration, as follows:
|
||||
|
||||
.Granted Authorities Mapper Bean Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -572,7 +589,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -593,7 +611,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2login-advanced-map-authorities-oauth2userservice]]
|
||||
==== Delegation-based Strategy with OAuth2UserService
|
||||
|
@ -605,8 +623,10 @@ The `OAuth2UserRequest` (and `OidcUserRequest`) provides you access to the assoc
|
|||
The following example shows how to implement and configure a delegation-based strategy using an OpenID Connect 1.0 UserService:
|
||||
|
||||
.OAuth2UserService Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -648,7 +668,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -690,7 +711,8 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -699,7 +721,7 @@ class OAuth2LoginSecurityConfig {
|
|||
/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[oauth2login-advanced-oauth2-user-service]]
|
||||
|
@ -720,21 +742,21 @@ The default implementation `OAuth2UserRequestEntityConverter` builds a `RequestE
|
|||
On the other end, if you need to customize the post-handling of the UserInfo Response, you need to provide `DefaultOAuth2UserService.setRestOperations()` with a custom configured `RestOperations`.
|
||||
The default `RestOperations` is configured as follows:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
----
|
||||
====
|
||||
|
||||
`OAuth2ErrorResponseErrorHandler` is a `ResponseErrorHandler` that can handle an OAuth 2.0 Error (400 Bad Request).
|
||||
It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error parameters to an `OAuth2Error`.
|
||||
|
||||
Whether you customize `DefaultOAuth2UserService` or provide your own implementation of `OAuth2UserService`, you need to configure it as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -759,7 +781,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -784,7 +807,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[oauth2login-advanced-oidc-user-service]]
|
||||
|
@ -798,8 +821,10 @@ If you need to customize the pre-processing of the UserInfo Request or the post-
|
|||
|
||||
Whether you customize `OidcUserService` or provide your own implementation of `OAuth2UserService` for OpenID Connect 1.0 Provider's, you need to configure it as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -824,7 +849,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -849,7 +875,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[oauth2login-advanced-idtoken-verify]]
|
||||
|
@ -866,8 +892,10 @@ The JWS algorithm resolver is a `Function` that accepts a `ClientRegistration` a
|
|||
|
||||
The following code shows how to configure the `OidcIdTokenDecoderFactory` `@Bean` to default to `MacAlgorithm.HS256` for all `ClientRegistration` instances:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -878,7 +906,8 @@ public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -888,7 +917,7 @@ fun idTokenDecoderFactory(): JwtDecoderFactory<ClientRegistration?> {
|
|||
return idTokenDecoderFactory
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -910,7 +939,6 @@ One of the strategies available is https://openid.net/specs/openid-connect-rpini
|
|||
If the OpenID Provider supports both Session Management and https://openid.net/specs/openid-connect-discovery-1_0.html[Discovery], the client can obtain the `end_session_endpoint` `URL` from the OpenID Provider's https://openid.net/specs/openid-connect-session-1_0.html#OPMetadata[Discovery Metadata].
|
||||
You can do so by configuring the `ClientRegistration` with the `issuer-uri`, as follows:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -926,12 +954,13 @@ spring:
|
|||
okta:
|
||||
issuer-uri: https://dev-1234.oktapreview.com
|
||||
----
|
||||
====
|
||||
|
||||
Also, you can configure `OidcClientInitiatedLogoutSuccessHandler`, which implements RP-Initiated Logout, as follows:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -967,7 +996,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -1000,7 +1030,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
|
|
@ -69,10 +69,8 @@ spring:
|
|||
----
|
||||
+
|
||||
.OAuth Client properties
|
||||
====
|
||||
<1> `spring.security.oauth2.client.registration` is the base property prefix for OAuth Client properties.
|
||||
<2> Following the base property prefix is the ID for the xref:servlet/oauth2/client/index.adoc#oauth2Client-client-registration[`ClientRegistration`], such as Google.
|
||||
====
|
||||
|
||||
. Replace the values in the `client-id` and `client-secret` property with the OAuth 2.0 credentials you created earlier.
|
||||
|
||||
|
@ -164,7 +162,6 @@ As demonstrated previously, when we <<oauth2login-sample-application-config,conf
|
|||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -176,7 +173,6 @@ spring:
|
|||
client-id: google-client-id
|
||||
client-secret: google-client-secret
|
||||
----
|
||||
====
|
||||
|
||||
[TIP]
|
||||
The auto-defaulting of client properties works seamlessly here because the `registrationId` (`google`) matches the `GOOGLE` `enum` (case-insensitive) in `CommonOAuth2Provider`.
|
||||
|
@ -185,7 +181,6 @@ For cases where you may want to specify a different `registrationId`, such as `g
|
|||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -200,7 +195,6 @@ spring:
|
|||
----
|
||||
<1> The `registrationId` is set to `google-login`.
|
||||
<2> The `provider` property is set to `google`, which will leverage the auto-defaulting of client properties set in `CommonOAuth2Provider.GOOGLE.getBuilder()`.
|
||||
====
|
||||
|
||||
[[oauth2login-custom-provider-properties]]
|
||||
== Configuring Custom Provider Properties
|
||||
|
@ -213,7 +207,6 @@ For these cases, Spring Boot 2.x provides the following base property for config
|
|||
|
||||
The following listing shows an example:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -233,7 +226,6 @@ spring:
|
|||
jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys
|
||||
----
|
||||
<1> The base property (`spring.security.oauth2.client.provider.okta`) allows for custom configuration of protocol endpoint locations.
|
||||
====
|
||||
|
||||
[[oauth2login-override-boot-autoconfig]]
|
||||
== Overriding Spring Boot 2.x Auto-configuration
|
||||
|
@ -256,8 +248,10 @@ If you need to override the auto-configuration based on your specific requiremen
|
|||
|
||||
The following example shows how to register a `ClientRegistrationRepository` `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -287,7 +281,8 @@ public class OAuth2LoginConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -315,7 +310,7 @@ class OAuth2LoginConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[oauth2login-provide-securityfilterchain-bean]]
|
||||
|
@ -324,8 +319,10 @@ class OAuth2LoginConfig {
|
|||
The following example shows how to register a `SecurityFilterChain` `@Bean` with `@EnableWebSecurity` and enable OAuth 2.0 login through `httpSecurity.oauth2Login()`:
|
||||
|
||||
.OAuth2 Login Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -344,7 +341,8 @@ public class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -362,7 +360,7 @@ class OAuth2LoginSecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[oauth2login-completely-override-autoconfiguration]]
|
||||
|
@ -371,8 +369,10 @@ class OAuth2LoginSecurityConfig {
|
|||
The following example shows how to completely override the auto-configuration by registering a `ClientRegistrationRepository` `@Bean` and a `SecurityFilterChain` `@Bean`.
|
||||
|
||||
.Overriding the auto-configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -412,7 +412,8 @@ public class OAuth2LoginConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary",attrs="-attributes"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -452,7 +453,7 @@ class OAuth2LoginConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[[oauth2login-javaconfig-wo-boot]]
|
||||
|
@ -461,8 +462,10 @@ class OAuth2LoginConfig {
|
|||
If you are not able to use Spring Boot 2.x and would like to configure one of the pre-defined providers in `CommonOAuth2Provider` (for example, Google), apply the following configuration:
|
||||
|
||||
.OAuth2 Login Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -505,7 +508,8 @@ public class OAuth2LoginConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -550,7 +554,8 @@ open class OAuth2LoginConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http auto-config="true">
|
||||
|
@ -574,4 +579,4 @@ open class OAuth2LoginConfig {
|
|||
<b:constructor-arg ref="authorizedClientService"/>
|
||||
</b:bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -12,8 +12,10 @@ For example, you may have a need to read the bearer token from a custom header.
|
|||
To achieve this, you can expose a `DefaultBearerTokenResolver` as a bean, or wire an instance into the DSL, as you can see in the following example:
|
||||
|
||||
.Custom Bearer Token Header
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -24,7 +26,8 @@ BearerTokenResolver bearerTokenResolver() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -35,7 +38,8 @@ fun bearerTokenResolver(): BearerTokenResolver {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -47,7 +51,7 @@ fun bearerTokenResolver(): BearerTokenResolver {
|
|||
<property name="bearerTokenHeaderName" value="Proxy-Authorization"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Or, in circumstances where a provider is using both a custom header and value, you can use `HeaderBearerTokenResolver` instead.
|
||||
|
||||
|
@ -56,8 +60,10 @@ Or, in circumstances where a provider is using both a custom header and value, y
|
|||
Or, you may wish to read the token from a form parameter, which you can do by configuring the `DefaultBearerTokenResolver`, as you can see below:
|
||||
|
||||
.Form Parameter Bearer Token
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
|
||||
|
@ -68,7 +74,8 @@ http
|
|||
);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val resolver = DefaultBearerTokenResolver()
|
||||
|
@ -80,7 +87,8 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -92,15 +100,17 @@ http {
|
|||
<property name="allowFormEncodedBodyParameter" value="true"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
== Bearer Token Propagation
|
||||
|
||||
Now that your resource server has validated the token, it might be handy to pass it to downstream services.
|
||||
This is quite simple with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServletBearerExchangeFilterFunction.html[ServletBearerExchangeFilterFunction]`, which you can see in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -111,7 +121,8 @@ public WebClient rest() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -121,15 +132,17 @@ fun rest(): WebClient {
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
When the above `WebClient` is used to perform requests, Spring Security will look up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential.
|
||||
Then, it will propagate that token in the `Authorization` header.
|
||||
|
||||
For example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
this.rest.get()
|
||||
|
@ -139,7 +152,8 @@ this.rest.get()
|
|||
.block()
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
this.rest.get()
|
||||
|
@ -148,14 +162,16 @@ this.rest.get()
|
|||
.bodyToMono<String>()
|
||||
.block()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Will invoke the `https://other-service.example.com/endpoint`, adding the bearer token `Authorization` header for you.
|
||||
|
||||
In places where you need to override this behavior, it's a simple matter of supplying the header yourself, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
this.rest.get()
|
||||
|
@ -166,7 +182,8 @@ this.rest.get()
|
|||
.block()
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
this.rest.get()
|
||||
|
@ -176,7 +193,7 @@ this.rest.get()
|
|||
.bodyToMono<String>()
|
||||
.block()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain.
|
||||
|
||||
|
@ -188,8 +205,10 @@ To obtain this level of support, please use the OAuth 2.0 Client filter.
|
|||
|
||||
There is no `RestTemplate` equivalent for `ServletBearerExchangeFilterFunction` at the moment, but you can propagate the request's bearer token quite simply with your own interceptor:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -213,7 +232,8 @@ RestTemplate rest() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -236,7 +256,7 @@ fun rest(): RestTemplate {
|
|||
return rest
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
||||
[NOTE]
|
||||
|
@ -259,8 +279,10 @@ WWW-Authenticate: Bearer error_code="invalid_token", error_description="Unsuppor
|
|||
|
||||
Additionally, it is published as an `AuthenticationFailureBadCredentialsEvent`, which you can xref:servlet/authentication/events.adoc#servlet-events[listen for in your application] like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -274,7 +296,8 @@ public class FailureEvents {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -287,4 +310,4 @@ class FailureEvents {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -164,8 +164,10 @@ There are two ``@Bean``s that Spring Boot generates on Resource Server's behalf.
|
|||
The first is a `SecurityFilterChain` that configures the app as a resource server. When including `spring-security-oauth2-jose`, this `SecurityFilterChain` looks like:
|
||||
|
||||
.Default JWT Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -179,7 +181,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -195,15 +198,17 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
return http.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If the application doesn't expose a `SecurityFilterChain` bean, then Spring Boot will expose the above default one.
|
||||
|
||||
Replacing this is as simple as exposing the bean within the application:
|
||||
|
||||
.Custom JWT Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -226,7 +231,8 @@ public class MyCustomSecurityConfiguration {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -249,7 +255,7 @@ class MyCustomSecurityConfiguration {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The above requires the scope of `message:read` for any URL that starts with `/messages/`.
|
||||
|
||||
|
@ -259,8 +265,10 @@ Methods on the `oauth2ResourceServer` DSL will also override or replace auto con
|
|||
For example, the second `@Bean` Spring Boot creates is a `JwtDecoder`, which <<oauth2resourceserver-jwt-architecture-jwtdecoder,decodes `String` tokens into validated instances of `Jwt`>>:
|
||||
|
||||
.JWT Decoder
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -269,7 +277,8 @@ public JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -277,7 +286,7 @@ fun jwtDecoder(): JwtDecoder {
|
|||
return JwtDecoders.fromIssuerLocation(issuerUri)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
Calling `{security-api-url}org/springframework/security/oauth2/jwt/JwtDecoders.html#fromIssuerLocation-java.lang.String-[JwtDecoders#fromIssuerLocation]` is what invokes the Provider Configuration or Authorization Server Metadata endpoint in order to derive the JWK Set Uri.
|
||||
|
@ -291,8 +300,10 @@ Or, if you're not using Spring Boot at all, then both of these components - the
|
|||
The filter chain is specified like so:
|
||||
|
||||
.Default JWT Configuration
|
||||
====
|
||||
.Xml
|
||||
[tabs]
|
||||
======
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -302,13 +313,15 @@ The filter chain is specified like so:
|
|||
</oauth2-resource-server>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
And the `JwtDecoder` like so:
|
||||
|
||||
.JWT Decoder
|
||||
====
|
||||
.Xml
|
||||
[tabs]
|
||||
======
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<bean id="jwtDecoder"
|
||||
|
@ -317,7 +330,7 @@ And the `JwtDecoder` like so:
|
|||
<constructor-arg value="${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-jwkseturi-dsl]]
|
||||
=== Using `jwkSetUri()`
|
||||
|
@ -325,8 +338,10 @@ And the `JwtDecoder` like so:
|
|||
An authorization server's JWK Set Uri can be configured <<oauth2resourceserver-jwt-jwkseturi,as a configuration property>> or it can be supplied in the DSL:
|
||||
|
||||
.JWK Set Uri Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -348,7 +363,8 @@ public class DirectlyConfiguredJwkSetUri {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -371,7 +387,8 @@ class DirectlyConfiguredJwkSetUri {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -381,7 +398,7 @@ class DirectlyConfiguredJwkSetUri {
|
|||
</oauth2-resource-server>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Using `jwkSetUri()` takes precedence over any configuration property.
|
||||
|
||||
|
@ -391,8 +408,10 @@ Using `jwkSetUri()` takes precedence over any configuration property.
|
|||
More powerful than `jwkSetUri()` is `decoder()`, which will completely replace any Boot auto configuration of <<oauth2resourceserver-jwt-architecture-jwtdecoder,`JwtDecoder`>>:
|
||||
|
||||
.JWT Decoder Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -414,7 +433,8 @@ public class DirectlyConfiguredJwtDecoder {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -437,7 +457,8 @@ class DirectlyConfiguredJwtDecoder {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -447,7 +468,7 @@ class DirectlyConfiguredJwtDecoder {
|
|||
</oauth2-resource-server>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This is handy when deeper configuration, like <<oauth2resourceserver-jwt-validation,validation>>, <<oauth2resourceserver-jwt-claimsetmapping,mapping>>, or <<oauth2resourceserver-jwt-timeouts,request timeouts>>, is necessary.
|
||||
|
||||
|
@ -457,8 +478,10 @@ This is handy when deeper configuration, like <<oauth2resourceserver-jwt-validat
|
|||
Or, exposing a <<oauth2resourceserver-jwt-architecture-jwtdecoder,`JwtDecoder`>> `@Bean` has the same effect as `decoder()`.
|
||||
You can construct one with a `jwkSetUri` like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -467,7 +490,8 @@ public JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -475,12 +499,14 @@ fun jwtDecoder(): JwtDecoder {
|
|||
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
or you can use the issuer and have `NimbusJwtDecoder` look up the `jwkSetUri` when `build()` is invoked, like the following:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -489,7 +515,8 @@ public JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -497,12 +524,14 @@ fun jwtDecoder(): JwtDecoder {
|
|||
return NimbusJwtDecoder.withIssuerLocation(issuer).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Or, if the defaults work for you, you can also use `JwtDecoders`, which does the above in addition to configuring the decoder's validator:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -511,7 +540,8 @@ public JwtDecoders jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -519,7 +549,7 @@ fun jwtDecoder(): JwtDecoders {
|
|||
return JwtDecoders.fromIssuerLocation(issuer)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-decoder-algorithm]]
|
||||
== Configuring Trusted Algorithms
|
||||
|
@ -549,8 +579,10 @@ spring:
|
|||
|
||||
For greater power, though, we can use a builder that ships with `NimbusJwtDecoder`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -560,7 +592,8 @@ JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -569,12 +602,14 @@ fun jwtDecoder(): JwtDecoder {
|
|||
.jwsAlgorithm(RS512).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Calling `jwsAlgorithm` more than once will configure `NimbusJwtDecoder` to trust more than one algorithm, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -584,7 +619,8 @@ JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -593,12 +629,14 @@ fun jwtDecoder(): JwtDecoder {
|
|||
.jwsAlgorithm(RS512).jwsAlgorithm(ES512).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Or, you can call `jwsAlgorithms`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -611,7 +649,8 @@ JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -623,7 +662,7 @@ fun jwtDecoder(): JwtDecoder {
|
|||
}.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-decoder-jwk-response]]
|
||||
=== From JWK Set response
|
||||
|
@ -633,8 +672,10 @@ Since Spring Security's JWT support is based off of Nimbus, you can use all it's
|
|||
For example, Nimbus has a `JWSKeySelector` implementation that will select the set of algorithms based on the JWK Set URI response.
|
||||
You can use it to generate a `NimbusJwtDecoder` like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -651,7 +692,8 @@ public JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -663,7 +705,7 @@ fun jwtDecoder(): JwtDecoder {
|
|||
return NimbusJwtDecoder(jwtProcessor)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-decoder-public-key]]
|
||||
== Trusting a Single Asymmetric Key
|
||||
|
@ -689,8 +731,10 @@ spring:
|
|||
|
||||
Or, to allow for a more sophisticated lookup, you can post-process the `RsaKeyConversionServicePostProcessor`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -701,7 +745,8 @@ BeanFactoryPostProcessor conversionServiceCustomizer() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -712,7 +757,7 @@ fun conversionServiceCustomizer(): BeanFactoryPostProcessor {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Specify your key's location:
|
||||
|
||||
|
@ -723,29 +768,34 @@ key.location: hfds://my-key.pub
|
|||
|
||||
And then autowire the value:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Value("${key.location}")
|
||||
RSAPublicKey key;
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Value("\${key.location}")
|
||||
val key: RSAPublicKey? = null
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-decoder-public-key-builder]]
|
||||
=== Using a Builder
|
||||
|
||||
To wire an `RSAPublicKey` directly, you can simply use the appropriate `NimbusJwtDecoder` builder, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -754,7 +804,8 @@ public JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -762,7 +813,7 @@ fun jwtDecoder(): JwtDecoder {
|
|||
return NimbusJwtDecoder.withPublicKey(this.key).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-decoder-secret-key]]
|
||||
== Trusting a Single Symmetric Key
|
||||
|
@ -770,8 +821,10 @@ fun jwtDecoder(): JwtDecoder {
|
|||
Using a single symmetric key is also simple.
|
||||
You can simply load in your `SecretKey` and use the appropriate `NimbusJwtDecoder` builder, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -780,7 +833,8 @@ public JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -788,7 +842,7 @@ fun jwtDecoder(): JwtDecoder {
|
|||
return NimbusJwtDecoder.withSecretKey(key).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-authorization]]
|
||||
== Configuring Authorization
|
||||
|
@ -802,8 +856,10 @@ When this is the case, Resource Server will attempt to coerce these scopes into
|
|||
This means that to protect an endpoint or method with a scope derived from a JWT, the corresponding expressions should include this prefix:
|
||||
|
||||
.Authorization Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -823,7 +879,8 @@ public class DirectlyConfiguredJwkSetUri {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -846,7 +903,8 @@ class DirectlyConfiguredJwkSetUri {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -857,25 +915,28 @@ class DirectlyConfiguredJwkSetUri {
|
|||
</oauth2-resource-server>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Or similarly with method security:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
public List<Message> getMessages(...) {}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
fun getMessages(): List<Message> { }
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-authorization-extraction]]
|
||||
=== Extracting Authorities Manually
|
||||
|
@ -893,8 +954,10 @@ Let's say that that your authorization server communicates authorities in a cust
|
|||
In that case, you can configure the claim that <<oauth2resourceserver-jwt-architecture-jwtauthenticationconverter,`JwtAuthenticationConverter`>> should inspect, like so:
|
||||
|
||||
.Authorities Claim Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -908,7 +971,8 @@ public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -922,7 +986,8 @@ fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -944,14 +1009,16 @@ fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
|
|||
<property name="authoritiesClaimName" value="authorities"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
You can also configure the authority prefix to be different as well.
|
||||
Instead of prefixing each authority with `SCOPE_`, you can change it to `ROLE_` like so:
|
||||
|
||||
.Authorities Prefix Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -965,7 +1032,8 @@ public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -979,7 +1047,8 @@ fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -1001,14 +1070,16 @@ fun jwtAuthenticationConverter(): JwtAuthenticationConverter {
|
|||
<property name="authorityPrefix" value="ROLE_"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Or, you can remove the prefix altogether by calling `JwtGrantedAuthoritiesConverter#setAuthorityPrefix("")`.
|
||||
|
||||
For more flexibility, the DSL supports entirely replacing the converter with any class that implements `Converter<Jwt, AbstractAuthenticationToken>`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
static class CustomAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
|
||||
|
@ -1038,7 +1109,8 @@ public class CustomAuthenticationConverterConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
internal class CustomAuthenticationConverter : Converter<Jwt, AbstractAuthenticationToken> {
|
||||
|
@ -1068,7 +1140,7 @@ class CustomAuthenticationConverterConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-validation]]
|
||||
== Configuring Validation
|
||||
|
@ -1087,8 +1159,10 @@ This can cause some implementation heartburn as the number of collaborating serv
|
|||
|
||||
Resource Server uses `JwtTimestampValidator` to verify a token's validity window, and it can be configured with a `clockSkew` to alleviate the above problem:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1106,7 +1180,8 @@ JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1122,7 +1197,7 @@ fun jwtDecoder(): JwtDecoder {
|
|||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
By default, Resource Server configures a clock skew of 60 seconds.
|
||||
|
@ -1132,8 +1207,10 @@ By default, Resource Server configures a clock skew of 60 seconds.
|
|||
|
||||
Adding a check for <<_supplying_audiences, the `aud` claim>> is simple with the `OAuth2TokenValidator` API:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
OAuth2TokenValidator<Jwt> audienceValidator() {
|
||||
|
@ -1141,19 +1218,22 @@ OAuth2TokenValidator<Jwt> audienceValidator() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
fun audienceValidator(): OAuth2TokenValidator<Jwt?> {
|
||||
return JwtClaimValidator<List<String>>(AUD) { aud -> aud.contains("messaging") }
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Or, for more control you can implement your own `OAuth2TokenValidator`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
static class AudienceValidator implements OAuth2TokenValidator<Jwt> {
|
||||
|
@ -1176,7 +1256,8 @@ OAuth2TokenValidator<Jwt> audienceValidator() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
internal class AudienceValidator : OAuth2TokenValidator<Jwt> {
|
||||
|
@ -1197,12 +1278,14 @@ fun audienceValidator(): OAuth2TokenValidator<Jwt> {
|
|||
return AudienceValidator()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Then, to add into a resource server, it's a matter of specifying the <<oauth2resourceserver-jwt-architecture-jwtdecoder,`JwtDecoder`>> instance:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1220,7 +1303,8 @@ JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1236,7 +1320,7 @@ fun jwtDecoder(): JwtDecoder {
|
|||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
As stated earlier, you can instead <<_supplying_audiences, configure `aud` validation in Boot>>.
|
||||
|
@ -1273,8 +1357,10 @@ By default, `MappedJwtClaimSetConverter` will attempt to coerce claims into the
|
|||
|
||||
An individual claim's conversion strategy can be configured using `MappedJwtClaimSetConverter.withDefaults`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1289,7 +1375,8 @@ JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1303,7 +1390,7 @@ fun jwtDecoder(): JwtDecoder {
|
|||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
This will keep all the defaults, except it will override the default claim converter for `sub`.
|
||||
|
||||
[[oauth2resourceserver-jwt-claimsetmapping-add]]
|
||||
|
@ -1311,46 +1398,54 @@ This will keep all the defaults, except it will override the default claim conve
|
|||
|
||||
`MappedJwtClaimSetConverter` can also be used to add a custom claim, for example, to adapt to an existing system:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("custom", custom -> "value"));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
MappedJwtClaimSetConverter.withDefaults(mapOf("custom" to Converter<Any, String> { "value" }))
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-claimsetmapping-remove]]
|
||||
=== Removing a Claim
|
||||
|
||||
And removing a claim is also simple, using the same API:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("legacyclaim", legacy -> null));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
MappedJwtClaimSetConverter.withDefaults(mapOf("legacyclaim" to Converter<Any, Any> { null }))
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-claimsetmapping-rename]]
|
||||
=== Renaming a Claim
|
||||
|
||||
In more sophisticated scenarios, like consulting multiple claims at once or renaming a claim, Resource Server accepts any class that implements `Converter<Map<String, Object>, Map<String,Object>>`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class UsernameSubClaimAdapter implements Converter<Map<String, Object>, Map<String, Object>> {
|
||||
|
@ -1368,7 +1463,8 @@ public class UsernameSubClaimAdapter implements Converter<Map<String, Object>, M
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class UsernameSubClaimAdapter : Converter<Map<String, Any?>, Map<String, Any?>> {
|
||||
|
@ -1381,12 +1477,14 @@ class UsernameSubClaimAdapter : Converter<Map<String, Any?>, Map<String, Any?>>
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
And then, the instance can be supplied like normal:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1397,7 +1495,8 @@ JwtDecoder jwtDecoder() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1407,7 +1506,7 @@ fun jwtDecoder(): JwtDecoder {
|
|||
return jwtDecoder
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-jwt-timeouts]]
|
||||
== Configuring Timeouts
|
||||
|
@ -1419,8 +1518,10 @@ Further, it doesn't take into account more sophisticated patterns like back-off
|
|||
|
||||
To adjust the way in which Resource Server connects to the authorization server, `NimbusJwtDecoder` accepts an instance of `RestOperations`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1435,7 +1536,8 @@ public JwtDecoder jwtDecoder(RestTemplateBuilder builder) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1447,15 +1549,17 @@ fun jwtDecoder(builder: RestTemplateBuilder): JwtDecoder {
|
|||
return NimbusJwtDecoder.withIssuerLocation(issuer).restOperations(rest).build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Also by default, Resource Server caches in-memory the authorization server's JWK set for 5 minutes, which you may want to adjust.
|
||||
Further, it doesn't take into account more sophisticated caching patterns like eviction or using a shared cache.
|
||||
|
||||
To adjust the way in which Resource Server caches the JWK set, `NimbusJwtDecoder` accepts an instance of `Cache`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1466,7 +1570,8 @@ public JwtDecoder jwtDecoder(CacheManager cacheManager) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -1476,7 +1581,7 @@ fun jwtDecoder(cacheManager: CacheManager): JwtDecoder {
|
|||
.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
When given a `Cache`, Resource Server will use the JWK Set Uri as the key and the JWK Set JSON as the value.
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@ For example, you may support more than one tenant where one tenant issues JWTs a
|
|||
|
||||
If this decision must be made at request-time, then you can use an `AuthenticationManagerResolver` to achieve it, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -22,7 +24,8 @@ AuthenticationManagerResolver<HttpServletRequest> tokenAuthenticationManagerReso
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -41,15 +44,17 @@ fun tokenAuthenticationManagerResolver
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
NOTE: The implementation of `useJwt(HttpServletRequest)` will likely depend on custom request material like the path.
|
||||
|
||||
And then specify this `AuthenticationManagerResolver` in the DSL:
|
||||
|
||||
.Authentication Manager Resolver
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
http
|
||||
|
@ -61,7 +66,8 @@ http
|
|||
);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
http {
|
||||
|
@ -74,14 +80,15 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
<oauth2-resource-server authentication-manager-resolver-ref="tokenAuthenticationManagerResolver"/>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-multitenancy]]
|
||||
== Multi-tenancy
|
||||
|
@ -101,8 +108,10 @@ In each case, there are two things that need to be done and trade-offs associate
|
|||
One way to differentiate tenants is by the issuer claim. Since the issuer claim accompanies signed JWTs, this can be done with the `JwtIssuerAuthenticationManagerResolver`, like so:
|
||||
|
||||
.Multi-tenancy Tenant by JWT Claim
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver
|
||||
|
@ -117,7 +126,8 @@ http
|
|||
);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val customAuthenticationManagerResolver = JwtIssuerAuthenticationManagerResolver
|
||||
|
@ -132,7 +142,8 @@ http {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -149,7 +160,7 @@ http {
|
|||
</constructor-arg>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This is nice because the issuer endpoints are loaded lazily.
|
||||
In fact, the corresponding `JwtAuthenticationProvider` is instantiated only when the first request with the corresponding issuer is sent.
|
||||
|
@ -160,8 +171,10 @@ This allows for an application startup that is independent from those authorizat
|
|||
Of course, you may not want to restart the application each time a new tenant is added.
|
||||
In this case, you can configure the `JwtIssuerAuthenticationManagerResolver` with a repository of `AuthenticationManager` instances, which you can edit at runtime, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
private void addManager(Map<String, AuthenticationManager> authenticationManagers, String issuer) {
|
||||
|
@ -184,7 +197,8 @@ http
|
|||
);
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
private fun addManager(authenticationManagers: MutableMap<String, AuthenticationManager>, issuer: String) {
|
||||
|
@ -207,7 +221,7 @@ http {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
In this case, you construct `JwtIssuerAuthenticationManagerResolver` with a strategy for obtaining the `AuthenticationManager` given the issuer.
|
||||
This approach allows us to add and remove elements from the repository (shown as a `Map` in the snippet) at runtime.
|
||||
|
@ -221,8 +235,10 @@ You may have observed that this strategy, while simple, comes with the trade-off
|
|||
|
||||
This extra parsing can be alleviated by configuring the xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-architecture-jwtdecoder[`JwtDecoder`] directly with a `JWTClaimsSetAwareJWSKeySelector` from Nimbus:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -264,7 +280,8 @@ public class TenantJWSKeySelector
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -301,7 +318,7 @@ class TenantJWSKeySelector(tenants: TenantRepository) : JWTClaimsSetAwareJWSKeyS
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
<1> A hypothetical source for tenant information
|
||||
<2> A cache for `JWKKeySelector`s, keyed by tenant identifier
|
||||
<3> Looking up the tenant is more secure than simply calculating the JWK Set endpoint on the fly - the lookup acts as a list of allowed tenants
|
||||
|
@ -315,8 +332,10 @@ Without this, you have no guarantee that the issuer hasn't been altered by a bad
|
|||
|
||||
Next, we can construct a `JWTProcessor`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -328,7 +347,8 @@ JWTProcessor jwtProcessor(JWTClaimSetJWSKeySelector keySelector) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -338,7 +358,7 @@ fun jwtProcessor(keySelector: JWTClaimsSetAwareJWSKeySelector<SecurityContext>):
|
|||
return jwtProcessor
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
As you are already seeing, the trade-off for moving tenant-awareness down to this level is more configuration.
|
||||
We have just a bit more.
|
||||
|
@ -346,8 +366,10 @@ We have just a bit more.
|
|||
Next, we still want to make sure you are validating the issuer.
|
||||
But, since the issuer may be different per JWT, then you'll need a tenant-aware validator, too:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -378,7 +400,8 @@ public class TenantJwtIssuerValidator implements OAuth2TokenValidator<Jwt> {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Component
|
||||
|
@ -406,12 +429,14 @@ class TenantJwtIssuerValidator(tenants: TenantRepository) : OAuth2TokenValidator
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Now that we have a tenant-aware processor and a tenant-aware validator, we can proceed with creating our xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-architecture-jwtdecoder[`JwtDecoder`]:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -424,7 +449,8 @@ JwtDecoder jwtDecoder(JWTProcessor jwtProcessor, OAuth2TokenValidator<Jwt> jwtVa
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -435,7 +461,7 @@ fun jwtDecoder(jwtProcessor: JWTProcessor<SecurityContext>?, jwtValidator: OAuth
|
|||
return decoder
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
We've finished talking about resolving the tenant.
|
||||
|
||||
|
|
|
@ -106,8 +106,10 @@ Once a token is authenticated, an instance of `BearerTokenAuthentication` is set
|
|||
|
||||
This means that it's available in `@Controller` methods when using `@EnableWebMvc` in your configuration:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
|
@ -116,7 +118,8 @@ public String foo(BearerTokenAuthentication authentication) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
|
@ -124,12 +127,14 @@ fun foo(authentication: BearerTokenAuthentication): String {
|
|||
return authentication.tokenAttributes["sub"].toString() + " is the subject"
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Since `BearerTokenAuthentication` holds an `OAuth2AuthenticatedPrincipal`, that also means that it's available to controller methods, too:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
|
@ -138,7 +143,8 @@ public String foo(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal principa
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@GetMapping("/foo")
|
||||
|
@ -146,7 +152,7 @@ fun foo(@AuthenticationPrincipal principal: OAuth2AuthenticatedPrincipal): Strin
|
|||
return principal.getAttribute<Any>("sub").toString() + " is the subject"
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
=== Looking Up Attributes Via SpEL
|
||||
|
||||
|
@ -154,8 +160,10 @@ Of course, this also means that attributes can be accessed via SpEL.
|
|||
|
||||
For example, if using `@EnableGlobalMethodSecurity` so that you can use `@PreAuthorize` annotations, you can do:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PreAuthorize("principal?.attributes['sub'] == 'foo'")
|
||||
|
@ -164,7 +172,8 @@ public String forFoosEyesOnly() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreAuthorize("principal?.attributes['sub'] == 'foo'")
|
||||
|
@ -172,7 +181,7 @@ fun forFoosEyesOnly(): String {
|
|||
return "foo"
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-opaque-sansboot]]
|
||||
== Overriding or Replacing Boot Auto Configuration
|
||||
|
@ -183,8 +192,10 @@ The first is a `SecurityFilterChain` that configures the app as a resource serve
|
|||
When use Opaque Token, this `SecurityFilterChain` looks like:
|
||||
|
||||
.Default Opaque Token Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -198,7 +209,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -214,15 +226,17 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
return http.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If the application doesn't expose a `SecurityFilterChain` bean, then Spring Boot will expose the above default one.
|
||||
|
||||
Replacing this is as simple as exposing the bean within the application:
|
||||
|
||||
.Custom Opaque Token Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -245,7 +259,8 @@ public class MyCustomSecurityConfiguration {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -268,7 +283,7 @@ class MyCustomSecurityConfiguration {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The above requires the scope of `message:read` for any URL that starts with `/messages/`.
|
||||
|
||||
|
@ -277,8 +292,10 @@ Methods on the `oauth2ResourceServer` DSL will also override or replace auto con
|
|||
[[oauth2resourceserver-opaque-introspector]]
|
||||
For example, the second `@Bean` Spring Boot creates is an `OpaqueTokenIntrospector`, <<oauth2resourceserver-opaque-architecture-introspector,which decodes `String` tokens into validated instances of `OAuth2AuthenticatedPrincipal`>>:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -287,7 +304,8 @@ public OpaqueTokenIntrospector introspector() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -295,7 +313,7 @@ fun introspector(): OpaqueTokenIntrospector {
|
|||
return NimbusOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If the application doesn't expose an <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> bean, then Spring Boot will expose the above default one.
|
||||
|
||||
|
@ -308,8 +326,10 @@ Or, if you're not using Spring Boot at all, then all of these components - the f
|
|||
The filter chain is specified like so:
|
||||
|
||||
.Default Opaque Token Configuration
|
||||
====
|
||||
.Xml
|
||||
[tabs]
|
||||
======
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -320,13 +340,15 @@ The filter chain is specified like so:
|
|||
</oauth2-resource-server>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
And the <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> like so:
|
||||
|
||||
.Opaque Token Introspector
|
||||
====
|
||||
.Xml
|
||||
[tabs]
|
||||
======
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<bean id="opaqueTokenIntrospector"
|
||||
|
@ -336,19 +358,21 @@ And the <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntr
|
|||
<constructor-arg value="${spring.security.oauth2.resourceserver.opaquetoken.client_secret}"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
And the `OpaqueTokenAuthenticationConverter` like so:
|
||||
|
||||
.Opaque Token Authentication Converter
|
||||
====
|
||||
.Xml
|
||||
[tabs]
|
||||
======
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="primary"]
|
||||
----
|
||||
<bean id="opaqueTokenAuthenticationConverter"
|
||||
class="com.example.CustomOpaqueTokenAuthenticationConverter"/>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-opaque-introspectionuri-dsl]]
|
||||
=== Using `introspectionUri()`
|
||||
|
@ -356,8 +380,10 @@ And the `OpaqueTokenAuthenticationConverter` like so:
|
|||
An authorization server's Introspection Uri can be configured <<oauth2resourceserver-opaque-introspectionuri,as a configuration property>> or it can be supplied in the DSL:
|
||||
|
||||
.Introspection URI Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -380,7 +406,8 @@ public class DirectlyConfiguredIntrospectionUri {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -404,7 +431,8 @@ class DirectlyConfiguredIntrospectionUri {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<bean id="opaqueTokenIntrospector"
|
||||
|
@ -414,7 +442,7 @@ class DirectlyConfiguredIntrospectionUri {
|
|||
<constructor-arg value="secret"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Using `introspectionUri()` takes precedence over any configuration property.
|
||||
|
||||
|
@ -424,8 +452,10 @@ Using `introspectionUri()` takes precedence over any configuration property.
|
|||
More powerful than `introspectionUri()` is `introspector()`, which will completely replace any Boot auto configuration of <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>>:
|
||||
|
||||
.Introspector Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -447,7 +477,8 @@ public class DirectlyConfiguredIntrospector {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -470,7 +501,8 @@ class DirectlyConfiguredIntrospector {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -480,7 +512,7 @@ class DirectlyConfiguredIntrospector {
|
|||
</oauth2-resource-server>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This is handy when deeper configuration, like <<oauth2resourceserver-opaque-authorization-extraction,authority mapping>>, <<oauth2resourceserver-opaque-jwt-introspector,JWT revocation>>, or <<oauth2resourceserver-opaque-timeouts,request timeouts>>, is necessary.
|
||||
|
||||
|
@ -509,8 +541,10 @@ When this is the case, Resource Server will attempt to coerce these scopes into
|
|||
This means that to protect an endpoint or method with a scope derived from an Opaque Token, the corresponding expressions should include this prefix:
|
||||
|
||||
.Authorization Opaque Token Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -530,7 +564,8 @@ public class MappedAuthorities {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -553,7 +588,8 @@ class MappedAuthorities {
|
|||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
Xml::
|
||||
+
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<http>
|
||||
|
@ -564,25 +600,28 @@ class MappedAuthorities {
|
|||
</oauth2-resource-server>
|
||||
</http>
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Or similarly with method security:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
public List<Message> getMessages(...) {}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
fun getMessages(): List<Message?> {}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-opaque-authorization-extraction]]
|
||||
=== Extracting Authorities Manually
|
||||
|
@ -603,8 +642,10 @@ Then Resource Server would generate an `Authentication` with two authorities, on
|
|||
|
||||
This can, of course, be customized using a custom <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> that takes a look at the attribute set and converts in its own way:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class CustomAuthoritiesOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
||||
|
@ -626,7 +667,8 @@ public class CustomAuthoritiesOpaqueTokenIntrospector implements OpaqueTokenIntr
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class CustomAuthoritiesOpaqueTokenIntrospector : OpaqueTokenIntrospector {
|
||||
|
@ -644,12 +686,14 @@ class CustomAuthoritiesOpaqueTokenIntrospector : OpaqueTokenIntrospector {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Thereafter, this custom introspector can be configured simply by exposing it as a `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -658,7 +702,8 @@ public OpaqueTokenIntrospector introspector() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -666,7 +711,7 @@ fun introspector(): OpaqueTokenIntrospector {
|
|||
return CustomAuthoritiesOpaqueTokenIntrospector()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-opaque-timeouts]]
|
||||
== Configuring Timeouts
|
||||
|
@ -678,8 +723,10 @@ Further, it doesn't take into account more sophisticated patterns like back-off
|
|||
|
||||
To adjust the way in which Resource Server connects to the authorization server, `NimbusOpaqueTokenIntrospector` accepts an instance of `RestOperations`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -694,7 +741,8 @@ public OpaqueTokenIntrospector introspector(RestTemplateBuilder builder, OAuth2R
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -707,7 +755,7 @@ fun introspector(builder: RestTemplateBuilder, properties: OAuth2ResourceServerP
|
|||
return NimbusOpaqueTokenIntrospector(introspectionUri, rest)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-opaque-jwt-introspector]]
|
||||
== Using Introspection with JWTs
|
||||
|
@ -739,8 +787,10 @@ Now what?
|
|||
|
||||
In this case, you can create a custom <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> that still hits the endpoint, but then updates the returned principal to have the JWTs claims as the attributes:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class JwtOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
||||
|
@ -767,7 +817,8 @@ public class JwtOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class JwtOpaqueTokenIntrospector : OpaqueTokenIntrospector {
|
||||
|
@ -790,12 +841,14 @@ class JwtOpaqueTokenIntrospector : OpaqueTokenIntrospector {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Thereafter, this custom introspector can be configured simply by exposing it as a `@Bean`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -804,7 +857,8 @@ public OpaqueTokenIntrospector introspector() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -812,7 +866,7 @@ fun introspector(): OpaqueTokenIntrospector {
|
|||
return JwtOpaqueTokenIntrospector()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[oauth2resourceserver-opaque-userinfo]]
|
||||
== Calling a `/userinfo` Endpoint
|
||||
|
@ -828,8 +882,10 @@ This implementation below does three things:
|
|||
* Looks up the appropriate client registration associated with the `/userinfo` endpoint
|
||||
* Invokes and returns the response from the `/userinfo` endpoint
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
||||
|
@ -854,7 +910,8 @@ public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class UserInfoOpaqueTokenIntrospector : OpaqueTokenIntrospector {
|
||||
|
@ -875,13 +932,15 @@ class UserInfoOpaqueTokenIntrospector : OpaqueTokenIntrospector {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If you aren't using `spring-security-oauth2-client`, it's still quite simple.
|
||||
You will simply need to invoke the `/userinfo` with your own instance of `WebClient`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
||||
|
@ -897,7 +956,8 @@ public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
class UserInfoOpaqueTokenIntrospector : OpaqueTokenIntrospector {
|
||||
|
@ -910,12 +970,14 @@ class UserInfoOpaqueTokenIntrospector : OpaqueTokenIntrospector {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Either way, having created your <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>>, you should publish it as a `@Bean` to override the defaults:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -924,7 +986,8 @@ OpaqueTokenIntrospector introspector() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -932,4 +995,4 @@ fun introspector(): OpaqueTokenIntrospector {
|
|||
return UserInfoOpaqueTokenIntrospector(...)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
|
|
@ -23,8 +23,10 @@ By default, Spring Security uses an `HttpSessionSaml2AuthenticationRequestReposi
|
|||
|
||||
If you have a custom implementation of `Saml2AuthenticationRequestRepository`, you may configure it by exposing it as a `@Bean` as shown in the following example:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -33,7 +35,8 @@ Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authent
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -41,7 +44,7 @@ open fun authenticationRequestRepository(): Saml2AuthenticationRequestRepository
|
|||
return CustomSaml2AuthenticationRequestRepository()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-saml2login-sp-initiated-factory-signing]]
|
||||
== Changing How the `<saml2:AuthnRequest>` Gets Sent
|
||||
|
@ -53,8 +56,10 @@ This can be configured automatically via `RelyingPartyRegistrations`, or you can
|
|||
|
||||
|
||||
.Not Requiring Signed AuthnRequests
|
||||
====
|
||||
.Boot
|
||||
[tabs]
|
||||
======
|
||||
Boot::
|
||||
+
|
||||
[source,yaml,role="primary"]
|
||||
----
|
||||
spring:
|
||||
|
@ -67,7 +72,8 @@ spring:
|
|||
singlesignon.sign-request: false
|
||||
----
|
||||
|
||||
.Java
|
||||
Java::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
|
||||
|
@ -79,7 +85,8 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit
|
|||
.build();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
var relyingPartyRegistration: RelyingPartyRegistration =
|
||||
|
@ -91,7 +98,7 @@ var relyingPartyRegistration: RelyingPartyRegistration =
|
|||
}
|
||||
.build();
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Otherwise, you will need to specify a private key to `RelyingPartyRegistration#signingX509Credentials` so that Spring Security can sign the `<saml2:AuthnRequest>` before sending.
|
||||
|
||||
|
@ -102,8 +109,10 @@ You can configure the algorithm based on the asserting party's xref:servlet/saml
|
|||
|
||||
Or, you can provide it manually:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
String metadataLocation = "classpath:asserting-party-metadata.xml";
|
||||
|
@ -116,7 +125,8 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations.fr
|
|||
.build();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
var metadataLocation = "classpath:asserting-party-metadata.xml"
|
||||
|
@ -133,7 +143,7 @@ var relyingPartyRegistration: RelyingPartyRegistration =
|
|||
}
|
||||
.build();
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
NOTE: The snippet above uses the OpenSAML `SignatureConstants` class to supply the algorithm name.
|
||||
But, that's just for convenience.
|
||||
|
@ -143,8 +153,10 @@ Since the datatype is `String`, you can supply the name of the algorithm directl
|
|||
Some asserting parties require that the `<saml2:AuthnRequest>` be POSTed.
|
||||
This can be configured automatically via `RelyingPartyRegistrations`, or you can supply it manually, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("okta")
|
||||
|
@ -156,7 +168,8 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit
|
|||
.build();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
var relyingPartyRegistration: RelyingPartyRegistration? =
|
||||
|
@ -168,7 +181,7 @@ var relyingPartyRegistration: RelyingPartyRegistration? =
|
|||
}
|
||||
.build()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-saml2login-sp-initiated-factory-custom-authnrequest]]
|
||||
== Customizing OpenSAML's `AuthnRequest` Instance
|
||||
|
@ -178,8 +191,10 @@ For example, you may want `ForceAuthN` to be set to `true`, which Spring Securit
|
|||
|
||||
You can customize elements of OpenSAML's `AuthnRequest` by publishing an `OpenSaml4AuthenticationRequestResolver` as a `@Bean`, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -194,7 +209,8 @@ Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyReg
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -208,5 +224,5 @@ fun authenticationRequestResolver(registrations : RelyingPartyRegistrationReposi
|
|||
return authenticationRequestResolver
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
|
|
|
@ -19,8 +19,10 @@ To configure these, you'll use the `saml2Login#authenticationManager` method in
|
|||
The default endpoint is `+/login/saml2/sso/{registrationId}+`.
|
||||
You can change this in the DSL and in the associated metadata like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -34,7 +36,8 @@ SecurityFilterChain securityFilters(HttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -50,23 +53,26 @@ fun securityFilters(val http: HttpSecurity): SecurityFilterChain {
|
|||
return http.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
and:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml/SSO")
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml/SSO")
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[relyingpartyregistrationresolver-apply]]
|
||||
== Changing `RelyingPartyRegistration` lookup
|
||||
|
@ -77,8 +83,10 @@ Or, if it cannot find one in either of those cases, then it attempts to look it
|
|||
There are a number of circumstances where you might need something more sophisticated, like if you are supporting `ARTIFACT` binding.
|
||||
In those cases, you can customize lookup through a custom `AuthenticationConverter`, which you can customize like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -92,7 +100,8 @@ SecurityFilterChain securityFilters(HttpSecurity http, AuthenticationConverter a
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -108,7 +117,7 @@ fun securityFilters(val http: HttpSecurity, val converter: AuthenticationConvert
|
|||
return http.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-saml2login-opensamlauthenticationprovider-clockskew]]
|
||||
== Setting a Clock Skew
|
||||
|
@ -116,8 +125,10 @@ fun securityFilters(val http: HttpSecurity, val converter: AuthenticationConvert
|
|||
It's not uncommon for the asserting and relying parties to have system clocks that aren't perfectly synchronized.
|
||||
For that reason, you can configure ``OpenSaml4AuthenticationProvider``'s default assertion validator with some tolerance:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -148,7 +159,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -178,7 +190,7 @@ open class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-saml2login-opensamlauthenticationprovider-userdetailsservice]]
|
||||
== Coordinating with a `UserDetailsService`
|
||||
|
@ -186,8 +198,10 @@ open class SecurityConfig {
|
|||
Or, perhaps you would like to include user details from a legacy `UserDetailsService`.
|
||||
In that case, the response authentication converter can come in handy, as can be seen below:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -221,7 +235,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -254,7 +269,7 @@ open class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
<1> First, call the default converter, which extracts attributes and authorities from the response
|
||||
<2> Second, call the xref:servlet/authentication/passwords/user-details-service.adoc#servlet-authentication-userdetailsservice[`UserDetailsService`] using the relevant information
|
||||
<3> Third, return a custom authentication that includes the user details
|
||||
|
@ -298,8 +313,10 @@ To perform additional validation, you can configure your own assertion validator
|
|||
[[servlet-saml2login-opensamlauthenticationprovider-onetimeuse]]
|
||||
For example, you can use OpenSAML's `OneTimeUseConditionValidator` to also validate a `<OneTimeUse>` condition, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
|
||||
|
@ -322,7 +339,8 @@ provider.setAssertionValidator(assertionToken -> {
|
|||
});
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
var provider = OpenSaml4AuthenticationProvider()
|
||||
|
@ -344,7 +362,7 @@ provider.setAssertionValidator { assertionToken ->
|
|||
result.concat(Saml2Error(INVALID_ASSERTION, context.validationFailureMessage))
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
While recommended, it's not necessary to call ``OpenSaml4AuthenticationProvider``'s default assertion validator.
|
||||
|
@ -362,8 +380,10 @@ The assertion decrypter is for decrypting encrypted elements of the `<saml2:Asse
|
|||
You can replace ``OpenSaml4AuthenticationProvider``'s default decryption strategy with your own.
|
||||
For example, if you have a separate service that decrypts the assertions in a `<saml2:Response>`, you can use it instead like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
MyDecryptionService decryptionService = ...;
|
||||
|
@ -371,30 +391,34 @@ OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider()
|
|||
provider.setResponseElementsDecrypter((responseToken) -> decryptionService.decrypt(responseToken.getResponse()));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val decryptionService: MyDecryptionService = ...
|
||||
val provider = OpenSaml4AuthenticationProvider()
|
||||
provider.setResponseElementsDecrypter { responseToken -> decryptionService.decrypt(responseToken.response) }
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If you are also decrypting individual elements in a `<saml2:Assertion>`, you can customize the assertion decrypter, too:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
provider.setAssertionElementsDecrypter((assertionToken) -> decryptionService.decrypt(assertionToken.getAssertion()));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
provider.setAssertionElementsDecrypter { assertionToken -> decryptionService.decrypt(assertionToken.assertion) }
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
NOTE: There are two separate decrypters since assertions can be signed separately from responses.
|
||||
Trying to decrypt a signed assertion's elements before signature verification may invalidate the signature.
|
||||
|
@ -407,8 +431,10 @@ If your asserting party signs the response only, then it's safe to decrypt all e
|
|||
Of course, the `authenticationManager` DSL method can be also used to perform a completely custom SAML 2.0 authentication.
|
||||
This authentication manager should expect a `Saml2AuthenticationToken` object containing the SAML 2.0 Response XML data.
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -431,7 +457,8 @@ public class SecurityConfig {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -452,7 +479,7 @@ open class SecurityConfig {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-saml2login-authenticatedprincipal]]
|
||||
== Using `Saml2AuthenticatedPrincipal`
|
||||
|
@ -462,8 +489,10 @@ Once the relying party validates an assertion, the result is a `Saml2Authenticat
|
|||
|
||||
This means that you can access the principal in your controller like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -477,7 +506,8 @@ public class MainController {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Controller
|
||||
|
@ -490,7 +520,7 @@ class MainController {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
Because the SAML 2.0 specification allows for each attribute to have multiple values, you can either call `getAttribute` to get the list of attributes or `getFirstAttribute` to get the first in the list.
|
||||
|
|
|
@ -80,7 +80,6 @@ Also, this configuration presupposes that you have already xref:servlet/saml2/me
|
|||
|
||||
In a Spring Boot application, to specify an identity provider's metadata, create configuration similar to the following:
|
||||
|
||||
====
|
||||
[source,yml]
|
||||
----
|
||||
spring:
|
||||
|
@ -96,7 +95,6 @@ spring:
|
|||
singlesignon.url: https://idp.example.com/issuer/sso
|
||||
singlesignon.sign-request: false
|
||||
----
|
||||
====
|
||||
|
||||
where:
|
||||
|
||||
|
@ -117,14 +115,12 @@ These are frequently abbreviated as AP and RP, respectively.
|
|||
|
||||
As configured <<saml2-specifying-identity-provider-metadata,earlier>>, the application processes any `+POST /login/saml2/sso/{registrationId}+` request containing a `SAMLResponse` parameter:
|
||||
|
||||
====
|
||||
[source,http]
|
||||
----
|
||||
POST /login/saml2/sso/adfs HTTP/1.1
|
||||
|
||||
SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZ...
|
||||
----
|
||||
====
|
||||
|
||||
There are two ways to induce your asserting party to generate a `SAMLResponse`:
|
||||
|
||||
|
@ -198,8 +194,10 @@ The resulting `Authentication#getPrincipal` is a Spring Security `Saml2Authentic
|
|||
|
||||
Any class that uses both Spring Security and OpenSAML should statically initialize `OpenSamlInitializationService` at the beginning of the class:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
static {
|
||||
|
@ -208,7 +206,8 @@ static {
|
|||
----
|
||||
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
companion object {
|
||||
|
@ -217,7 +216,7 @@ companion object {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
This replaces OpenSAML's `InitializationService#initialize`.
|
||||
|
||||
|
@ -227,8 +226,10 @@ In these circumstances, you may instead want to call `OpenSamlInitializationServ
|
|||
For example, when sending an unsigned AuthNRequest, you may want to force reauthentication.
|
||||
In that case, you can register your own `AuthnRequestMarshaller`, like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
static {
|
||||
|
@ -255,7 +256,8 @@ static {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
companion object {
|
||||
|
@ -281,7 +283,7 @@ companion object {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The `requireInitialize` method may be called only once per application instance.
|
||||
|
||||
|
@ -294,8 +296,10 @@ The first is a `SecurityFilterChain` that configures the application as a relyin
|
|||
When including `spring-security-saml2-service-provider`, the `SecurityFilterChain` looks like:
|
||||
|
||||
.Default SAML 2.0 Login Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -309,7 +313,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
|
@ -323,15 +328,17 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|||
return http.build()
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
If the application does not expose a `SecurityFilterChain` bean, Spring Boot exposes the preceding default one.
|
||||
|
||||
You can replace this by exposing the bean within the application:
|
||||
|
||||
.Custom SAML 2.0 Login Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -350,7 +357,8 @@ public class MyCustomSecurityConfiguration {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -370,7 +378,7 @@ class MyCustomSecurityConfiguration {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
The preceding example requires the role of `USER` for any URL that starts with `/messages/`.
|
||||
|
||||
|
@ -382,8 +390,10 @@ You can override the default by publishing your own `RelyingPartyRegistrationRep
|
|||
For example, you can look up the asserting party's configuration by hitting its metadata endpoint:
|
||||
|
||||
.Relying Party Registration Repository
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Value("${metadata.location}")
|
||||
|
@ -399,7 +409,8 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Value("\${metadata.location}")
|
||||
|
@ -414,7 +425,7 @@ open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
|
|||
return InMemoryRelyingPartyRegistrationRepository(registration)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-saml2login-relyingpartyregistrationid]]
|
||||
[NOTE]
|
||||
|
@ -423,8 +434,10 @@ The `registrationId` is an arbitrary value that you choose for differentiating b
|
|||
Alternatively, you can provide each detail manually:
|
||||
|
||||
.Relying Party Registration Repository Manual Configuration
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Value("${verification.key}")
|
||||
|
@ -447,7 +460,8 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exc
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Value("\${verification.key}")
|
||||
|
@ -474,20 +488,22 @@ open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository {
|
|||
return InMemoryRelyingPartyRegistrationRepository(registration)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`X509Support` is an OpenSAML class, used in the preceding snippet for brevity.
|
||||
====
|
||||
|
||||
[[servlet-saml2login-relyingpartyregistrationrepository-dsl]]
|
||||
|
||||
[[servlet-saml2login-relyingpartyregistrationrepository-dsl]]
|
||||
Alternatively, you can directly wire up the repository by using the DSL, which also overrides the auto-configured `SecurityFilterChain`:
|
||||
|
||||
.Custom Relying Party Registration DSL
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -508,7 +524,8 @@ public class MyCustomSecurityConfiguration {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Configuration
|
||||
|
@ -529,7 +546,7 @@ class MyCustomSecurityConfiguration {
|
|||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
|
@ -547,8 +564,10 @@ Also, you can provide asserting party metadata like its `Issuer` value, where it
|
|||
|
||||
The following `RelyingPartyRegistration` is the minimum required for most setups:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
|
||||
|
@ -556,7 +575,9 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
|
|||
.registrationId("my-id")
|
||||
.build();
|
||||
----
|
||||
.Kotlin
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val relyingPartyRegistration = RelyingPartyRegistrations
|
||||
|
@ -564,7 +585,7 @@ val relyingPartyRegistration = RelyingPartyRegistrations
|
|||
.registrationId("my-id")
|
||||
.build()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Note that you can also create a `RelyingPartyRegistration` from an arbitrary `InputStream` source.
|
||||
One such example is when the metadata is stored in a database:
|
||||
|
@ -582,8 +603,10 @@ try (InputStream source = new ByteArrayInputStream(xml.getBytes())) {
|
|||
|
||||
A more sophisticated setup is also possible:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("my-id")
|
||||
|
@ -598,7 +621,8 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit
|
|||
.build();
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val relyingPartyRegistration =
|
||||
|
@ -615,7 +639,7 @@ val relyingPartyRegistration =
|
|||
}
|
||||
.build()
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -688,8 +712,10 @@ At a minimum, you need to have a certificate from the asserting party so that th
|
|||
To construct a `Saml2X509Credential` that you can use to verify assertions from the asserting party, you can load the file and use
|
||||
the `CertificateFactory`:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Resource resource = new ClassPathResource("ap.crt");
|
||||
|
@ -700,7 +726,8 @@ try (InputStream is = resource.getInputStream()) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val resource = ClassPathResource("ap.crt")
|
||||
|
@ -710,7 +737,7 @@ resource.inputStream.use {
|
|||
)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Suppose that the asserting party is going to also encrypt the assertion.
|
||||
In that case, the relying party needs a private key to decrypt the encrypted value.
|
||||
|
@ -718,8 +745,10 @@ In that case, the relying party needs a private key to decrypt the encrypted val
|
|||
In that case, you need an `RSAPrivateKey` as well as its corresponding `X509Certificate`.
|
||||
You can load the first by using Spring Security's `RsaKeyConverters` utility class and the second as you did before:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
X509Certificate certificate = relyingPartyDecryptionCertificate();
|
||||
|
@ -730,7 +759,8 @@ try (InputStream is = resource.getInputStream()) {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
val certificate: X509Certificate = relyingPartyDecryptionCertificate()
|
||||
|
@ -740,7 +770,7 @@ resource.inputStream.use {
|
|||
return Saml2X509Credential.decryption(rsa, certificate)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[TIP]
|
||||
====
|
||||
|
@ -762,7 +792,6 @@ The duplication can be alleviated in a few different ways.
|
|||
|
||||
First, in YAML this can be alleviated with references:
|
||||
|
||||
====
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
|
@ -780,14 +809,15 @@ spring:
|
|||
identityprovider:
|
||||
entity-id: ...
|
||||
----
|
||||
====
|
||||
|
||||
Second, in a database, you need not replicate the model of `RelyingPartyRegistration`.
|
||||
|
||||
Third, in Java, you can create a custom configuration method:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
private RelyingPartyRegistration.Builder
|
||||
|
@ -814,7 +844,8 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
|
|||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
private fun addRelyingPartyDetails(builder: RelyingPartyRegistration.Builder): RelyingPartyRegistration.Builder {
|
||||
|
@ -842,7 +873,7 @@ open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
|
|||
return InMemoryRelyingPartyRegistrationRepository(okta, azure)
|
||||
}
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
[[servlet-saml2login-rpr-relyingpartyregistrationresolver]]
|
||||
=== Resolving the `RelyingPartyRegistration` from the Request
|
||||
|
@ -870,8 +901,10 @@ In this case, the identity provider's metadata endpoint returns multiple `<md:ID
|
|||
|
||||
These multiple asserting parties can be accessed in a single call to `RelyingPartyRegistrations` like so:
|
||||
|
||||
====
|
||||
.Java
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Collection<RelyingPartyRegistration> registrations = RelyingPartyRegistrations
|
||||
|
@ -884,7 +917,8 @@ Collection<RelyingPartyRegistration> registrations = RelyingPartyRegistrations
|
|||
.collect(Collectors.toList()));
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
Kotlin::
|
||||
+
|
||||
[source,java,role="secondary"]
|
||||
----
|
||||
var registrations: Collection<RelyingPartyRegistration> = RelyingPartyRegistrations
|
||||
|
@ -897,7 +931,7 @@ var registrations: Collection<RelyingPartyRegistration> = RelyingPartyRegistrati
|
|||
}
|
||||
.collect(Collectors.toList()));
|
||||
----
|
||||
====
|
||||
======
|
||||
|
||||
Note that because the registration id is set to a random value, this will change certain SAML 2.0 endpoints to be unpredictable.
|
||||
There are several ways to address this; let's focus on a way that suits the specific use case of federation.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue