Add remaining Kotlin samples to reference docs

Closes gh-8172
This commit is contained in:
Eleftheria Stein 2021-06-24 11:49:13 +02:00
parent 94a3adb928
commit da9d7414bd
8 changed files with 1029 additions and 63 deletions

View File

@ -10,7 +10,9 @@ 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`. Users can integrate the `CorsWebFilter` with Spring Security by providing a `CorsConfigurationSource`.
For example, the following will integrate CORS support within Spring Security: For example, the following will integrate CORS support within Spring Security:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
CorsConfigurationSource corsConfigurationSource() { CorsConfigurationSource corsConfigurationSource() {
@ -23,9 +25,26 @@ CorsConfigurationSource corsConfigurationSource() {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
val configuration = CorsConfiguration()
configuration.allowedOrigins = listOf("https://example.com")
configuration.allowedMethods = listOf("GET", "POST")
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", configuration)
return source
}
----
====
The following will disable the CORS integration within Spring Security: The following will disable the CORS integration within Spring Security:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
@ -35,3 +54,18 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http.build(); return http.build();
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
// ...
cors {
disable()
}
}
}
----
====

View File

@ -14,7 +14,9 @@ You can find a few sample applications that demonstrate the code below:
You can find a minimal RSocket Security configuration below: You can find a minimal RSocket Security configuration below:
[source,java] ====
.Java
[source,java,role="primary"]
----- -----
@Configuration @Configuration
@EnableRSocketSecurity @EnableRSocketSecurity
@ -32,6 +34,25 @@ public class HelloRSocketSecurityConfig {
} }
----- -----
.Kotlin
[source,kotlin,role="secondary"]
----
@Configuration
@EnableRSocketSecurity
open class HelloRSocketSecurityConfig {
@Bean
open fun userDetailsService(): MapReactiveUserDetailsService {
val user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build()
return MapReactiveUserDetailsService(user)
}
}
----
====
This configuration enables <<rsocket-authentication-simple,simple authentication>> and sets up <<rsocket-authorization,rsocket-authorization>> to require an authenticated user for any request. This configuration enables <<rsocket-authentication-simple,simple authentication>> and sets up <<rsocket-authorization,rsocket-authorization>> to require an authenticated user for any request.
== Adding SecuritySocketAcceptorInterceptor == Adding SecuritySocketAcceptorInterceptor
@ -86,7 +107,9 @@ 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. 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. An explicit configuration can be found below.
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) { PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
@ -101,17 +124,45 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
open fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
rsocket
.authorizePayload { authorize -> authorize
.anyRequest().authenticated()
.anyExchange().permitAll()
}
.simpleAuthentication(withDefaults())
return rsocket.build()
}
----
====
The RSocket sender can send credentials using `SimpleAuthenticationEncoder` which can be added to Spring's `RSocketStrategies`. The RSocket sender can send credentials using `SimpleAuthenticationEncoder` which can be added to Spring's `RSocketStrategies`.
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
RSocketStrategies.Builder strategies = ...; RSocketStrategies.Builder strategies = ...;
strategies.encoder(new SimpleAuthenticationEncoder()); strategies.encoder(new SimpleAuthenticationEncoder());
---- ----
.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: It can then be used to send a username and password to the receiver in the setup:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
MimeType authenticationMimeType = MimeType authenticationMimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()); MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
@ -122,9 +173,24 @@ Mono<RSocketRequester> requester = RSocketRequester.builder()
.connectTcp(host, port); .connectTcp(host, port);
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
val authenticationMimeType: MimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
val credentials = UsernamePasswordMetadata("user", "password")
val requester: Mono<RSocketRequester> = RSocketRequester.builder()
.setupMetadata(credentials, authenticationMimeType)
.rsocketStrategies(strategies.build())
.connectTcp(host, port)
----
====
Alternatively or additionally, a username and password can be sent in a request. Alternatively or additionally, a username and password can be sent in a request.
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
Mono<RSocketRequester> requester; Mono<RSocketRequester> requester;
UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password"); UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("user", "password");
@ -138,6 +204,26 @@ public Mono<AirportLocation> findRadar(String code) {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
import org.springframework.messaging.rsocket.retrieveMono
// ...
var requester: Mono<RSocketRequester>? = null
var credentials = UsernamePasswordMetadata("user", "password")
open fun findRadar(code: String): Mono<AirportLocation> {
return requester!!.flatMap { req ->
req.route("find.radar.{code}", code)
.metadata(credentials, authenticationMimeType)
.retrieveMono<AirportLocation>()
}
}
----
====
[[rsocket-authentication-jwt]] [[rsocket-authentication-jwt]]
=== JWT === JWT
@ -147,7 +233,9 @@ 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. 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: An example configuration can be found below:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) { PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
@ -162,10 +250,28 @@ PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity rsocket) {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun rsocketInterceptor(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
rsocket
.authorizePayload { authorize -> authorize
.anyRequest().authenticated()
.anyExchange().permitAll()
}
.jwt(withDefaults())
return rsocket.build()
}
----
====
The configuration above relies on the existence of a `ReactiveJwtDecoder` `@Bean` being present. 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: An example of creating one from the issuer can be found below:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
ReactiveJwtDecoder jwtDecoder() { ReactiveJwtDecoder jwtDecoder() {
@ -174,10 +280,23 @@ ReactiveJwtDecoder jwtDecoder() {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun jwtDecoder(): ReactiveJwtDecoder {
return ReactiveJwtDecoders
.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. 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: For example, the token can be sent at setup time:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
MimeType authenticationMimeType = MimeType authenticationMimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()); MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
@ -187,9 +306,24 @@ Mono<RSocketRequester> requester = RSocketRequester.builder()
.connectTcp(host, port); .connectTcp(host, port);
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
val authenticationMimeType: MimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
val token: BearerTokenMetadata = ...
val requester = RSocketRequester.builder()
.setupMetadata(token, authenticationMimeType)
.connectTcp(host, port)
----
====
Alternatively or additionally, the token can be sent in a request. Alternatively or additionally, the token can be sent in a request.
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
MimeType authenticationMimeType = MimeType authenticationMimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()); MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
@ -205,6 +339,24 @@ public Mono<AirportLocation> findRadar(String code) {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
val authenticationMimeType: MimeType =
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.string)
var requester: Mono<RSocketRequester>? = null
val token: BearerTokenMetadata = ...
open fun findRadar(code: String): Mono<AirportLocation> {
return this.requester!!.flatMap { req ->
req.route("find.radar.{code}", code)
.metadata(token, authenticationMimeType)
.retrieveMono<AirportLocation>()
}
}
----
====
[[rsocket-authorization]] [[rsocket-authorization]]
== RSocket Authorization == RSocket Authorization
@ -212,7 +364,9 @@ RSocket authorization is performed with `AuthorizationPayloadInterceptor` which
The DSL can be used to setup authorization rules based upon the `PayloadExchange`. The DSL can be used to setup authorization rules based upon the `PayloadExchange`.
An example configuration can be found below: An example configuration can be found below:
[[source,java]] ====
.Java
[source,java,role="primary"]
---- ----
rsocket rsocket
.authorizePayload(authz -> .authorizePayload(authz ->
@ -227,6 +381,23 @@ rsocket
.anyExchange().permitAll() // <6> .anyExchange().permitAll() // <6>
); );
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
rsocket
.authorizePayload { authz ->
authz
.setup().hasRole("SETUP") // <1>
.route("fetch.profile.me").authenticated() // <2>
.matcher { payloadExchange -> isMatch(payloadExchange) } // <3>
.hasRole("CUSTOM")
.route("fetch.profile.{username}") // <4>
.access { authentication, context -> checkFriends(authentication, context) }
.anyRequest().authenticated() // <5>
.anyExchange().permitAll()
} // <6>
----
====
<1> Setting up a connection requires the authority `ROLE_SETUP` <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 <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` <3> In this rule we setup a custom matcher where authorization requires the user to have the authority `ROLE_CUSTOM`

