Add remaining Kotlin samples to reference docs
Closes gh-8172
This commit is contained in:
parent
94a3adb928
commit
da9d7414bd
|
@ -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`.
|
||||
For example, the following will integrate CORS support within Spring Security:
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
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:
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
||||
|
@ -35,3 +54,18 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
|||
return http.build();
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
// ...
|
||||
cors {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
|
|
@ -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:
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
-----
|
||||
@Configuration
|
||||
@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.
|
||||
|
||||
== 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.
|
||||
An explicit configuration can be found below.
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
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`.
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
RSocketStrategies.Builder strategies = ...;
|
||||
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:
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
MimeType authenticationMimeType =
|
||||
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
|
||||
|
@ -122,9 +173,24 @@ Mono<RSocketRequester> requester = RSocketRequester.builder()
|
|||
.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.
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
Mono<RSocketRequester> requester;
|
||||
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]]
|
||||
=== 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.
|
||||
An example configuration can be found below:
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
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.
|
||||
An example of creating one from the issuer can be found below:
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
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.
|
||||
For example, the token can be sent at setup time:
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
MimeType authenticationMimeType =
|
||||
MimeTypeUtils.parseMimeType(WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
|
||||
|
@ -187,9 +306,24 @@ Mono<RSocketRequester> requester = RSocketRequester.builder()
|
|||
.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.
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
MimeType authenticationMimeType =
|
||||
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
|
||||
|
||||
|
@ -212,7 +364,9 @@ 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:
|
||||
|
||||
[[source,java]]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
rsocket
|
||||
.authorizePayload(authz ->
|
||||
|
@ -227,6 +381,23 @@ rsocket
|
|||
.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`
|
||||
<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`
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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:
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Configuration
|
||||
@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
|
||||
<2> Use `PathPatternParserServerWebExchangeMatcher` to state that this `SecurityWebFilterChain` will only apply to URL paths that start with `/api/`
|
||||
<3> Specify the authentication mechanisms that will be used for `/api/**` endpoints
|
||||
|
|
|
@ -4,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.
|
||||
|
||||
Below is an example of a reactive x509 security configuration:
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
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.
|
||||
|
||||
The next example demonstrates how these defaults can be overridden.
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
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.
|
||||
|
||||
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.
|
||||
|
|
|
@ -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:
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@GetMapping("/")
|
||||
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.
|
||||
The reason is that Spring MVC resolves the parameter using `HttpServletRequest#getPrincipal`, which is `null` when the request is anonymous.
|
||||
|
||||
|
|
|
@ -820,12 +820,22 @@ class DirectlyConfiguredJwkSetUri : WebSecurityConfigurerAdapter() {
|
|||
|
||||
Or similarly with method security:
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
public List<Message> getMessages(...) {}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@PreAuthorize("hasAuthority('SCOPE_messages')")
|
||||
fun getMessages(): List<Message> { }
|
||||
----
|
||||
====
|
||||
|
||||
[[oauth2resourceserver-jwt-authorization-extraction]]
|
||||
==== Extracting Authorities Manually
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
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.
|
||||
|
||||
[source,java]
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
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`.
|
||||
An example of the output is displayed below.
|
||||
|
||||
|
|
Loading…
Reference in New Issue