diff --git a/docs/modules/ROOT/pages/features/authentication/password-storage.adoc b/docs/modules/ROOT/pages/features/authentication/password-storage.adoc index 38b016fc07..70d5ad7b17 100644 --- a/docs/modules/ROOT/pages/features/authentication/password-storage.adoc +++ b/docs/modules/ROOT/pages/features/authentication/password-storage.adoc @@ -67,26 +67,31 @@ Instead Spring Security introduces `DelegatingPasswordEncoder` which solves all You can easily construct an instance of `DelegatingPasswordEncoder` 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 may create your own custom instance. For example: .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 ---- -==== Such that `id` is an identifier 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 would have a `PasswordEncoder` id of `bcrypt` and encodedPassword of `$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG`. When matching it would delegate to `BCryptPasswordEncoder` @@ -182,12 +184,10 @@ In the `DelegatingPasswordEncoder` we constructed above, that means that the res The end result would look like: .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"] ---- User 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 @@ User 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,13 +290,11 @@ The easiest way to properly encode your password is to use the https://docs.spri For example, the following will encode the password of `password` for use with <>: .Spring Boot CLI encodepassword Example -==== [source,attrs="-attributes"] ---- spring encodepassword password {bcrypt}$2a$10$X5wFBtLrL/kHcmrOGGTrGufsBX8CJ0WpQpF3pgeuxBB/H73BK1DW6 ---- -==== [[authentication-password-storage-dpe-troubleshoot]] === Troubleshooting @@ -336,8 +340,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 @@ -346,7 +352,8 @@ String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result)); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- // Create an encoder with strength 16 @@ -354,7 +361,7 @@ val encoder = BCryptPasswordEncoder(16) val result: String = encoder.encode("myPassword") assertTrue(encoder.matches("myPassword", result)) ---- -==== +====== [[authentication-password-storage-argon2]] == Argon2PasswordEncoder @@ -366,8 +373,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 @@ -376,7 +385,8 @@ String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result)); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- // Create an encoder with all the defaults @@ -384,7 +394,7 @@ val encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8() val result: String = encoder.encode("myPassword") assertTrue(encoder.matches("myPassword", result)) ---- -==== +====== [[authentication-password-storage-pbkdf2]] == Pbkdf2PasswordEncoder @@ -395,8 +405,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 @@ -405,7 +417,8 @@ String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result)); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- // Create an encoder with all the defaults @@ -413,7 +426,7 @@ val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8() val result: String = encoder.encode("myPassword") assertTrue(encoder.matches("myPassword", result)) ---- -==== +====== [[authentication-password-storage-scrypt]] == SCryptPasswordEncoder @@ -423,8 +436,10 @@ In order to defeat password cracking on custom hardware scrypt is a deliberately 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 @@ -433,7 +448,8 @@ String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result)); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- // Create an encoder with all the defaults @@ -441,7 +457,7 @@ val encoder = SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8() val result: String = encoder.encode("myPassword") assertTrue(encoder.matches("myPassword", result)) ---- -==== +====== [[authentication-password-storage-other]] == Other PasswordEncoders @@ -466,8 +482,10 @@ You should instead migrate to using `DelegatingPasswordEncoder` to support secur ==== .NoOpPasswordEncoder -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -476,14 +494,16 @@ public static PasswordEncoder passwordEncoder() { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -491,7 +511,7 @@ fun passwordEncoder(): PasswordEncoder { return NoOpPasswordEncoder.getInstance(); } ---- -==== +====== [NOTE] ==== @@ -509,36 +529,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"] ---- ---- -.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 @@ -547,13 +573,15 @@ http ) ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -562,6 +590,6 @@ http { } } ---- -==== +====== With the above configuration, when a password manager navigates to `/.well-known/change-password`, then Spring Security will redirect to `/update-password`. diff --git a/docs/modules/ROOT/pages/features/exploits/csrf.adoc b/docs/modules/ROOT/pages/features/exploits/csrf.adoc index 8c9517363e..c65218755c 100644 --- a/docs/modules/ROOT/pages/features/exploits/csrf.adoc +++ b/docs/modules/ROOT/pages/features/exploits/csrf.adoc @@ -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] ----
---- -==== 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] ----
---- -==== 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 the actual CSRF token is required to be in an HTTP parameter named `_csrf Our application's transfer form would look like: .Synchronizer Token Form -==== [source,html] ----
---- -==== 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, 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 JSO For example, a malicious user can create a http://blog.opensecurityresearch.com/2012/02/json-csrf-with-parameter-padding.html[CSRF with JSON using the following form]: .CSRF with JSON form -==== [source,html] ----
@@ -254,13 +241,11 @@ For example, a malicious user can create a http://blog.opensecurityresearch.com/ value="Win Money!"/>
---- -==== This will produce the following JSON structure .CSRF with JSON request -==== [source,javascript] ---- { "amount": 100, @@ -269,13 +254,11 @@ This will produce the following JSON structure "ignore_me": "=test" } ---- -==== If an application were not validating the Content-Type, then 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 shown below: .CSRF with JSON Spring MVC form -==== [source,html] ----
@@ -284,7 +267,6 @@ Depending on the setup, a Spring MVC application that validates the Content-Type value="Win Money!"/>
---- -==== [[csrf-when-stateless]] === CSRF and Stateless Browser Applications @@ -393,7 +375,6 @@ In some applications a form parameter can be used to override the HTTP method. For example, the form below could be used to treat the HTTP method as a `delete` rather than a `post`. .CSRF Hidden HTTP Method Form -==== [source,html] ----
---- -==== Overriding the HTTP method occurs in a filter. diff --git a/docs/modules/ROOT/pages/features/exploits/headers.adoc b/docs/modules/ROOT/pages/features/exploits/headers.adoc index e142718275..f4f641d73d 100644 --- a/docs/modules/ROOT/pages/features/exploits/headers.adoc +++ b/docs/modules/ROOT/pages/features/exploits/headers.adoc @@ -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: 1; mode=block ---- -==== NOTE: Strict-Transport-Security is only added on HTTPS requests @@ -62,14 +60,12 @@ If a user authenticates to view sensitive information and then logs out, we don' 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 ---- -==== In order to be secure by default, Spring Security adds these headers by default. However, if your application provides its own cache control headers Spring Security will back out of the way. @@ -102,12 +98,10 @@ A malicious user might create a http://webblaze.cs.berkeley.edu/papers/barth-cab Spring Security disables content sniffing by default 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) @@ -137,12 +131,10 @@ For example, Spring Security's default behavior is to add the following header w .Strict Transport Security HTTP Response Header -==== [source] ---- Strict-Transport-Security: max-age=31536000 ; includeSubDomains ; preload ---- -==== The optional `includeSubDomains` directive instructs the browser that subdomains (e.g. secure.mybank.example.com) should also be treated as an HSTS domain. @@ -247,12 +239,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 will be 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, then the violation will be reported by the user-agent to the declared URL. @@ -260,12 +250,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 will instruct 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.com/. @@ -276,12 +264,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 may 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.com_, the user-agent will send a violation report to the declared URL specified by the _report-uri_ directive, but still allow the violating resource to load nevertheless. @@ -308,12 +294,10 @@ page the user was on. Spring Security's approach is to use 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. @@ -328,12 +312,10 @@ Refer to the relevant sections to see how to configure both xref:servlet/exploit https://wicg.github.io/feature-policy/[Feature Policy] is a mechanism that allows 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. @@ -350,12 +332,10 @@ Refer to the relevant sections to see how to configure both xref:servlet/exploit https://w3c.github.io/webappsec-permissions-policy/[Permissions Policy] is a mechanism that allows web developers to 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. diff --git a/docs/modules/ROOT/pages/features/integrations/concurrency.adoc b/docs/modules/ROOT/pages/features/integrations/concurrency.adoc index 69c5978bcb..a2fab046b3 100644 --- a/docs/modules/ROOT/pages/features/integrations/concurrency.adoc +++ b/docs/modules/ROOT/pages/features/integrations/concurrency.adoc @@ -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. diff --git a/docs/modules/ROOT/pages/features/integrations/cryptography.adoc b/docs/modules/ROOT/pages/features/integrations/cryptography.adoc index 192acbbe71..3a97af9a9d 100644 --- a/docs/modules/ROOT/pages/features/integrations/cryptography.adoc +++ b/docs/modules/ROOT/pages/features/integrations/cryptography.adoc @@ -20,19 +20,22 @@ Encryptors are thread-safe. 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 using 256 bit AES encryption with Galois Counter Mode (GCM). @@ -46,19 +49,22 @@ The provided salt should be in hex-encoded String form, be random, and be at lea Such a salt may be generated 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 ---- -==== +====== Users may also use the `standard` encryption method, which is 256-bit AES in Cipher Block Chaining (CBC) Mode. This mode is not https://en.wikipedia.org/wiki/Authenticated_encryption[authenticated] and does not provide any @@ -70,19 +76,22 @@ For a more secure alternative, users should prefer `Encryptors.stronger`. 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 the database. @@ -90,19 +99,22 @@ Encrypted results are returned as hex-encoded strings for easy storage on the fi Use the Encryptors.queryableText factory method to construct a "queryable" TextEncryptor: .Queryable TextEncryptor -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Encryptors.queryableText("password", "salt"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- Encryptors.queryableText("password", "salt") ---- -==== +====== The difference between a queryable TextEncryptor and a standard TextEncryptor has to do with initialization vector (iv) handling. The iv used in a queryable TextEncryptor#encrypt operation is shared, or constant, and is not randomly generated. @@ -121,74 +133,86 @@ KeyGenerators are thread-safe. 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. There is also a KeyGenerators.secureRandom variant that 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 Use the KeyGenerators.string factory method to construct a 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 @@ -219,8 +243,10 @@ The default value is 10. You can change this value in your deployed system without affecting existing passwords, as the value is also stored in the encoded hash. .BCryptPasswordEncoder -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @@ -230,7 +256,8 @@ String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result)); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @@ -239,15 +266,17 @@ val encoder = BCryptPasswordEncoder(16) val result: String = encoder.encode("myPassword") assertTrue(encoder.matches("myPassword", result)) ---- -==== +====== The `Pbkdf2PasswordEncoder` implementation uses PBKDF2 algorithm to hash the passwords. In order to defeat password cracking PBKDF2 is a deliberately slow algorithm and should be tuned to take about .5 seconds to verify a password on your system. .Pbkdf2PasswordEncoder -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- // Create an encoder with all the defaults @@ -256,7 +285,8 @@ String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result)); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- // Create an encoder with all the defaults @@ -264,4 +294,4 @@ val encoder = Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8() val result: String = encoder.encode("myPassword") assertTrue(encoder.matches("myPassword", result)) ---- -==== +====== diff --git a/docs/modules/ROOT/pages/features/integrations/data.adoc b/docs/modules/ROOT/pages/features/integrations/data.adoc index c37f96d302..fedac6b9f8 100644 --- a/docs/modules/ROOT/pages/features/integrations/data.adoc +++ b/docs/modules/ROOT/pages/features/integrations/data.adoc @@ -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 { fun findInbox(pageable: Pageable?): Page? } ---- -==== +====== 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. diff --git a/docs/modules/ROOT/pages/features/integrations/jackson.adoc b/docs/modules/ROOT/pages/features/integrations/jackson.adoc index 1f67bf98a2..561d23ec6d 100644 --- a/docs/modules/ROOT/pages/features/integrations/jackson.adoc +++ b/docs/modules/ROOT/pages/features/integrations/jackson.adoc @@ -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] ==== diff --git a/docs/modules/ROOT/pages/getting-spring-security.adoc b/docs/modules/ROOT/pages/getting-spring-security.adoc index 5c40c297da..4bd6b5f826 100644 --- a/docs/modules/ROOT/pages/getting-spring-security.adoc +++ b/docs/modules/ROOT/pages/getting-spring-security.adoc @@ -30,7 +30,6 @@ Alternatively, you can manually add the starter, as the following example shows: .pom.xml -==== [source,xml,subs="verbatim,attributes"] ---- @@ -41,13 +40,11 @@ Alternatively, you can manually add the starter, as the following example shows: ---- -==== 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 may do so by providing a Maven property, as the following example shows: .pom.xml -==== [source,xml,subs="verbatim,attributes"] ---- @@ -55,14 +52,12 @@ If you wish to override the Spring Security version, you may do so by providing {spring-security-version} ---- -==== Since Spring Security makes breaking changes only in major releases, it is safe to 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, as the following example shows: .pom.xml -==== [source,xml,subs="verbatim,attributes"] ---- @@ -70,7 +65,6 @@ You can do so by adding a Maven property, as the following example shows: {spring-core-version} ---- -==== If you use additional features (such as LDAP, OpenID, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies]. @@ -80,7 +74,6 @@ If you use additional features (such as LDAP, OpenID, and others), you need to a When you use Spring Security without Spring Boot, the preferred way is to use Spring Security's BOM to ensure 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"] ---- @@ -96,12 +89,10 @@ When you use Spring Security without Spring Boot, the preferred way is to use Sp ---- -==== A minimal Spring Security Maven set of dependencies typically looks like the following: .pom.xml -==== [source,xml,subs="verbatim,attributes"] ---- @@ -116,7 +107,6 @@ A minimal Spring Security Maven set of dependencies typically looks like the fol ---- -==== If you use additional features (such as LDAP, OpenID, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies]. @@ -125,7 +115,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 `` section of your `pom.xml` as the following example shows: .pom.xml -==== [source,xml,subs="verbatim,attributes"] ---- @@ -141,7 +130,6 @@ The easiest way to resolve this is to use the `spring-framework-bom` within the ---- -==== The preceding example ensures that all the transitive dependencies of Spring Security use the Spring {spring-core-version} modules. @@ -155,7 +143,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, as the following example shows: .pom.xml -==== [source,xml] ---- @@ -167,12 +154,10 @@ If you use a SNAPSHOT version, you need to ensure that you have the Spring Snaps ---- -==== 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] ---- @@ -184,7 +169,6 @@ If you use a milestone or release candidate version, you need to ensure that you ---- -==== [[getting-gradle]] == Gradle @@ -201,7 +185,6 @@ The simplest and preferred method to use the starter is to use https://docs.spri Alternatively, you can manually add the starter, as the following example shows: .build.gradle -==== [source,groovy] [subs="verbatim,attributes"] ---- @@ -209,32 +192,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 may do so by providing a Gradle property, as the following example shows: .build.gradle -==== [source,groovy] [subs="verbatim,attributes"] ---- ext['spring-security.version']='{spring-security-version}' ---- -==== Since Spring Security makes breaking changes only in major releases, it is safe to 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, as the following example shows: .build.gradle -==== [source,groovy] [subs="verbatim,attributes"] ---- ext['spring.version']='{spring-core-version}' ---- -==== If you use additional features (such as LDAP, OpenID, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies]. @@ -244,7 +222,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], as the following example shows: .build.gradle -==== [source,groovy] [subs="verbatim,attributes"] ---- @@ -258,12 +235,10 @@ dependencyManagement { } } ---- -==== A minimal Spring Security Maven set of dependencies typically looks like the following: .build.gradle -==== [source,groovy] [subs="verbatim,attributes"] ---- @@ -272,7 +247,6 @@ dependencies { compile "org.springframework.security:spring-security-config" } ---- -==== If you use additional features (such as LDAP, OpenID, and others), you need to also include the appropriate xref:modules.adoc#modules[Project Modules and Dependencies]. @@ -282,7 +256,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], as the following example shows: .build.gradle -==== [source,groovy] [subs="verbatim,attributes"] ---- @@ -296,7 +269,6 @@ dependencyManagement { } } ---- -==== The preceding example ensures that all the transitive dependencies of Spring Security use the Spring {spring-core-version} modules. @@ -305,35 +277,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 you have the Spring Snapshot repository defined, as the following example shows: .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, as the following example shows: .build.gradle -==== [source,groovy] ---- repositories { maven { url 'https://repo.spring.io/milestone' } } ---- -==== diff --git a/docs/modules/ROOT/pages/migration/index.adoc b/docs/modules/ROOT/pages/migration/index.adoc index f112b4947d..b73cf791c7 100644 --- a/docs/modules/ROOT/pages/migration/index.adoc +++ b/docs/modules/ROOT/pages/migration/index.adoc @@ -36,8 +36,10 @@ If you are xref:features/authentication/password-storage.adoc#authentication-pas If you use the default constructor, you should begin by changing: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -46,7 +48,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -54,12 +57,14 @@ fun passwordEncoder(): PasswordEncoder { return Pbkdf2PasswordEncoder() } ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -68,7 +73,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -76,12 +82,14 @@ fun passwordEncoder(): PasswordEncoder { return Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_5() } ---- -==== +====== Or, if you have custom settings, change to the constructor that specifies all settings, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -91,7 +99,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -100,12 +109,14 @@ fun passwordEncoder(): PasswordEncoder { return current } ---- -==== +====== Change them to use the fully-specified constructor, like the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -115,7 +126,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -124,15 +136,17 @@ fun passwordEncoder(): PasswordEncoder { return current } ---- -==== +====== ==== Use `DelegatingPasswordEncoder` Once you are not using the deprecated constructor, the next step is to prepare your code to upgrade to the latest standards by using `DelegatingPasswordEncoder`. The following code configures the delegating encoder to detect passwords that are using `current` and replace them with the latest: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -146,7 +160,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -159,7 +174,7 @@ fun passwordEncoder(): PasswordEncoder { return delegating } ---- -==== +====== === Update `SCryptPasswordEncoder` @@ -169,8 +184,10 @@ If you are xref:features/authentication/password-storage.adoc#authentication-pas If you use the default constructor, you should begin by changing: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -179,7 +196,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -187,12 +205,14 @@ fun passwordEncoder(): PasswordEncoder { return SCryptPasswordEncoder() } ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -201,7 +221,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -209,15 +230,17 @@ fun passwordEncoder(): PasswordEncoder { return SCryptPasswordEncoder.defaultsForSpringSecurity_v4_1() } ---- -==== +====== ==== Use `DelegatingPasswordEncoder` Once you are not using the deprecated constructor, the next step is to prepare your code to upgrade to the latest standards by using `DelegatingPasswordEncoder`. The following code configures the delegating encoder to detect passwords that are using `current` and replace them with the latest: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -231,7 +254,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -244,7 +268,7 @@ fun passwordEncoder(): PasswordEncoder { return delegating } ---- -==== +====== === Update `Argon2PasswordEncoder` @@ -254,8 +278,10 @@ If you are xref:features/authentication/password-storage.adoc#authentication-pas If you use the default constructor, you should begin by changing: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -264,7 +290,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -272,12 +299,14 @@ fun passwordEncoder(): PasswordEncoder { return Argon2PasswordEncoder() } ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -286,7 +315,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -294,15 +324,17 @@ fun passwordEncoder(): PasswordEncoder { return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_2() } ---- -==== +====== ==== Use `DelegatingPasswordEncoder` Once you are not using the deprecated constructor, the next step is to prepare your code to upgrade to the latest standards by using `DelegatingPasswordEncoder`. The following code configures the delegating encoder to detect passwords that are using `current` and replace them with the latest: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -316,7 +348,8 @@ PasswordEncoder passwordEncoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -329,7 +362,7 @@ fun passwordEncoder(): PasswordEncoder { return delegating } ---- -==== +====== == Stop using `Encryptors.queryableText` @@ -340,8 +373,10 @@ To upgrade, you will either need to re-encrypt with a supported mechanism or sto Consider the following pseudocode for reading each encrypted entry from a table, decrypting it, and then re-encrypting it using a supported mechanism: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- TextEncryptor deprecated = Encryptors.queryableText(password, salt); @@ -353,7 +388,7 @@ for (MyEntry entry : entries) { entryService.save(entry) } ---- -==== +====== <1> - The above uses the deprecated `queryableText` to convert the value to plaintext. <2> - Then, the value is re-encrypted with a supported Spring Security mechanism. diff --git a/docs/modules/ROOT/pages/migration/reactive.adoc b/docs/modules/ROOT/pages/migration/reactive.adoc index eab5e4e0a1..bdc5b97efc 100644 --- a/docs/modules/ROOT/pages/migration/reactive.adoc +++ b/docs/modules/ROOT/pages/migration/reactive.adoc @@ -13,8 +13,10 @@ In Spring Security 5.8, the method `tokenFromMultipartDataEnabled` was deprecate To address the deprecation, the following code: .Configure `tokenFromMultipartDataEnabled` with DSL -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -28,7 +30,8 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -41,13 +44,15 @@ open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChai } } ---- -==== +====== can be replaced with: .Configure `tokenFromMultipartDataEnabled` with `ServerCsrfTokenRequestAttributeHandler` -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -63,7 +68,8 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -78,15 +84,17 @@ open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChai } } ---- -==== +====== === Protect against CSRF BREACH You can opt into Spring Security 6's default support for BREACH protection of the `CsrfToken` using the following configuration: .`CsrfToken` BREACH Protection -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -102,7 +110,8 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -117,7 +126,7 @@ open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChai } } ---- -==== +====== [[reactive-csrf-breach-opt-out]] === Opt-out Steps @@ -131,8 +140,10 @@ If you are using AngularJS and the https://angular.io/api/common/http/HttpClient In this case, you can configure Spring Security to validate the raw `CsrfToken` from the cookie while keeping CSRF BREACH protection of the response using a custom `ServerCsrfTokenRequestHandler` with delegation, like so: .Configure `CsrfToken` BREACH Protection to validate raw tokens -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -163,7 +174,8 @@ WebFilter csrfCookieWebFilter() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -192,15 +204,17 @@ fun csrfCookieWebFilter(): WebFilter { } } ---- -==== +====== ==== I need to opt out of CSRF BREACH protection for another reason If CSRF BREACH protection does not work for you for another reason, you can opt out using the following configuration: .Opt out of `CsrfToken` BREACH protection -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -215,7 +229,8 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -229,7 +244,7 @@ open fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChai } } ---- -==== +====== == Use `AuthorizationManager` for Method Security @@ -245,35 +260,41 @@ In Spring Security 5.8, `useAuthorizationManager` was added to {security-api-url To opt in, change `useAuthorizationManager` to `true` like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableReactiveMethodSecurity ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableReactiveMethodSecurity ---- -==== +====== changes to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableReactiveMethodSecurity(useAuthorizationManager = true) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableReactiveMethodSecurity(useAuthorizationManager = true) ---- -==== +====== [[reactive-check-for-annotationconfigurationexceptions]] === Check for ``AnnotationConfigurationException``s @@ -286,35 +307,41 @@ If after turning on `useAuthorizationManager` you see ``AnnotationConfigurationE If you ran into trouble with `AuthorizationManager` for reactive method security, you can opt out by changing: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableReactiveMethodSecurity ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableReactiveMethodSecurity ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableReactiveMethodSecurity(useAuthorizationManager = false) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableReactiveMethodSecurity(useAuthorizationManager = false) ---- -==== +====== == Propagate ``AuthenticationServiceException``s @@ -327,21 +354,24 @@ To prepare for the 6.0 default, `httpBasic` and `oauth2ResourceServer` should be For each, construct the appropriate authentication entry point for `httpBasic` and for `oauth2ResourceServer`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- ServerAuthenticationEntryPoint bearerEntryPoint = new BearerTokenServerAuthenticationEntryPoint(); ServerAuthenticationEntryPoint basicEntryPoint = new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val bearerEntryPoint: ServerAuthenticationEntryPoint = BearerTokenServerAuthenticationEntryPoint() val basicEntryPoint: ServerAuthenticationEntryPoint = HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED) ---- -==== +====== [NOTE] ==== @@ -350,8 +380,10 @@ If you use a custom `AuthenticationEntryPoint` for either or both mechanisms, us Then, construct and configure a `ServerAuthenticationEntryPointFailureHandler` for each one: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- AuthenticationFailureHandler bearerFailureHandler = new ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint); @@ -360,7 +392,8 @@ AuthenticationFailureHandler basicFailureHandler = new ServerAuthenticationEntry basicFailureHandler.setRethrowAuthenticationServiceException(true) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val bearerFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(bearerEntryPoint) @@ -368,12 +401,14 @@ bearerFailureHandler.setRethrowAuthenticationServiceException(true) val basicFailureHandler: AuthenticationFailureHandler = ServerAuthenticationEntryPointFailureHandler(basicEntryPoint) basicFailureHandler.setRethrowAuthenticationServiceException(true) ---- -==== +====== Finally, wire each authentication failure handler into the DSL, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -381,7 +416,8 @@ http .oauth2ResourceServer((oauth2) -> oauth2.authenticationFailureHandler(bearerFailureHandler)) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -393,7 +429,7 @@ http { } } ---- -==== +====== [[reactive-authenticationfailurehandler-opt-out]] === Opt-out Steps @@ -408,8 +444,10 @@ In 6.0, `@Configuration` is removed from `@EnableWebFluxSecurity` and `@EnableRe To prepare for this, wherever you are using one of these annotations, you may need to add `@Configuration`. For example, `@EnableReactiveMethodSecurity` changes from: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableReactiveMethodSecurity @@ -417,8 +455,12 @@ public class MyConfiguration { // ... } ---- +====== -.Kotlin +[tabs] +====== +Kotlin:: ++ [source,java,role="primary"] ---- @EnableReactiveMethodSecurity @@ -426,12 +468,14 @@ open class MyConfiguration { // ... } ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -440,8 +484,12 @@ public class MyConfiguration { // ... } ---- +====== -.Kotlin +[tabs] +====== +Kotlin:: ++ [source,java,role="primary"] ---- @Configuration @@ -450,7 +498,7 @@ open class MyConfiguration { // ... } ---- -==== +====== == Address OAuth2 Client Deprecations @@ -494,8 +542,10 @@ The following annotations had their `@Configuration` removed: For example, if you are using `@EnableWebFluxSecurity`, you will need to change: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebFluxSecurity @@ -503,12 +553,14 @@ public class SecurityConfig { // ... } ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -517,6 +569,6 @@ public class SecurityConfig { // ... } ---- -==== +====== And the same applies to every other annotation listed above. diff --git a/docs/modules/ROOT/pages/migration/servlet/authentication.adoc b/docs/modules/ROOT/pages/migration/servlet/authentication.adoc index 5fddab930a..cd8b2ee5d1 100644 --- a/docs/modules/ROOT/pages/migration/servlet/authentication.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/authentication.adoc @@ -17,8 +17,10 @@ See the xref:servlet/authentication/rememberme.adoc#_tokenbasedremembermeservice [[servlet-opt-in-sha256-sha256-encoding]] .Use Spring Security 6 defaults for encoding, SHA-256 for encoding and MD5 for matching -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -46,7 +48,8 @@ public class SecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -61,15 +64,17 @@ public class SecurityConfig { ---- -==== +====== At some point, you will want to fully migrate to Spring Security 6 defaults. But how do you know when it is safe to do so? Let's suppose that you deployed your application using SHA-256 as the encoding algorithm (as you have done <>) on November 1st, if you have the value for the `tokenValiditySeconds` property set to N days (14 is the default), you can migrate to SHA-256 N days after November 1st (which is November 15th in this example). By that time, all the tokens generated with MD5 will have expired. .Use Spring Security 6 defaults, SHA-256 for both encoding and matching -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -97,7 +102,8 @@ public class SecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -112,13 +118,15 @@ public class SecurityConfig { ---- -==== +====== If you are having problems with the Spring Security 6 defaults, you can explicitly opt into 5.8 defaults using the following configuration: .Use MD5 for both encoding and matching algorithms -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -146,7 +154,8 @@ public class SecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -161,7 +170,7 @@ public class SecurityConfig { ---- -==== +====== == Propagate ``AuthenticationServiceException``s @@ -172,8 +181,10 @@ Because ``AuthenticationServiceException``s represent a server-side error instea To prepare for the 6.0 default, wire `AuthenticationFilter` instances with a `AuthenticationFailureHandler` that rethrows ``AuthenticationServiceException``s, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- AuthenticationFilter authenticationFilter = new AuthenticationFilter(...); @@ -182,7 +193,8 @@ handler.setRethrowAuthenticationServiceException(true); authenticationFilter.setAuthenticationFailureHandler(handler); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val authenticationFilter: AuthenticationFilter = new AuthenticationFilter(...) @@ -191,7 +203,8 @@ handler.setRethrowAuthenticationServiceException(true) authenticationFilter.setAuthenticationFailureHandler(handler) ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -203,15 +216,17 @@ authenticationFilter.setAuthenticationFailureHandler(handler) ---- -==== +====== [[servlet-authenticationfailurehandler-opt-out]] === Opt-out Steps If rethrowing ``AuthenticationServiceException``s gives you trouble, you can set the value to false instead of taking the 6.0 default, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- AuthenticationFilter authenticationFilter = new AuthenticationFilter(...); @@ -220,7 +235,8 @@ handler.setRethrowAuthenticationServiceException(false); authenticationFilter.setAuthenticationFailureHandler(handler); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val authenticationFilter: AuthenticationFilter = new AuthenticationFilter(...) @@ -229,7 +245,8 @@ handler.setRethrowAuthenticationServiceException(false) authenticationFilter.setAuthenticationFailureHandler(handler) ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -241,4 +258,4 @@ authenticationFilter.setAuthenticationFailureHandler(handler) ---- -==== +====== diff --git a/docs/modules/ROOT/pages/migration/servlet/authorization.adoc b/docs/modules/ROOT/pages/migration/servlet/authorization.adoc index 4c596570c3..142ac19541 100644 --- a/docs/modules/ROOT/pages/migration/servlet/authorization.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/authorization.adoc @@ -16,93 +16,109 @@ The new annotation and XML element activate Spring's xref:servlet/authorization/ This means that the following two listings are functionally equivalent: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableGlobalMethodSecurity(prePostEnabled = true) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableGlobalMethodSecurity(prePostEnabled = true) ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== and: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableMethodSecurity ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableMethodSecurity ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== For applications not using the pre-post annotations, make sure to turn it off to avoid activating unwanted behavior. For example, a listing like: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableGlobalMethodSecurity(securedEnabled = true) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableGlobalMethodSecurity(securedEnabled = true) ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== should change to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableMethodSecurity(securedEnabled = true, prePostEnabled = false) ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== === Change the `order` value in `@EnableTransactionManagement` @@ -119,47 +135,55 @@ The `@PreFilter` interceptor has an order of 100; `@PostAuthorize`, 200; and so So, if after updating you find that your method security expressions are not working due to not having an open transaction, please change your transaction annotation definition from the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableTransactionManagement ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableTransactionManagement ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableTransactionManagement(order = 0) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableTransactionManagement(order = 0) ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== In this way, the transaction AOP advice will be placed before Spring Security's advice and the transaction will be open when your authorization SpEL expressions are evaluated. @@ -178,8 +202,10 @@ Instead, you can create a custom bean with the authorization methods that you ne For example, let's say you are wanting a custom evaluation of `@PostAuthorize("hasAuthority('ADMIN')")`. You can create a custom `@Bean` like this one: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- class MyAuthorizer { @@ -191,7 +217,8 @@ class MyAuthorizer { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class MyAuthorizer { @@ -202,31 +229,36 @@ class MyAuthorizer { } } ---- -==== +====== and then refer to it in the annotation like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @PreAuthorize("@authz.isAdmin(#root)") ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @PreAuthorize("@authz.isAdmin(#root)") ---- -==== +====== ==== I'd still prefer to subclass `DefaultMethodSecurityExpressionHandler` If you must continue subclassing `DefaultMethodSecurityExpressionHandler`, you can still do so. Instead, override the `createEvaluationContext(Supplier, MethodInvocation)` method like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Component @@ -245,7 +277,8 @@ class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Component @@ -262,7 +295,7 @@ class MyExpressionHandler: DefaultMethodSecurityExpressionHandler { } } ---- -==== +====== ==== Opt-out Steps @@ -276,8 +309,10 @@ This helps keep its API simple. If you have a custom {security-api-url}org/springframework/security/access/PermissionEvaluator.html[`PermissionEvaluator`] `@Bean`, please change it from: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -286,7 +321,8 @@ static PermissionEvaluator permissionEvaluator() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- companion object { @@ -296,12 +332,14 @@ companion object { } } ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -312,7 +350,8 @@ static MethodSecurityExpressionHandler expressionHandler() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- companion object { @@ -324,7 +363,7 @@ companion object { } } ---- -==== +====== === Replace any custom method-security ``AccessDecisionManager``s @@ -347,8 +386,10 @@ Having done that, please follow the details in the reference manual for xref:ser If your application uses {security-api-url}org/springframework/security/access/vote/AffirmativeBased.html[`AffirmativeBased`], then you can construct an equivalent {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`], like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- AuthorizationManager authorization = AuthorizationManagers.anyOf( @@ -356,14 +397,15 @@ AuthorizationManager authorization = AuthorizationManagers.any ) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val authorization = AuthorizationManagers.anyOf( // ... your list of authorization managers ) ---- -==== +====== Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. @@ -381,8 +423,10 @@ You should either change the class to implement {security-api-url}org/springfram Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution. By way of example, though, here is what adapting {security-api-url}org/springframework/security/access/SecurityMetadataSource.html[`SecurityMetadataSource`] and {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] for `@PreAuthorize` would look like: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public final class PreAuthorizeAuthorizationManagerAdapter implements AuthorizationManager { @@ -411,7 +455,7 @@ public final class PreAuthorizeAuthorizationManagerAdapter implements Authorizat } } ---- -==== +====== Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. @@ -440,8 +484,10 @@ It is quite straightforward to adapt a `RunAsManager`, though, to the `Authoriza Here is some pseudocode to get you started: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public final class RunAsAuthorizationManagerAdapter implements AuthorizationManager { @@ -460,7 +506,7 @@ public final class RunAsAuthorizationManagerAdapter implements AuthorizationM } } ---- -==== +====== Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/method-security.adoc#jc-method-security-custom-authorization-manager[adding a custom `AuthorizationManager`]. @@ -485,8 +531,10 @@ To prepare for this, ensure that authorization rules exist are declared for ever For example, an application configuration like: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Override @@ -497,7 +545,8 @@ protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { @@ -507,7 +556,8 @@ override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -515,12 +565,14 @@ override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { ---- -==== +====== should change to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Override @@ -533,7 +585,8 @@ protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { @@ -545,7 +598,8 @@ override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -557,7 +611,7 @@ override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { ---- -==== +====== === Add `@EnableWebSocketSecurity` @@ -572,8 +626,10 @@ If you are using Java Configuration, add {security-api-url}org/springframework/s For example, you can add it to your websocket security configuration class, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSocketSecurity @@ -583,7 +639,8 @@ public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBro } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSocketSecurity @@ -592,7 +649,7 @@ class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer( // ... } ---- -==== +====== This will make a prototype instance of `MessageMatcherDelegatingAuthorizationManager.Builder` available to encourage configuration by composition instead of extension. @@ -602,8 +659,10 @@ To start using `AuthorizationManager`, you can set the `use-authorization-manage For example, the following application configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Override @@ -616,7 +675,8 @@ protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { @@ -628,7 +688,8 @@ override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -640,12 +701,14 @@ override fun configureInbound(messages: MessageSecurityMetadataSourceRegistry) { ---- -==== +====== changes to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -659,7 +722,8 @@ AuthorizationManager> messageSecurity(MessageMatcherDelegatingAuthori } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -673,7 +737,8 @@ fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.B } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -685,7 +750,7 @@ fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.B ---- -==== +====== === Stop Implementing `AbstractSecurityWebSocketMessageBrokerConfigurer` @@ -693,8 +758,10 @@ If you are using Java configuration, you can now simply extend `WebSocketMessage For example, if your class that extends `AbstractSecurityWebSocketMessageBrokerConfigurer` is called `WebSocketSecurityConfig`, then: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSocketSecurity @@ -704,7 +771,8 @@ public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBro } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSocketSecurity @@ -713,12 +781,14 @@ class WebSocketSecurityConfig: AbstractSecurityWebSocketMessageBrokerConfigurer( // ... } ---- -==== +====== changes to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSocketSecurity @@ -728,7 +798,8 @@ public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSocketSecurity @@ -737,7 +808,7 @@ class WebSocketSecurityConfig: WebSocketMessageBrokerConfigurer { // ... } ---- -==== +====== [[servlet-authorizationmanager-messages-opt-out]] === Opt-out Steps @@ -748,8 +819,10 @@ In case you had trouble, take a look at these scenarios for optimal opt out beha If you are having trouble setting an `anyRequest` authorization rule of `denyAll`, please use {security-api-url}org/springframework/security/messaging/access/intercept/MessageMatcherDelegatingAuthorizationManager.Builder.Constraint.html#permitAll()[`permitAll`] instead, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -763,7 +836,8 @@ AuthorizationManager> messageSecurity(MessageMatcherDelegatingAuthori } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -777,7 +851,8 @@ fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.B } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -787,7 +862,7 @@ fun messageSecurity(val messages: MessageMatcherDelegatingAuthorizationManager.B ---- -==== +====== ==== I cannot get CSRF working, need some other `AbstractSecurityWebSocketMessageBrokerConfigurer` feature, or am having trouble with `AuthorizationManager` @@ -796,7 +871,6 @@ Even though it is deprecated, it will not be removed in 6.0. In the case of XML, you can opt out of `AuthorizationManager` by setting `use-authorization-manager="false"`: -==== .Xml [source,xml,role="secondary"] ---- @@ -805,11 +879,9 @@ In the case of XML, you can opt out of `AuthorizationManager` by setting `use-au ---- -==== to: -==== .Xml [source,xml,role="secondary"] ---- @@ -818,7 +890,6 @@ to: ---- -==== == Use `AuthorizationManager` for Request Security @@ -842,8 +913,10 @@ You may already have an `anyRequest` rule defined that you are happy with in whi Adding `denyAll` to the end looks like changing: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -855,7 +928,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -867,7 +941,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -875,12 +950,14 @@ http { ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -893,7 +970,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -906,7 +984,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -915,7 +994,7 @@ http { ---- -==== +====== If you have already migrated to `authorizeHttpRequests`, the recommended change is the same. @@ -925,8 +1004,10 @@ To opt in to using `AuthorizationManager`, you can use `authorizeHttpRequests` o Change: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -939,7 +1020,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -952,7 +1034,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -961,12 +1044,14 @@ http { ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -979,7 +1064,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -992,7 +1078,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -1001,7 +1088,7 @@ http { ---- -==== +====== === Migrate SpEL expressions to `AuthorizationManager` @@ -1014,8 +1101,10 @@ For completeness, both options will be demonstrated. First, if you have the following SpEL: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1028,7 +1117,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1040,12 +1130,14 @@ http { } } ---- -==== +====== Then you can compose your own `AuthorizationManager` with Spring Security authorization primitives like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1058,7 +1150,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1070,12 +1163,14 @@ http { } } ---- -==== +====== Or you can use `WebExpressionAuthorizationManager` in the following way: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1090,7 +1185,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1104,7 +1200,7 @@ http { } } ---- -==== +====== [[switch-filter-all-dispatcher-types]] === Switch to filter all dispatcher types @@ -1119,8 +1215,10 @@ So, finally, change your authorization rules to filter all dispatcher types. To do this, you should change: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1133,7 +1231,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1146,7 +1245,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -1155,12 +1255,14 @@ http { ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1173,7 +1275,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1186,7 +1289,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -1195,23 +1299,27 @@ http { ---- -==== +====== And, the `FilterChainProxy` should be registered for all dispatcher types as well. If you are using Spring Boot, https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.security.spring.security.filter.dispatcher-types[you have to change the `spring.security.filter.dispatcher-types` property] to include all dispatcher types: -==== -.application.properties +[tabs] +====== +application.properties:: ++ [source,properties,role="primary"] ---- spring.security.filter.dispatcher-types=request,async,error,forward,include ---- -==== +====== If you are xref:servlet/configuration/java.adoc#_abstractsecuritywebapplicationinitializer[using the `AbstractSecurityWebApplicationInitializer`] you should override the `getSecurityDispatcherTypes` method and return all dispatcher types: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- import org.springframework.security.web.context.*; @@ -1226,7 +1334,7 @@ public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplic } ---- -==== +====== ==== Permit `FORWARD` when using Spring MVC @@ -1236,8 +1344,10 @@ As we saw on the <>, Spring Consider the following common configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -1255,12 +1365,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti return http.build(); } ---- -==== +====== and one of the following equivalents MVC view mapping configurations: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Controller @@ -1273,10 +1385,12 @@ public class MyController { } ---- -==== +====== -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -1289,15 +1403,17 @@ public class MyWebMvcConfigurer implements WebMvcConfigurer { } ---- -==== +====== With either configuration, when there is a request to `/login`, Spring MVC will perform a *forward* to the view `login`, which, with the default configuration, is under `src/main/resources/templates/login.html` path. The security configuration permits requests to `/login` but every other request will be denied, including the `FORWARD` request to the view under `/templates/login.html`. To fix this, you should configure Spring Security to permit `FORWARD` requests: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1309,7 +1425,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1321,7 +1438,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -1334,7 +1452,7 @@ http { ---- -==== +====== === Replace any custom filter-security ``AccessDecisionManager``s @@ -1346,8 +1464,10 @@ Read on to find the best match for your situation. If your application uses {security-api-url}org/springframework/security/access/vote/UnanimousBased.html[`UnanimousBased`], you should first adapt or replace any ``AccessDecisionVoter``s and then you can construct an `AuthorizationManager` like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -1358,7 +1478,8 @@ AuthorizationManager requestAuthorization() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -1369,7 +1490,8 @@ fun requestAuthorization(): AuthorizationManager { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- { ---- -==== +====== then, wire it into the DSL like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1395,7 +1519,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1406,12 +1531,13 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== [NOTE] ==== @@ -1423,8 +1549,10 @@ See xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization If your application uses {security-api-url}org/springframework/security/access/vote/AffirmativeBased.html[`AffirmativeBased`], then you can construct an equivalent {security-api-url}org/springframework/security/authorization/AuthorizationManager.html[`AuthorizationManager`], like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -1435,7 +1563,8 @@ AuthorizationManager requestAuthorization() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -1446,7 +1575,8 @@ fun requestAuthorization(): AuthorizationManager { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- { ---- -==== +====== then, wire it into the DSL like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1472,7 +1604,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1483,12 +1616,13 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== [NOTE] ==== @@ -1511,8 +1645,10 @@ You should either change the class to implement {security-api-url}org/springfram Without knowing what your custom voter is doing, it is impossible to recommend a general-purpose solution. By way of example, though, here is what adapting {security-api-url}org/springframework/security/access/SecurityMetadataSource.html[`SecurityMetadataSource`] and {security-api-url}org/springframework/security/access/AccessDecisionVoter.html[`AccessDecisionVoter`] for `anyRequest().authenticated()` would look like: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public final class AnyRequestAuthenticatedAuthorizationManagerAdapter implements AuthorizationManager { @@ -1541,7 +1677,7 @@ public final class AnyRequestAuthenticatedAuthorizationManagerAdapter implements } } ---- -==== +====== Once you have implemented `AuthorizationManager`, please follow the details in the reference manual for xref:servlet/authorization/authorize-http-requests.adoc#custom-authorization-manager[adding a custom `AuthorizationManager`]. @@ -1553,7 +1689,6 @@ Therefore, if you are using `GrantedAuthorityDefaults` to change the prefix of y For example, you will have to change from: -==== .authorizeRequests with custom role prefix [source,java] ---- @@ -1571,11 +1706,9 @@ public GrantedAuthorityDefaults grantedAuthorityDefaults() { return new GrantedAuthorityDefaults("MYPREFIX_"); } ---- -==== to: -==== .authorizeHttpRequests with hasAuthority and custom role prefix [source,java] ---- @@ -1588,7 +1721,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } ---- -==== This should be supported in the future, see https://github.com/spring-projects/spring-security/issues/13215[gh-13227] for more details. @@ -1601,8 +1733,10 @@ In case you had trouble, take a look at these scenarios for optimal opt out beha If you cannot secure all dispatcher types, first try and declare which dispatcher types should not require authorization like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1616,7 +1750,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1630,7 +1765,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -1649,12 +1785,14 @@ http { ---- -==== +====== Or, if that doesn't work, then you can explicitly opt out of the behavior by setting `filter-all-dispatcher-types` and `filterAllDispatcherTypes` to `false`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1666,7 +1804,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1678,7 +1817,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -1686,12 +1826,14 @@ http { ---- -==== +====== or, if you are still using `authorizeRequests` or `use-authorization-manager="false"`, set `oncePerRequest` to `true`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1703,7 +1845,8 @@ http // ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1715,7 +1858,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -1723,14 +1867,16 @@ http { ---- -==== +====== ==== I cannot declare an authorization rule for all requests If you are having trouble setting an `anyRequest` authorization rule of `denyAll`, please use {security-api-url}org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationConfigurer.AuthorizedUrl.html#permitAll()[`permitAll`] instead, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -1741,7 +1887,8 @@ http ) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -1753,7 +1900,8 @@ http { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -1762,7 +1910,7 @@ http { ---- -==== +====== ==== I cannot migrate my SpEL or my `AccessDecisionManager` @@ -1772,7 +1920,6 @@ First, if you still need `authorizeRequests`, you are welcome to keep using it. Second, if you still need your custom `access-decision-manager-ref` or have some other reason to opt out of `AuthorizationManager`, do: -==== .Xml [source,xml,role="secondary"] ---- @@ -1781,4 +1928,3 @@ Second, if you still need your custom `access-decision-manager-ref` or have some ---- -==== diff --git a/docs/modules/ROOT/pages/migration/servlet/config.adoc b/docs/modules/ROOT/pages/migration/servlet/config.adoc index 5800642926..0335035b47 100644 --- a/docs/modules/ROOT/pages/migration/servlet/config.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/config.adoc @@ -10,8 +10,10 @@ In 6.0, `@Configuration` is removed from `@EnableWebSecurity`, `@EnableMethodSec To prepare for this, wherever you are using one of these annotations, you may need to add `@Configuration`. For example, `@EnableMethodSecurity` changes from: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableMethodSecurity @@ -19,8 +21,12 @@ public class MyConfiguration { // ... } ---- +====== -.Kotlin +[tabs] +====== +Kotlin:: ++ [source,java,role="primary"] ---- @EnableMethodSecurity @@ -28,12 +34,14 @@ open class MyConfiguration { // ... } ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -42,8 +50,12 @@ public class MyConfiguration { // ... } ---- +====== -.Kotlin +[tabs] +====== +Kotlin:: ++ [source,java,role="primary"] ---- @Configuration @@ -52,7 +64,7 @@ open class MyConfiguration { // ... } ---- -==== +====== [[use-new-requestmatchers]] == Use the new `requestMatchers` methods @@ -67,8 +79,10 @@ In summary, the new methods choose the `MvcRequestMatcher` implementation if you To start using the new methods, you can replace the deprecated methods with the new ones. For example, the following application configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -88,12 +102,14 @@ public class SecurityConfig { } ---- -==== +====== can be changed to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -113,13 +129,15 @@ public class SecurityConfig { } ---- -==== +====== If you have Spring MVC in the classpath and are using the `mvcMatchers` methods, you can replace it with the new methods and Spring Security will choose the `MvcRequestMatcher` implementation for you. The following configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -139,12 +157,14 @@ public class SecurityConfig { } ---- -==== +====== is equivalent to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -164,12 +184,14 @@ public class SecurityConfig { } ---- -==== +====== If you are customizing the `servletPath` property of the `MvcRequestMatcher`, you can now use the `MvcRequestMatcher.Builder` to create `MvcRequestMatcher` instances that share the same servlet path: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -190,12 +212,14 @@ public class SecurityConfig { } ---- -==== +====== The code above can be rewritten using the `MvcRequestMatcher.Builder` and the `requestMatchers` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -217,13 +241,15 @@ public class SecurityConfig { } ---- -==== +====== If you are having problem with the new `requestMatchers` methods, you can always switch back to the `RequestMatcher` implementation that you were using. For example, if you still want to use `AntPathRequestMatcher` and `RegexRequestMatcher` implementations, you can use the `requestMatchers` method that accepts a `RequestMatcher` instance: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; @@ -247,14 +273,16 @@ public class SecurityConfig { } ---- -==== +====== Note that the above sample uses static factory methods from {security-api-url}org/springframework/security/web/util/matcher/AntPathRequestMatcher.html[`AntPathRequestMatcher`] and {security-api-url}org/springframework/security/web/util/matcher/RegexRequestMatcher.html[`RegexRequestMatcher`] to improve readability. If you are using the `WebSecurityCustomizer` interface, you can replace the deprecated `antMatchers` methods: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -262,12 +290,14 @@ public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2"); } ---- -==== +====== with their `requestMatchers` counterparts: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -275,12 +305,14 @@ public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().requestMatchers("/ignore1", "/ignore2"); } ---- -==== +====== The same way, if you are customizing the CSRF configuration to ignore some paths, you can replace the deprecated methods with the `requestMatchers` methods: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -292,12 +324,14 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } ---- -==== +====== can be changed to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -309,7 +343,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } ---- -==== +====== [[use-new-security-matchers]] == Use the new `securityMatchers` methods @@ -323,8 +357,10 @@ Another reason for adding the `securityMatchers` methods is to avoid confusion w The following configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -338,12 +374,14 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } ---- -==== +====== can be rewritten using the `securityMatchers` methods: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -357,12 +395,14 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } ---- -==== +====== If you are using a custom `RequestMatcher` in your `HttpSecurity` configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -380,12 +420,14 @@ public class MyCustomRequestMatcher implements RequestMatcher { // ... } ---- -==== +====== you can do the same using `securityMatcher`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -403,12 +445,14 @@ public class MyCustomRequestMatcher implements RequestMatcher { // ... } ---- -==== +====== If you are combining multiple `RequestMatcher` implementations in your `HttpSecurity` configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -426,12 +470,14 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } ---- -==== +====== you can change it by using `securityMatchers`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -448,12 +494,14 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } ---- -==== +====== If you are having problems with the `securityMatchers` methods choosing the `RequestMatcher` implementation for you, you can always choose the `RequestMatcher` implementation yourself: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; @@ -471,7 +519,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } ---- -==== +====== == Stop Using `WebSecurityConfigurerAdapter` @@ -481,8 +529,10 @@ Spring Security 5.4 introduced the capability to publish a `SecurityFilterChain` In 6.0, `WebSecurityConfigurerAdapter` is removed. To prepare for this change, you can replace constructs like: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -500,7 +550,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -519,12 +570,14 @@ open class SecurityConfiguration: WebSecurityConfigurerAdapter() { } ---- -==== +====== with: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -543,7 +596,8 @@ public class SecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -562,15 +616,17 @@ open class SecurityConfiguration { } ---- -==== +====== === Publish a `WebSecurityCustomizer` Bean Spring Security 5.4 https://github.com/spring-projects/spring-security/issues/8978[introduced `WebSecurityCustomizer`] to replace `configure(WebSecurity web)` in `WebSecurityConfigurerAdapter`. To prepare for its removal, you can replace code like the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -584,7 +640,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -596,12 +653,14 @@ open class SecurityConfiguration: WebSecurityConfigurerAdapter() { } ---- -==== +====== with: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -615,7 +674,8 @@ public class SecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -628,7 +688,7 @@ open class SecurityConfiguration { } ---- -==== +====== === Publish an `AuthenticationManager` Bean @@ -639,8 +699,10 @@ Preparing for its removal will differ based on your reason for using it. If you are using `auth.ldapAuthentication()` for xref:servlet/authentication/passwords/ldap.adoc[LDAP authentication support], you can replace: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -659,7 +721,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -676,12 +739,14 @@ open class SecurityConfiguration: WebSecurityConfigurerAdapter() { } ---- -==== +====== with: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -705,7 +770,8 @@ public class SecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -727,14 +793,16 @@ open class SecurityConfiguration { } } ---- -==== +====== ==== JDBC Authentication If you are using `auth.jdbcAuthentication()` for xref:servlet/authentication/passwords/jdbc.adoc[JDBC Authentication support], you can replace: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -761,7 +829,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -786,12 +855,14 @@ open class SecurityConfiguration: WebSecurityConfigurerAdapter() { } } ---- -==== +====== with: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -818,7 +889,8 @@ public class SecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -844,14 +916,16 @@ open class SecurityConfiguration { } } ---- -==== +====== ==== In-Memory Authentication If you are using `auth.inMemoryAuthentication()` for xref:servlet/authentication/passwords/in-memory.adoc[In-Memory Authentication support], you can replace: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -869,7 +943,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -885,12 +960,14 @@ open class SecurityConfiguration: WebSecurityConfigurerAdapter() { } } ---- -==== +====== with: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -907,7 +984,8 @@ public class SecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -923,7 +1001,7 @@ open class SecurityConfiguration { } } ---- -==== +====== == Add `@Configuration` to `@Enable*` annotations @@ -942,8 +1020,10 @@ The following annotations had their `@Configuration` removed: For example, if you are using `@EnableWebSecurity`, you will need to change: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -951,12 +1031,14 @@ public class SecurityConfig { // ... } ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -965,7 +1047,7 @@ public class SecurityConfig { // ... } ---- -==== +====== And the same applies to every other annotation listed above. diff --git a/docs/modules/ROOT/pages/migration/servlet/exploits.adoc b/docs/modules/ROOT/pages/migration/servlet/exploits.adoc index 15e6716f6b..ec0d4960bc 100644 --- a/docs/modules/ROOT/pages/migration/servlet/exploits.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/exploits.adoc @@ -25,8 +25,10 @@ To opt into the new Spring Security 6 default, the following configuration can b [[servlet-opt-in-defer-loading-csrf-token]] .Defer Loading `CsrfToken` -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -43,7 +45,8 @@ public SecurityFilterChain springSecurity(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -60,7 +63,8 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -71,7 +75,7 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { class="org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler" p:csrfRequestAttributeName="_csrf"/> ---- -==== +====== [NOTE] ==== @@ -92,8 +96,10 @@ In this case, you have several options for restoring the behavior your client-si One option is to add a `Filter` that eagerly renders the `CsrfToken` to the response regardless of which request is made first, like so: .Add a `Filter` to return a cookie on the response -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -128,7 +134,8 @@ private static final class CsrfCookieFilter extends OncePerRequestFilter { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -159,7 +166,7 @@ class CsrfCookieFilter : OncePerRequestFilter() { } ---- -==== +====== The option above does not require changes to the single-page application, but does cause the `CsrfToken` to be loaded on every request. If you do not wish to add a `Filter` to eagerly load tokens on every request, additional options are listed below. @@ -170,8 +177,10 @@ If you are using sessions, your application will benefit from deferred tokens. Instead of opting out, another option is to add a new `@RestController` with a `/csrf` endpoint, like so: .Add a `/csrf` endpoint -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @RestController @@ -185,7 +194,8 @@ public class CsrfController { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @RestController @@ -198,7 +208,7 @@ class CsrfController { } ---- -==== +====== [NOTE] ==== @@ -225,8 +235,10 @@ If you simply wish to opt out of deferred tokens altogether, that option is list If deferred tokens break your application for another reason, then you can explicitly opt into the 5.8 defaults using the following configuration: .Explicit Configure `CsrfToken` with 5.8 Defaults -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -243,7 +255,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -260,7 +273,8 @@ open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -274,7 +288,7 @@ open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
---- -==== +====== [NOTE] ==== @@ -287,8 +301,10 @@ This causes the `CsrfToken` to be loaded on every request. If the steps for <> work for you, then you can also opt into Spring Security 6's default support for BREACH protection of the `CsrfToken` using the following configuration: .`CsrfToken` BREACH Protection -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -305,7 +321,8 @@ DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -322,7 +339,8 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -333,7 +351,7 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { class="org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler" p:csrfRequestAttributeName="_csrf"/> ---- -==== +====== [[servlet-csrf-breach-opt-out]] === Opt-out Steps @@ -347,8 +365,10 @@ If you are using AngularJS and the https://angular.io/api/common/http/HttpClient In this case, you can configure Spring Security to validate the raw `CsrfToken` from the cookie while keeping CSRF BREACH protection of the response using a custom `CsrfTokenRequestHandler` with delegation, like so: .Configure `CsrfToken` BREACH Protection to validate raw tokens -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -371,7 +391,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -393,7 +414,8 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -405,7 +427,7 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { class="org.springframework.security.web.csrf.CookieCsrfTokenRepository" p:cookieHttpOnly="false"/> ---- -==== +====== This is the RECOMMENDED way to configure Spring Security to work with a client-side application that uses cookie values, because it continues to allow the response to return a randomized value for the CSRF token in case the application returns HTML or other responses that could be vulnerable to BREACH without your knowledge. @@ -431,8 +453,10 @@ If CSRF BREACH protection does not work for you for another reason, you can opt If the steps for <> work for normal HTTP requests and you are using xref:servlet/integrations/websocket.adoc[WebSocket Security] support, then you can also opt into Spring Security 6's default support for BREACH protection of the `CsrfToken` with xref:servlet/integrations/websocket.adoc#websocket-sameorigin-csrf[Stomp headers]. .WebSocket Security BREACH Protection -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -441,7 +465,8 @@ ChannelInterceptor csrfChannelInterceptor() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -450,19 +475,22 @@ open fun csrfChannelInterceptor(): ChannelInterceptor { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== If configuring CSRF BREACH protection for WebSocket Security gives you trouble, you can configure the 5.8 default using the following configuration: .Configure WebSocket Security with 5.8 default -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -471,7 +499,8 @@ ChannelInterceptor csrfChannelInterceptor() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -480,10 +509,11 @@ open fun csrfChannelInterceptor(): ChannelInterceptor { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== diff --git a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc index 21681ae462..58d60e7205 100644 --- a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc @@ -20,8 +20,10 @@ If you are using authorization rules or expressions such as `hasRole("USER")` or To opt into the new Spring Security 6 defaults, the following configuration can be used. .Configure oauth2Login() with 6.0 defaults -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -63,7 +65,8 @@ private GrantedAuthoritiesMapper grantedAuthoritiesMapper() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -94,14 +97,15 @@ private fun grantedAuthoritiesMapper(): GrantedAuthoritiesMapper { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== [[servlet-oauth2-login-authorities-opt-out]] === Opt-out Steps @@ -109,8 +113,10 @@ private fun grantedAuthoritiesMapper(): GrantedAuthoritiesMapper { If configuring the new authorities gives you trouble, you can opt out and explicitly use the 5.8 authority of `ROLE_USER` with the following configuration. .Configure oauth2Login() with 5.8 defaults -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -152,7 +158,8 @@ private GrantedAuthoritiesMapper grantedAuthoritiesMapper() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -183,14 +190,15 @@ private fun grantedAuthoritiesMapper(): GrantedAuthoritiesMapper { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== == Address OAuth2 Client Deprecations diff --git a/docs/modules/ROOT/pages/migration/servlet/saml2.adoc b/docs/modules/ROOT/pages/migration/servlet/saml2.adoc index 210968646c..f9dc14e540 100644 --- a/docs/modules/ROOT/pages/migration/servlet/saml2.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/saml2.adoc @@ -9,8 +9,10 @@ As such, Spring Security 6 drops support for it, bumping up its OpenSAML baselin To prepare for the upgrade, update your pom to depend on OpenSAML 4 instead of 3: -==== -.Maven +[tabs] +====== +Maven:: ++ [source,maven,role="primary"] ---- @@ -32,7 +34,8 @@ To prepare for the upgrade, update your pom to depend on OpenSAML 4 instead of 3 ---- -.Gradle +Gradle:: ++ [source,gradle,role="secondary"] ---- dependencies { @@ -43,7 +46,7 @@ dependencies { } } ---- -==== +====== You must use at least OpenSAML 4.1.1 to update to Spring Security 6's SAML support. @@ -57,8 +60,10 @@ As such, some adjustment will be required to make the challenge. Consider the following representative usage of `OpenSamlAuthenticationProvider`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- OpenSamlAuthenticationProvider versionThree = new OpenSamlAuthenticationProvider(); @@ -66,19 +71,22 @@ versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor); versionThree.setResponseTimeValidationSkew(myDuration); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val versionThree: OpenSamlAuthenticationProvider = OpenSamlAuthenticationProvider() versionThree.setAuthoritiesExtractor(myAuthoritiesExtractor) versionThree.setResponseTimeValidationSkew(myDuration) ---- -==== +====== This should change to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Converter delegate = OpenSaml4AuthenticationProvider @@ -96,7 +104,8 @@ Converter validator = OpenSaml4Au versionFour.setAssertionValidator(validator); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val delegate = OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter() @@ -114,7 +123,7 @@ val validator = OpenSaml4AuthenticationProvider .createDefaultAssertionValidatorWithParameters({ p -> p.put(CLOCK_SKEW, myDuration) }) versionFour.setAssertionValidator(validator) ---- -==== +====== == Stop Using SAML 2.0 `Converter` constructors @@ -136,8 +145,10 @@ Most applications need do nothing; however, if you use or configure `Saml2Authen If you are calling `OpenSaml4AuthenticationReqeustFactory#setAuthenticationRequestContextConverter`, for example, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -167,12 +178,14 @@ Saml2AuthenticationRequestFactory authenticationRequestFactory() { return factory; } ---- -==== +====== to ensure that ForceAuthn is set to `true`, you can instead do: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -182,7 +195,7 @@ Saml2AuthenticationRequestResolver authenticationRequestResolver(RelyingPartyReg return resolver; } ---- -==== +====== Also, since `setAuthnRequestCustomizer` has direct access to the `HttpServletRequest`, there is no need for a `Saml2AuthenticationRequestContextResolver`. Simply use `setAuthnRequestCustomizer` to read directly from `HttpServletRequest` this information you need. @@ -191,8 +204,10 @@ Simply use `setAuthnRequestCustomizer` to read directly from `HttpServletRequest Instead of doing: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -202,12 +217,14 @@ Saml2AuthenticationRequestFactory authenticationRequestFactory() { return factory; } ---- -==== +====== you can do: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -218,7 +235,7 @@ Saml2AuthenticationRequestResolver authenticationRequestResolver() { return resolver; } ---- -==== +====== [NOTE] ==== @@ -235,37 +252,43 @@ It also is valuable in that it more closely aligns with the design of `OAuth2Log Most applications do not construct this class directly since `Saml2WebSsoAuthenticationFilter` does. However, in the event that your application constructs one, please change from: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- new Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(), registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials()) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- Saml2AuthenticationToken(saml2Response, registration.getSingleSignOnServiceLocation(), registration.getAssertingParty().getEntityId(), registration.getEntityId(), registration.getCredentials()) ---- -==== +====== to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- new Saml2AuthenticationToken(saml2Response, registration) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- Saml2AuthenticationToken(saml2Response, registration) ---- -==== +====== == Use `RelyingPartyRegistration` updated methods @@ -275,8 +298,10 @@ As more capabilities were added to `RelyingPartyRegistration`, it became necessa The deprecated methods in `RelyingPartyRegstration` are removed. To prepare for that, consider the following representative usage of `RelyingPartyRegistration`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- String idpEntityId = registration.getRemoteIdpEntityId(); @@ -288,7 +313,8 @@ List verifying = registration.getCredentials().stream() .collect(Collectors.toList()); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val idpEntityId: String = registration.getRemoteIdpEntityId() @@ -298,12 +324,14 @@ val localEntityId: String = registration.getLocalEntityIdTemplate() val verifying: List = registration.getCredentials() .filter(Saml2X509Credential::isSignatureVerficationCredential) ---- -==== +====== This should change to: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- String assertingPartyEntityId = registration.getAssertingPartyDetails().getEntityId(); @@ -313,7 +341,8 @@ String entityId = registration.getEntityId(); List verifying = registration.getAssertingPartyDetails().getVerificationX509Credentials(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val assertingPartyEntityId: String = registration.getAssertingPartyDetails().getEntityId() @@ -322,6 +351,6 @@ val singleSignOnServiceLocation: String = registration.getAssertingPartyDetails( val entityId: String = registration.getEntityId() val verifying: List = registration.getAssertingPartyDetails().getVerificationX509Credentials() ---- -==== +====== For a complete listing of all changed methods, please see {security-api-url}org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.html[``RelyingPartyRegistration``'s JavaDoc]. diff --git a/docs/modules/ROOT/pages/migration/servlet/session-management.adoc b/docs/modules/ROOT/pages/migration/servlet/session-management.adoc index 83e479e407..18391fca84 100644 --- a/docs/modules/ROOT/pages/migration/servlet/session-management.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/session-management.adoc @@ -28,8 +28,10 @@ In Spring Security 6, the default `SecurityContextRepository` is `DelegatingSecu To opt into the new Spring Security 6 default, the following configuration can be used. .Configure SecurityContextRepository with 6.0 defaults -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -46,7 +48,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -64,7 +67,8 @@ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -80,7 +84,7 @@ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== [IMPORTANT] ==== @@ -103,8 +107,10 @@ If you have implemented `SecurityContextRepository` yourself and added an implem To get started implementing the new method, use the following example to provide a `DeferredSecurityContext`: .Provide `DeferredSecurityContext` -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Override @@ -135,7 +141,8 @@ public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- override fun loadDeferredContext(request: HttpServletRequest): DeferredSecurityContext { @@ -159,7 +166,7 @@ override fun loadDeferredContext(request: HttpServletRequest): DeferredSecurityC } } ---- -==== +====== [[requestcache-query-optimization]] == Optimize Querying of `RequestCache` @@ -186,8 +193,10 @@ This means that there is no need to detect when `Authentication` is done and thu To opt into the new Spring Security 6 default, the following configuration can be used. .Require Explicit `SessionAuthenticationStrategy` Invocation -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -201,7 +210,8 @@ DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -215,7 +225,8 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -223,13 +234,15 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== If this breaks your application, then you can explicitly opt into the 5.8 defaults using the following configuration: .Explicit use Spring Security 5.8 defaults for `SessionAuthenticationStrategy` -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -243,7 +256,8 @@ DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -257,7 +271,8 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -265,4 +280,4 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== diff --git a/docs/modules/ROOT/pages/reactive/authentication/logout.adoc b/docs/modules/ROOT/pages/reactive/authentication/logout.adoc index d4dafaa9c3..60d27138b1 100644 --- a/docs/modules/ROOT/pages/reactive/authentication/logout.adoc +++ b/docs/modules/ROOT/pages/reactive/authentication/logout.adoc @@ -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 { } } ---- +====== diff --git a/docs/modules/ROOT/pages/reactive/authentication/x509.adoc b/docs/modules/ROOT/pages/reactive/authentication/x509.adoc index 880632b060..02a40de498 100644 --- a/docs/modules/ROOT/pages/reactive/authentication/x509.adoc +++ b/docs/modules/ROOT/pages/reactive/authentication/x509.adoc @@ -4,8 +4,10 @@ Similar to xref:servlet/authentication/x509.adoc#servlet-x509[Servlet X.509 authentication], reactive x509 authentication filter allows extracting an authentication token from a certificate provided by a client. Below is an example of a reactive x509 security configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -19,7 +21,8 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -32,14 +35,16 @@ fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { } } ---- -==== +====== In the configuration above, when neither `principalExtractor` nor `authenticationManager` is provided defaults will be 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 user account with a name extracted by `principalExtractor` exists and it is not locked, disabled, or expired. The next example demonstrates how these defaults can be overridden. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -66,7 +71,8 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -88,7 +94,7 @@ fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { } } ---- -==== +====== In this 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 will be authenticated. diff --git a/docs/modules/ROOT/pages/reactive/authorization/authorize-http-requests.adoc b/docs/modules/ROOT/pages/reactive/authorization/authorize-http-requests.adoc index 2a4faefc65..c1e27d2309 100644 --- a/docs/modules/ROOT/pages/reactive/authorization/authorize-http-requests.adoc +++ b/docs/modules/ROOT/pages/reactive/authorization/authorize-http-requests.adoc @@ -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. diff --git a/docs/modules/ROOT/pages/reactive/authorization/method.adoc b/docs/modules/ROOT/pages/reactive/authorization/method.adoc index 1f9b9a844d..2efc242fbf 100644 --- a/docs/modules/ROOT/pages/reactive/authorization/method.adoc +++ b/docs/modules/ROOT/pages/reactive/authorization/method.adoc @@ -32,8 +32,10 @@ For earlier versions, please read about similar support with < 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> 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 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() ---- -==== +====== with `this::findMessageByUsername` defined as: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Mono findMessageByUsername(String username) { @@ -337,19 +362,22 @@ Mono findMessageByUsername(String username) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- fun findMessageByUsername(username: String): Mono { return Mono.just("Hi $username") } ---- -==== +====== Below is a minimal method security configuration when using method security in reactive applications. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableReactiveMethodSecurity @@ -370,7 +398,8 @@ public class SecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableReactiveMethodSecurity @@ -390,12 +419,14 @@ class SecurityConfig { } } ---- -==== +====== Consider the following class: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Component @@ -407,7 +438,8 @@ public class HelloWorldMessageService { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Component @@ -418,12 +450,14 @@ class HelloWorldMessageService { } } ---- -==== +====== Or, the following class using Kotlin coroutines: -==== -.Kotlin +[tabs] +====== +Kotlin:: ++ [source,kotlin,role="primary"] ---- @Component @@ -435,7 +469,7 @@ class HelloWorldMessageService { } } ---- -==== +====== Combined with our configuration above, `@PreAuthorize("hasRole('ADMIN')")` will ensure that `findByMessage` is only invoked by a user with the role `ADMIN`. @@ -445,8 +479,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"] ---- @EnableWebFluxSecurity @@ -482,7 +518,8 @@ public class SecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebFluxSecurity @@ -513,6 +550,6 @@ class SecurityConfig { } } ---- -==== +====== You can find a complete sample in {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method] diff --git a/docs/modules/ROOT/pages/reactive/configuration/webflux.adoc b/docs/modules/ROOT/pages/reactive/configuration/webflux.adoc index 83a409e1c3..25d319b5e0 100644 --- a/docs/modules/ROOT/pages/reactive/configuration/webflux.adoc +++ b/docs/modules/ROOT/pages/reactive/configuration/webflux.adoc @@ -14,8 +14,10 @@ You can find a few sample applications that demonstrate the code below: You can find a minimal WebFlux Security configuration below: .Minimal WebFlux Security Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ----- @@ -34,7 +36,8 @@ public class HelloWebfluxSecurityConfig { } ----- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ----- @EnableWebFluxSecurity @@ -51,7 +54,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 log in page and a default log out page, sets up security related HTTP headers, CSRF protection, and more. @@ -60,8 +63,10 @@ This configuration provides form and http basic authentication, sets up authoriz You can find an explicit version of the minimal WebFlux Security configuration below: .Explicit WebFlux Security Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ----- @Configuration @@ -91,7 +96,8 @@ public class HelloWebfluxSecurityConfig { } ----- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ----- import org.springframework.security.config.web.server.invoke @@ -122,7 +128,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. @@ -139,8 +145,10 @@ You can configure multiple `SecurityWebFilterChain` instances to separate config For example, you can isolate configuration for URLs that start with `/api`, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -178,7 +186,8 @@ static class MultiSecurityHttpConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.security.config.web.server.invoke @@ -218,7 +227,7 @@ 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/` diff --git a/docs/modules/ROOT/pages/reactive/exploits/csrf.adoc b/docs/modules/ROOT/pages/reactive/exploits/csrf.adoc index cc43b90ef9..bbe040e362 100644 --- a/docs/modules/ROOT/pages/reactive/exploits/csrf.adoc +++ b/docs/modules/ROOT/pages/reactive/exploits/csrf.adoc @@ -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 using: .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, it is simple to disable CSRF protection if it xref:features/exploits/cs 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 @@ An alternate implementation `XorServerCsrfTokenRequestAttributeHandler` is avail You can configure `XorServerCsrfTokenRequestAttributeHandler` using the following Java configuration: .Configure 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< For example, the following code will place the `CsrfToken` on the default attribute name (`_csrf`) used by Spring Security's <> 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 <> that works without any additional work. @@ -200,14 +212,12 @@ In order to post an HTML form the CSRF token must be included in the form as a h For example, the rendered HTML might look like: .CSRF Token HTML -==== [source,html] ---- ---- -==== Next we will discuss various ways of including the CSRF token in a form as a hidden input. @@ -227,7 +237,6 @@ If the <> for including the actual CSRF toke The Thymeleaf sample below assumes that you <> the `CsrfToken` on an attribute named `_csrf`. .CSRF Token in Form with Request Attribute -==== [source,html] ----
---- -==== [[webflux-csrf-include-ajax]] ==== Ajax and JSON Requests @@ -261,7 +269,6 @@ An alternative pattern to < @@ -272,13 +279,11 @@ The HTML might look something like this: ---- -==== Once the meta tags contained the CSRF token, the JavaScript code would read the meta tags and include the CSRF token as a header. If you were using jQuery, this could be done with the following: .AJAX send CSRF Token -==== [source,javascript] ---- $(function () { @@ -289,13 +294,11 @@ $(function () { }); }); ---- -==== The sample below assumes that you <> the `CsrfToken` on an attribute named `_csrf`. An example of doing this with Thymeleaf is shown below: .CSRF meta tag JSP -==== [source,html] ---- @@ -307,7 +310,6 @@ An example of doing this with Thymeleaf is shown below: ---- -==== [[webflux-csrf-considerations]] == CSRF Considerations @@ -339,8 +341,10 @@ For example, the following Java Configuration will perform logout with the URL ` // 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, this can be configured 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-considerations-override-method]] === HiddenHttpMethodFilter diff --git a/docs/modules/ROOT/pages/reactive/exploits/headers.adoc b/docs/modules/ROOT/pages/reactive/exploits/headers.adoc index 30b4779fd9..3b6c016100 100644 --- a/docs/modules/ROOT/pages/reactive/exploits/headers.adoc +++ b/docs/modules/ROOT/pages/reactive/exploits/headers.adoc @@ -16,8 +16,10 @@ For example, assume that you want the defaults except you wish to specify `SAMEO You can easily do this 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. An example is provided below: .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 @@ Details on how to do this can be found 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"] ---- @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 @@ Spring Security includes xref:features/exploits/headers.adoc#headers-content-typ However, you can disable it with: .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 is an example of explicitly providing 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 using xref:featu You can customize frame options to use the same origin using the following: .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 block reflected XSS attacks us You can disable `X-XSS-Protection` with the following Configuration: .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,7 +310,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { } } ---- -==== +====== [[webflux-headers-csp]] == Content Security Policy (CSP) @@ -299,18 +320,18 @@ The web application author must declare the security policy(s) to enforce and/or For example, given 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/ ---- -==== You can enable the CSP header as shown below: .Content Security Policy -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -326,7 +347,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -341,13 +363,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 @@ -364,7 +388,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -380,7 +405,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { } } ---- -==== +====== [[webflux-headers-referrer]] == Referrer Policy @@ -389,8 +414,10 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-referre You can enable the Referrer Policy header using configuration as shown below: .Referrer Policy Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -406,7 +433,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -421,7 +449,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { } } ---- -==== +====== [[webflux-headers-feature]] @@ -431,18 +459,18 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-feature The following `Feature-Policy` header: .Feature-Policy Example -==== [source] ---- Feature-Policy: geolocation 'self' ---- -==== You can enable the Feature Policy header as shown below: .Feature-Policy Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -456,7 +484,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -469,7 +498,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { } } ---- -==== +====== [[webflux-headers-permissions]] @@ -479,18 +508,18 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-permiss The following `Permissions-Policy` header: .Permissions-Policy Example -==== [source] ---- Permissions-Policy: geolocation=(self) ---- -==== You can enable the Permissions Policy header as shown below: .Permissions-Policy Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -506,7 +535,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -521,7 +551,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { } } ---- -==== +====== [[webflux-headers-clear-site-data]] @@ -531,17 +561,17 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-clear-s The following Clear-Site-Data header: .Clear-Site-Data Example -==== ---- Clear-Site-Data: "cache", "cookies" ---- -==== can be sent on log out with the following configuration: .Clear-Site-Data Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -559,7 +589,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -577,7 +608,7 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { } } ---- -==== +====== [[webflux-headers-cross-origin-policies]] == Cross-Origin Policies @@ -595,8 +626,10 @@ Spring Security does not add < @@ -24,12 +26,13 @@ You can add Spring Security to your Spring Boot project by adding `spring-boot-s ---- -.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 diff --git a/docs/modules/ROOT/pages/reactive/integrations/cors.adoc b/docs/modules/ROOT/pages/reactive/integrations/cors.adoc index 832bd5c025..007cd01938 100644 --- a/docs/modules/ROOT/pages/reactive/integrations/cors.adoc +++ b/docs/modules/ROOT/pages/reactive/integrations/cors.adoc @@ -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 } } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/reactive/integrations/rsocket.adoc b/docs/modules/ROOT/pages/reactive/integrations/rsocket.adoc index 924cdbacda..71a3d1d1d2 100644 --- a/docs/modules/ROOT/pages/reactive/integrations/rsocket.adoc +++ b/docs/modules/ROOT/pages/reactive/integrations/rsocket.adoc @@ -14,8 +14,10 @@ You can find a few sample applications that demonstrate the code below: 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 <> and sets up <> to require an authenticated user for any request. @@ -61,8 +64,10 @@ For Spring Security to work we need to apply `SecuritySocketAcceptorInterceptor` This is what connects our `PayloadSocketAcceptorInterceptor` we created with the RSocket infrastructure. In a Spring Boot application this is done automatically 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 using `AuthenticationPayloadExchangeConverter` which is automatically setup using the `simpleAuthentication` portion of the DSL. An explicit configuration can be found below. -==== -.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 using `SimpleAuthenticationEncoder` which can be added 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()) ---- -==== +====== It can then be used 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 requester = RSocketRequester.builder() .connectTcp(host, port); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val authenticationMimeType: MimeType = @@ -200,12 +215,14 @@ val requester: Mono = 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 requester; @@ -220,7 +237,8 @@ public Mono 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 { } } ---- -==== +====== [[rsocket-authentication-jwt]] === JWT @@ -249,8 +267,10 @@ The support comes in the form of authenticating a JWT (determining the JWT is va The RSocket receiver can decode the credentials using `BearerPayloadExchangeConverter` which is automatically setup using the `jwt` portion of the DSL. An example configuration can be found below: -==== -.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 just a simple String. For example, the token can be sent at setup time: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- MimeType authenticationMimeType = @@ -322,7 +348,8 @@ Mono 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, the token can be sent in a request. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- MimeType authenticationMimeType = @@ -355,7 +384,8 @@ public Mono findRadar(String code) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val authenticationMimeType: MimeType = @@ -371,7 +401,7 @@ open fun findRadar(code: String): Mono { } } ---- -==== +====== [[rsocket-authorization]] == RSocket Authorization @@ -380,8 +410,10 @@ RSocket authorization is performed with `AuthorizationPayloadInterceptor` which The DSL can be used to setup authorization rules based upon the `PayloadExchange`. An example configuration can be found below: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- rsocket @@ -397,7 +429,9 @@ rsocket .anyExchange().permitAll() // <6> ); ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,role="secondary"] ---- rsocket @@ -413,7 +447,7 @@ rsocket .anyExchange().permitAll() } // <6> ---- -==== +====== <1> Setting up a connection requires the authority `ROLE_SETUP` <2> If the route is `fetch.profile.me` authorization only requires the user be authenticated <3> In this rule we setup a custom matcher where authorization requires the user to have the authority `ROLE_CUSTOM` diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc index 6e42dcb698..2c4d2e2cd7 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/authorization-grants.adoc @@ -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` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebFluxSecurity @@ -154,7 +156,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebFluxSecurity @@ -192,7 +195,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. @@ -217,8 +220,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 authorizationRequestCustomizer() { @@ -228,7 +233,8 @@ private Consumer authorizationRequestCustomi } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- private fun authorizationRequestCustomizer(): Consumer { @@ -241,7 +247,7 @@ private fun authorizationRequestCustomizer(): Consumer = ... @@ -733,7 +760,7 @@ val authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.bui authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) ---- -==== +====== [NOTE] `ReactiveOAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordReactiveOAuth2AuthorizedClientProvider`, @@ -762,8 +789,10 @@ spring: ...and the `ReactiveOAuth2AuthorizedClientManager` `@Bean`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -807,7 +836,9 @@ private Function>> contextAttri }; } ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -846,12 +877,14 @@ private fun contextAttributesMapper(): Function> and therefore inherits it's capabilities. @@ -58,8 +61,10 @@ It directly uses an < 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 index() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/") @@ -169,7 +181,7 @@ fun index(): Mono { .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. diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/client-authentication.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/client-authentication.adoc index 4840edcb1c..f54d06caa9 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/client-authentication.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/client-authentication.adoc @@ -36,8 +36,10 @@ spring: The following example shows how to configure `WebClientReactiveAuthorizationCodeTokenResponseClient`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Function jwkResolver = (clientRegistration) -> { @@ -59,7 +61,8 @@ tokenResponseClient.addParametersConverter( new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver)); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val jwkResolver: Function = @@ -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 jwkResolver = (clientRegistration) -> { @@ -127,7 +132,8 @@ tokenResponseClient.addParametersConverter( new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver)); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val jwkResolver = Function { 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>` to `setJwtClientAssertionCustomizer()`. The following example shows how to customize claims of the JWT: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Function 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") } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc b/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc index 95ce3fd7c6..43a9f33199 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/client/core.adoc @@ -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>> contextAttri } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -376,7 +391,7 @@ private fun contextAttributesMapper(): Function idTokenDecoderFactory() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -632,7 +661,7 @@ fun idTokenDecoderFactory(): ReactiveJwtDecoderFactory { 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. @@ -668,8 +697,10 @@ spring: ...and the `OidcClientInitiatedServerLogoutSuccessHandler`, which implements RP-Initiated Logout, may be configured as follows: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebFluxSecurity @@ -705,7 +736,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebFluxSecurity @@ -737,7 +769,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. diff --git a/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc b/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc index 037fcff5f1..7d26cea2be 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/login/core.adoc @@ -61,10 +61,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:reactive/oauth2/client/core.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. @@ -244,8 +242,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 @@ -275,7 +275,8 @@ public class OAuth2LoginConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- @Configuration @@ -304,7 +305,7 @@ class OAuth2LoginConfig { } } ---- -==== +====== [[webflux-oauth2-login-register-securitywebfilterchain-bean]] @@ -313,8 +314,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"] ---- @EnableWebFluxSecurity @@ -333,7 +336,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebFluxSecurity @@ -350,7 +354,7 @@ class OAuth2LoginSecurityConfig { } } ---- -==== +====== [[webflux-oauth2-login-completely-override-autoconfiguration]] @@ -359,8 +363,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"] ---- @EnableWebFluxSecurity @@ -401,7 +407,8 @@ public class OAuth2LoginConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- @EnableWebFluxSecurity @@ -440,7 +447,7 @@ class OAuth2LoginConfig { } } ---- -==== +====== [[webflux-oauth2-login-javaconfig-wo-boot]] @@ -449,8 +456,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"] ---- @EnableWebFluxSecurity @@ -493,7 +502,8 @@ public class OAuth2LoginConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebFluxSecurity @@ -536,4 +546,4 @@ class OAuth2LoginConfig { } } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/bearer-tokens.adoc b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/bearer-tokens.adoc index 30a4c8fd07..1ab9f3536f 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/bearer-tokens.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/bearer-tokens.adoc @@ -10,8 +10,10 @@ For example, you may have a need to read the bearer token from a custom header. To achieve this, you can wire an instance of `ServerBearerTokenAuthenticationConverter` into the DSL, as you can see in the following example: .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're in possession of a bearer token, it might be handy to pass that to downstream services. This is quite simple with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServerBearerExchangeFilterFunction.html[ServerBearerExchangeFilterFunction]`, which you can see in the following example: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -52,7 +57,8 @@ public WebClient rest() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -62,15 +68,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() @@ -79,7 +87,8 @@ this.rest.get() .bodyToMono(String.class) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- this.rest.get() @@ -87,14 +96,16 @@ this.rest.get() .retrieve() .bodyToMono() ---- -==== +====== 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() @@ -104,7 +115,8 @@ this.rest.get() .bodyToMono(String.class) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- rest.get() @@ -113,7 +125,7 @@ rest.get() .retrieve() .bodyToMono() ---- -==== +====== In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain. diff --git a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc index ffac9a718d..733c4d31ae 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/jwt.adoc @@ -112,8 +112,10 @@ There are two ``@Bean``s that Spring Boot generates on Resource Server's behalf. The first is a `SecurityWebFilterChain` that configures the app 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 @@ -127,7 +129,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -142,15 +145,17 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain } } ---- -==== +====== If the application doesn't expose a `SecurityWebFilterChain` bean, then Spring Boot will expose the above default one. Replacing this is as simple as exposing the bean within the application: .Replacing SecurityWebFilterChain -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -167,7 +172,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -183,7 +189,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain } } ---- -==== +====== The above requires the scope of `message:read` for any URL that starts with `/messages/`. @@ -192,8 +198,10 @@ Methods on the `oauth2ResourceServer` DSL will also override or replace auto con 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 @@ -202,7 +210,8 @@ public ReactiveJwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -210,7 +219,7 @@ fun jwtDecoder(): ReactiveJwtDecoder { return ReactiveJwtDecoders.fromIssuerLocation(issuerUri) } ---- -==== +====== [NOTE] Calling `{security-api-url}org/springframework/security/oauth2/jwt/ReactiveJwtDecoders.html#fromIssuerLocation-java.lang.String-[ReactiveJwtDecoders#fromIssuerLocation]` is what invokes the Provider Configuration or Authorization Server Metadata endpoint in order to derive the JWK Set Uri. @@ -223,8 +232,10 @@ And its configuration can be overridden using `jwkSetUri()` or replaced using `d An authorization server's JWK Set Uri can be configured <> or it can be supplied in the DSL: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -242,7 +253,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -259,7 +271,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain } } ---- -==== +====== Using `jwkSetUri()` takes precedence over any configuration property. @@ -268,8 +280,10 @@ Using `jwkSetUri()` takes precedence over any configuration property. More powerful than `jwkSetUri()` is `decoder()`, which will completely replace any Boot auto configuration of `JwtDecoder`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -287,7 +301,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -304,7 +319,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain } } ---- -==== +====== This is handy when deeper configuration, like <>, is necessary. @@ -313,8 +328,10 @@ This is handy when deeper configuration, like < getMessages(...) {} ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @PreAuthorize("hasAuthority('SCOPE_messages')") fun getMessages(): Flux { } ---- -==== +====== [[webflux-oauth2resourceserver-jwt-authorization-extraction]] ==== Extracting Authorities Manually @@ -638,8 +683,10 @@ Or, at other times, the resource server may need to adapt the attribute or a com To this end, the DSL exposes `jwtAuthenticationConverter()`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -665,7 +712,8 @@ Converter> grantedAuthoritiesExtractor() } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -688,15 +736,17 @@ fun grantedAuthoritiesExtractor(): Converter> { @@ -727,12 +778,14 @@ internal class GrantedAuthoritiesExtractor : Converter>`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- static class CustomAuthenticationConverter implements Converter> { @@ -742,7 +795,8 @@ static class CustomAuthenticationConverter implements Converter> { @@ -751,7 +805,7 @@ internal class CustomAuthenticationConverter : Converter { @@ -829,7 +888,8 @@ public class AudienceValidator implements OAuth2TokenValidator { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class AudienceValidator : OAuth2TokenValidator { @@ -843,12 +903,14 @@ class AudienceValidator : OAuth2TokenValidator { } } ---- -==== +====== Then, to add into a resource server, it's a matter of specifying the `ReactiveJwtDecoder` instance: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -866,7 +928,8 @@ ReactiveJwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -879,4 +942,4 @@ fun jwtDecoder(): ReactiveJwtDecoder { return jwtDecoder } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/multitenancy.adoc b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/multitenancy.adoc index 0b713f306d..ebf4399213 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/multitenancy.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/multitenancy.adoc @@ -17,8 +17,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 `JwtIssuerReactiveAuthenticationManagerResolver`, like so: -==== -.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 Of course, 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, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- private Mono 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 the issuer. This approach allows us to add and remove elements from the repository (shown as a `Map` in the snippet) at runtime. diff --git a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc index e607861206..6c01b16d66 100644 --- a/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc +++ b/docs/modules/ROOT/pages/reactive/oauth2/resource-server/opaque-token.adoc @@ -82,8 +82,10 @@ Once a token is authenticated, an instance of `BearerTokenAuthentication` is set This means that it's available in `@Controller` methods when using `@EnableWebFlux` in your configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/foo") @@ -92,7 +94,8 @@ public Mono foo(BearerTokenAuthentication authentication) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/foo") @@ -100,12 +103,14 @@ fun foo(authentication: BearerTokenAuthentication): Mono { 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") @@ -114,7 +119,8 @@ public Mono foo(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal pr } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/foo") @@ -122,7 +128,7 @@ fun foo(@AuthenticationPrincipal principal: OAuth2AuthenticatedPrincipal): Mono< return Mono.just(principal.getAttribute("sub").toString() + " is the subject") } ---- -==== +====== === Looking Up Attributes Via SpEL @@ -130,8 +136,10 @@ Of course, this also means that attributes can be accessed via SpEL. For example, if using `@EnableReactiveMethodSecurity` so that you can use `@PreAuthorize` annotations, you can do: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @PreAuthorize("principal?.attributes['sub'] = 'foo'") @@ -140,7 +148,8 @@ public Mono forFoosEyesOnly() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @PreAuthorize("principal.attributes['sub'] = 'foo'") @@ -148,7 +157,7 @@ fun forFoosEyesOnly(): Mono { return Mono.just("foo") } ---- -==== +====== [[webflux-oauth2resourceserver-opaque-sansboot]] == Overriding or Replacing Boot Auto Configuration @@ -158,8 +167,10 @@ There are two ``@Bean``s that Spring Boot generates on Resource Server's behalf. The first is a `SecurityWebFilterChain` that configures the app as a resource server. When use Opaque Token, this `SecurityWebFilterChain` looks like: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -173,7 +184,8 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -188,15 +200,17 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain } } ---- -==== +====== If the application doesn't expose a `SecurityWebFilterChain` bean, then Spring Boot will expose the above default one. Replacing this is as simple as exposing the bean within the application: .Replacing SecurityWebFilterChain -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebFluxSecurity @@ -218,7 +232,8 @@ public class MyCustomSecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -236,7 +251,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain } } ---- -==== +====== The above requires the scope of `message:read` for any URL that starts with `/messages/`. @@ -244,8 +259,10 @@ Methods on the `oauth2ResourceServer` DSL will also override or replace auto con 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 @@ -254,7 +271,8 @@ public ReactiveOpaqueTokenIntrospector introspector() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -262,7 +280,7 @@ fun introspector(): ReactiveOpaqueTokenIntrospector { return NimbusReactiveOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret) } ---- -==== +====== If the application doesn't expose a `ReactiveOpaqueTokenIntrospector` bean, then Spring Boot will expose the above default one. @@ -273,8 +291,10 @@ And its configuration can be overridden using `introspectionUri()` and `introspe An authorization server's Introspection Uri can be configured <> or it can be supplied in the DSL: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebFluxSecurity @@ -296,7 +316,8 @@ public class DirectlyConfiguredIntrospectionUri { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -314,7 +335,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain } } ---- -==== +====== Using `introspectionUri()` takes precedence over any configuration property. @@ -323,8 +344,10 @@ Using `introspectionUri()` takes precedence over any configuration property. More powerful than `introspectionUri()` is `introspector()`, which will completely replace any Boot auto configuration of `ReactiveOpaqueTokenIntrospector`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebFluxSecurity @@ -345,7 +368,8 @@ public class DirectlyConfiguredIntrospector { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -362,7 +386,7 @@ fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain } } ---- -==== +====== This is handy when deeper configuration, like <>or <> is necessary. @@ -371,8 +395,10 @@ This is handy when deeper configuration, like < getMessages(...) {} ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @PreAuthorize("hasAuthority('SCOPE_messages')") fun getMessages(): Flux { } ---- -==== +====== [[webflux-oauth2resourceserver-opaque-authorization-extraction]] === Extracting Authorities Manually @@ -478,8 +511,10 @@ Then Resource Server would generate an `Authentication` with two authorities, on This can, of course, be customized using a custom `ReactiveOpaqueTokenIntrospector` 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 ReactiveOpaqueTokenIntrospector { @@ -501,7 +536,8 @@ public class CustomAuthoritiesOpaqueTokenIntrospector implements ReactiveOpaqueT } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class CustomAuthoritiesOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector { @@ -521,12 +557,14 @@ class CustomAuthoritiesOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector } } ---- -==== +====== Thereafter, this custom introspector can be configured simply by exposing it as a `@Bean`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -535,7 +573,8 @@ public ReactiveOpaqueTokenIntrospector introspector() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -543,7 +582,7 @@ fun introspector(): ReactiveOpaqueTokenIntrospector { return CustomAuthoritiesOpaqueTokenIntrospector() } ---- -==== +====== [[webflux-oauth2resourceserver-opaque-jwt-introspector]] == Using Introspection with JWTs @@ -575,8 +614,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 { @@ -602,7 +643,8 @@ public class JwtOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntrospect } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class JwtOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector { @@ -625,12 +667,14 @@ class JwtOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector { } } ---- -==== +====== Thereafter, this custom introspector can be configured simply by exposing it as a `@Bean`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -639,7 +683,8 @@ public ReactiveOpaqueTokenIntrospector introspector() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -647,7 +692,7 @@ fun introspector(): ReactiveOpaqueTokenIntrospector { return JwtOpaqueTokenIntrospector() } ---- -==== +====== [[webflux-oauth2resourceserver-opaque-userinfo]] == Calling a `/userinfo` Endpoint @@ -663,8 +708,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 ReactiveOpaqueTokenIntrospector { @@ -693,7 +740,8 @@ public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntro } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector { @@ -716,13 +764,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 { @@ -738,7 +788,8 @@ public class UserInfoOpaqueTokenIntrospector implements ReactiveOpaqueTokenIntro } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class UserInfoOpaqueTokenIntrospector : ReactiveOpaqueTokenIntrospector { @@ -751,12 +802,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 @@ -765,7 +818,8 @@ ReactiveOpaqueTokenIntrospector introspector() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -773,4 +827,4 @@ fun introspector(): ReactiveOpaqueTokenIntrospector { return UserInfoOpaqueTokenIntrospector() } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/reactive/test/method.adoc b/docs/modules/ROOT/pages/reactive/test/method.adoc index ddbaa6b09b..09a4953bbd 100644 --- a/docs/modules/ROOT/pages/reactive/test/method.adoc +++ b/docs/modules/ROOT/pages/reactive/test/method.adoc @@ -4,8 +4,10 @@ For example, we can test our example from xref:reactive/authorization/method.adoc#jc-erms[EnableReactiveMethodSecurity] using the same setup and annotations we did in xref:servlet/test/method.adoc#test-method[Testing Method Security]. Here is a minimal sample of 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 { } } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/reactive/test/web/authentication.adoc b/docs/modules/ROOT/pages/reactive/test/web/authentication.adoc index 18e69d1068..7b1bf028d5 100644 --- a/docs/modules/ROOT/pages/reactive/test/web/authentication.adoc +++ b/docs/modules/ROOT/pages/reactive/test/web/authentication.adoc @@ -3,8 +3,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; @@ -65,7 +67,8 @@ public void messageWhenMutateWithMockAdminThenOk() throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.test.web.reactive.server.expectBody @@ -112,6 +115,6 @@ fun messageWhenMutateWithMockAdminThenOk() { .expectBody().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]. diff --git a/docs/modules/ROOT/pages/reactive/test/web/csrf.adoc b/docs/modules/ROOT/pages/reactive/test/web/csrf.adoc index 6846449c9e..c5f31cf0c9 100644 --- a/docs/modules/ROOT/pages/reactive/test/web/csrf.adoc +++ b/docs/modules/ROOT/pages/reactive/test/web/csrf.adoc @@ -3,8 +3,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; @@ -17,7 +19,8 @@ this.rest ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf @@ -29,4 +32,4 @@ this.rest .uri("/login") ... ---- -==== +====== diff --git a/docs/modules/ROOT/pages/reactive/test/web/oauth2.adoc b/docs/modules/ROOT/pages/reactive/test/web/oauth2.adoc index cdabd0f038..63b49684df 100644 --- a/docs/modules/ROOT/pages/reactive/test/web/oauth2.adoc +++ b/docs/modules/ROOT/pages/reactive/test/web/oauth2.adoc @@ -5,8 +5,10 @@ When it comes to OAuth 2.0, xref:reactive/test/method.adoc#test-erms[the same pr For example, for a controller that looks like this: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -15,7 +17,8 @@ public Mono foo(Principal user) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -23,14 +26,16 @@ fun foo(user: Principal): Mono { return Mono.just(user.name) } ---- -==== +====== There's nothing OAuth2-specific about it, so you will likely be able to simply xref:reactive/test/method.adoc#test-erms[use `@WithMockUser`] and be fine. But, in cases where your controllers are bound to some aspect of Spring Security's OAuth 2.0 support, like the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -39,7 +44,8 @@ public Mono foo(@AuthenticationPrincipal OidcUser user) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -47,7 +53,7 @@ fun foo(@AuthenticationPrincipal user: OidcUser): Mono { return Mono.just(user.idToken.subject) } ---- -==== +====== then Spring Security's test support can come in handy. @@ -59,15 +65,18 @@ Certainly this would be a daunting task, which is why Spring Security ships with For example, we can tell Spring Security to include a default `OidcUser` using the `SecurityMockServerConfigurers#mockOidcLogin` method, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client .mutateWith(mockOidcLogin()).get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client @@ -75,59 +84,68 @@ client .get().uri("/endpoint") .exchange() ---- -==== +====== What this will do is configure the associated `MockServerRequest` with an `OidcUser` that includes a simple `OidcIdToken`, `OidcUserInfo`, and `Collection` of granted authorities. Specifically, it will include an `OidcIdToken` with a `sub` claim set to `user`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(user.getIdToken().getClaim("sub")).isEqualTo("user"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(user.idToken.getClaim("sub")).isEqualTo("user") ---- -==== +====== an `OidcUserInfo` with no claims set: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(user.getUserInfo().getClaims()).isEmpty(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(user.userInfo.claims).isEmpty() ---- -==== +====== and a `Collection` of authorities with just one authority, `SCOPE_read`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(user.getAuthorities()).hasSize(1); assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(user.authorities).hasSize(1) assertThat(user.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read")) ---- -==== +====== Spring Security does the necessary work to make sure that the `OidcUser` instance is available for xref:servlet/integrations/mvc.adoc#mvc-authentication-principal[the `@AuthenticationPrincipal` annotation]. @@ -141,8 +159,10 @@ In many circumstances, your method is protected by filter or method security and In this case, you can supply what granted authorities you need using the `authorities()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -152,7 +172,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client @@ -161,7 +182,7 @@ client ) .get().uri("/endpoint").exchange() ---- -==== +====== [[webflux-testing-oidc-login-claims]] === Configuring Claims @@ -171,8 +192,10 @@ And while granted authorities are quite common across all of Spring Security, we Let's say, for example, that you've got a `user_id` claim that indicates the user's id in your system. You might access it like so in a controller: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -182,7 +205,8 @@ public Mono foo(@AuthenticationPrincipal OidcUser oidcUser) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -191,12 +215,14 @@ fun foo(@AuthenticationPrincipal oidcUser: OidcUser): Mono { // ... } ---- -==== +====== In that case, you'd want to specify that claim with the `idToken()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -206,7 +232,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client @@ -215,7 +242,7 @@ client ) .get().uri("/endpoint").exchange() ---- -==== +====== since `OidcUser` collects its claims from `OidcIdToken`. @@ -235,8 +262,10 @@ That last one is handy if you: For example, let's say that your authorization server sends the principal name in the `user_name` claim instead of the `sub` claim. In that case, you can configure an `OidcUser` by hand: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- OidcUser oidcUser = new DefaultOidcUser( @@ -249,7 +278,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val oidcUser: OidcUser = DefaultOidcUser( @@ -262,7 +292,7 @@ client .mutateWith(mockOidcLogin().oidcUser(oidcUser)) .get().uri("/endpoint").exchange() ---- -==== +====== [[webflux-testing-oauth2-login]] == Testing OAuth 2.0 Login @@ -272,8 +302,10 @@ And because of that, Spring Security also has test support for non-OIDC use case Let's say that we've got a controller that gets the logged-in user as an `OAuth2User`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -282,7 +314,8 @@ public Mono foo(@AuthenticationPrincipal OAuth2User oauth2User) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -290,12 +323,14 @@ fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): Mono { return Mono.just(oauth2User.getAttribute("sub")) } ---- -==== +====== In that case, we can tell Spring Security to include a default `OAuth2User` using the `SecurityMockServerConfigurers#mockOAuth2Login` method, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -303,50 +338,57 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client .mutateWith(mockOAuth2Login()) .get().uri("/endpoint").exchange() ---- -==== +====== What this will do is configure the associated `MockServerRequest` with an `OAuth2User` that includes a simple `Map` of attributes and `Collection` of granted authorities. Specifically, it will include a `Map` with a key/value pair of `sub`/`user`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat((String) user.getAttribute("sub")).isEqualTo("user"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(user.getAttribute("sub")).isEqualTo("user") ---- -==== +====== and a `Collection` of authorities with just one authority, `SCOPE_read`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(user.getAuthorities()).hasSize(1); assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(user.authorities).hasSize(1) assertThat(user.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read")) ---- -==== +====== Spring Security does the necessary work to make sure that the `OAuth2User` instance is available for xref:servlet/integrations/mvc.adoc#mvc-authentication-principal[the `@AuthenticationPrincipal` annotation]. @@ -360,8 +402,10 @@ In many circumstances, your method is protected by filter or method security and In this case, you can supply what granted authorities you need using the `authorities()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -371,7 +415,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client @@ -380,7 +425,7 @@ client ) .get().uri("/endpoint").exchange() ---- -==== +====== [[webflux-testing-oauth2-login-claims]] === Configuring Claims @@ -390,8 +435,10 @@ And while granted authorities are quite common across all of Spring Security, we Let's say, for example, that you've got a `user_id` attribute that indicates the user's id in your system. You might access it like so in a controller: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -401,7 +448,8 @@ public Mono foo(@AuthenticationPrincipal OAuth2User oauth2User) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -410,12 +458,14 @@ fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): Mono { // ... } ---- -==== +====== In that case, you'd want to specify that attribute with the `attributes()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -425,7 +475,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client @@ -434,7 +485,7 @@ client ) .get().uri("/endpoint").exchange() ---- -==== +====== [[webflux-testing-oauth2-login-user]] === Additional Configurations @@ -451,8 +502,10 @@ That last one is handy if you: For example, let's say that your authorization server sends the principal name in the `user_name` claim instead of the `sub` claim. In that case, you can configure an `OAuth2User` by hand: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- OAuth2User oauth2User = new DefaultOAuth2User( @@ -465,7 +518,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val oauth2User: OAuth2User = DefaultOAuth2User( @@ -478,7 +532,7 @@ client .mutateWith(mockOAuth2Login().oauth2User(oauth2User)) .get().uri("/endpoint").exchange() ---- -==== +====== [[webflux-testing-oauth2-client]] == Testing OAuth 2.0 Clients @@ -486,8 +540,10 @@ client Independent of how your user authenticates, you may have other tokens and client registrations that are in play for the request you are testing. For example, your controller may be relying on the client credentials grant to get a token that isn't associated with the user at all: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -499,7 +555,8 @@ public Mono foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2Author } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.web.reactive.function.client.bodyToMono @@ -514,13 +571,15 @@ fun foo(@RegisteredOAuth2AuthorizedClient("my-app") authorizedClient: OAuth2Auth .bodyToMono() } ---- -==== +====== Simulating this handshake with the authorization server could be cumbersome. Instead, you can use `SecurityMockServerConfigurers#mockOAuth2Client` to add a `OAuth2AuthorizedClient` into a mock `ServerOAuth2AuthorizedClientRepository`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -528,68 +587,78 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client .mutateWith(mockOAuth2Client("my-app")) .get().uri("/endpoint").exchange() ---- -==== +====== What this will do is create an `OAuth2AuthorizedClient` that has a simple `ClientRegistration`, `OAuth2AccessToken`, and resource owner name. Specifically, it will include a `ClientRegistration` with a client id of "test-client" and client secret of "test-secret": -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(authorizedClient.getClientRegistration().getClientId()).isEqualTo("test-client"); assertThat(authorizedClient.getClientRegistration().getClientSecret()).isEqualTo("test-secret"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(authorizedClient.clientRegistration.clientId).isEqualTo("test-client") assertThat(authorizedClient.clientRegistration.clientSecret).isEqualTo("test-secret") ---- -==== +====== a resource owner name of "user": -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(authorizedClient.getPrincipalName()).isEqualTo("user"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(authorizedClient.principalName).isEqualTo("user") ---- -==== +====== and an `OAuth2AccessToken` with just one scope, `read`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(authorizedClient.getAccessToken().getScopes()).hasSize(1); assertThat(authorizedClient.getAccessToken().getScopes()).containsExactly("read"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(authorizedClient.accessToken.scopes).hasSize(1) assertThat(authorizedClient.accessToken.scopes).containsExactly("read") ---- -==== +====== The client can then be retrieved as normal using `@RegisteredOAuth2AuthorizedClient` in a controller method. @@ -599,8 +668,10 @@ The client can then be retrieved as normal using `@RegisteredOAuth2AuthorizedCli In many circumstances, the OAuth 2.0 access token comes with a set of scopes. If your controller inspects these, say like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -616,7 +687,8 @@ public Mono foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2Author } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.web.reactive.function.client.bodyToMono @@ -635,12 +707,14 @@ fun foo(@RegisteredOAuth2AuthorizedClient("my-app") authorizedClient: OAuth2Auth // ... } ---- -==== +====== then you can configure the scope using the `accessToken()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -650,7 +724,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client @@ -659,7 +734,7 @@ client ) .get().uri("/endpoint").exchange() ---- -==== +====== [[webflux-testing-oauth2-client-registration]] === Additional Configurations @@ -676,8 +751,10 @@ For example, let's say that you are wanting to use one of your app's `ClientRegi In that case, your test can autowire the `ReactiveClientRegistrationRepository` and look up the one your test needs: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Autowired @@ -692,7 +769,8 @@ client .get().uri("/exchange").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Autowired @@ -706,7 +784,7 @@ client ) .get().uri("/exchange").exchange() ---- -==== +====== [[webflux-testing-jwt]] == Testing JWT Authentication @@ -723,21 +801,24 @@ We'll look at two of them now: The first way is via a `WebTestClientConfigurer`. The simplest of these would be to use the `SecurityMockServerConfigurers#mockJwt` method like the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client .mutateWith(mockJwt()).get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client .mutateWith(mockJwt()).get().uri("/endpoint").exchange() ---- -==== +====== What this will do is create a mock `Jwt`, passing it correctly through any authentication APIs so that it's available for your authorization mechanisms to verify. @@ -756,8 +837,10 @@ By default, the `JWT` that it creates has the following characteristics: And the resulting `Jwt`, were it tested, would pass in the following way: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(jwt.getTokenValue()).isEqualTo("token"); @@ -765,21 +848,24 @@ assertThat(jwt.getHeaders().get("alg")).isEqualTo("none"); assertThat(jwt.getSubject()).isEqualTo("sub"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(jwt.tokenValue).isEqualTo("token") assertThat(jwt.headers["alg"]).isEqualTo("none") assertThat(jwt.subject).isEqualTo("sub") ---- -==== +====== These values can, of course be configured. Any headers or claims can be configured with their corresponding methods: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -788,7 +874,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client @@ -797,10 +884,12 @@ client }) .get().uri("/endpoint").exchange() ---- -==== +====== -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -808,7 +897,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client @@ -817,13 +907,15 @@ client }) .get().uri("/endpoint").exchange() ---- -==== +====== The `scope` and `scp` claims are processed the same way here as they are in a normal bearer token request. However, this can be overridden simply by providing the list of `GrantedAuthority` instances that you need for your test: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -831,19 +923,22 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client .mutateWith(mockJwt().authorities(SimpleGrantedAuthority("SCOPE_messages"))) .get().uri("/endpoint").exchange() ---- -==== +====== Or, if you have a custom `Jwt` to `Collection` converter, you can also use that to derive the authorities: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -851,19 +946,22 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client .mutateWith(mockJwt().authorities(MyConverter())) .get().uri("/endpoint").exchange() ---- -==== +====== You can also specify a complete `Jwt`, for which `{security-api-url}org/springframework/security/oauth2/jwt/Jwt.Builder.html[Jwt.Builder]` comes quite handy: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Jwt jwt = Jwt.withTokenValue("token") @@ -877,7 +975,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val jwt: Jwt = Jwt.withTokenValue("token") @@ -890,15 +989,17 @@ client .mutateWith(mockJwt().jwt(jwt)) .get().uri("/endpoint").exchange() ---- -==== +====== === `authentication()` `WebTestClientConfigurer` The second way is by using the `authentication()` `Mutator`. Essentially, you can instantiate your own `JwtAuthenticationToken` and provide it in your test, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Jwt jwt = Jwt.withTokenValue("token") @@ -913,7 +1014,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val jwt = Jwt.withTokenValue("token") @@ -927,7 +1029,7 @@ client .mutateWith(mockAuthentication(token)) .get().uri("/endpoint").exchange() ---- -==== +====== Note that as an alternative to these, you can also mock the `ReactiveJwtDecoder` bean itself with a `@MockBean` annotation. @@ -939,8 +1041,10 @@ To help with that, Spring Security has test support for opaque tokens. Let's say that we've got a controller that retrieves the authentication as a `BearerTokenAuthentication`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -949,7 +1053,8 @@ public Mono foo(BearerTokenAuthentication authentication) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -957,12 +1062,14 @@ fun foo(authentication: BearerTokenAuthentication): Mono { return Mono.just(authentication.tokenAttributes["sub"] as String?) } ---- -==== +====== In that case, we can tell Spring Security to include a default `BearerTokenAuthentication` using the `SecurityMockServerConfigurers#mockOpaqueToken` method, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -970,50 +1077,57 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client .mutateWith(mockOpaqueToken()) .get().uri("/endpoint").exchange() ---- -==== +====== What this will do is configure the associated `MockHttpServletRequest` with a `BearerTokenAuthentication` that includes a simple `OAuth2AuthenticatedPrincipal`, `Map` of attributes, and `Collection` of granted authorities. Specifically, it will include a `Map` with a key/value pair of `sub`/`user`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat((String) token.getTokenAttributes().get("sub")).isEqualTo("user"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(token.tokenAttributes["sub"] as String?).isEqualTo("user") ---- -==== +====== and a `Collection` of authorities with just one authority, `SCOPE_read`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(token.getAuthorities()).hasSize(1); assertThat(token.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(token.authorities).hasSize(1) assertThat(token.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read")) ---- -==== +====== Spring Security does the necessary work to make sure that the `BearerTokenAuthentication` instance is available for your controller methods. @@ -1024,8 +1138,10 @@ In many circumstances, your method is protected by filter or method security and In this case, you can supply what granted authorities you need using the `authorities()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -1035,7 +1151,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client @@ -1044,7 +1161,7 @@ client ) .get().uri("/endpoint").exchange() ---- -==== +====== [[webflux-testing-opaque-token-attributes]] === Configuring Claims @@ -1054,8 +1171,10 @@ And while granted authorities are quite common across all of Spring Security, we Let's say, for example, that you've got a `user_id` attribute that indicates the user's id in your system. You might access it like so in a controller: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -1065,7 +1184,8 @@ public Mono foo(BearerTokenAuthentication authentication) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -1074,12 +1194,14 @@ fun foo(authentication: BearerTokenAuthentication): Mono { // ... } ---- -==== +====== In that case, you'd want to specify that attribute with the `attributes()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- client @@ -1089,7 +1211,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- client @@ -1098,7 +1221,7 @@ client ) .get().uri("/endpoint").exchange() ---- -==== +====== [[webflux-testing-opaque-token-principal]] === Additional Configurations @@ -1114,8 +1237,10 @@ It's handy if you: For example, let's say that your authorization server sends the principal name in the `user_name` attribute instead of the `sub` attribute. In that case, you can configure an `OAuth2AuthenticatedPrincipal` by hand: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Map attributes = Collections.singletonMap("user_name", "foo_user"); @@ -1129,7 +1254,8 @@ client .get().uri("/endpoint").exchange(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val attributes: Map = mapOf(Pair("user_name", "foo_user")) @@ -1143,6 +1269,6 @@ client .mutateWith(mockOpaqueToken().principal(principal)) .get().uri("/endpoint").exchange() ---- -==== +====== Note that as an alternative to using `mockOpaqueToken()` test support, you can also mock the `OpaqueTokenIntrospector` bean itself with a `@MockBean` annotation. diff --git a/docs/modules/ROOT/pages/reactive/test/web/setup.adoc b/docs/modules/ROOT/pages/reactive/test/web/setup.adoc index fac5ca177f..766453995d 100644 --- a/docs/modules/ROOT/pages/reactive/test/web/setup.adoc +++ b/docs/modules/ROOT/pages/reactive/test/web/setup.adoc @@ -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 { // ... } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/appendix/faq.adoc b/docs/modules/ROOT/pages/servlet/appendix/faq.adoc index 87dcff4564..6503a1df6f 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/faq.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/faq.adoc @@ -108,12 +108,12 @@ If you are using other technologies which you aren't familiar with then you shou .. <> .. <> . Miscellaneous + .. <> .. <> .. <> .. <> .. <> - [[appendix-faq-bad-credentials]] === When I try to log in, I get an error message that says "Bad Credentials". What's wrong? @@ -196,8 +196,10 @@ This will be different in different companies, so you have to find it out yourse Before adding a Spring Security LDAP configuration to an application, it's a good idea to write a simple test 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"] ---- @@ -216,7 +218,8 @@ public void ldapAuthenticationIsSuccessful() throws Exception { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test @@ -230,7 +233,7 @@ fun ldapAuthenticationIsSuccessful() { val ctx = InitialLdapContext(env, null) } ---- -==== +====== === Session Management @@ -516,8 +519,10 @@ To load the data from an alternative source, you must be using an explicitly dec You can't use the namespace. You would then implement `FilterInvocationSecurityMetadataSource` to load the data as you please for a particular `FilterInvocation` footnote:[The `FilterInvocation` object contains the `HttpServletRequest`, so you can obtain the URL or any other relevant information on which to base your decision on what the list of returned attributes will contain.]. A very basic outline would look something like this: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @@ -546,7 +551,8 @@ You would then implement `FilterInvocationSecurityMetadataSource` to load the da ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class MyFilterSecurityMetadataSource : FilterInvocationSecurityMetadataSource { @@ -569,7 +575,7 @@ class MyFilterSecurityMetadataSource : FilterInvocationSecurityMetadataSource { } } ---- -==== +====== For more information, look at the code for `DefaultFilterInvocationSecurityMetadataSource`. @@ -582,8 +588,10 @@ The `DefaultLdapAuthoritiesPopulator` loads the user authorities from the LDAP d To use JDBC instead, you can implement the interface yourself, using whatever SQL is appropriate for your schema: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @@ -609,7 +617,8 @@ To use JDBC instead, you can implement the interface yourself, using whatever SQ ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class MyAuthoritiesPopulator : LdapAuthoritiesPopulator { @@ -629,7 +638,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 using explicit Spring beans in the LDAP chapter of the reference manual. Note that you can't use the namespace for configuration in this case. @@ -647,8 +656,10 @@ More information can be found 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`. Let's say 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 make use of it while authenticating the user. The processor class would look like this: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @@ -674,7 +685,8 @@ public class CustomBeanPostProcessor implements BeanPostProcessor { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class CustomBeanPostProcessor : BeanPostProcessor { @@ -692,7 +704,7 @@ class CustomBeanPostProcessor : BeanPostProcessor { } } ---- -==== +====== You would then register this bean in your application context. Spring will automatically invoke it on the beans defined in the application context. diff --git a/docs/modules/ROOT/pages/servlet/architecture.adoc b/docs/modules/ROOT/pages/servlet/architecture.adoc index 901f78734c..962193212a 100644 --- a/docs/modules/ROOT/pages/servlet/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/architecture.adoc @@ -28,8 +28,10 @@ In this instance the `Filter` will typically write 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` only impacts downstream ``Filter``s and the `Servlet`, the order each `Filter` is invoked is extremely important. @@ -70,8 +73,10 @@ image::{figures}/delegatingfilterproxy.png[] The pseudo code of `DelegatingFilterProxy` can be seen below. .`DelegatingFilterProxy` Pseudo Code -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary",subs="+quotes,+macros"] ---- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { @@ -83,7 +88,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",subs="+quotes,+macros"] ---- fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) { @@ -94,7 +100,7 @@ fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterCh delegate.doFilter(request, response) } ---- -==== +====== Another benefit of `DelegatingFilterProxy` is that it allows delaying looking `Filter` bean instances. This is important because the container needs to register the `Filter` instances before the container can startup. @@ -275,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 @@ -291,7 +299,8 @@ SecurityFilterChain springSecurity(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -306,7 +315,8 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -316,7 +326,7 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== [[requestcacheawarefilter]] diff --git a/docs/modules/ROOT/pages/servlet/authentication/anonymous.adoc b/docs/modules/ROOT/pages/servlet/authentication/anonymous.adoc index d30fd584bf..e048c48554 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/anonymous.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/anonymous.adoc @@ -108,8 +108,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("/") @@ -122,7 +124,8 @@ public String method(Authentication authentication) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/") @@ -134,7 +137,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. @@ -142,8 +145,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("/") @@ -152,11 +157,12 @@ public String method(@CurrentSecurityContext SecurityContext context) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/") fun method(@CurrentSecurityContext context : SecurityContext) : String = context!!.authentication!!.name ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc b/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc index 6b8ec271d5..2b1e75f2dc 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/architecture.adoc @@ -31,8 +31,10 @@ If it contains a value, then it is used as the currently authenticated user. The simplest way to indicate a user is authenticated is to set the `SecurityContextHolder` directly. .Setting `SecurityContextHolder` -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- SecurityContext context = SecurityContextHolder.createEmptyContext(); // <1> @@ -43,7 +45,8 @@ context.setAuthentication(authentication); SecurityContextHolder.setContext(context); // <3> ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val context: SecurityContext = SecurityContextHolder.createEmptyContext() // <1> @@ -52,7 +55,7 @@ context.authentication = authentication SecurityContextHolder.setContext(context) // <3> ---- -==== +====== <1> We start by creating an empty `SecurityContext`. It is important to create a new `SecurityContext` instance instead of using `SecurityContextHolder.getContext().setAuthentication(authentication)` to avoid race conditions across multiple threads. @@ -66,8 +69,10 @@ Spring Security will use this information for xref:servlet/authorization/index.a If you wish to obtain information about the authenticated principal, you can do so by accessing the `SecurityContextHolder`. .Access Currently Authenticated User -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- SecurityContext context = SecurityContextHolder.getContext(); @@ -77,7 +82,8 @@ Object principal = authentication.getPrincipal(); Collection authorities = authentication.getAuthorities(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val context = SecurityContextHolder.getContext() @@ -86,7 +92,7 @@ val username = authentication.name val principal = authentication.principal val authorities = authentication.authorities ---- -==== +====== // FIXME: add links to HttpServletRequest.getRemoteUser() and @CurrentSecurityContext @AuthenticationPrincipal diff --git a/docs/modules/ROOT/pages/servlet/authentication/cas.adoc b/docs/modules/ROOT/pages/servlet/authentication/cas.adoc index 1917e156ae..e7c5aabb94 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/cas.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/cas.adoc @@ -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 diff --git a/docs/modules/ROOT/pages/servlet/authentication/events.adoc b/docs/modules/ROOT/pages/servlet/authentication/events.adoc index c016d37574..f504476446 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/events.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/events.adoc @@ -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` will probably do fine: -==== -.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 via 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 And, you can 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 } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/authentication/logout.adoc b/docs/modules/ROOT/pages/servlet/authentication/logout.adoc index 17bc623e81..a007ba7c51 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/logout.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/logout.adoc @@ -16,8 +16,10 @@ The default is that accessing the URL `/logout` will log the user out by: Similar to configuring login capabilities, however, you also have various options to further customize your logout requirements: .Logout Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public SecurityFilterChain filterChain(HttpSecurity http) { @@ -34,7 +36,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ----- open fun filterChain(http: HttpSecurity): SecurityFilterChain { @@ -51,7 +54,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { // ... } ----- -==== +====== <1> Provides logout support. <2> The URL that triggers log out to occur (default is `/logout`). diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/basic.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/basic.adoc index 0a91b86b5c..304c465185 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/basic.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/basic.adoc @@ -58,9 +58,11 @@ However, as soon as any servlet based configuration is provided, HTTP Basic must A minimal, explicit configuration can be found below: .Explicit HTTP Basic Configuration -==== +[tabs] +====== +Java:: ++ [source,java,role="primary"] -.Java ---- @Bean public SecurityFilterChain filterChain(HttpSecurity http) { @@ -71,8 +73,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- +XML:: ++ [source,xml,role="secondary"] -.XML ---- @@ -80,8 +83,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) { ---- +Kotlin:: ++ [source,kotlin,role="secondary"] -.Kotlin ---- @Bean open fun filterChain(http: HttpSecurity): SecurityFilterChain { @@ -92,4 +96,4 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { return http.build() } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/digest.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/digest.adoc index 0474c51883..00bd431997 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/digest.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/digest.adoc @@ -25,21 +25,21 @@ 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 will need to ensure 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`. The following provides an example of configuring Digest Authentication with Java Configuration: .Digest Authentication -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Autowired @@ -67,7 +67,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc index c01afe9fdc..72294d68a3 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/form.adoc @@ -67,8 +67,10 @@ However, as soon as any servlet based configuration is provided, form based log A minimal, explicit Java configuration can be found below: .Form Log In -==== -.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"] ---- @@ -87,7 +90,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- open fun filterChain(http: HttpSecurity): SecurityFilterChain { @@ -97,7 +101,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { // ... } ---- -==== +====== In this configuration Spring Security will render a default log in page. Most production applications will require a custom log in form. @@ -106,8 +110,10 @@ Most production applications will require a custom log in form. The configuration below demonstrates how to provide a custom log in form. .Custom Log In 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"] ---- @@ -130,7 +137,8 @@ public SecurityFilterChain filterChain(HttpSecurity 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 Below is a https://www.thymeleaf.org/[Thymeleaf] template that produces an HTML login form that complies with a login page of `/login`: -.Log In Form -==== -.src/main/resources/templates/login.html +.Log In Form - src/main/resources/templates/login.html [source,xml] ---- @@ -178,7 +184,6 @@ Below is a https://www.thymeleaf.org/[Thymeleaf] template that produces an HTML ---- -==== There are a few key points about the default HTML form: @@ -197,8 +202,10 @@ If you are using Spring MVC, you will need a controller that maps `GET /login` t A minimal sample `LoginController` can be seen below: .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 { } } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/in-memory.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/in-memory.adoc index 62fb993c0d..5c1c1bdfc1 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/in-memory.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/in-memory.adoc @@ -8,8 +8,10 @@ Spring Security's `InMemoryUserDetailsManager` implements xref:servlet/authentic In this sample we use xref:features/authentication/password-storage.adoc#authentication-password-storage-boot-cli[Spring Boot CLI] to encode the password 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"] ---- @@ -41,7 +44,8 @@ public UserDetailsService users() { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- @Bean @@ -59,7 +63,7 @@ fun users(): UserDetailsService { return InMemoryUserDetailsManager(user, admin) } ---- -==== +====== The samples above store the passwords in a secure format, but leave a lot to be desired in terms of getting started experience. @@ -69,8 +73,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 @@ -91,7 +97,8 @@ public UserDetailsService users() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -111,13 +118,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]. . `+{noop}+` XML Configuration -==== [source,xml,attrs="-attributes"] ---- @@ -129,4 +135,3 @@ For demos or just getting started, you can choose to prefix the password with `+ authorities="ROLE_USER,ROLE_ADMIN" /> ---- -==== diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/jdbc.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/jdbc.adoc index c5ca66a613..872cdcbd52 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/jdbc.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/jdbc.adoc @@ -30,7 +30,6 @@ The default schema is also exposed as a classpath resource named `org/springfram ==== .Default User Schema -==== [source,sql] ---- create table users( @@ -46,13 +45,11 @@ 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. You can find the default Oracle Schema for users below. .Default User Schema for Oracle Databases -==== [source,sql] ---- CREATE TABLE USERS ( @@ -69,7 +66,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 @@ -78,7 +74,6 @@ If your application is leveraging groups, you will need to provide the groups sc The default schema for groups can be found below. .Default Group Schema -==== [source,sql] ---- create table groups ( @@ -99,7 +94,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 @@ -108,8 +102,10 @@ Before we configure `JdbcUserDetailsManager`, we must create a `DataSource`. In our example, we will setup 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 <>. .Embedded Data Source -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -121,7 +117,8 @@ DataSource dataSource() { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -129,7 +126,8 @@ DataSource dataSource() { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -140,7 +138,7 @@ fun dataSource(): DataSource { .build() } ---- -==== +====== In a production environment, you will want to ensure you setup a connection to an external database. @@ -151,9 +149,11 @@ In this sample we use xref:features/authentication/password-storage.adoc#authent 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 @@ -175,7 +175,8 @@ UserDetailsManager users(DataSource dataSource) { } ---- -.XML +XML:: ++ [source,xml,role="secondary",attrs="-attributes"] ---- @@ -188,7 +189,8 @@ UserDetailsManager users(DataSource dataSource) { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- @Bean @@ -209,4 +211,4 @@ fun users(dataSource: DataSource): UserDetailsManager { return users } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc index 127c71fb99..a4fb293f41 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/ldap.adoc @@ -97,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], then specify the following dependencies: .UnboundID Dependencies -==== -.Maven +[tabs] +====== +Maven:: ++ [source,xml,role="primary",subs="verbatim,attributes"] ---- @@ -109,21 +111,24 @@ If you wish to use https://ldap.com/unboundid-ldap-sdk-for-java/[UnboundID], the ---- -.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 @@ -132,7 +137,8 @@ public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -140,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 @@ -157,7 +165,8 @@ UnboundIdContainer ldapContainer() { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -173,7 +183,7 @@ fun ldapContainer(): UnboundIdContainer { return UnboundIdContainer("dc=springframework,dc=org","classpath:users.ldif") } ---- -==== +====== [[servlet-authentication-ldap-apacheds]] === Embedded ApacheDS Server @@ -188,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], then specify the following dependencies: .ApacheDS Dependencies -==== -.Maven +[tabs] +====== +Maven:: ++ [source,xml,role="primary",subs="+attributes"] ---- @@ -206,7 +218,8 @@ If you wish to use https://directory.apache.org/apacheds/[Apache DS], then speci ---- -.Gradle +Gradle:: ++ [source,groovy,role="secondary",subs="+attributes"] ---- depenendencies { @@ -214,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 @@ -230,7 +245,8 @@ ApacheDSContainer ldapContainer() { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -246,7 +263,7 @@ fun ldapContainer(): ApacheDSContainer { return ApacheDSContainer("dc=springframework,dc=org", "classpath:users.ldif") } ---- -==== +====== [[servlet-authentication-ldap-contextsource]] == LDAP ContextSource @@ -256,8 +273,10 @@ This is done by creating an LDAP `ContextSource`, which is the equivalent of a J 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 @@ -269,7 +288,8 @@ public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -279,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) { @@ -293,21 +315,23 @@ ContextSource contextSource(UnboundIdContainer container) { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -.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 @@ -336,8 +360,10 @@ The advantage to using bind authentication is that the user's secrets (i.e. pass An example of bind authentication configuration can be found below. .Bind Authentication -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary",attrs="-attributes"] ---- @Bean @@ -348,14 +374,16 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou } ---- -.XML +XML:: ++ [source,xml,role="secondary",attrs="-attributes"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- @Bean @@ -365,15 +393,17 @@ fun authenticationManager(contextSource: BaseLdapPathContextSource): Authenticat return factory.createAuthenticationManager() } ---- -==== +====== This 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 wished 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 @@ -385,7 +415,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou } ---- -.XML +XML:: ++ [source,xml,role="secondary",attrs="-attributes"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- @Bean @@ -404,7 +436,7 @@ fun authenticationManager(contextSource: BaseLdapPathContextSource): Authenticat return factory.createAuthenticationManager() } ---- -==== +====== If used with the `ContextSource` <>, this would perform a search under the DN `ou=people,dc=springframework,dc=org` using `+(uid={0})+` as a filter. Again the user login name is substituted for the parameter in the filter name, so it will search for an entry with the `uid` attribute equal to the user name. @@ -418,8 +450,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 @@ -431,7 +465,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou } ---- -.XML +XML:: ++ [source,xml,role="secondary",attrs="-attributes"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -452,13 +488,15 @@ fun authenticationManager(contextSource: BaseLdapPathContextSource?): Authentica return factory.createAuthenticationManager() } ---- -==== +====== A more advanced configuration with some customizations can be found below. .Password Compare Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -471,7 +509,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou } ---- -.XML +XML:: ++ [source,xml,role="secondary",attrs="-attributes"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -497,7 +537,7 @@ fun authenticationManager(contextSource: BaseLdapPathContextSource): Authenticat return factory.createAuthenticationManager() } ---- -==== +====== <1> Specify the password attribute as `pwd` @@ -506,8 +546,10 @@ fun authenticationManager(contextSource: BaseLdapPathContextSource): Authenticat Spring Security's `LdapAuthoritiesPopulator` is used to determine what authorites are returned for the user. .LdapAuthoritiesPopulator Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary",attrs="-attributes"] ---- @Bean @@ -528,7 +570,8 @@ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSou } ---- -.XML +XML:: ++ [source,xml,role="secondary",attrs="-attributes"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- @Bean @@ -557,7 +601,7 @@ fun authenticationManager( return factory.createAuthenticationManager() } ---- -==== +====== == Active Directory @@ -571,8 +615,10 @@ This is not currently supported, but hopefully will be in a future version.]. An example configuration can be seen below: .Example Active Directory Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -581,7 +627,8 @@ ActiveDirectoryLdapAuthenticationProvider authenticationProvider() { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -599,4 +647,4 @@ fun authenticationProvider(): ActiveDirectoryLdapAuthenticationProvider { return ActiveDirectoryLdapAuthenticationProvider("example.com", "ldap://company.example.com/") } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc index d557c04041..52aa178f47 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/user-details-service.adoc @@ -10,8 +10,10 @@ For example, the following will customize authentication assuming that `CustomUs NOTE: This is only used if the `AuthenticationManagerBuilder` has not been populated and no `AuthenticationProviderBean` is defined. .Custom UserDetailsService Bean -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -20,18 +22,20 @@ CustomUserDetailsService customUserDetailsService() { } ---- -.XML +XML:: ++ [source,java,role="secondary"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean fun customUserDetailsService() = CustomUserDetailsService() ---- -==== +====== // FIXME: Add CustomUserDetails example with links to @AuthenticationPrincipal diff --git a/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc b/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc index b2f0e21db0..34d2ef1e4f 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/persistence.adoc @@ -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]] @@ -89,8 +83,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) { @@ -103,7 +99,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -112,7 +109,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) { ---- -==== +====== [[delegatingsecuritycontextrepository]] === DelegatingSecurityContextRepository @@ -122,8 +119,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 @@ -140,7 +139,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -158,7 +158,8 @@ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -174,7 +175,7 @@ fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== [NOTE] ==== diff --git a/docs/modules/ROOT/pages/servlet/authentication/preauth.adoc b/docs/modules/ROOT/pages/servlet/authentication/preauth.adoc index 406c3f871f..edcdaaf640 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/preauth.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/preauth.adoc @@ -30,8 +30,10 @@ This class will check the current contents of the security context and, if empty Subclasses override the following methods to obtain this information: .Override AbstractPreAuthenticatedProcessingFilter -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request); @@ -39,14 +41,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 will create a `PreAuthenticatedAuthenticationToken` containing the returned data and submit it for authentication. diff --git a/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc b/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc index 1d85c90377..552efda260 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/rememberme.adoc @@ -18,7 +18,6 @@ If you are using an authentication provider which doesn't use a `UserDetailsServ 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 + ":" @@ -30,7 +29,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 ---- -==== As such the remember-me token is valid only for the period specified, and provided that the username, password and key does not change. Notably, this has a potential security issue in that a captured remember-me token will be usable from any user agent until such time as the token expires. @@ -41,7 +39,6 @@ Alternatively, remember-me services should simply 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 just by adding the `` element: -==== [source,xml] ---- @@ -49,7 +46,6 @@ If you are familiar with the topics discussed in the chapter on xref:servlet/con ---- -==== The `UserDetailsService` will normally be 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. @@ -60,7 +56,6 @@ This approach is based on the article https://web.archive.org/web/20180819014446 There is a discussion on this in the comments section of this article.]. To use the this approach with namespace configuration, you would supply a datasource reference: -==== [source,xml] ---- @@ -68,11 +63,9 @@ To use the this approach with namespace configuration, you would supply a dataso ---- -==== The database should contain a `persistent_logins` table, created using the following SQL (or equivalent): -==== [source,ddl] ---- create table persistent_logins (username varchar(64) not null, @@ -80,7 +73,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 @@ -89,7 +81,6 @@ It is also used within `BasicAuthenticationFilter`. The hooks will invoke a concrete `RememberMeServices` at the appropriate times. The interface looks like this: -==== [source,java] ---- Authentication autoLogin(HttpServletRequest request, HttpServletResponse response); @@ -99,7 +90,6 @@ void loginFail(HttpServletRequest request, HttpServletResponse response); void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication); ---- -==== Please refer to the Javadoc for a fuller discussion on what the methods do, although note at this stage that `AbstractAuthenticationProcessingFilter` only calls the `loginFail()` and `loginSuccess()` methods. The `autoLogin()` method is called by `RememberMeAuthenticationFilter` whenever the `SecurityContextHolder` does not contain an `Authentication`. @@ -122,8 +112,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 @@ -146,7 +138,9 @@ RememberMeServices rememberMeServices(UserDetailsService userDetailsService) { return rememberMe; } ---- -.XML + +XML:: ++ [source,xml,role="secondary"] ---- @@ -161,11 +155,10 @@ RememberMeServices rememberMeServices(UserDetailsService userDetailsService) { ---- -==== +====== The following beans are required in an application context to enable remember-me services: -==== [source,xml] ---- ---- -==== Don't forget 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`). diff --git a/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc b/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc index 9f135184d9..236209b10d 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/session-management.adoc @@ -6,8 +6,10 @@ Once you have got an application that is xref:servlet/authentication/index.adoc[ This is done automatically by default, so no additional code is necessary, though there are some steps you should consider. The first is setting the `requireExplicitSave` property in `HttpSecurity`. You can do it like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -21,7 +23,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -36,14 +39,15 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== The most straightforward reason for this is that it is xref:migration/servlet/session-management.adoc#_require_explicit_saving_of_securitycontextrepository[becoming the default value in 6.0], so this will make sure you are ready for that. @@ -100,8 +104,10 @@ This means that there is no need to detect when `Authentication` is done and thu To opt into the new Spring Security 6 default, the following configuration should be used. .Require Explicit `SessionAuthenticationStrategy` Invocation -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -115,7 +121,8 @@ SecurityFilterChain springSecurity(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -129,7 +136,8 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -137,7 +145,7 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== ==== Things To Consider When Moving Away From `SessionManagementFilter` @@ -171,8 +179,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 @@ -188,7 +198,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -205,7 +216,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -213,7 +225,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== [NOTE] ==== @@ -230,8 +242,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 = @@ -256,7 +270,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` @@ -280,14 +294,16 @@ The reason is that it doesn't remove it from the `SecurityContextRepository`, wh To make sure the authentication is properly cleared and saved, you can invoke {security-api-url}/org/springframework/security/web/authentication/logout/SecurityContextLogoutHandler.html[the `SecurityContextLogoutHandler`] which does that for us, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- SecurityContextLogoutHandler handler = new SecurityContextLogoutHandler(); <1> handler.logout(httpServletRequest, httpServletResponse, null); <2> ---- -==== +====== <1> Create a new instance of `SecurityContextLogoutHandler` <2> Call the `logout` method passing in the `HttpServletRequest`, `HttpServletResponse` and a `null` authentication because it is not required for this handler. @@ -302,8 +318,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 @@ -317,7 +335,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -332,19 +351,20 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== The above configuration is <> 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. @@ -358,8 +378,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 @@ -379,7 +401,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]. @@ -408,8 +430,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 @@ -418,7 +442,8 @@ public HttpSessionEventPublisher httpSessionEventPublisher() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -427,7 +452,8 @@ open fun httpSessionEventPublisher(): HttpSessionEventPublisher { } ---- -.web.xml +web.xml:: ++ [source,xml,role="secondary"] ---- @@ -436,12 +462,14 @@ open fun httpSessionEventPublisher(): HttpSessionEventPublisher { ---- -==== +====== Then add the following lines to your security configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -454,7 +482,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -470,7 +499,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -480,15 +510,17 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== 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) @@ -518,14 +550,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 @@ -539,7 +573,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -556,7 +591,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -565,7 +601,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== The second login will then be rejected. @@ -575,8 +611,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) @@ -607,7 +645,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]. @@ -619,8 +657,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 @@ -633,7 +673,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -647,7 +688,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -655,7 +697,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== 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. @@ -666,8 +708,10 @@ If that is your case, you might want to < @@ -703,15 +749,17 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { ---- -==== +====== [[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 @@ -724,7 +772,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -738,7 +787,8 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -756,14 +806,16 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
---- -==== +====== 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 @@ -776,7 +828,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -790,14 +843,15 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== Unfortunately, this cannot be guaranteed to work with every servlet container, so you need to test it in your environment. @@ -807,14 +861,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] ---- Header always set Set-Cookie "JSESSIONID=;Path=/tutorial;Expires=Thu, 01 Jan 1970 00:00:00 GMT" ---- -==== 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]. @@ -843,8 +895,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 @@ -859,7 +913,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -875,14 +930,15 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== 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. @@ -896,8 +952,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( @@ -908,7 +966,7 @@ SecurityContext context = SecurityContextHolder.createEmptyContext(); <1> context.setAuthentication(authentication); <2> SecurityContextHolder.setContext(authentication); <3> ---- -==== +====== 1. Creates an empty `SecurityContext` instance by accessing the `SecurityContextHolder` statically. 2. Sets the `Authentication` object in the `SecurityContext` instance. @@ -923,8 +981,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 { @@ -943,7 +1003,7 @@ public class SomeClass { } ---- -==== +====== 1. Creates an empty `SecurityContext` instance using the configured `SecurityContextHolderStrategy`. 2. Sets the `Authentication` object in the `SecurityContext` instance. @@ -956,8 +1016,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 @@ -970,7 +1032,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -984,14 +1047,15 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/authorization/acls.adoc b/docs/modules/ROOT/pages/servlet/authorization/acls.adoc index 1735e9000b..d683f9e346 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/acls.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/acls.adoc @@ -148,8 +148,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) @@ -170,7 +172,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) @@ -189,7 +192,7 @@ aclService.createAcl(oi) acl!!.insertAce(acl.entries.size, p, sid, true) aclService.updateAcl(acl) ---- -==== +====== In the example above, we're retrieving the ACL associated with the "Foo" domain object with identifier number 44. diff --git a/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc b/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc index 56ad4df0cc..ce7d401ccd 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/architecture.adoc @@ -110,8 +110,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 @@ -137,15 +139,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 @@ -167,7 +171,7 @@ public class AccessDecisionVoterAuthorizationManagerAdapter implements Authoriza } } ---- -==== +====== And then wire it into your `SecurityFilterChain`. @@ -184,8 +188,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 @@ -198,7 +204,8 @@ AccessDecisionVoter hierarchyVoter() { } ---- -.Xml +Xml:: ++ [source,java,role="secondary"] ---- @@ -216,7 +223,7 @@ AccessDecisionVoter hierarchyVoter() { ---- -==== +====== Here we have four roles in a hierarchy `ROLE_ADMIN => ROLE_STAFF => ROLE_USER => ROLE_GUEST`. A user who is authenticated with `ROLE_ADMIN`, will behave as if they have all four roles when security constraints are evaluated against an `AuthorizationManager` adapted to call the above `RoleHierarchyVoter`. diff --git a/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc b/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc index 7be023bfb7..bccf75ac17 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc @@ -16,8 +16,10 @@ You can override the default when you declare a `SecurityFilterChain`. Instead of using xref:servlet/authorization/authorize-http-requests.adoc#servlet-authorize-requests-defaults[`authorizeRequests`], use `authorizeHttpRequests`, like so: .Use authorizeHttpRequests -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -31,7 +33,7 @@ SecurityFilterChain web(HttpSecurity http) throws AuthenticationException { return http.build(); } ---- -==== +====== This improves on `authorizeRequests` in a number of ways: @@ -56,8 +58,10 @@ In this case the xref:servlet/architecture.adoc#servlet-exceptiontranslationfilt We can configure Spring Security to have different rules by adding more rules in order of precedence. .Authorize Requests -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -75,7 +79,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> We specified multiple URL patterns that any user can access. @@ -91,8 +95,10 @@ This is a good strategy if you do not want to accidentally forget to update your You can take a bean-based approach by constructing your own xref:servlet/authorization/architecture.adoc#authz-delegate-authorization-manager[`RequestMatcherDelegatingAuthorizationManager`] like so: .Configure RequestMatcherDelegatingAuthorizationManager -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -127,7 +133,7 @@ AuthorizationManager requestMatcherAuthorizationMan return (context) -> manager.check(context.getRequest()); } ---- -==== +====== You can also wire xref:servlet/authorization/architecture.adoc#authz-custom-authorization-manager[your own custom authorization managers] for any request matcher. @@ -135,8 +141,10 @@ You can also wire xref:servlet/authorization/architecture.adoc#authz-custom-auth Here is an example of mapping a custom authorization manager to the `my/authorized/endpoint`: .Custom Authorization Manager -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -150,13 +158,15 @@ SecurityFilterChain web(HttpSecurity http) throws Exception { return http.build(); } ---- -==== +====== Or you can provide it for all requests as seen below: .Custom Authorization Manager for All Requests -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -170,14 +180,16 @@ SecurityFilterChain web(HttpSecurity http) throws Exception { return http.build(); } ---- -==== +====== By default, the `AuthorizationFilter` does not apply to `DispatcherType.ERROR` and `DispatcherType.ASYNC`. We can configure Spring Security to apply the authorization rules to all dispatcher types by using the `shouldFilterAllDispatcherTypes` method: .Set shouldFilterAllDispatcherTypes to true -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -192,7 +204,9 @@ SecurityFilterChain web(HttpSecurity http) throws Exception { return http.build(); } ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -206,14 +220,16 @@ open fun web(http: HttpSecurity): SecurityFilterChain { return http.build() } ---- -==== +====== Now with the authorization rules applying to all dispatcher types, you have more control of the authorization on them. For example, you may want to configure `shouldFilterAllDispatcherTypes` to `true` but not apply authorization on requests with dispatcher type `ASYNC` or `FORWARD`. .Permit ASYNC and FORWARD dispatcher type -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -229,7 +245,9 @@ SecurityFilterChain web(HttpSecurity http) throws Exception { return http.build(); } ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -244,13 +262,15 @@ open fun web(http: HttpSecurity): SecurityFilterChain { return http.build() } ---- -==== +====== You can also customize it to require a specific role for a dispatcher type: .Require ADMIN for Dispatcher Type ERROR -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -266,7 +286,9 @@ SecurityFilterChain web(HttpSecurity http) throws Exception { return http.build(); } ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -281,7 +303,7 @@ open fun web(http: HttpSecurity): SecurityFilterChain { return http.build() } ---- -==== +====== == Request Matchers @@ -290,8 +312,10 @@ We use `securityMatchers` to determine if a given `HttpSecurity` should be appli 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 @@ -312,7 +336,9 @@ public class SecurityConfig { } } ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -334,7 +360,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 @@ -346,8 +372,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> @@ -380,7 +408,9 @@ public class MyCustomRequestMatcher implements RequestMatcher { } } ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher <1> @@ -406,7 +436,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` @@ -421,37 +451,43 @@ However, `WebExpressionAuthorizationManager` is available to help migrate legacy 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 -> 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)`. diff --git a/docs/modules/ROOT/pages/servlet/authorization/authorize-requests.adoc b/docs/modules/ROOT/pages/servlet/authorization/authorize-requests.adoc index 1cc97bc13b..632d9af979 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/authorize-requests.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/authorize-requests.adoc @@ -30,8 +30,10 @@ The explicit configuration looks like: [[servlet-authorize-requests-defaults]] .Every Request Must be Authenticated -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -45,7 +47,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -54,7 +57,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -68,13 +72,15 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { return http.build() } ---- -==== +====== We can configure Spring Security to have different rules by adding more rules in order of precedence. .Authorize Requests -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -91,7 +97,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -107,7 +114,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -126,7 +134,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { return http.build() } ---- -==== +====== <1> There are multiple authorization rules specified. Each rule is considered in the order they were declared. <2> We specified multiple URL patterns that any user can access. @@ -147,8 +155,10 @@ In some scenarios, you may want to apply the filter to every request. You can configure Spring Security to apply the authorization rules to every request by using the `filterSecurityInterceptorOncePerRequest` method: .Set filterSecurityInterceptorOncePerRequest to false -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -164,20 +174,23 @@ SecurityFilterChain web(HttpSecurity http) throws Exception { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== You can also configure authorization based on the request dispatcher type: .Permit ASYNC dispatcher type -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -193,7 +206,9 @@ SecurityFilterChain web(HttpSecurity http) throws Exception { return http.build(); } ---- -.XML + +XML:: ++ [source,xml,role="secondary"] ---- @@ -205,4 +220,4 @@ SecurityFilterChain web(HttpSecurity http) throws Exception {
---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/authorization/events.adoc b/docs/modules/ROOT/pages/servlet/authorization/events.adoc index d85611a0d3..adb41eb0af 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/events.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/events.adoc @@ -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, } } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/authorization/expression-based.adoc b/docs/modules/ROOT/pages/servlet/authorization/expression-based.adoc index 49b213ba78..abddf5099e 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/expression-based.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/expression-based.adoc @@ -114,8 +114,10 @@ So if you aren't using the namespace and want to use expressions, you will have If you wish to extend the expressions that are available, you can easily refer to any Spring Bean you expose. For example, assuming you have a Bean with the name of `webSecurity` that contains the following method signature: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public class WebSecurity { @@ -125,7 +127,8 @@ public class WebSecurity { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class WebSecurity { @@ -134,13 +137,15 @@ class WebSecurity { } } ---- -==== +====== You could refer to the method using: .Refer to method -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -150,7 +155,8 @@ http ) ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -160,7 +166,8 @@ http ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- http { @@ -169,7 +176,7 @@ http { } } ---- -==== +====== [[el-access-web-path-variables]] === Path Variables in Web Security Expressions @@ -180,8 +187,10 @@ For example, consider a RESTful application that looks up a user by id from the You can easily refer to the path variable by placing it in the pattern. For example, if you had a Bean with the name of `webSecurity` that contains the following method signature: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public class WebSecurity { @@ -191,7 +200,8 @@ public class WebSecurity { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class WebSecurity { @@ -200,13 +210,15 @@ class WebSecurity { } } ---- -==== +====== You could refer to the method using: .Path Variables -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary",attrs="-attributes"] ---- http @@ -216,7 +228,8 @@ http ); ---- -.XML +XML:: ++ [source,xml,role="secondary",attrs="-attributes"] ---- @@ -226,7 +239,8 @@ http ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- http { @@ -235,7 +249,7 @@ http { } } ---- -==== +====== In this configuration URLs that match would pass in the path variable (and convert it) into checkUserId method. For example, if the URL were `/user/123/resource`, then the id passed in would be `123`. @@ -260,41 +274,47 @@ Their use is enabled through the `global-method-security` namespace element: The most obviously useful annotation is `@PreAuthorize` which decides whether a method can actually be invoked or not. For example (from the {gh-samples-url}/servlet/xml/java/contacts[Contacts] sample application) -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @PreAuthorize("hasRole('USER')") public void create(Contact contact); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @PreAuthorize("hasRole('USER')") fun create(contact: Contact?) ---- -==== +====== which means that access will only be allowed for users with the role "ROLE_USER". Obviously the same thing could easily be achieved using a traditional configuration and a simple configuration attribute for the required role. But what about: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @PreAuthorize("hasPermission(#contact, 'admin')") public void deletePermission(Contact contact, Sid recipient, Permission permission); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @PreAuthorize("hasPermission(#contact, 'admin')") fun deletePermission(contact: Contact?, recipient: Sid?, permission: Permission?) ---- -==== +====== Here we're actually using a method argument as part of the expression to decide whether the current user has the "admin" permission for the given contact. The built-in `hasPermission()` expression is linked into the Spring Security ACL module through the application context, as we'll <>. @@ -310,8 +330,10 @@ For example: + -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- import org.springframework.security.access.method.P; @@ -322,7 +344,8 @@ import org.springframework.security.access.method.P; public void doSomething(@P("c") Contact contact); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.security.access.method.P @@ -332,7 +355,7 @@ import org.springframework.security.access.method.P @PreAuthorize("#c.name == authentication.name") fun doSomething(@P("c") contact: Contact?) ---- -==== +====== + @@ -344,8 +367,10 @@ For example: + -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- import org.springframework.data.repository.query.Param; @@ -356,7 +381,8 @@ import org.springframework.data.repository.query.Param; Contact findContactByName(@Param("n") String name); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.data.repository.query.Param @@ -366,7 +392,7 @@ import org.springframework.data.repository.query.Param @PreAuthorize("#n == authentication.name") fun findContactByName(@Param("n") name: String?): Contact? ---- -==== +====== + @@ -385,21 +411,24 @@ Any Spring-EL functionality is available within the expression, so you can also For example, if you wanted a particular method to only allow access to a user whose username matched that of the contact, you could write -- -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @PreAuthorize("#contact.name == authentication.name") public void doSomething(Contact contact); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @PreAuthorize("#contact.name == authentication.name") fun doSomething(contact: Contact?) ---- -==== +====== Here we are accessing another built-in expression, `authentication`, which is the `Authentication` stored in the security context. You can also access its "principal" property directly, using the expression `principal`. @@ -417,8 +446,10 @@ Spring Security supports filtering of collections, arrays, maps and streams usin This is most commonly performed on the return value of a method. For example: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @PreAuthorize("hasRole('USER')") @@ -426,14 +457,15 @@ For example: public List getAll(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @PreAuthorize("hasRole('USER')") @PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, 'admin')") fun getAll(): List ---- -==== +====== When using the `@PostFilter` annotation, Spring Security iterates through the returned collection or map and removes any elements for which the supplied expression is false. For an array, a new array instance will be returned containing filtered elements. @@ -507,8 +539,10 @@ For example, consider the following: Instead of repeating this everywhere, we can create a meta annotation that can be used instead. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Retention(RetentionPolicy.RUNTIME) @@ -516,14 +550,15 @@ Instead of repeating this everywhere, we can create a meta annotation that can b public @interface ContactPermission {} ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Retention(AnnotationRetention.RUNTIME) @PreAuthorize("#contact.name == authentication.name") annotation class ContactPermission ---- -==== +====== Meta annotations can be used for any of the Spring Security method security annotations. In order to remain compliant with the specification JSR-250 annotations do not support meta annotations. diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index ad5ddd188c..9daa121718 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -29,8 +29,10 @@ For earlier versions, please read about similar support with < ---- -==== +====== 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 `DefaultAuthorizationMethodInterceptorChain` for it to make the actual decision: .Method Security Annotation Usage -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public interface BankService { @@ -76,7 +82,8 @@ public interface BankService { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- interface BankService { @@ -90,13 +97,15 @@ interface BankService { fun post(account : Account, amount : Double) : Account } ---- -==== +====== You can enable support for Spring Security's `@Secured` annotation using: .@Secured Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableMethodSecurity(securedEnabled = true) @@ -105,7 +114,8 @@ public class MethodSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableMethodSecurity(securedEnabled = true) @@ -114,18 +124,21 @@ class MethodSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== or JSR-250 using: .JSR-250 Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableMethodSecurity(jsr250Enabled = true) @@ -134,7 +147,8 @@ public class MethodSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableMethodSecurity(jsr250Enabled = true) @@ -143,12 +157,13 @@ class MethodSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== === Customizing Authorization @@ -158,8 +173,10 @@ Spring Security's `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFil If you need to customize the way that expressions are handled, you can expose a custom `MethodSecurityExpressionHandler`, like so: .Custom MethodSecurityExpressionHandler -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -170,7 +187,8 @@ static MethodSecurityExpressionHandler methodSecurityExpressionHandler() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- companion object { @@ -183,7 +201,8 @@ companion object { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -195,7 +214,7 @@ companion object { ---- -==== +====== [TIP] ==== @@ -208,8 +227,10 @@ Also, for role-based authorization, Spring Security adds a default `ROLE_` prefi 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 @@ -218,7 +239,8 @@ static GrantedAuthorityDefaults grantedAuthorityDefaults() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- companion object { @@ -229,7 +251,8 @@ companion object { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -238,7 +261,7 @@ companion object { ---- -==== +====== [TIP] ==== @@ -261,8 +284,10 @@ If that authorization denies access, the value is not returned, and an `AccessDe To recreate what adding `@EnableMethodSecurity` does by default, you would publish the following configuration: .Full Pre-post Method Security Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableMethodSecurity(prePostEnabled = false) @@ -293,7 +318,8 @@ class MethodSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableMethodSecurity(prePostEnabled = false) @@ -324,7 +350,8 @@ class MethodSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -342,15 +369,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 @@ -362,7 +391,8 @@ Advisor postFilterAuthorizationMethodInterceptor() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -374,7 +404,8 @@ fun postFilterAuthorizationMethodInterceptor() : Advisor { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== 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"] ---- @EnableMethodSecurity(prePostEnabled = false) @@ -403,7 +436,8 @@ class MethodSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableMethodSecurity(prePostEnabled = false) @@ -416,7 +450,8 @@ class MethodSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -427,7 +462,7 @@ class MethodSecurityConfig { class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor" factory-method="preAuthorize"/> ---- -==== +====== Or, you may have a custom before-method `AuthorizationManager` that you want to add to the list. @@ -436,9 +471,11 @@ In this case, you will need to tell Spring Security both the `AuthorizationManag Thus, you can configure Spring Security to invoke your `AuthorizationManager` in between `@PreAuthorize` and `@PostAuthorize` like so: .Custom Before Advisor -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableMethodSecurity @@ -456,7 +493,8 @@ class MethodSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableMethodSecurity @@ -474,7 +512,8 @@ class MethodSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -496,7 +535,7 @@ class MethodSecurityConfig { value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1}"/> ---- -==== +====== [TIP] ==== @@ -509,8 +548,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 { @@ -521,7 +562,8 @@ public interface BankService { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- interface BankService { @@ -531,7 +573,7 @@ interface BankService { fun readAccount(id : Long) : Account } ---- -==== +====== You can supply your own `AuthorizationMethodInterceptor` to customize how access to the return value is evaluated. @@ -539,8 +581,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"] ---- @EnableMethodSecurity @@ -556,7 +600,8 @@ class MethodSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableMethodSecurity @@ -572,7 +617,8 @@ class MethodSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -594,7 +640,7 @@ class MethodSecurityConfig { value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1}"/> ---- -==== +====== and it will be invoked after the `@PostAuthorize` interceptor. @@ -604,8 +650,10 @@ and it will be invoked after the `@PostAuthorize` interceptor. We can enable annotation-based security using the `@EnableGlobalMethodSecurity` annotation on any `@Configuration` instance. For example, the following would enable Spring Security's `@Secured` annotation. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableGlobalMethodSecurity(securedEnabled = true) @@ -614,7 +662,8 @@ public class MethodSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableGlobalMethodSecurity(securedEnabled = true) @@ -622,14 +671,16 @@ open 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 AccessDecisionManager for it to make the actual decision: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public interface BankService { @@ -645,7 +696,8 @@ public Account post(Account account, double amount); } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- interface BankService { @@ -659,12 +711,14 @@ interface BankService { fun post(account: Account, amount: Double): Account } ---- -==== +====== Support for JSR-250 annotations can be enabled using -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableGlobalMethodSecurity(jsr250Enabled = true) @@ -673,7 +727,8 @@ public class MethodSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableGlobalMethodSecurity(jsr250Enabled = true) @@ -681,13 +736,15 @@ open class MethodSecurityConfig { // ... } ---- -==== +====== These are standards-based and allow simple role-based constraints to be applied but do not have the power Spring Security's native annotations. To use the new expression-based syntax, you would use -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableGlobalMethodSecurity(prePostEnabled = true) @@ -696,7 +753,8 @@ public class MethodSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableGlobalMethodSecurity(prePostEnabled = true) @@ -704,12 +762,14 @@ open class MethodSecurityConfig { // ... } ---- -==== +====== and the equivalent Java code would be -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public interface BankService { @@ -725,7 +785,8 @@ public Account post(Account account, double amount); } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- interface BankService { @@ -739,7 +800,7 @@ interface BankService { fun post(account: Account, amount: Double): Account } ---- -==== +====== == GlobalMethodSecurityConfiguration @@ -747,8 +808,10 @@ Sometimes you may need to perform operations that are more complicated than are For these instances, you can extend the `GlobalMethodSecurityConfiguration` ensuring that the `@EnableGlobalMethodSecurity` annotation is present on your subclass. For example, if you wanted to provide a custom `MethodSecurityExpressionHandler`, you could use the following configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableGlobalMethodSecurity(prePostEnabled = true) @@ -761,7 +824,8 @@ public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableGlobalMethodSecurity(prePostEnabled = true) @@ -772,7 +836,7 @@ open class MethodSecurityConfig : GlobalMethodSecurityConfiguration() { } } ---- -==== +====== For additional information about methods that can be overridden, refer to the `GlobalMethodSecurityConfiguration` Javadoc. @@ -791,8 +855,10 @@ Adding an annotation to a method (on an class or interface) would then limit the Spring Security's native annotation support defines a set of attributes for the method. These will be passed to the `AccessDecisionManager` for it to make the actual decision: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public interface BankService { @@ -809,7 +875,8 @@ public Account post(Account account, double amount); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- interface BankService { @@ -823,7 +890,7 @@ interface BankService { fun post(account: Account, amount: Double): Account } ---- -==== +====== Support for JSR-250 annotations can be enabled using @@ -842,8 +909,10 @@ To use the new expression-based syntax, you would use and the equivalent Java code would be -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public interface BankService { @@ -859,7 +928,8 @@ public Account post(Account account, double amount); } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- interface BankService { @@ -873,7 +943,7 @@ interface BankService { fun post(account: Account, amount: Double): Account } ---- -==== +====== Expression-based annotations are a good choice if you need to define simple rules that go beyond checking the role names against the user's list of authorities. diff --git a/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc b/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc index 39a56ec3c0..5d946265b0 100644 --- a/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc +++ b/docs/modules/ROOT/pages/servlet/exploits/csrf.adoc @@ -36,7 +36,6 @@ You can configure `CookieCsrfTokenRepository` in XML using the following: .Store CSRF Token in a Cookie with XML Configuration -==== [source,xml] ---- @@ -47,7 +46,6 @@ You can configure `CookieCsrfTokenRepository` in XML using the following: class="org.springframework.security.web.csrf.CookieCsrfTokenRepository" p:cookieHttpOnly="false"/> ---- -==== [NOTE] ==== @@ -60,8 +58,10 @@ If you do not need the ability to read the cookie with JavaScript directly, it i You can configure `CookieCsrfTokenRepository` in Java Configuration using: .Store CSRF Token in a Cookie -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -78,7 +78,8 @@ public class WebSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -95,7 +96,7 @@ class SecurityConfig { } } ---- -==== +====== [NOTE] ==== @@ -113,7 +114,6 @@ The XML configuration below will disable CSRF protection. .Disable CSRF XML Configuration -==== [source,xml] ---- @@ -121,13 +121,14 @@ The XML configuration below will disable CSRF protection. ---- -==== The Java configuration below will disable CSRF protection. .Disable CSRF -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -143,7 +144,8 @@ public class WebSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -161,7 +163,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-csrf-configure-request-handler]] ==== Configure CsrfTokenRequestHandler @@ -174,7 +176,6 @@ An alternate implementation `XorCsrfTokenRequestAttributeHandler` is available t You can configure `XorCsrfTokenRequestAttributeHandler` in XML using the following: .Configure BREACH protection XML Configuration -==== [source,xml] ---- @@ -184,13 +185,14 @@ You can configure `XorCsrfTokenRequestAttributeHandler` in XML using the followi ---- -==== You can configure `XorCsrfTokenRequestAttributeHandler` in Java Configuration using the following: .Configure BREACH protection -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -207,7 +209,8 @@ public class WebSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -224,7 +227,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-csrf-include]] === Include the CSRF Token @@ -242,14 +245,12 @@ In order to post an HTML form the CSRF token must be included in the form as a h For example, the rendered HTML might look like: .CSRF Token HTML -==== [source,html] ---- ---- -==== Next we will discuss various ways of including the CSRF token in a form as a hidden input. @@ -273,7 +274,6 @@ If the <> for including the actual CSRF toke An example of doing this with a JSP is shown below: .CSRF Token in Form with Request Attribute -==== [source,xml] ---- @@ -286,7 +286,6 @@ An example of doing this with a JSP is shown below: value="${_csrf.token}"/> ---- -==== [[servlet-csrf-include-ajax]] ==== Ajax and JSON Requests @@ -308,7 +307,6 @@ An alternative pattern to < @@ -319,13 +317,11 @@ The HTML might look something like this: ---- -==== Once the meta tags contained the CSRF token, the JavaScript code would read the meta tags and include the CSRF token as a header. If you were using jQuery, this could be done with the following: .AJAX send CSRF Token -==== [source,javascript] ---- $(function () { @@ -336,7 +332,6 @@ $(function () { }); }); ---- -==== [[servlet-csrf-include-ajax-meta-tag]] ====== csrfMeta tag @@ -350,7 +345,6 @@ If the <> for including the actual CSRF toke An example of doing this with a JSP is shown below: .CSRF meta tag JSP -==== [source,html] ---- @@ -362,7 +356,6 @@ An example of doing this with a JSP is shown below: ---- -==== [[servlet-csrf-considerations]] == CSRF Considerations @@ -392,8 +385,10 @@ If you really want to use HTTP GET with logout you can do so, but remember this For example, the following Java Configuration will perform logout with the URL `/logout` is requested with any HTTP method: .Log out with HTTP GET -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -410,7 +405,8 @@ public class WebSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -427,7 +423,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-considerations-csrf-timeouts]] @@ -474,8 +470,10 @@ In general, this is the recommended approach because the temporary file upload s To ensure `MultipartFilter` is specified before the Spring Security filter with java configuration, users can override beforeSpringSecurityFilterChain as shown below: .Initializer MultipartFilter -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer { @@ -487,7 +485,8 @@ public class SecurityApplicationInitializer extends AbstractSecurityWebApplicati } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer() { @@ -496,12 +495,11 @@ class SecurityApplicationInitializer : AbstractSecurityWebApplicationInitializer } } ---- -==== +====== To ensure `MultipartFilter` is specified before the Spring Security filter with XML configuration, users can ensure the element of the `MultipartFilter` is placed before the springSecurityFilterChain within the web.xml as shown below: .web.xml - MultipartFilter -==== [source,xml] ---- @@ -521,7 +519,6 @@ To ensure `MultipartFilter` is specified before the Spring Security filter with /* ---- -==== [[servlet-csrf-considerations-multipart-url]] ==== Include CSRF Token in URL @@ -531,14 +528,12 @@ Since the `CsrfToken` is exposed as an `HttpServletRequest` < ---- -==== [[servlet-csrf-considerations-override-method]] === HiddenHttpMethodFilter diff --git a/docs/modules/ROOT/pages/servlet/exploits/firewall.adoc b/docs/modules/ROOT/pages/servlet/exploits/firewall.adoc index 7560afd6f0..103815f682 100644 --- a/docs/modules/ROOT/pages/servlet/exploits/firewall.adoc +++ b/docs/modules/ROOT/pages/servlet/exploits/firewall.adoc @@ -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 leverage Spring MVC's Matrix Variables, the following configuration could be used: .Allow Matrix Variables -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -58,7 +60,8 @@ public StrictHttpFirewall httpFirewall() { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -78,7 +82,7 @@ fun httpFirewall(): StrictHttpFirewall { return firewall } ---- -==== +====== The `StrictHttpFirewall` provides an allowed list of valid HTTP methods that are allowed to protect against https://owasp.org/www-community/attacks/Cross_Site_Tracing[Cross Site Tracing (XST)] and https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/06-Test_HTTP_Methods[HTTP Verb Tampering]. The default valid methods are "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", and "PUT". @@ -87,8 +91,10 @@ For example, the following will only allow 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"] ---- ---- -.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 requesting t If you must allow any HTTP method (not recommended), you can use `StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)`. This will disable validation of the HTTP method entirely. -[[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. @@ -148,8 +156,10 @@ NOTE: Also, parameter values can be controlled with `setAllowedParameterValues(P For example, to switch off this check, you can wire your `StrictHttpFirewall` with ``Predicate``s that always return `true`, like so: .Allow Any Header Name, Header Value, and Parameter Name -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -162,7 +172,8 @@ public StrictHttpFirewall httpFirewall() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -174,7 +185,7 @@ fun httpFirewall(): StrictHttpFirewall { return firewall } ---- -==== +====== Or, there might be a specific value that you need to allow. @@ -184,8 +195,10 @@ Due to this fact, some application servers will parse this value into two separa You can address this with the `setAllowedHeaderValues` method, as you can see below: .Allow Certain User Agents -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -198,7 +211,8 @@ public StrictHttpFirewall httpFirewall() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -210,13 +224,15 @@ fun httpFirewall(): StrictHttpFirewall { return firewall } ---- -==== +====== In the case of header values, you may instead consider parsing them as UTF-8 at verification time like so: .Parse Headers As UTF-8 -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- firewall.setAllowedHeaderValues((header) -> { @@ -225,7 +241,8 @@ firewall.setAllowedHeaderValues((header) -> { }); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- firewall.setAllowedHeaderValues { @@ -233,4 +250,4 @@ firewall.setAllowedHeaderValues { return allowed.matcher(parsed).matches() } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/exploits/headers.adoc b/docs/modules/ROOT/pages/servlet/exploits/headers.adoc index 1502b61073..fe90742737 100644 --- a/docs/modules/ROOT/pages/servlet/exploits/headers.adoc +++ b/docs/modules/ROOT/pages/servlet/exploits/headers.adoc @@ -16,8 +16,10 @@ For example, assume that you want the defaults except you wish to specify `SAMEO You can easily do this with the following Configuration: .Customize Default Security Headers -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -37,7 +39,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -49,7 +52,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -68,7 +72,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. An example is provided below: @@ -76,8 +80,10 @@ An example is provided below: If you are using Spring Security's Configuration the following will only add xref:features/exploits/headers.adoc#headers-cache-control[Cache Control]. .Customize Cache Control Headers -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -97,7 +103,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -109,7 +116,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -129,13 +137,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"] ---- @EnableWebSecurity @@ -151,7 +161,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -161,7 +172,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -178,7 +190,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-cache-control]] == Cache Control @@ -194,8 +206,10 @@ Details on how to do this can be found 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 @@ -214,7 +228,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -226,7 +241,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -245,7 +261,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-content-type-options]] == Content Type Options @@ -254,8 +270,10 @@ Spring Security includes xref:features/exploits/headers.adoc#headers-content-typ However, you can disable it with: .Content Type Options Disabled -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -274,7 +292,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -286,7 +305,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -305,7 +325,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-hsts]] == HTTP Strict Transport Security (HSTS) @@ -315,8 +335,10 @@ However, you can customize the results explicitly. For example, the following is an example of explicitly providing HSTS: .Strict Transport Security -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -338,7 +360,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -353,7 +376,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -374,7 +398,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-hpkp]] == HTTP Public Key Pinning (HPKP) @@ -383,8 +407,10 @@ For passivity reasons, Spring Security provides servlet support for xref:feature You can enable HPKP headers with the following Configuration: .HTTP Public Key Pinning -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -405,7 +431,9 @@ public class WebSecurityConfig { } } ---- -.XML + +XML:: ++ [source,xml,role="secondary"] ---- @@ -424,7 +452,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -446,7 +475,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-frame-options]] == X-Frame-Options @@ -456,8 +485,10 @@ By default, Spring Security disables rendering within an iframe using xref:featu You can customize frame options to use the same origin within a Configuration using the following: .X-Frame-Options: SAMEORIGIN -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -477,7 +508,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -491,7 +523,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -510,7 +543,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-xss-protection]] == X-XSS-Protection @@ -520,8 +553,10 @@ However, you can change this default. For example, the following Configuration specifies that Spring Security should no longer instruct browsers to block the content: .X-XSS-Protection Customization -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -541,7 +576,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -553,7 +589,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -573,7 +610,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-csp]] == Content Security Policy (CSP) @@ -584,18 +621,18 @@ The web application author must declare the security policy(s) to enforce and/or For example, given 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/ ---- -==== You can enable the CSP header as shown below: .Content Security Policy -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -615,7 +652,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -628,7 +666,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -648,13 +687,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"] ---- @EnableWebSecurity @@ -675,7 +716,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -689,7 +731,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -710,7 +753,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-referrer]] == Referrer Policy @@ -719,8 +762,10 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-referre You can enable the Referrer Policy header using the configuration as shown below: .Referrer Policy -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -740,7 +785,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -752,7 +798,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -772,7 +819,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-feature]] == Feature Policy @@ -781,18 +828,18 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-feature The following `Feature-Policy` header: .Feature-Policy Example -==== [source] ---- Feature-Policy: geolocation 'self' ---- -==== can enable the Feature Policy header using the configuration shown below: .Feature-Policy -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -810,7 +857,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -822,7 +870,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -840,7 +889,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-permissions]] == Permissions Policy @@ -849,18 +898,18 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-permiss The following `Permissions-Policy` header: .Permissions-Policy Example -==== [source] ---- Permissions-Policy: geolocation=(self) ---- -==== can enable the Permissions Policy header using the configuration shown below: .Permissions-Policy -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -880,7 +929,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -892,7 +942,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -912,7 +963,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-clear-site-data]] == Clear Site Data @@ -921,17 +972,17 @@ Spring Security does not add xref:features/exploits/headers.adoc#headers-clear-s The following Clear-Site-Data header: .Clear-Site-Data Example -==== ---- Clear-Site-Data: "cache", "cookies" ---- -==== can be sent on log out with the following configuration: .Clear-Site-Data -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -949,7 +1000,8 @@ public class WebSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -967,7 +1019,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-cross-origin-policies]] == Cross-Origin Policies @@ -985,8 +1037,10 @@ Spring Security does not add < @@ -1079,7 +1138,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -1097,7 +1157,7 @@ class SecurityConfig { } } ---- -==== +====== [[servlet-headers-writer]] === Headers Writer @@ -1107,8 +1167,10 @@ Let's take a look at an example of using an custom instance of `XFrameOptionsHea If you wanted to explicitly configure <> it could be done with the following Configuration: .Headers Writer -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -1126,7 +1188,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -1144,7 +1207,8 @@ See https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsi c:frameOptionsMode="SAMEORIGIN"/> ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -1162,7 +1226,7 @@ class SecurityConfig { } } ---- -==== +====== [[headers-delegatingrequestmatcherheaderwriter]] === DelegatingRequestMatcherHeaderWriter @@ -1174,8 +1238,10 @@ You could use the `DelegatingRequestMatcherHeaderWriter` to do so. An example of using `DelegatingRequestMatcherHeaderWriter` in Java Configuration can be seen below: .DelegatingRequestMatcherHeaderWriter Java Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -1197,7 +1263,8 @@ public class WebSecurityConfig { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -1222,7 +1289,8 @@ public class WebSecurityConfig { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -1244,4 +1312,4 @@ class SecurityConfig { } } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/exploits/http.adoc b/docs/modules/ROOT/pages/servlet/exploits/http.adoc index e167320e6a..e2ae77d2c9 100644 --- a/docs/modules/ROOT/pages/servlet/exploits/http.adoc +++ b/docs/modules/ROOT/pages/servlet/exploits/http.adoc @@ -13,8 +13,10 @@ If a client makes a request using HTTP rather than HTTPS, Spring Security can be For example, the following Java configuration will redirect 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 will redirect all HTTP requests to HTTPS .Redirect to HTTPS with XML Configuration -==== [source,xml] ---- @@ -65,7 +67,6 @@ The following XML configuration will redirect all HTTP requests to HTTPS ... ---- -==== [[servlet-hsts]] diff --git a/docs/modules/ROOT/pages/servlet/getting-started.adoc b/docs/modules/ROOT/pages/servlet/getting-started.adoc index adf4aa555f..afa72c899e 100644 --- a/docs/modules/ROOT/pages/servlet/getting-started.adoc +++ b/docs/modules/ROOT/pages/servlet/getting-started.adoc @@ -21,7 +21,6 @@ You can now https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle The following example shows how to do so (and the beginning of the output from doing so): .Running Spring Boot Application -==== [source,bash] ---- $ ./mvn spring-boot:run @@ -32,7 +31,6 @@ Using generated security password: 8e557245-73e2-4286-969a-ff57fe326336 ... ---- -==== [[servlet-hello-auto-configuration]] diff --git a/docs/modules/ROOT/pages/servlet/integrations/cors.adoc b/docs/modules/ROOT/pages/servlet/integrations/cors.adoc index 39f7ac3df7..56062c6a16 100644 --- a/docs/modules/ROOT/pages/servlet/integrations/cors.adoc +++ b/docs/modules/ROOT/pages/servlet/integrations/cors.adoc @@ -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` using the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -36,7 +38,8 @@ public class WebSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -62,7 +65,7 @@ open class WebSecurityConfig { } } ---- -==== +====== or in XML @@ -79,8 +82,10 @@ or in XML If you are using Spring MVC's CORS support, you can omit specifying the `CorsConfigurationSource` and Spring Security will leverage the CORS configuration provided to Spring MVC. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -98,7 +103,8 @@ public class WebSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -115,7 +121,7 @@ open class WebSecurityConfig { } } ---- -==== +====== or in XML diff --git a/docs/modules/ROOT/pages/servlet/integrations/data.adoc b/docs/modules/ROOT/pages/servlet/integrations/data.adoc index 214db25618..426ac3c3e0 100644 --- a/docs/modules/ROOT/pages/servlet/integrations/data.adoc +++ b/docs/modules/ROOT/pages/servlet/integrations/data.adoc @@ -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,7 +30,7 @@ fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension { return SecurityEvaluationContextExtension() } ---- -==== +====== In XML Configuration, this would look like: @@ -42,8 +45,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 @@ -53,7 +58,8 @@ public interface MessageRepository extends PagingAndSortingRepository { fun findInbox(pageable: Pageable): Page } ---- -==== +====== 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. diff --git a/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc b/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc index 0af922acbb..1e384623ab 100644 --- a/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc +++ b/docs/modules/ROOT/pages/servlet/integrations/mvc.adoc @@ -56,8 +56,10 @@ For a `web.xml` this means that you should place your configuration in the `Disp Below `WebSecurityConfiguration` in placed in the ``DispatcherServlet``s `ApplicationContext`. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public class SecurityInitializer extends @@ -81,7 +83,8 @@ public class SecurityInitializer extends } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class SecurityInitializer : AbstractAnnotationConfigDispatcherServletInitializer() { @@ -101,7 +104,7 @@ class SecurityInitializer : AbstractAnnotationConfigDispatcherServletInitializer } } ---- -==== +====== [NOTE] ==== @@ -114,26 +117,31 @@ This is what is known as https://en.wikipedia.org/wiki/Defense_in_depth_(computi Consider a controller that is mapped as follows: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @RequestMapping("/admin") public String admin() { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @RequestMapping("/admin") fun admin(): String { ---- -==== +====== If we wanted to restrict access to this controller method to admin users, a developer can provide authorization rules by matching on the `HttpServletRequest` with the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -146,7 +154,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -159,7 +168,7 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain { return http.build() } ---- -==== +====== or in XML @@ -182,8 +191,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 @@ -198,7 +209,8 @@ public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospe } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -213,7 +225,7 @@ open fun filterChain(http: HttpSecurity, introspector: HandlerMappingIntrospecto return http.build() } ---- -==== +====== [[mvc-authentication-principal]] @@ -237,8 +249,10 @@ Once `AuthenticationPrincipalArgumentResolver` is properly configured, you can b Consider a situation where a custom `UserDetailsService` that returns an `Object` that implements `UserDetails` and your own `CustomUser` `Object`. The `CustomUser` of the currently authenticated user could be accessed using the following code: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @RequestMapping("/messages/inbox") @@ -251,7 +265,8 @@ public ModelAndView findMessagesForUser() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @RequestMapping("/messages/inbox") @@ -262,12 +277,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. For example: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -281,7 +298,8 @@ public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser cust } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @RequestMapping("/messages/inbox") @@ -290,15 +308,17 @@ open fun findMessagesForUser(@AuthenticationPrincipal customUser: CustomUser?): // .. find messages for this user and return them ... } ---- -==== +====== Sometimes it may be necessary 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 returns an `Object` that implements `UserDetails` and provides a method named `getCustomUser` to access `CustomUser`. For example, it might look like: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public class CustomUserUserDetails extends User { @@ -309,7 +329,8 @@ public class CustomUserUserDetails extends User { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class CustomUserUserDetails( @@ -321,12 +342,14 @@ class CustomUserUserDetails( val customUser: CustomUser? = null } ---- -==== +====== We could then access the `CustomUser` 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; @@ -340,7 +363,8 @@ public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "c } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -353,13 +377,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, the following could be used if we were using JPA to manage our Users and 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; @@ -377,7 +403,8 @@ public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntity } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.security.core.annotation.AuthenticationPrincipal @@ -396,7 +423,7 @@ open fun updateName( // ... } ---- -==== +====== We can further remove our dependency on Spring Security by making `@AuthenticationPrincipal` a meta annotation on our own annotation. Below we demonstrate how we could do this on an annotation named `@CurrentUser`. @@ -404,8 +431,10 @@ Below we demonstrate how we could do this on an annotation named `@CurrentUser`. NOTE: It is important to realize that in order to remove the dependency on Spring Security, it is the consuming application that would create `@CurrentUser`. 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}) @@ -415,7 +444,8 @@ This step is not strictly required, but assists in isolating your dependency to public @interface CurrentUser {} ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE) @@ -424,13 +454,15 @@ public @interface CurrentUser {} @AuthenticationPrincipal annotation class CurrentUser ---- -==== +====== Now that `@CurrentUser` has been specified, we can use it to signal to resolve our `CustomUser` of the currently authenticated user. We have also isolated our dependency on Spring Security to a single file. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @RequestMapping("/messages/inbox") @@ -440,7 +472,8 @@ public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @RequestMapping("/messages/inbox") @@ -449,7 +482,7 @@ open fun findMessagesForUser(@CurrentUser customUser: CustomUser?): ModelAndView // .. find messages for this user and return them ... } ---- -==== +====== [[mvc-async]] @@ -459,8 +492,10 @@ Spring Web MVC 3.2+ has excellent support for https://docs.spring.io/spring/docs With no additional configuration, Spring Security will automatically setup the `SecurityContext` to the `Thread` that invokes a `Callable` returned by your controllers. For example, the following method will automatically have 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) @@ -475,7 +510,8 @@ return new Callable() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @RequestMapping(method = [RequestMethod.POST]) @@ -486,7 +522,7 @@ open fun processUpload(file: MultipartFile?): Callable { } } ---- -==== +====== [NOTE] .Associating SecurityContext to Callable's @@ -554,8 +590,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 @@ -568,7 +606,8 @@ public class CsrfController { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @RestController @@ -579,7 +618,7 @@ class CsrfController { } } ---- -==== +====== It is important to keep the `CsrfToken` a secret from other domains. This means if you are using 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. diff --git a/docs/modules/ROOT/pages/servlet/integrations/servlet-api.adoc b/docs/modules/ROOT/pages/servlet/integrations/servlet-api.adoc index 741a0629f7..434e85b5b9 100644 --- a/docs/modules/ROOT/pages/servlet/integrations/servlet-api.adoc +++ b/docs/modules/ROOT/pages/servlet/integrations/servlet-api.adoc @@ -24,8 +24,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(); @@ -36,7 +38,8 @@ String firstName = userDetails.getFirstName(); String lastName = userDetails.getLastName(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val auth: Authentication = httpServletRequest.getUserPrincipal() @@ -46,7 +49,7 @@ val userDetails: MyCustomUserDetails = auth.principal as MyCustomUserDetails val firstName: String = userDetails.firstName val lastName: String = userDetails.lastName ---- -==== +====== [NOTE] ==== @@ -60,19 +63,22 @@ The https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.h Typically users should not pass in the "ROLE_" prefix into 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. @@ -93,8 +99,10 @@ If they are not authenticated, the configured AuthenticationEntryPoint will be u 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 can be used to authenticate the user with the current `AuthenticationManager`. For example, the following would attempt to authenticate with the username "user" and password "password": -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- try { @@ -104,7 +112,8 @@ httpServletRequest.login("user","password"); } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- try { @@ -113,7 +122,7 @@ try { // fail to authenticate } ---- -==== +====== [NOTE] ==== @@ -135,8 +144,10 @@ The https://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html#start%2 Using Spring Security's concurrency support, Spring Security overrides the AsyncContext.start(Runnable) to ensure that the current SecurityContext is used when processing the Runnable. For example, the following would output the current user's Authentication: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- final AsyncContext async = httpServletRequest.startAsync(); @@ -155,7 +166,8 @@ async.start(new Runnable() { }); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val async: AsyncContext = httpServletRequest.startAsync() @@ -171,7 +183,7 @@ async.start { } } ---- -==== +====== [[servletapi-async]] === Async Servlet Support @@ -217,8 +229,10 @@ Prior to Spring Security 3.2, the SecurityContext from the SecurityContextHolder This can cause issues in an Async environment. For example, consider the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- httpServletRequest.startAsync(); @@ -238,7 +252,8 @@ new Thread("AsyncThread") { }.start(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- httpServletRequest.startAsync() @@ -256,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 when we commit the HttpServletResponse there is no SecurityContext. diff --git a/docs/modules/ROOT/pages/servlet/integrations/websocket.adoc b/docs/modules/ROOT/pages/servlet/integrations/websocket.adoc index aed0ec96b9..635ea29def 100644 --- a/docs/modules/ROOT/pages/servlet/integrations/websocket.adoc +++ b/docs/modules/ROOT/pages/servlet/integrations/websocket.adoc @@ -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>` bean or in 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,14 +65,15 @@ open class WebSocketSecurityConfig { // <1> <2> } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== This will ensure that: @@ -82,8 +86,10 @@ This will ensure that: 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 @@ -97,7 +103,8 @@ public class WebSocketSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -110,19 +117,22 @@ open class WebSocketSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== 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 @@ -143,7 +153,8 @@ public class WebSocketSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -162,7 +173,8 @@ open class WebSocketSecurityConfig { } ---- -.Xml +Xml:: ++ [source,kotlin,role="secondary"] ---- @@ -185,7 +197,7 @@ open class WebSocketSecurityConfig { ---- -==== +====== This will ensure that: @@ -295,8 +307,10 @@ var token = "${_csrf.token}"; If you are using static HTML, you can expose the `CsrfToken` on a REST endpoint. For example, the following would expose the `CsrfToken` on the URL /csrf -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @RestController @@ -309,7 +323,8 @@ public class CsrfController { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @RestController @@ -320,7 +335,7 @@ class CsrfController { } } ---- -==== +====== The JavaScript can make a REST call to the endpoint and use the response to populate the headerName and the token. @@ -344,8 +359,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 @@ -367,7 +384,8 @@ public class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -388,20 +406,23 @@ open class WebSocketSecurityConfig : WebSocketMessageBrokerConfigurer { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== On the other hand, if you are using the <> 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 @@ -416,7 +437,8 @@ public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBro } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -429,7 +451,7 @@ open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfi } } ---- -==== +====== [[websocket-expression-handler]] === Custom Expression Handler @@ -495,8 +517,10 @@ For example, the following will instruct Spring Security to use "X-Frame-Options Similarly, you can customize frame options to use the same origin within Java Configuration using the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -516,7 +540,8 @@ public class WebSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -535,7 +560,7 @@ open class WebSecurityConfig { } } ---- -==== +====== [[websocket-sockjs-csrf]] === SockJS & Relaxing CSRF @@ -554,8 +579,10 @@ We can easily achieve this by providing a CSRF RequestMatcher. Our Java Configuration makes this extremely easy. For example, if our stomp endpoint is "/chat" we can disable CSRF protection for only URLs that start with "/chat/" using the following configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Configuration @@ -581,7 +608,8 @@ public class WebSecurityConfig { ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -604,7 +632,7 @@ open class WebSecurityConfig { // ... ---- -==== +====== If we are using XML based configuration, we can use the xref:servlet/appendix/namespace/http.adoc#nsa-csrf-request-matcher-ref[csrf@request-matcher-ref]. For example: @@ -640,8 +668,10 @@ For example: 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 @@ -655,7 +685,8 @@ public class WebSocketSecurityConfig } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Configuration @@ -665,7 +696,7 @@ open class WebSocketSecurityConfig : AbstractSecurityWebSocketMessageBrokerConfi } } ---- -==== +====== This will ensure that: diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc index 1623466180..66127949c8 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/authorization-grants.adoc @@ -111,8 +111,10 @@ OPTIONAL. Space delimited, case sensitive list of ASCII string values that speci The following example shows how to configure the `DefaultOAuth2AuthorizationRequestResolver` with a `Consumer` that customizes the Authorization Request for `oauth2Login()`, by including the request parameter `prompt=consent`. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -156,7 +158,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -197,7 +200,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. @@ -222,8 +225,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 authorizationRequestCustomizer() { @@ -233,7 +238,8 @@ private Consumer authorizationRequestCustomi } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- private fun authorizationRequestCustomizer(): Consumer { @@ -246,7 +252,7 @@ private fun authorizationRequestCustomizer(): Consumer @@ -320,7 +330,7 @@ class OAuth2ClientSecurityConfig { ---- -==== +====== === Requesting an Access Token @@ -351,8 +361,10 @@ IMPORTANT: The custom `Converter` must return a valid `RequestEntity` representa On the other end, if you need to customize the post-handling of the Token Response, you will 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( @@ -362,7 +374,8 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList( restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val restTemplate = RestTemplate(listOf( @@ -371,7 +384,7 @@ val restTemplate = RestTemplate(listOf( restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler() ---- -==== +====== TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request. @@ -384,8 +397,10 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error Whether you customize `DefaultAuthorizationCodeTokenResponseClient` or provide your own implementation of `OAuth2AccessTokenResponseClient`, you'll need to configure it as shown in the following example: .Access Token Response Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -405,7 +420,8 @@ public class OAuth2ClientSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -425,7 +441,8 @@ class OAuth2ClientSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -434,7 +451,7 @@ class OAuth2ClientSecurityConfig { ---- -==== +====== [[oauth2Client-refresh-token-grant]] @@ -473,8 +490,10 @@ IMPORTANT: The custom `Converter` must return a valid `RequestEntity` representa On the other end, if you need to customize the post-handling of the Token Response, you will 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( @@ -484,7 +503,8 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList( restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val restTemplate = RestTemplate(listOf( @@ -493,7 +513,7 @@ val restTemplate = RestTemplate(listOf( restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler() ---- -==== +====== TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request. @@ -505,8 +525,10 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error Whether you customize `DefaultRefreshTokenTokenResponseClient` 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 @@ -523,7 +545,8 @@ OAuth2AuthorizedClientProvider authorizedClientProvider = authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- // Customize @@ -538,7 +561,7 @@ val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) ---- -==== +====== [NOTE] `OAuth2AuthorizedClientProviderBuilder.builder().refreshToken()` configures a `RefreshTokenOAuth2AuthorizedClientProvider`, @@ -584,8 +607,10 @@ IMPORTANT: The custom `Converter` must return a valid `RequestEntity` representa On the other end, if you need to customize the post-handling of the Token Response, you will 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( @@ -595,7 +620,8 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList( restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val restTemplate = RestTemplate(listOf( @@ -604,7 +630,7 @@ val restTemplate = RestTemplate(listOf( restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler() ---- -==== +====== TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request. @@ -616,8 +642,10 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error Whether you customize `DefaultClientCredentialsTokenResponseClient` 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 @@ -633,7 +661,8 @@ OAuth2AuthorizedClientProvider authorizedClientProvider = authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- // Customize @@ -647,7 +676,7 @@ val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) ---- -==== +====== [NOTE] `OAuth2AuthorizedClientProviderBuilder.builder().clientCredentials()` configures a `ClientCredentialsOAuth2AuthorizedClientProvider`, @@ -676,8 +705,10 @@ spring: ...and the `OAuth2AuthorizedClientManager` `@Bean`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -699,7 +730,8 @@ public OAuth2AuthorizedClientManager authorizedClientManager( } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -715,12 +747,14 @@ fun authorizedClientManager( return authorizedClientManager } ---- -==== +====== You may obtain the `OAuth2AccessToken` as follows: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Controller @@ -752,7 +786,8 @@ public class OAuth2ClientController { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class OAuth2ClientController { @@ -780,7 +815,7 @@ class OAuth2ClientController { } } ---- -==== +====== [NOTE] `HttpServletRequest` and `HttpServletResponse` are both OPTIONAL attributes. @@ -823,8 +858,10 @@ IMPORTANT: The custom `Converter` must return a valid `RequestEntity` representa On the other end, if you need to customize the post-handling of the Token Response, you will 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( @@ -834,7 +871,8 @@ RestTemplate restTemplate = new RestTemplate(Arrays.asList( restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler()); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val restTemplate = RestTemplate(listOf( @@ -843,7 +881,7 @@ val restTemplate = RestTemplate(listOf( restTemplate.errorHandler = OAuth2ErrorResponseErrorHandler() ---- -==== +====== TIP: Spring MVC `FormHttpMessageConverter` is required as it's used when sending the OAuth 2.0 Access Token Request. @@ -855,8 +893,10 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error Whether you customize `DefaultPasswordTokenResponseClient` 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 @@ -873,7 +913,8 @@ OAuth2AuthorizedClientProvider authorizedClientProvider = authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val passwordTokenResponseClient: OAuth2AccessTokenResponseClient = ... @@ -887,7 +928,7 @@ val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder() authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider) ---- -==== +====== [NOTE] `OAuth2AuthorizedClientProviderBuilder.builder().password()` configures a `PasswordOAuth2AuthorizedClientProvider`, @@ -916,8 +957,10 @@ spring: ...and the `OAuth2AuthorizedClientManager` `@Bean`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -960,7 +1003,9 @@ private Function> contextAttributesM }; } ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -998,12 +1043,14 @@ private fun contextAttributesMapper(): Function `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("/") @@ -163,7 +174,8 @@ public String index() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/") @@ -183,7 +195,7 @@ fun index(): String { return "index" } ---- -==== +====== <1> `clientRegistrationId()` is a `static` method in `ServletOAuth2AuthorizedClientExchangeFilterFunction`. @@ -195,8 +207,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 @@ -210,7 +224,8 @@ WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -222,7 +237,7 @@ fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClien .build() } ---- -==== +====== [WARNING] It is recommended to be cautious with this feature since all HTTP requests will receive the access token. @@ -231,8 +246,10 @@ Alternatively, if `setDefaultClientRegistrationId("okta")` is configured with a The following code shows the specific configuration: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -246,7 +263,8 @@ WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -258,7 +276,7 @@ fun webClient(authorizedClientManager: OAuth2AuthorizedClientManager?): WebClien .build() } ---- -==== +====== [WARNING] It is recommended to be cautious with this feature since all HTTP requests will receive the access token. diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/client-authentication.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/client-authentication.adoc index 566055a1dd..c9fe6e27d2 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/client-authentication.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/client-authentication.adoc @@ -36,8 +36,10 @@ spring: The following example shows how to configure `DefaultAuthorizationCodeTokenResponseClient`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Function jwkResolver = (clientRegistration) -> { @@ -63,7 +65,8 @@ DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = tokenResponseClient.setRequestEntityConverter(requestEntityConverter); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val jwkResolver: Function = @@ -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 jwkResolver = (clientRegistration) -> { @@ -138,7 +143,8 @@ DefaultClientCredentialsTokenResponseClient tokenResponseClient = tokenResponseClient.setRequestEntityConverter(requestEntityConverter); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val jwkResolver = Function { 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>` to `setJwtClientAssertionCustomizer()`. The following example shows how to customize claims of the JWT: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Function 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") } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc b/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc index e02d387d1f..055b98bbbb 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/client/core.adoc @@ -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 `ClientRegistrationRepository` as a `@ The following listing shows an example: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Controller @@ -128,7 +133,8 @@ public class OAuth2ClientController { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Controller @@ -148,7 +154,7 @@ class OAuth2ClientController { } } ---- -==== +====== [[oauth2Client-authorized-client]] == OAuth2AuthorizedClient @@ -169,8 +175,10 @@ From a developer perspective, the `OAuth2AuthorizedClientRepository` or `OAuth2A The following listing shows an example: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Controller @@ -193,7 +201,8 @@ public class OAuth2ClientController { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Controller @@ -214,7 +223,7 @@ class OAuth2ClientController { } } ---- -==== +====== [NOTE] Spring Boot 2.x auto-configuration registers an `OAuth2AuthorizedClientRepository` and/or `OAuth2AuthorizedClientService` `@Bean` in the `ApplicationContext`. @@ -248,8 +257,10 @@ The `OAuth2AuthorizedClientProviderBuilder` may be used to configure and build t 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 @@ -274,7 +285,8 @@ public OAuth2AuthorizedClientManager authorizedClientManager( } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -293,7 +305,7 @@ fun authorizedClientManager( return authorizedClientManager } ---- -==== +====== When an authorization attempt succeeds, the `DefaultOAuth2AuthorizedClientManager` will delegate to the `OAuth2AuthorizationSuccessHandler`, which (by default) will save the `OAuth2AuthorizedClient` via the `OAuth2AuthorizedClientRepository`. 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 `OAuth2AuthorizedClientRepository` via the `RemoveAuthorizedClientOAuth2AuthorizationFailureHandler`. @@ -304,8 +316,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 @@ -349,7 +363,8 @@ private Function> contextAttributesM } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -387,7 +402,7 @@ private fun contextAttributesMapper(): Function @@ -95,14 +97,15 @@ The following code shows the complete configuration options available in the xre ---- -==== 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 @@ -127,7 +130,8 @@ public OAuth2AuthorizedClientManager authorizedClientManager( } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -146,4 +150,4 @@ fun authorizedClientManager( return authorizedClientManager } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc b/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc index 221a9c15ea..64316de3b4 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc @@ -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"] ---- @EnableWebSecurity @@ -38,7 +40,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -66,7 +69,7 @@ class OAuth2LoginSecurityConfig { } } ---- -==== +====== The main goal of the `oauth2Login()` DSL was to closely align with the naming, as defined in the specifications. @@ -90,8 +93,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"] ---- @EnableWebSecurity @@ -127,7 +132,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -163,14 +169,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] ---- @@ -190,7 +195,6 @@ The following code shows the complete configuration options available in the xre jwt-decoder-factory-ref="jwtDecoderFactory"/> ---- -==== The following sections go into more detail on each of the configuration options available: @@ -227,8 +231,10 @@ To override the default login page, configure `oauth2Login().loginPage()` and (o The following listing shows an example: .OAuth2 Login Page Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -250,7 +256,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -271,7 +278,8 @@ class OAuth2LoginSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -280,7 +288,7 @@ class OAuth2LoginSecurityConfig { /> ---- -==== +====== [IMPORTANT] You need to provide a `@Controller` with a `@RequestMapping("/login/oauth2")` that is capable of rendering the custom login page. @@ -313,8 +321,10 @@ The default Authorization Response `baseUri` (redirection endpoint) is `*/login/ If you would like to customize the Authorization Response `baseUri`, configure it as shown in the following example: .Redirection Endpoint Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -334,7 +344,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -354,7 +365,8 @@ class OAuth2LoginSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -363,7 +375,7 @@ class OAuth2LoginSecurityConfig { /> ---- -==== +====== [IMPORTANT] ==== @@ -371,7 +383,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",attrs="-attributes"] ---- return CommonOAuth2Provider.GOOGLE.getBuilder("google") @@ -381,7 +396,8 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google") .build(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- return CommonOAuth2Provider.GOOGLE.getBuilder("google") @@ -390,6 +406,7 @@ return CommonOAuth2Provider.GOOGLE.getBuilder("google") .redirectUri("{baseUrl}/login/oauth2/callback/{registrationId}") .build() ---- +====== ==== @@ -423,8 +440,10 @@ There are a couple of options to choose from when mapping user authorities: Provide an implementation of `GrantedAuthoritiesMapper` and configure it as shown in the following example: .Granted Authorities Mapper Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -473,7 +492,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -512,7 +532,8 @@ class OAuth2LoginSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -521,13 +542,15 @@ class OAuth2LoginSecurityConfig { /> ---- -==== +====== Alternatively, you may register a `GrantedAuthoritiesMapper` `@Bean` to have it automatically applied to the configuration, as shown in the following example: .Granted Authorities Mapper Bean Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -547,7 +570,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -567,7 +591,7 @@ class OAuth2LoginSecurityConfig { } } ---- -==== +====== [[oauth2login-advanced-map-authorities-oauth2userservice]] ==== Delegation-based strategy with OAuth2UserService @@ -579,8 +603,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"] ---- @EnableWebSecurity @@ -621,7 +647,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -662,7 +689,8 @@ class OAuth2LoginSecurityConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -671,7 +699,7 @@ class OAuth2LoginSecurityConfig { /> ---- -==== +====== [[oauth2login-advanced-oauth2-user-service]] @@ -701,8 +729,10 @@ It uses an `OAuth2ErrorHttpMessageConverter` for converting the OAuth 2.0 Error Whether you customize `DefaultOAuth2UserService` or provide your own implementation of `OAuth2UserService`, you'll need to configure it as shown in the following example: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -726,7 +756,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -750,7 +781,7 @@ class OAuth2LoginSecurityConfig { } } ---- -==== +====== [[oauth2login-advanced-oidc-user-service]] @@ -764,8 +795,10 @@ If you need to customize the pre-processing of the UserInfo Request and/or the p Whether you customize `OidcUserService` or provide your own implementation of `OAuth2UserService` 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"] ---- @EnableWebSecurity @@ -789,7 +822,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -813,7 +847,7 @@ class OAuth2LoginSecurityConfig { } } ---- -==== +====== [[oauth2login-advanced-idtoken-verify]] @@ -830,8 +864,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 @@ -842,7 +878,8 @@ public JwtDecoderFactory idTokenDecoderFactory() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -852,7 +889,7 @@ fun idTokenDecoderFactory(): JwtDecoderFactory { 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. @@ -888,8 +925,10 @@ spring: ...and the `OidcClientInitiatedLogoutSuccessHandler`, which implements RP-Initiated Logout, may be configured as follows: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -924,7 +963,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -956,7 +996,7 @@ class OAuth2LoginSecurityConfig { } } ---- -==== +====== NOTE: `OidcClientInitiatedLogoutSuccessHandler` supports the `+{baseUrl}+` placeholder. If used, the application's base URL, like `https://app.example.org`, will replace it at request time. diff --git a/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc b/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc index 8d19b5d73d..2e438d6eae 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/login/core.adoc @@ -60,10 +60,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. @@ -243,8 +241,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 @@ -274,7 +274,8 @@ public class OAuth2LoginConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- @Configuration @@ -302,7 +303,7 @@ class OAuth2LoginConfig { } } ---- -==== +====== [[oauth2login-provide-securityfilterchain-bean]] @@ -311,8 +312,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"] ---- @EnableWebSecurity @@ -330,7 +333,8 @@ public class OAuth2LoginSecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -347,7 +351,7 @@ class OAuth2LoginSecurityConfig { } } ---- -==== +====== [[oauth2login-completely-override-autoconfiguration]] @@ -356,8 +360,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 @@ -397,7 +403,8 @@ public class OAuth2LoginConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary",attrs="-attributes"] ---- @Configuration @@ -437,7 +444,7 @@ class OAuth2LoginConfig { } } ---- -==== +====== [[oauth2login-javaconfig-wo-boot]] @@ -446,8 +453,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"] ---- @EnableWebSecurity @@ -489,7 +498,8 @@ public class OAuth2LoginConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -533,7 +543,8 @@ open class OAuth2LoginConfig { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -557,4 +568,4 @@ open class OAuth2LoginConfig { ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/bearer-tokens.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/bearer-tokens.adoc index 4365c9e6e2..c4b43a56c5 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/bearer-tokens.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/bearer-tokens.adoc @@ -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"] ---- @@ -47,7 +51,7 @@ fun bearerTokenResolver(): BearerTokenResolver { ---- -==== +====== 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"] ---- @@ -92,15 +100,17 @@ http { ---- -==== +====== == 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() .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() .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 { } } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc index 3d4baef203..fc158ab418 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc @@ -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"] ---- @EnableWebSecurity @@ -225,7 +230,8 @@ public class MyCustomSecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -247,7 +253,7 @@ class MyCustomSecurityConfiguration { } } ---- -==== +====== The above requires the scope of `message:read` for any URL that starts with `/messages/`. @@ -257,8 +263,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 <>: .JWT Decoder -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -267,7 +275,8 @@ public JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -275,7 +284,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. @@ -289,8 +298,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"] ---- @@ -300,13 +311,15 @@ The filter chain is specified like so: ---- -==== +====== And the `JwtDecoder` like so: .JWT Decoder -==== -.Xml +[tabs] +====== +Xml:: ++ [source,xml,role="primary"] ---- ---- -==== +====== [[oauth2resourceserver-jwt-jwkseturi-dsl]] === Using `jwkSetUri()` @@ -323,8 +336,10 @@ And the `JwtDecoder` like so: An authorization server's JWK Set Uri can be configured <> or it can be supplied in the DSL: .JWK Set Uri Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -345,7 +360,8 @@ public class DirectlyConfiguredJwkSetUri { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -367,7 +383,8 @@ class DirectlyConfiguredJwkSetUri { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -377,7 +394,7 @@ class DirectlyConfiguredJwkSetUri { ---- -==== +====== Using `jwkSetUri()` takes precedence over any configuration property. @@ -387,8 +404,10 @@ Using `jwkSetUri()` takes precedence over any configuration property. More powerful than `jwkSetUri()` is `decoder()`, which will completely replace any Boot auto configuration of <>: .JWT Decoder Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -409,7 +428,8 @@ public class DirectlyConfiguredJwtDecoder { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -431,7 +451,8 @@ class DirectlyConfiguredJwtDecoder { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -441,7 +462,7 @@ class DirectlyConfiguredJwtDecoder { ---- -==== +====== This is handy when deeper configuration, like <>, <>, or <>, is necessary. @@ -450,8 +471,10 @@ This is handy when deeper configuration, like <> `@Bean` has the same effect as `decoder()`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -460,7 +483,8 @@ public JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -468,7 +492,7 @@ fun jwtDecoder(): JwtDecoder { return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build() } ---- -==== +====== [[oauth2resourceserver-jwt-decoder-algorithm]] == Configuring Trusted Algorithms @@ -498,8 +522,10 @@ spring: For greater power, though, we can use a builder that ships with `NimbusJwtDecoder`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -509,7 +535,8 @@ JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -518,12 +545,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 @@ -533,7 +562,8 @@ JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -542,12 +572,14 @@ fun jwtDecoder(): JwtDecoder { .jwsAlgorithm(RS512).jwsAlgorithm(ES512).build() } ---- -==== +====== Or, you can call `jwsAlgorithms`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -560,7 +592,8 @@ JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -572,7 +605,7 @@ fun jwtDecoder(): JwtDecoder { }.build() } ---- -==== +====== [[oauth2resourceserver-jwt-decoder-jwk-response]] === From JWK Set response @@ -582,8 +615,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 @@ -600,7 +635,8 @@ public JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -612,7 +648,7 @@ fun jwtDecoder(): JwtDecoder { return NimbusJwtDecoder(jwtProcessor) } ---- -==== +====== [[oauth2resourceserver-jwt-decoder-public-key]] == Trusting a Single Asymmetric Key @@ -638,8 +674,10 @@ spring: Or, to allow for a more sophisticated lookup, you can post-process the `RsaKeyConversionServicePostProcessor`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -650,7 +688,8 @@ BeanFactoryPostProcessor conversionServiceCustomizer() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -661,7 +700,7 @@ fun conversionServiceCustomizer(): BeanFactoryPostProcessor { } } ---- -==== +====== Specify your key's location: @@ -672,29 +711,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 @@ -703,7 +747,8 @@ public JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -711,7 +756,7 @@ fun jwtDecoder(): JwtDecoder { return NimbusJwtDecoder.withPublicKey(this.key).build() } ---- -==== +====== [[oauth2resourceserver-jwt-decoder-secret-key]] == Trusting a Single Symmetric Key @@ -719,8 +764,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 @@ -729,7 +776,8 @@ public JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -737,7 +785,7 @@ fun jwtDecoder(): JwtDecoder { return NimbusJwtDecoder.withSecretKey(key).build() } ---- -==== +====== [[oauth2resourceserver-jwt-authorization]] == Configuring Authorization @@ -751,8 +799,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"] ---- @EnableWebSecurity @@ -771,7 +821,8 @@ public class DirectlyConfiguredJwkSetUri { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -793,7 +844,8 @@ class DirectlyConfiguredJwkSetUri { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -804,25 +856,28 @@ class DirectlyConfiguredJwkSetUri { ---- -==== +====== Or similarly with method security: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @PreAuthorize("hasAuthority('SCOPE_messages')") public List getMessages(...) {} ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @PreAuthorize("hasAuthority('SCOPE_messages')") fun getMessages(): List { } ---- -==== +====== [[oauth2resourceserver-jwt-authorization-extraction]] === Extracting Authorities Manually @@ -840,8 +895,10 @@ Let's say that that your authorization server communicates authorities in a cust In that case, you can configure the claim that <> should inspect, like so: .Authorities Claim Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -855,7 +912,8 @@ public JwtAuthenticationConverter jwtAuthenticationConverter() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -869,7 +927,8 @@ fun jwtAuthenticationConverter(): JwtAuthenticationConverter { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -891,14 +950,16 @@ fun jwtAuthenticationConverter(): JwtAuthenticationConverter { ---- -==== +====== 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 @@ -912,7 +973,8 @@ public JwtAuthenticationConverter jwtAuthenticationConverter() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -926,7 +988,8 @@ fun jwtAuthenticationConverter(): JwtAuthenticationConverter { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -948,14 +1011,16 @@ fun jwtAuthenticationConverter(): JwtAuthenticationConverter { ---- -==== +====== 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`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- static class CustomAuthenticationConverter implements Converter { @@ -984,7 +1049,8 @@ public class CustomAuthenticationConverterConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- internal class CustomAuthenticationConverter : Converter { @@ -1013,7 +1079,7 @@ class CustomAuthenticationConverterConfig { } } ---- -==== +====== [[oauth2resourceserver-jwt-validation]] == Configuring Validation @@ -1032,8 +1098,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 @@ -1051,7 +1119,8 @@ JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -1067,7 +1136,7 @@ fun jwtDecoder(): JwtDecoder { return jwtDecoder } ---- -==== +====== [NOTE] By default, Resource Server configures a clock skew of 60 seconds. @@ -1077,8 +1146,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 audienceValidator() { @@ -1086,19 +1157,22 @@ OAuth2TokenValidator audienceValidator() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- fun audienceValidator(): OAuth2TokenValidator { return JwtClaimValidator>(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 { @@ -1121,7 +1195,8 @@ OAuth2TokenValidator audienceValidator() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- internal class AudienceValidator : OAuth2TokenValidator { @@ -1142,12 +1217,14 @@ fun audienceValidator(): OAuth2TokenValidator { return AudienceValidator() } ---- -==== +====== Then, to add into a resource server, it's a matter of specifying the <> instance: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -1165,7 +1242,8 @@ JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -1181,7 +1259,7 @@ fun jwtDecoder(): JwtDecoder { return jwtDecoder } ---- -==== +====== [TIP] As stated earlier, you can instead <<_supplying_audiences, configure `aud` validation in Boot>>. @@ -1218,8 +1296,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 @@ -1234,7 +1314,8 @@ JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -1248,7 +1329,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]] @@ -1256,46 +1337,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 { "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 { 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>`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public class UsernameSubClaimAdapter implements Converter, Map> { @@ -1313,7 +1402,8 @@ public class UsernameSubClaimAdapter implements Converter, M } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class UsernameSubClaimAdapter : Converter, Map> { @@ -1326,12 +1416,14 @@ class UsernameSubClaimAdapter : Converter, Map> } } ---- -==== +====== And then, the instance can be supplied like normal: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -1342,7 +1434,8 @@ JwtDecoder jwtDecoder() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -1352,7 +1445,7 @@ fun jwtDecoder(): JwtDecoder { return jwtDecoder } ---- -==== +====== [[oauth2resourceserver-jwt-timeouts]] == Configuring Timeouts @@ -1364,8 +1457,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 @@ -1380,7 +1475,8 @@ public JwtDecoder jwtDecoder(RestTemplateBuilder builder) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -1392,15 +1488,17 @@ fun jwtDecoder(builder: RestTemplateBuilder): JwtDecoder { return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).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 @@ -1411,7 +1509,8 @@ public JwtDecoder jwtDecoder(CacheManager cacheManager) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -1421,7 +1520,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. diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/multitenancy.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/multitenancy.adoc index e30a43db9c..4f68e1e58f 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/multitenancy.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/multitenancy.adoc @@ -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 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"] ---- ---- -==== +====== [[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"] ---- @@ -149,7 +160,7 @@ http { ---- -==== +====== 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 authenticationManagers, String issuer) { @@ -184,7 +197,8 @@ http ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- private fun addManager(authenticationManagers: MutableMap, 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): 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 { } ---- -.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 jwtVa } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -435,7 +461,7 @@ fun jwtDecoder(jwtProcessor: JWTProcessor?, jwtValidator: OAuth return decoder } ---- -==== +====== We've finished talking about resolving the tenant. diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc index 6edffde5c6..c1a8eef973 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc @@ -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("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"] ---- @EnableWebSecurity @@ -244,7 +258,8 @@ public class MyCustomSecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -266,7 +281,7 @@ class MyCustomSecurityConfiguration { } } ---- -==== +====== The above requires the scope of `message:read` for any URL that starts with `/messages/`. @@ -275,8 +290,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`, <>: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -285,7 +302,8 @@ public OpaqueTokenIntrospector introspector() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -293,7 +311,7 @@ fun introspector(): OpaqueTokenIntrospector { return NimbusOpaqueTokenIntrospector(introspectionUri, clientId, clientSecret) } ---- -==== +====== If the application doesn't expose an <> bean, then Spring Boot will expose the above default one. @@ -306,8 +324,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"] ---- @@ -318,13 +338,15 @@ The filter chain is specified like so: ---- -==== +====== And the <> like so: .Opaque Token Introspector -==== -.Xml +[tabs] +====== +Xml:: ++ [source,xml,role="primary"] ---- ---- -==== +====== And the `OpaqueTokenAuthenticationConverter` like so: .Opaque Token Authentication Converter -==== -.Xml +[tabs] +====== +Xml:: ++ [source,xml,role="primary"] ---- ---- -==== +====== [[oauth2resourceserver-opaque-introspectionuri-dsl]] === Using `introspectionUri()` @@ -354,8 +378,10 @@ And the `OpaqueTokenAuthenticationConverter` like so: An authorization server's Introspection Uri can be configured <> or it can be supplied in the DSL: .Introspection URI Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -377,7 +403,8 @@ public class DirectlyConfiguredIntrospectionUri { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -400,7 +427,8 @@ class DirectlyConfiguredIntrospectionUri { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== Using `introspectionUri()` takes precedence over any configuration property. @@ -420,8 +448,10 @@ Using `introspectionUri()` takes precedence over any configuration property. More powerful than `introspectionUri()` is `introspector()`, which will completely replace any Boot auto configuration of <>: .Introspector Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -442,7 +472,8 @@ public class DirectlyConfiguredIntrospector { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -464,7 +495,8 @@ class DirectlyConfiguredIntrospector { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -474,7 +506,7 @@ class DirectlyConfiguredIntrospector { ---- -==== +====== This is handy when deeper configuration, like <>, <>, or <>, is necessary. @@ -503,8 +535,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"] ---- @EnableWebSecurity @@ -523,7 +557,8 @@ public class MappedAuthorities { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -545,7 +580,8 @@ class MappedAuthorities { } ---- -.Xml +Xml:: ++ [source,xml,role="secondary"] ---- @@ -556,25 +592,28 @@ class MappedAuthorities { ---- -==== +====== Or similarly with method security: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @PreAuthorize("hasAuthority('SCOPE_messages')") public List getMessages(...) {} ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @PreAuthorize("hasAuthority('SCOPE_messages')") fun getMessages(): List {} ---- -==== +====== [[oauth2resourceserver-opaque-authorization-extraction]] === Extracting Authorities Manually @@ -595,8 +634,10 @@ Then Resource Server would generate an `Authentication` with two authorities, on This can, of course, be customized using a custom <> 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 { @@ -618,7 +659,8 @@ public class CustomAuthoritiesOpaqueTokenIntrospector implements OpaqueTokenIntr } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class CustomAuthoritiesOpaqueTokenIntrospector : OpaqueTokenIntrospector { @@ -636,12 +678,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 @@ -650,7 +694,8 @@ public OpaqueTokenIntrospector introspector() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -658,7 +703,7 @@ fun introspector(): OpaqueTokenIntrospector { return CustomAuthoritiesOpaqueTokenIntrospector() } ---- -==== +====== [[oauth2resourceserver-opaque-timeouts]] == Configuring Timeouts @@ -670,8 +715,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 @@ -686,7 +733,8 @@ public OpaqueTokenIntrospector introspector(RestTemplateBuilder builder, OAuth2R } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -699,7 +747,7 @@ fun introspector(builder: RestTemplateBuilder, properties: OAuth2ResourceServerP return NimbusOpaqueTokenIntrospector(introspectionUri, rest) } ---- -==== +====== [[oauth2resourceserver-opaque-jwt-introspector]] == Using Introspection with JWTs @@ -731,8 +779,10 @@ Now what? In this case, you can create a custom <> 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 { @@ -759,7 +809,8 @@ public class JwtOpaqueTokenIntrospector implements OpaqueTokenIntrospector { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class JwtOpaqueTokenIntrospector : OpaqueTokenIntrospector { @@ -782,12 +833,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 @@ -796,7 +849,8 @@ public OpaqueTokenIntrospector introspector() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -804,7 +858,7 @@ fun introspector(): OpaqueTokenIntrospector { return JwtOpaqueTokenIntrospector() } ---- -==== +====== [[oauth2resourceserver-opaque-userinfo]] == Calling a `/userinfo` Endpoint @@ -820,8 +874,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 { @@ -846,7 +902,8 @@ public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class UserInfoOpaqueTokenIntrospector : OpaqueTokenIntrospector { @@ -867,13 +924,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 { @@ -889,7 +948,8 @@ public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class UserInfoOpaqueTokenIntrospector : OpaqueTokenIntrospector { @@ -902,12 +962,14 @@ class UserInfoOpaqueTokenIntrospector : OpaqueTokenIntrospector { } } ---- -==== +====== Either way, having created your <>, you should publish it as a `@Bean` to override the defaults: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -916,7 +978,8 @@ OpaqueTokenIntrospector introspector() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -924,4 +987,4 @@ fun introspector(): OpaqueTokenIntrospector { return UserInfoOpaqueTokenIntrospector(...) } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc index c8a09a6737..76f4a23614 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/authentication-requests.adoc @@ -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 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 `` 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 `` 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 `` 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 } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc index 8cfced2152..e1cfdfee18 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/authentication.adoc @@ -20,8 +20,10 @@ To configure these, you'll use the `saml2Login#authenticationManager` method in To apply a `RelyingPartyRegistrationResolver` when processing `` payloads, you should first publish a `Saml2AuthenticationTokenConverter` bean like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -30,7 +32,8 @@ Saml2AuthenticationTokenConverter authenticationConverter(InMemoryRelyingPartyRe } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -38,13 +41,15 @@ fun authenticationConverter(val registrations: InMemoryRelyingPartyRegistrationR return Saml2AuthenticationTokenConverter(MyRelyingPartyRegistrationResolver(registrations)); } ---- -==== +====== Recall that the Assertion Consumer Service URL is `+/saml2/login/sso/{registrationId}+` by default. If you are no longer wanting the `registrationId` in the URL, change it in the filter chain and in your relying party metadata: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -58,7 +63,8 @@ SecurityFilterChain securityFilters(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -74,23 +80,26 @@ fun securityFilters(val http: HttpSecurity): SecurityFilterChain { return http.build() } ---- -==== +====== and: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml2/login/sso") ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml2/login/sso") ---- -==== +====== [[servlet-saml2login-opensamlauthenticationprovider-clockskew]] == Setting a Clock Skew @@ -98,8 +107,10 @@ relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml2/login/s 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"] ---- @EnableWebSecurity @@ -129,7 +140,8 @@ public class SecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -158,7 +170,7 @@ open class SecurityConfig { } } ---- -==== +====== [[servlet-saml2login-opensamlauthenticationprovider-userdetailsservice]] == Coordinating with a `UserDetailsService` @@ -166,8 +178,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"] ---- @EnableWebSecurity @@ -200,7 +214,8 @@ public class SecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -232,7 +247,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 @@ -276,8 +291,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 `` condition, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider(); @@ -300,7 +317,8 @@ provider.setAssertionValidator(assertionToken -> { }); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- var provider = OpenSaml4AuthenticationProvider() @@ -322,7 +340,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. @@ -340,8 +358,10 @@ The assertion decrypter is for decrypting encrypted elements of the ``, you can use it instead like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- MyDecryptionService decryptionService = ...; @@ -349,30 +369,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 ``, 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. @@ -385,8 +409,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"] ---- @EnableWebSecurity @@ -408,7 +434,8 @@ public class SecurityConfig { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -428,7 +455,7 @@ open class SecurityConfig { } } ---- -==== +====== [[servlet-saml2login-authenticatedprincipal]] == Using `Saml2AuthenticatedPrincipal` @@ -438,8 +465,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 @@ -453,7 +482,8 @@ public class MainController { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Controller @@ -466,7 +496,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. diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc index 3a331b4580..834e151864 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc @@ -188,8 +188,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, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- static { @@ -198,7 +200,8 @@ static { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- companion object { @@ -207,7 +210,7 @@ companion object { } } ---- -==== +====== This replaces OpenSAML's `InitializationService#initialize`. @@ -217,8 +220,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 { @@ -245,7 +250,8 @@ static { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- companion object { @@ -271,7 +277,7 @@ companion object { } } ---- -==== +====== The `requireInitialize` method may only be called once per application instance. @@ -284,8 +290,10 @@ The first is a `SecurityFilterChain` that configures the app as a relying party. 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 @@ -299,7 +307,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -313,15 +322,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. You can replace this by exposing the bean within the application: .Custom SAML 2.0 Login Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -339,7 +350,8 @@ public class MyCustomSecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -358,7 +370,7 @@ class MyCustomSecurityConfiguration { } } ---- -==== +====== The above requires the role of `USER` for any URL that starts with `/messages/`. @@ -370,8 +382,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 like so: .Relying Party Registration Repository -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Value("${metadata.location}") @@ -387,7 +401,8 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Value("\${metadata.location}") @@ -402,7 +417,7 @@ open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? { return InMemoryRelyingPartyRegistrationRepository(registration) } ---- -==== +====== [[servlet-saml2login-relyingpartyregistrationid]] [NOTE] @@ -411,8 +426,10 @@ The `registrationId` is an arbitrary value that you choose for differentiating b Or you can provide each detail manually, as you can see below: .Relying Party Registration Repository Manual Configuration -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Value("${verification.key}") @@ -435,7 +452,8 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exc } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Value("\${verification.key}") @@ -462,18 +480,20 @@ open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository { return InMemoryRelyingPartyRegistrationRepository(registration) } ---- -==== +====== [NOTE] Note that `X509Support` is an OpenSAML class, used here in the snippet for brevity -[[servlet-saml2login-relyingpartyregistrationrepository-dsl]] +[[servlet-saml2login-relyingpartyregistrationrepository-dsl]] Alternatively, you can directly wire up the repository using the DSL, which will also override the auto-configured `SecurityFilterChain`: .Custom Relying Party Registration DSL -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @EnableWebSecurity @@ -493,7 +513,8 @@ public class MyCustomSecurityConfiguration { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @EnableWebSecurity @@ -513,7 +534,7 @@ class MyCustomSecurityConfiguration { } } ---- -==== +====== [NOTE] A relying party can be multi-tenant by registering more than one relying party in the `RelyingPartyRegistrationRepository`. @@ -529,8 +550,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 @@ -538,7 +561,9 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations .registrationId("my-id") .build(); ---- -.Kotlin + +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val relyingPartyRegistration = RelyingPartyRegistrations @@ -546,7 +571,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: @@ -564,8 +589,10 @@ try (InputStream source = new ByteArrayInputStream(xml.getBytes())) { Though a more sophisticated setup is also possible, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("my-id") @@ -580,7 +607,8 @@ RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.wit .build(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val relyingPartyRegistration = @@ -597,7 +625,7 @@ val relyingPartyRegistration = } .build() ---- -==== +====== [TIP] The top-level metadata methods are details about the relying party. @@ -666,8 +694,10 @@ At a minimum, it's necessary to have a certificate from the asserting party so t To construct a `Saml2X509Credential` that you'll use to verify assertions from the asserting party, you can load the file and use the `CertificateFactory` like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Resource resource = new ClassPathResource("ap.crt"); @@ -678,7 +708,8 @@ try (InputStream is = resource.getInputStream()) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val resource = ClassPathResource("ap.crt") @@ -688,7 +719,7 @@ resource.inputStream.use { ) } ---- -==== +====== Let's say that the asserting party is going to also encrypt the assertion. In that case, the relying party will need a private key to be able to decrypt the encrypted value. @@ -696,8 +727,10 @@ In that case, the relying party will need a private key to be able to decrypt th In that case, you'll need an `RSAPrivateKey` as well as its corresponding `X509Certificate`. You can load the first using Spring Security's `RsaKeyConverters` utility class and the second as you did before: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- X509Certificate certificate = relyingPartyDecryptionCertificate(); @@ -708,7 +741,8 @@ try (InputStream is = resource.getInputStream()) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val certificate: X509Certificate = relyingPartyDecryptionCertificate() @@ -718,7 +752,7 @@ resource.inputStream.use { return Saml2X509Credential.decryption(rsa, certificate) } ---- -==== +====== [TIP] When you specify the locations of these files as the appropriate Spring Boot properties, then Spring Boot will perform these conversions for you. @@ -760,8 +794,10 @@ Second, in a database, it's not necessary to replicate `RelyingPartyRegistration Third, in Java, you can create a custom configuration method, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- private RelyingPartyRegistration.Builder @@ -788,7 +824,8 @@ public RelyingPartyRegistrationRepository relyingPartyRegistrations() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- private fun addRelyingPartyDetails(builder: RelyingPartyRegistration.Builder): RelyingPartyRegistration.Builder { @@ -816,7 +853,7 @@ open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? { return InMemoryRelyingPartyRegistrationRepository(okta, azure) } ---- -==== +====== [[servlet-saml2login-rpr-relyingpartyregistrationresolver]] === Resolving the `RelyingPartyRegistration` from the Request @@ -839,8 +876,10 @@ Remember that if you have any placeholders in your `RelyingPartyRegistration`, y You can provide a resolver that, for example, always returns the same `RelyingPartyRegistration`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public class SingleRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver { @@ -858,7 +897,8 @@ public class SingleRelyingPartyRegistrationResolver implements RelyingPartyRegis } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class SingleRelyingPartyRegistrationResolver(delegate: RelyingPartyRegistrationResolver) : RelyingPartyRegistrationResolver { @@ -867,7 +907,7 @@ class SingleRelyingPartyRegistrationResolver(delegate: RelyingPartyRegistrationR } } ---- -==== +====== [TIP] You might next take a look at how to use this resolver to customize xref:servlet/saml2/metadata.adoc#servlet-saml2login-metadata[`` metadata production]. @@ -882,8 +922,10 @@ This carries the implication that the assertion consumer service endpoint will b You can instead resolve the `registrationId` via the `Issuer`. A custom implementation of `RelyingPartyRegistrationResolver` that does this may look like: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public class SamlResponseIssuerRelyingPartyRegistrationResolver implements RelyingPartyRegistrationResolver { @@ -911,7 +953,8 @@ public class SamlResponseIssuerRelyingPartyRegistrationResolver implements Relyi } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class SamlResponseIssuerRelyingPartyRegistrationResolver(val registrations: InMemoryRelyingPartyRegistrationRepository): @@ -935,7 +978,7 @@ class SamlResponseIssuerRelyingPartyRegistrationResolver(val registrations: InMe } } ---- -==== +====== [TIP] You might next take a look at how to use this resolver to customize xref:servlet/saml2/login/authentication.adoc#relyingpartyregistrationresolver-apply[`` authentication]. @@ -948,8 +991,10 @@ In this case, the identity provider's metadata endpoint returns multiple ` registrations = RelyingPartyRegistrations @@ -962,7 +1007,8 @@ Collection registrations = RelyingPartyRegistrations .collect(Collectors.toList())); ---- -.Kotlin +Kotlin:: ++ [source,java,role="secondary"] ---- var registrations: Collection = RelyingPartyRegistrations @@ -974,7 +1020,7 @@ var registrations: Collection = 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. diff --git a/docs/modules/ROOT/pages/servlet/saml2/logout.adoc b/docs/modules/ROOT/pages/servlet/saml2/logout.adoc index 9dba271b78..87eb5528b3 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/logout.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/logout.adoc @@ -100,8 +100,10 @@ This URL is customizable in the DSL. For example, if you are migrating your existing relying party over to Spring Security, your asserting party may already be pointing to `GET /SLOService.saml2`. To reduce changes in configuration for the asserting party, you can configure the filter in the DSL like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- http @@ -110,7 +112,7 @@ http .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2")) ); ---- -==== +====== You should also configure these endpoints in your `RelyingPartyRegistration`. diff --git a/docs/modules/ROOT/pages/servlet/saml2/metadata.adoc b/docs/modules/ROOT/pages/servlet/saml2/metadata.adoc index 81aa06c72d..20e74a626f 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/metadata.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/metadata.adoc @@ -11,8 +11,10 @@ You can parse an asserting party's metadata xref:servlet/saml2/login/overview.ad When using the OpenSAML vendor support, the resulting `AssertingPartyDetails` will be of type `OpenSamlAssertingPartyDetails`. This means you'll be able to do get the underlying OpenSAML XMLObject by doing the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- OpenSamlAssertingPartyDetails details = (OpenSamlAssertingPartyDetails) @@ -20,22 +22,25 @@ OpenSamlAssertingPartyDetails details = (OpenSamlAssertingPartyDetails) EntityDescriptor openSamlEntityDescriptor = details.getEntityDescriptor(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val details: OpenSamlAssertingPartyDetails = registration.getAssertingPartyDetails() as OpenSamlAssertingPartyDetails; val openSamlEntityDescriptor: EntityDescriptor = details.getEntityDescriptor(); ---- -==== +====== [[publishing-relying-party-metadata]] == Producing `` Metadata You can publish a metadata endpoint by adding the `Saml2MetadataFilter` to the filter chain, as you'll see below: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- DefaultRelyingPartyRegistrationResolver relyingPartyRegistrationResolver = @@ -50,7 +55,8 @@ http .addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val relyingPartyRegistrationResolver: Converter = @@ -66,7 +72,7 @@ http { addFilterBefore(filter) } ---- -==== +====== You can use this metadata endpoint to register your relying party with your asserting party. This is often as simple as finding the correct form field to supply the metadata endpoint. @@ -74,42 +80,50 @@ This is often as simple as finding the correct form field to supply the metadata By default, the metadata endpoint is `+/saml2/service-provider-metadata/{registrationId}+`. You can change this by calling the `setRequestMatcher` method on the filter: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata/{registrationId}", "GET")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata/{registrationId}", "GET")) ---- -==== +====== Or, if you have registered a custom relying party registration resolver in the constructor, then you can specify a path without a `registrationId` hint, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- filter.setRequestMatcher(new AntPathRequestMatcher("/saml2/metadata", "GET")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- filter.setRequestMatcher(AntPathRequestMatcher("/saml2/metadata", "GET")) ---- -==== +====== == Changing the Way a `RelyingPartyRegistration` Is Looked Up To apply a custom `RelyingPartyRegistrationResolver` to the metadata endpoint, you can provide it directly in the filter constructor like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- RelyingPartyRegistrationResolver myRegistrationResolver = ...; @@ -119,6 +133,7 @@ Saml2MetadataFilter metadata = new Saml2MetadataFilter(myRegistrationResolver, n http.addFilterBefore(metadata, BasicAuthenticationFilter.class); ---- +====== .Kotlin ---- @@ -129,19 +144,20 @@ val metadata = new Saml2MetadataFilter(myRegistrationResolver, OpenSamlMetadataR http.addFilterBefore(metadata, BasicAuthenticationFilter::class.java); ---- -==== In the event that you are applying a `RelyingPartyRegistrationResolver` to remove the `registrationId` from the URI, you must also change the URI in the filter like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- metadata.setRequestMatcher("/saml2/metadata") ---- +====== .Kotlin ---- metadata.setRequestMatcher("/saml2/metadata") ---- -==== diff --git a/docs/modules/ROOT/pages/servlet/test/method.adoc b/docs/modules/ROOT/pages/servlet/test/method.adoc index 648659661b..a0eb300332 100644 --- a/docs/modules/ROOT/pages/servlet/test/method.adoc +++ b/docs/modules/ROOT/pages/servlet/test/method.adoc @@ -4,8 +4,10 @@ This section demonstrates how to use Spring Security's Test support to test method based security. We first introduce a `MessageService` that requires the user to be authenticated in order to access it. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public class HelloMessageService implements MessageService { @@ -19,7 +21,8 @@ public class HelloMessageService implements MessageService { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class HelloMessageService : MessageService { @@ -30,7 +33,7 @@ class HelloMessageService : MessageService { } } ---- -==== +====== The result of `getMessage` is a String saying "Hello" to the current Spring Security `Authentication`. An example of the output is displayed below. @@ -45,8 +48,10 @@ Hello org.springframework.security.authentication.UsernamePasswordAuthentication Before we can use Spring Security Test support, we must perform some setup. An example can be seen below: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @ExtendWith(SpringExtension.class) // <1> @@ -54,14 +59,15 @@ Before we can use Spring Security Test support, we must perform some setup. An e public class WithMockUserTests { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @ExtendWith(SpringExtension.class) @ContextConfiguration class WithMockUserTests { ---- -==== +====== This is a basic example of how to setup Spring Security Test. The highlights are: @@ -77,8 +83,10 @@ If you only need Spring Security related support, you can replace `@ContextConfi Remember we added the `@PreAuthorize` annotation to our `HelloMessageService` and so it requires an authenticated user to invoke it. If we ran the following test, we would expect the following test will pass: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Test(expected = AuthenticationCredentialsNotFoundException.class) @@ -87,7 +95,8 @@ public void getMessageUnauthenticated() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test(expected = AuthenticationCredentialsNotFoundException::class) @@ -95,7 +104,7 @@ fun getMessageUnauthenticated() { messageService.getMessage() } ---- -==== +====== [[test-method-withmockuser]] == @WithMockUser @@ -104,8 +113,10 @@ The question is "How could we most easily run the test as a specific user?" The answer is to use `@WithMockUser`. The following test will be run as a user with the username "user", the password "password", and the roles "ROLE_USER". -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Test @@ -116,7 +127,8 @@ String message = messageService.getMessage(); } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test @@ -126,7 +138,7 @@ fun getMessageWithMockUser() { // ... } ---- -==== +====== Specifically the following is true: @@ -139,8 +151,10 @@ Our example is nice because we are able to leverage a lot of defaults. What if we wanted to run the test with a different username? The following test would run with the username "customUser". Again, the user does not need to actually exist. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Test @@ -151,7 +165,8 @@ public void getMessageWithMockUserCustomUsername() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test @@ -161,13 +176,15 @@ fun getMessageWithMockUserCustomUsername() { // ... } ---- -==== +====== We can also easily customize the roles. For example, this test will be invoked with the username "admin" and the roles "ROLE_USER" and "ROLE_ADMIN". -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Test @@ -178,7 +195,8 @@ public void getMessageWithMockUserCustomUser() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test @@ -188,13 +206,15 @@ fun getMessageWithMockUserCustomUser() { // ... } ---- -==== +====== If we do not want the value to automatically be prefixed with ROLE_ we can leverage the authorities attribute. For example, this test will be invoked with the username "admin" and the authorities "USER" and "ADMIN". -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Test @@ -205,7 +225,8 @@ public void getMessageWithMockUserCustomAuthorities() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test @@ -215,14 +236,16 @@ fun getMessageWithMockUserCustomUsername() { // ... } ---- -==== +====== Of course it can be a bit tedious placing the annotation on every test method. Instead, we can place the annotation at the class level and every test will use the specified user. For example, the following would run every test with a user with the username "admin", the password "password", and the roles "ROLE_USER" and "ROLE_ADMIN". -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @ExtendWith(SpringExtension.class) @@ -231,7 +254,8 @@ For example, the following would run every test with a user with the username "a public class WithMockUserTests { ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @ExtendWith(SpringExtension.class) @@ -239,13 +263,15 @@ public class WithMockUserTests { @WithMockUser(username="admin",roles=["USER","ADMIN"]) class WithMockUserTests { ---- -==== +====== If you are using JUnit 5's `@Nested` test support, you can also place the annotation on the enclosing class to apply to all nested classes. For example, the following would run every test with a user with the username "admin", the password "password", and the roles "ROLE_USER" and "ROLE_ADMIN" for both test methods. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @ExtendWith(SpringExtension.class) @@ -265,7 +291,8 @@ public class WithMockUserTests { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @ExtendWith(SpringExtension::class) @@ -281,7 +308,7 @@ class WithMockUserTests { } } ---- -==== +====== By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event. This is the equivalent of happening before JUnit's `@Before`. @@ -300,8 +327,10 @@ Using `@WithAnonymousUser` allows running as an anonymous user. This is especially convenient when you wish to run most of your tests with a specific user, but want to run a few tests as an anonymous user. For example, the following will run withMockUser1 and withMockUser2 using <> and anonymous as an anonymous user. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @ExtendWith(SpringExtension.class) @@ -324,7 +353,8 @@ public class WithUserClassLevelAuthenticationTests { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @ExtendWith(SpringExtension.class) @@ -345,7 +375,7 @@ class WithUserClassLevelAuthenticationTests { } } ---- -==== +====== By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event. This is the equivalent of happening before JUnit's `@Before`. @@ -370,8 +400,10 @@ That is exactly what `@WithUserDetails` does. Assuming we have a `UserDetailsService` exposed as a bean, the following test will be invoked with an `Authentication` of type `UsernamePasswordAuthenticationToken` and a principal that is returned from the `UserDetailsService` with the username of "user". -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Test @@ -382,7 +414,8 @@ public void getMessageWithUserDetails() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test @@ -392,13 +425,15 @@ fun getMessageWithUserDetails() { // ... } ---- -==== +====== We can also customize the username used to lookup the user from our `UserDetailsService`. For example, this test would be run with a principal that is returned from the `UserDetailsService` with the username of "customUsername". -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Test @@ -409,7 +444,8 @@ public void getMessageWithUserDetailsCustomUsername() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test @@ -419,13 +455,15 @@ fun getMessageWithUserDetailsCustomUsername() { // ... } ---- -==== +====== We can also provide an explicit bean name to look up the `UserDetailsService`. For example, this test would look up the username of "customUsername" using the `UserDetailsService` with the bean name "myUserDetailsService". -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Test @@ -436,7 +474,8 @@ public void getMessageWithUserDetailsServiceBeanName() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test @@ -446,7 +485,7 @@ fun getMessageWithUserDetailsServiceBeanName() { // ... } ---- -==== +====== Like `@WithMockUser` we can also place our annotation at the class level so that every test uses the same user. However unlike `@WithMockUser`, `@WithUserDetails` requires the user to exist. @@ -471,8 +510,10 @@ We will now see an option that allows the most flexibility. We can create our own annotation that uses the `@WithSecurityContext` to create any `SecurityContext` we want. For example, we might create an annotation named `@WithMockCustomUser` as shown below: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Retention(RetentionPolicy.RUNTIME) @@ -485,22 +526,25 @@ public @interface WithMockCustomUser { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Retention(AnnotationRetention.RUNTIME) @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class) annotation class WithMockCustomUser(val username: String = "rob", val name: String = "Rob Winch") ---- -==== +====== You can see that `@WithMockCustomUser` is annotated with the `@WithSecurityContext` annotation. This is what signals to Spring Security Test support that we intend to create a `SecurityContext` for the test. The `@WithSecurityContext` annotation requires we specify a `SecurityContextFactory` that will create a new `SecurityContext` given our `@WithMockCustomUser` annotation. You can find our `WithMockCustomUserSecurityContextFactory` implementation below: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public class WithMockCustomUserSecurityContextFactory @@ -519,7 +563,8 @@ public class WithMockCustomUserSecurityContextFactory } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory { @@ -533,15 +578,17 @@ class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory(springSecurity()) .build() ---- -==== +====== If you find you are using the same user in many of your tests, it is recommended to move the user to a method. For example, you can specify the following in your own class named `CustomSecurityMockMvcRequestPostProcessors`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public static RequestPostProcessor rob() { @@ -177,19 +200,22 @@ public static RequestPostProcessor rob() { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- fun rob(): RequestPostProcessor { return user("rob").roles("ADMIN") } ---- -==== +====== Now you can perform a static import on `CustomSecurityMockMvcRequestPostProcessors` and use that within your tests: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- import static sample.CustomSecurityMockMvcRequestPostProcessors.*; @@ -200,7 +226,8 @@ mvc .perform(get("/").with(rob())) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import sample.CustomSecurityMockMvcRequestPostProcessors.* @@ -211,7 +238,7 @@ mvc.get("/") { with(rob()) } ---- -==== +====== [[test-mockmvc-withmockuser]] == Running as a User in Spring MVC Test with Annotations @@ -219,8 +246,10 @@ mvc.get("/") { As an alternative to using a `RequestPostProcessor` to create your user, you can use annotations described in xref:servlet/test/method.adoc[Testing Method Security]. For example, the following will run the test with the user with username "user", password "password", and role "ROLE_USER": -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Test @@ -232,7 +261,8 @@ mvc } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test @@ -243,12 +273,14 @@ fun requestProtectedUrlWithUser() { // ... } ---- -==== +====== Alternatively, the following will run the test with the user with username "user", password "password", and role "ROLE_ADMIN": -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Test @@ -260,7 +292,8 @@ mvc } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Test @@ -271,4 +304,4 @@ fun requestProtectedUrlWithUser() { // ... } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/csrf.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/csrf.adoc index 3449151fa5..13baeb550a 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/csrf.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/csrf.adoc @@ -4,57 +4,66 @@ When testing any non-safe HTTP methods and using Spring Security's CSRF protection, you must be sure to include a valid CSRF Token in the request. To specify a valid CSRF token as a request parameter use the CSRF xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`] like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(post("/").with(csrf())) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.post("/") { with(csrf()) } ---- -==== +====== If you like you can include CSRF token in the header instead: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(post("/").with(csrf().asHeader())) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.post("/") { with(csrf().asHeader()) } ---- -==== +====== You can also test providing an invalid CSRF token using the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(post("/").with(csrf().useInvalidToken())) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.post("/") { with(csrf().useInvalidToken()) } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/form-login.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/form-login.adoc index c578c99646..5a4bcd5caf 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/form-login.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/form-login.adoc @@ -3,56 +3,65 @@ You can easily create a request to test a form based authentication using Spring Security's testing support. For example, the following `formLogin` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`] will submit a POST to "/login" with the username "user", the password "password", and a valid CSRF token: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(formLogin()) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc .perform(formLogin()) ---- -==== +====== It is easy to customize the request. For example, the following will submit a POST to "/auth" with the username "admin", the password "pass", and a valid CSRF token: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(formLogin("/auth").user("admin").password("pass")) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc .perform(formLogin("/auth").user("admin").password("pass")) ---- -==== +====== We can also customize the parameters names that the username and password are included on. For example, this is the above request modified to include the username on the HTTP parameter "u" and the password on the HTTP parameter "p". -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(formLogin("/auth").user("u","admin").password("p","pass")) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc .perform(formLogin("/auth").user("u","admin").password("p","pass")) ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/http-basic.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/http-basic.adoc index bd47e0f5b7..9046f7d443 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/http-basic.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/http-basic.adoc @@ -4,22 +4,25 @@ While it has always been possible to authenticate with HTTP Basic, it was a bit Now this can be done using Spring Security's `httpBasic` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`]. For example, the snippet below: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(get("/").with(httpBasic("user","password"))) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/") { with(httpBasic("user","password")) } ---- -==== +====== will attempt to use HTTP Basic to authenticate a user with the username "user" and the password "password" by ensuring the following header is populated on the HTTP Request: diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/logout.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/logout.adoc index bbf0aff83c..9e82afbe76 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/logout.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/logout.adoc @@ -4,37 +4,43 @@ While fairly trivial using standard Spring MVC Test, you can use Spring Security's testing support to make testing log out easier. For example, the following `logout` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`] will submit a POST to "/logout" with a valid CSRF token: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(logout()) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc .perform(logout()) ---- -==== +====== You can also customize the URL to post to. For example, the snippet below will submit a POST to "/signout" with a valid CSRF token: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(logout("/signout")) ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc .perform(logout("/signout")) ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/oauth2.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/oauth2.adoc index 5c0a7e7937..562ee2d7c4 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/oauth2.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/oauth2.adoc @@ -5,8 +5,10 @@ When it comes to OAuth 2.0, the same principles covered earlier still apply: Ult For example, for a controller that looks like this: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -15,7 +17,8 @@ public String foo(Principal user) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -23,14 +26,16 @@ fun foo(user: Principal): String { return user.name } ---- -==== +====== There's nothing OAuth2-specific about it, so you will likely be able to simply xref:servlet/test/method.adoc#test-method-withmockuser[use `@WithMockUser`] and be fine. But, in cases where your controllers are bound to some aspect of Spring Security's OAuth 2.0 support, like the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -39,7 +44,8 @@ public String foo(@AuthenticationPrincipal OidcUser user) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -47,7 +53,7 @@ fun foo(@AuthenticationPrincipal user: OidcUser): String { return user.idToken.subject } ---- -==== +====== then Spring Security's test support can come in handy. @@ -59,74 +65,86 @@ Certainly this would be a daunting task, which is why Spring Security ships with For example, we can tell Spring Security to include a default `OidcUser` using the `oidcLogin` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`], like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(get("/endpoint").with(oidcLogin())); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { with(oidcLogin()) } ---- -==== +====== What this will do is configure the associated `MockHttpServletRequest` with an `OidcUser` that includes a simple `OidcIdToken`, `OidcUserInfo`, and `Collection` of granted authorities. Specifically, it will include an `OidcIdToken` with a `sub` claim set to `user`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(user.getIdToken().getClaim("sub")).isEqualTo("user"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(user.idToken.getClaim("sub")).isEqualTo("user") ---- -==== +====== an `OidcUserInfo` with no claims set: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(user.getUserInfo().getClaims()).isEmpty(); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(user.userInfo.claims).isEmpty() ---- -==== +====== and a `Collection` of authorities with just one authority, `SCOPE_read`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(user.getAuthorities()).hasSize(1); assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(user.authorities).hasSize(1) assertThat(user.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read")) ---- -==== +====== Spring Security does the necessary work to make sure that the `OidcUser` instance is available for xref:servlet/integrations/mvc.adoc#mvc-authentication-principal[the `@AuthenticationPrincipal` annotation]. @@ -140,8 +158,10 @@ In many circumstances, your method is protected by filter or method security and In this case, you can supply what granted authorities you need using the `authorities()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -152,7 +172,8 @@ mvc ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -161,7 +182,7 @@ mvc.get("/endpoint") { ) } ---- -==== +====== [[testing-oidc-login-claims]] == Configuring Claims @@ -171,8 +192,10 @@ And while granted authorities are quite common across all of Spring Security, we Let's say, for example, that you've got a `user_id` claim that indicates the user's id in your system. You might access it like so in a controller: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -182,7 +205,8 @@ public String foo(@AuthenticationPrincipal OidcUser oidcUser) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -191,12 +215,14 @@ fun foo(@AuthenticationPrincipal oidcUser: OidcUser): String { // ... } ---- -==== +====== In that case, you'd want to specify that claim with the `idToken()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -207,7 +233,8 @@ mvc ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -218,7 +245,7 @@ mvc.get("/endpoint") { ) } ---- -==== +====== since `OidcUser` collects its claims from `OidcIdToken`. @@ -238,8 +265,10 @@ That last one is handy if you: For example, let's say that your authorization server sends the principal name in the `user_name` claim instead of the `sub` claim. In that case, you can configure an `OidcUser` by hand: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- OidcUser oidcUser = new DefaultOidcUser( @@ -253,7 +282,8 @@ mvc ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val oidcUser: OidcUser = DefaultOidcUser( @@ -266,7 +296,7 @@ mvc.get("/endpoint") { with(oidcLogin().oidcUser(oidcUser)) } ---- -==== +====== [[testing-oauth2-login]] == Testing OAuth 2.0 Login @@ -276,8 +306,10 @@ And because of that, Spring Security also has test support for non-OIDC use case Let's say that we've got a controller that gets the logged-in user as an `OAuth2User`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -286,7 +318,8 @@ public String foo(@AuthenticationPrincipal OAuth2User oauth2User) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -294,62 +327,71 @@ fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): String? { return oauth2User.getAttribute("sub") } ---- -==== +====== In that case, we can tell Spring Security to include a default `OAuth2User` using the `oauth2Login` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`], like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(get("/endpoint").with(oauth2Login())); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { with(oauth2Login()) } ---- -==== +====== What this will do is configure the associated `MockHttpServletRequest` with an `OAuth2User` that includes a simple `Map` of attributes and `Collection` of granted authorities. Specifically, it will include a `Map` with a key/value pair of `sub`/`user`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat((String) user.getAttribute("sub")).isEqualTo("user"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(user.getAttribute("sub")).isEqualTo("user") ---- -==== +====== and a `Collection` of authorities with just one authority, `SCOPE_read`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(user.getAuthorities()).hasSize(1); assertThat(user.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(user.authorities).hasSize(1) assertThat(user.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read")) ---- -==== +====== Spring Security does the necessary work to make sure that the `OAuth2User` instance is available for xref:servlet/integrations/mvc.adoc#mvc-authentication-principal[the `@AuthenticationPrincipal` annotation]. @@ -363,8 +405,10 @@ In many circumstances, your method is protected by filter or method security and In this case, you can supply what granted authorities you need using the `authorities()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -375,7 +419,8 @@ mvc ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -384,7 +429,7 @@ mvc.get("/endpoint") { ) } ---- -==== +====== [[testing-oauth2-login-claims]] == Configuring Claims @@ -394,8 +439,10 @@ And while granted authorities are quite common across all of Spring Security, we Let's say, for example, that you've got a `user_id` attribute that indicates the user's id in your system. You might access it like so in a controller: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -405,7 +452,8 @@ public String foo(@AuthenticationPrincipal OAuth2User oauth2User) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -414,12 +462,14 @@ fun foo(@AuthenticationPrincipal oauth2User: OAuth2User): String { // ... } ---- -==== +====== In that case, you'd want to specify that attribute with the `attributes()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -430,7 +480,8 @@ mvc ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -439,7 +490,7 @@ mvc.get("/endpoint") { ) } ---- -==== +====== [[testing-oauth2-login-user]] == Additional Configurations @@ -456,8 +507,10 @@ That last one is handy if you: For example, let's say that your authorization server sends the principal name in the `user_name` claim instead of the `sub` claim. In that case, you can configure an `OAuth2User` by hand: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- OAuth2User oauth2User = new DefaultOAuth2User( @@ -471,7 +524,8 @@ mvc ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val oauth2User: OAuth2User = DefaultOAuth2User( @@ -484,7 +538,7 @@ mvc.get("/endpoint") { with(oauth2Login().oauth2User(oauth2User)) } ---- -==== +====== [[testing-oauth2-client]] == Testing OAuth 2.0 Clients @@ -492,8 +546,10 @@ mvc.get("/endpoint") { Independent of how your user authenticates, you may have other tokens and client registrations that are in play for the request you are testing. For example, your controller may be relying on the client credentials grant to get a token that isn't associated with the user at all: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -506,7 +562,8 @@ public String foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2AuthorizedCl } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -518,20 +575,23 @@ fun foo(@RegisteredOAuth2AuthorizedClient("my-app") authorizedClient: OAuth2Auth .block() } ---- -==== +====== Simulating this handshake with the authorization server could be cumbersome. Instead, you can use the `oauth2Client` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`] to add a `OAuth2AuthorizedClient` into a mock `OAuth2AuthorizedClientRepository`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(get("/endpoint").with(oauth2Client("my-app"))); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -540,61 +600,70 @@ mvc.get("/endpoint") { ) } ---- -==== +====== What this will do is create an `OAuth2AuthorizedClient` that has a simple `ClientRegistration`, `OAuth2AccessToken`, and resource owner name. Specifically, it will include a `ClientRegistration` with a client id of "test-client" and client secret of "test-secret": -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(authorizedClient.getClientRegistration().getClientId()).isEqualTo("test-client"); assertThat(authorizedClient.getClientRegistration().getClientSecret()).isEqualTo("test-secret"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(authorizedClient.clientRegistration.clientId).isEqualTo("test-client") assertThat(authorizedClient.clientRegistration.clientSecret).isEqualTo("test-secret") ---- -==== +====== a resource owner name of "user": -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(authorizedClient.getPrincipalName()).isEqualTo("user"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(authorizedClient.principalName).isEqualTo("user") ---- -==== +====== and an `OAuth2AccessToken` with just one scope, `read`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(authorizedClient.getAccessToken().getScopes()).hasSize(1); assertThat(authorizedClient.getAccessToken().getScopes()).containsExactly("read"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(authorizedClient.accessToken.scopes).hasSize(1) assertThat(authorizedClient.accessToken.scopes).containsExactly("read") ---- -==== +====== The client can then be retrieved as normal using `@RegisteredOAuth2AuthorizedClient` in a controller method. @@ -604,8 +673,10 @@ The client can then be retrieved as normal using `@RegisteredOAuth2AuthorizedCli In many circumstances, the OAuth 2.0 access token comes with a set of scopes. If your controller inspects these, say like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -622,7 +693,8 @@ public String foo(@RegisteredOAuth2AuthorizedClient("my-app") OAuth2AuthorizedCl } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -638,12 +710,14 @@ fun foo(@RegisteredOAuth2AuthorizedClient("my-app") authorizedClient: OAuth2Auth // ... } ---- -==== +====== then you can configure the scope using the `accessToken()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -654,7 +728,8 @@ mvc ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -663,7 +738,7 @@ mvc.get("/endpoint") { ) } ---- -==== +====== [[testing-oauth2-client-registration]] == Additional Configurations @@ -680,8 +755,10 @@ For example, let's say that you are wanting to use one of your app's `ClientRegi In that case, your test can autowire the `ClientRegistrationRepository` and look up the one your test needs: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Autowired @@ -695,7 +772,8 @@ mvc .clientRegistration(this.clientRegistrationRepository.findByRegistrationId("facebook")))); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Autowired @@ -709,7 +787,7 @@ mvc.get("/endpoint") { ) } ---- -==== +====== [[testing-jwt]] == Testing JWT Authentication @@ -727,22 +805,25 @@ We'll look at two of them now: The first way is via the `jwt` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`]. The simplest of these would look something like this: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(get("/endpoint").with(jwt())); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { with(jwt()) } ---- -==== +====== What this will do is create a mock `Jwt`, passing it correctly through any authentication APIs so that it's available for your authorization mechanisms to verify. @@ -761,8 +842,10 @@ By default, the `JWT` that it creates has the following characteristics: And the resulting `Jwt`, were it tested, would pass in the following way: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(jwt.getTokenValue()).isEqualTo("token"); @@ -770,21 +853,24 @@ assertThat(jwt.getHeaders().get("alg")).isEqualTo("none"); assertThat(jwt.getSubject()).isEqualTo("sub"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(jwt.tokenValue).isEqualTo("token") assertThat(jwt.headers["alg"]).isEqualTo("none") assertThat(jwt.subject).isEqualTo("sub") ---- -==== +====== These values can, of course be configured. Any headers or claims can be configured with their corresponding methods: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -792,7 +878,8 @@ mvc .with(jwt().jwt(jwt -> jwt.header("kid", "one").claim("iss", "https://idp.example.org")))); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -801,10 +888,12 @@ mvc.get("/endpoint") { ) } ---- -==== +====== -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -812,7 +901,8 @@ mvc .with(jwt().jwt(jwt -> jwt.claims(claims -> claims.remove("scope"))))); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -821,13 +911,15 @@ mvc.get("/endpoint") { ) } ---- -==== +====== The `scope` and `scp` claims are processed the same way here as they are in a normal bearer token request. However, this can be overridden simply by providing the list of `GrantedAuthority` instances that you need for your test: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -835,7 +927,8 @@ mvc .with(jwt().authorities(new SimpleGrantedAuthority("SCOPE_messages")))); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -844,12 +937,14 @@ mvc.get("/endpoint") { ) } ---- -==== +====== Or, if you have a custom `Jwt` to `Collection` converter, you can also use that to derive the authorities: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -857,7 +952,8 @@ mvc .with(jwt().authorities(new MyConverter()))); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -866,12 +962,14 @@ mvc.get("/endpoint") { ) } ---- -==== +====== You can also specify a complete `Jwt`, for which `{security-api-url}org/springframework/security/oauth2/jwt/Jwt.Builder.html[Jwt.Builder]` comes quite handy: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Jwt jwt = Jwt.withTokenValue("token") @@ -885,7 +983,8 @@ mvc .with(jwt().jwt(jwt))); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val jwt: Jwt = Jwt.withTokenValue("token") @@ -900,15 +999,17 @@ mvc.get("/endpoint") { ) } ---- -==== +====== == `authentication()` `RequestPostProcessor` The second way is by using the `authentication()` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`]. Essentially, you can instantiate your own `JwtAuthenticationToken` and provide it in your test, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Jwt jwt = Jwt.withTokenValue("token") @@ -923,7 +1024,8 @@ mvc .with(authentication(token))); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val jwt = Jwt.withTokenValue("token") @@ -939,7 +1041,7 @@ mvc.get("/endpoint") { ) } ---- -==== +====== Note that as an alternative to these, you can also mock the `JwtDecoder` bean itself with a `@MockBean` annotation. @@ -951,8 +1053,10 @@ To help with that, Spring Security has test support for opaque tokens. Let's say that we've got a controller that retrieves the authentication as a `BearerTokenAuthentication`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -961,7 +1065,8 @@ public String foo(BearerTokenAuthentication authentication) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -969,62 +1074,71 @@ fun foo(authentication: BearerTokenAuthentication): String { return authentication.tokenAttributes["sub"] as String } ---- -==== +====== In that case, we can tell Spring Security to include a default `BearerTokenAuthentication` using the `opaqueToken` xref:servlet/test/mockmvc/request-post-processors.adoc[`RequestPostProcessor`] method, like so: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc .perform(get("/endpoint").with(opaqueToken())); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { with(opaqueToken()) } ---- -==== +====== What this will do is configure the associated `MockHttpServletRequest` with a `BearerTokenAuthentication` that includes a simple `OAuth2AuthenticatedPrincipal`, `Map` of attributes, and `Collection` of granted authorities. Specifically, it will include a `Map` with a key/value pair of `sub`/`user`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat((String) token.getTokenAttributes().get("sub")).isEqualTo("user"); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(token.tokenAttributes["sub"] as String).isEqualTo("user") ---- -==== +====== and a `Collection` of authorities with just one authority, `SCOPE_read`: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- assertThat(token.getAuthorities()).hasSize(1); assertThat(token.getAuthorities()).containsExactly(new SimpleGrantedAuthority("SCOPE_read")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- assertThat(token.authorities).hasSize(1) assertThat(token.authorities).containsExactly(SimpleGrantedAuthority("SCOPE_read")) ---- -==== +====== Spring Security does the necessary work to make sure that the `BearerTokenAuthentication` instance is available for your controller methods. @@ -1035,8 +1149,10 @@ In many circumstances, your method is protected by filter or method security and In this case, you can supply what granted authorities you need using the `authorities()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -1047,7 +1163,8 @@ mvc ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -1056,7 +1173,7 @@ mvc.get("/endpoint") { ) } ---- -==== +====== [[testing-opaque-token-attributes]] == Configuring Claims @@ -1066,8 +1183,10 @@ And while granted authorities are quite common across all of Spring Security, we Let's say, for example, that you've got a `user_id` attribute that indicates the user's id in your system. You might access it like so in a controller: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @GetMapping("/endpoint") @@ -1077,7 +1196,8 @@ public String foo(BearerTokenAuthentication authentication) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @GetMapping("/endpoint") @@ -1086,12 +1206,14 @@ fun foo(authentication: BearerTokenAuthentication): String { // ... } ---- -==== +====== In that case, you'd want to specify that attribute with the `attributes()` method: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -1102,7 +1224,8 @@ mvc ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc.get("/endpoint") { @@ -1111,7 +1234,7 @@ mvc.get("/endpoint") { ) } ---- -==== +====== [[testing-opaque-token-principal]] == Additional Configurations @@ -1127,8 +1250,10 @@ It's handy if you: For example, let's say that your authorization server sends the principal name in the `user_name` attribute instead of the `sub` attribute. In that case, you can configure an `OAuth2AuthenticatedPrincipal` by hand: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- Map attributes = Collections.singletonMap("user_name", "foo_user"); @@ -1143,7 +1268,8 @@ mvc ); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- val attributes: Map = Collections.singletonMap("user_name", "foo_user") @@ -1157,6 +1283,6 @@ mvc.get("/endpoint") { with(opaqueToken().principal(principal)) } ---- -==== +====== Note that as an alternative to using `opaqueToken()` test support, you can also mock the `OpaqueTokenIntrospector` bean itself with a `@MockBean` annotation. diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/request-builders.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/request-builders.adoc index 47de846c53..ebbf07c68d 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/request-builders.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/request-builders.adoc @@ -4,16 +4,19 @@ Spring MVC Test also provides a `RequestBuilder` interface that can be used to c Spring Security provides a few `RequestBuilder` implementations that can be used to make testing easier. In order to use Spring Security's `RequestBuilder` implementations ensure the following static import is used: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*; ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.* ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/request-post-processors.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/request-post-processors.adoc index 7cd154b8c7..c57d514fab 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/request-post-processors.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/request-post-processors.adoc @@ -5,16 +5,19 @@ Spring MVC Test provides a convenient interface called a `RequestPostProcessor` Spring Security provides a number of `RequestPostProcessor` implementations that make testing easier. In order to use Spring Security's `RequestPostProcessor` implementations ensure the following static import is used: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.* ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/result-matchers.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/result-matchers.adoc index b7d24f76db..8571418584 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/result-matchers.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/result-matchers.adoc @@ -4,20 +4,23 @@ At times it is desirable to make various security related assertions about a req To accommodate this need, Spring Security Test support implements Spring MVC Test's `ResultMatcher` interface. In order to use Spring Security's `ResultMatcher` implementations ensure the following static import is used: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*; ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.* ---- -==== +====== === Unauthenticated Assertion @@ -25,8 +28,10 @@ At times it may be valuable to assert that there is no authenticated user associ For example, you might want to test submitting an invalid username and password and verify that no user is authenticated. You can easily do this with Spring Security's testing support using something like the following: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -34,14 +39,15 @@ mvc .andExpect(unauthenticated()); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc .perform(formLogin().password("invalid")) .andExpect { unauthenticated() } ---- -==== +====== === Authenticated Assertion @@ -49,8 +55,10 @@ It is often times that we must assert that an authenticated user exists. For example, we may want to verify that we authenticated successfully. We could verify that a form based login was successful with the following snippet of code: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -58,19 +66,22 @@ mvc .andExpect(authenticated()); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc .perform(formLogin()) .andExpect { authenticated() } ---- -==== +====== If we wanted to assert the roles of the user, we could refine our previous code as shown below: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -78,19 +89,22 @@ mvc .andExpect(authenticated().withRoles("USER","ADMIN")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc .perform(formLogin()) .andExpect { authenticated().withRoles("USER","ADMIN") } ---- -==== +====== Alternatively, we could verify the username: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -98,19 +112,22 @@ mvc .andExpect(authenticated().withUsername("admin")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc .perform(formLogin().user("admin")) .andExpect { authenticated().withUsername("admin") } ---- -==== +====== We can also combine the assertions: -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -118,19 +135,22 @@ mvc .andExpect(authenticated().withUsername("admin").withRoles("USER", "ADMIN")); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc .perform(formLogin().user("admin")) .andExpect { authenticated().withUsername("admin").withRoles("USER", "ADMIN") } ---- -==== +====== We can also make arbitrary assertions on the authentication -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- mvc @@ -139,7 +159,8 @@ mvc assertThat(auth).isInstanceOf(UsernamePasswordAuthenticationToken.class))); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- mvc @@ -150,4 +171,4 @@ mvc } } ---- -==== +====== diff --git a/docs/modules/ROOT/pages/servlet/test/mockmvc/setup.adoc b/docs/modules/ROOT/pages/servlet/test/mockmvc/setup.adoc index 749f3cbff4..6722609f2b 100644 --- a/docs/modules/ROOT/pages/servlet/test/mockmvc/setup.adoc +++ b/docs/modules/ROOT/pages/servlet/test/mockmvc/setup.adoc @@ -8,8 +8,10 @@ For example: NOTE: Spring Security's testing support requires spring-test-4.1.3.RELEASE or greater. -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @@ -36,7 +38,8 @@ public class CsrfShowcaseTests { ... ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @ExtendWith(SpringExtension.class) @@ -58,6 +61,6 @@ class CsrfShowcaseTests { } // ... ---- -==== +====== <1> `SecurityMockMvcConfigurers.springSecurity()` will perform all of the initial setup we need to integrate Spring Security with Spring MVC Test diff --git a/docs/modules/ROOT/partials/servlet/architecture/request-cache-continue.adoc b/docs/modules/ROOT/partials/servlet/architecture/request-cache-continue.adoc index 4a60a37ef1..9680fa1928 100644 --- a/docs/modules/ROOT/partials/servlet/architecture/request-cache-continue.adoc +++ b/docs/modules/ROOT/partials/servlet/architecture/request-cache-continue.adoc @@ -1,6 +1,8 @@ .`RequestCache` Only Checks for Saved Requests if `continue` Parameter Present -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- @Bean @@ -16,7 +18,8 @@ DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -32,7 +35,8 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- @@ -43,4 +47,4 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { ---- -==== \ No newline at end of file +====== \ No newline at end of file diff --git a/docs/modules/ROOT/partials/servlet/architecture/security-context-explicit.adoc b/docs/modules/ROOT/partials/servlet/architecture/security-context-explicit.adoc index 2f2179c614..41b882d9e2 100644 --- a/docs/modules/ROOT/partials/servlet/architecture/security-context-explicit.adoc +++ b/docs/modules/ROOT/partials/servlet/architecture/security-context-explicit.adoc @@ -1,6 +1,8 @@ .Explicit Saving of SecurityContext -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- public SecurityFilterChain filterChain(HttpSecurity http) { @@ -13,7 +15,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) { } ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- @Bean @@ -27,14 +30,15 @@ open fun springSecurity(http: HttpSecurity): SecurityFilterChain { } ---- -.XML +XML:: ++ [source,xml,role="secondary"] ---- ---- -==== +====== Upon using the configuration, it is important that any code that sets the `SecurityContextHolder` with a `SecurityContext` also saves the `SecurityContext` to the `SecurityContextRepository` if it should be persisted between requests. @@ -42,35 +46,41 @@ Upon using the configuration, it is important that any code that sets the `Secur For example, the following code: .Setting `SecurityContextHolder` with `SecurityContextPersistenceFilter` -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- SecurityContextHolder.setContext(securityContext); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- SecurityContextHolder.setContext(securityContext) ---- -==== +====== should be replaced with .Setting `SecurityContextHolder` with `SecurityContextHolderFilter` -==== -.Java +[tabs] +====== +Java:: ++ [source,java,role="primary"] ---- SecurityContextHolder.setContext(securityContext); securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse); ---- -.Kotlin +Kotlin:: ++ [source,kotlin,role="secondary"] ---- SecurityContextHolder.setContext(securityContext) securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse) ---- -==== \ No newline at end of file +====== \ No newline at end of file