File diff suppressed because it is too large Load Diff

View File

@ -134,7 +134,9 @@ You can configure multiple `SecurityWebFilterChain` instances to separate config
For example, you can isolate configuration for URLs that start with `/api`, like so: For example, you can isolate configuration for URLs that start with `/api`, like so:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Configuration @Configuration
@EnableWebFluxSecurity @EnableWebFluxSecurity
@ -171,6 +173,46 @@ static class MultiSecurityHttpConfig {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Configuration
@EnableWebFluxSecurity
open class MultiSecurityHttpConfig {
@Order(Ordered.HIGHEST_PRECEDENCE) <1>
@Bean
open fun apiHttpSecurity(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
securityMatcher(PathPatternParserServerWebExchangeMatcher("/api/**")) <2>
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2ResourceServer {
jwt { } <3>
}
}
}
@Bean
open fun webHttpSecurity(http: ServerHttpSecurity): SecurityWebFilterChain { <4>
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
httpBasic { } <5>
}
}
@Bean
open fun userDetailsService(): ReactiveUserDetailsService {
return MapReactiveUserDetailsService(
PasswordEncodedUser.user(), PasswordEncodedUser.admin()
)
}
}
----
====
<1> Configure a `SecurityWebFilterChain` with an `@Order` to specify which `SecurityWebFilterChain` Spring Security should consider first <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/` <2> Use `PathPatternParserServerWebExchangeMatcher` to state that this `SecurityWebFilterChain` will only apply to URL paths that start with `/api/`
<3> Specify the authentication mechanisms that will be used for `/api/**` endpoints <3> Specify the authentication mechanisms that will be used for `/api/**` endpoints

View File

@ -4,7 +4,9 @@
Similar to <<servlet-x509,Servlet X.509 authentication>>, reactive x509 authentication filter allows extracting an authentication token from a certificate provided by a client. Similar to <<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: Below is an example of a reactive x509 security configuration:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
@ -17,11 +19,28 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
x509 { }
authorizeExchange {
authorize(anyExchange, authenticated)
}
}
}
----
====
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. 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. The next example demonstrates how these defaults can be overridden.
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@Bean @Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
@ -47,6 +66,30 @@ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
val customPrincipalExtractor = SubjectDnX509PrincipalExtractor()
customPrincipalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)")
val customAuthenticationManager = ReactiveAuthenticationManager { authentication: Authentication ->
authentication.isAuthenticated = "Trusted Org Unit" == authentication.name
Mono.just(authentication)
}
return http {
x509 {
principalExtractor = customPrincipalExtractor
authenticationManager = customAuthenticationManager
}
authorizeExchange {
authorize(anyExchange, authenticated)
}
}
}
----
====
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. 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.
For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, please refer to https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509. For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, please refer to https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509.

View File

@ -108,7 +108,9 @@ https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc
This means that a construct like this one: This means that a construct like this one:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@GetMapping("/") @GetMapping("/")
public String method(Authentication authentication) { public String method(Authentication authentication) {
@ -120,6 +122,20 @@ public String method(Authentication authentication) {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@GetMapping("/")
fun method(authentication: Authentication?): String {
return if (authentication is AnonymousAuthenticationToken) {
"anonymous"
} else {
"not anonymous"
}
}
----
====
will always return "not anonymous", even for anonymous requests. 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. The reason is that Spring MVC resolves the parameter using `HttpServletRequest#getPrincipal`, which is `null` when the request is anonymous.

View File

@ -820,12 +820,22 @@ class DirectlyConfiguredJwkSetUri : WebSecurityConfigurerAdapter() {
Or similarly with method security: Or similarly with method security:
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
@PreAuthorize("hasAuthority('SCOPE_messages')") @PreAuthorize("hasAuthority('SCOPE_messages')")
public List<Message> getMessages(...) {} public List<Message> getMessages(...) {}
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
@PreAuthorize("hasAuthority('SCOPE_messages')")
fun getMessages(): List<Message> { }
----
====
[[oauth2resourceserver-jwt-authorization-extraction]] [[oauth2resourceserver-jwt-authorization-extraction]]
==== Extracting Authorities Manually ==== Extracting Authorities Manually

View File

@ -4,7 +4,9 @@
This section demonstrates how to use Spring Security's Test support to test method based security. 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. We first introduce a `MessageService` that requires the user to be authenticated in order to access it.
[source,java] ====
.Java
[source,java,role="primary"]
---- ----
public class HelloMessageService implements MessageService { public class HelloMessageService implements MessageService {
@ -17,6 +19,19 @@ public class HelloMessageService implements MessageService {
} }
---- ----
.Kotlin
[source,kotlin,role="secondary"]
----
class HelloMessageService : MessageService {
@PreAuthorize("authenticated")
fun getMessage(): String {
val authentication: Authentication = SecurityContextHolder.getContext().authentication
return "Hello $authentication"
}
}
----
====
The result of `getMessage` is a String saying "Hello" to the current Spring Security `Authentication`. The result of `getMessage` is a String saying "Hello" to the current Spring Security `Authentication`.
An example of the output is displayed below. An example of the output is displayed below.