method.adoc 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. [[jc-erms]]
  2. = EnableReactiveMethodSecurity
  3. Spring Security supports method security using https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] which is setup using `ReactiveSecurityContextHolder`.
  4. For example, this demonstrates how to retrieve the currently logged in user's message.
  5. [NOTE]
  6. ====
  7. For this to work the return type of the method must be a `org.reactivestreams.Publisher` (for example, `Mono`/`Flux`).
  8. This is necessary to integrate with Reactor's `Context`.
  9. ====
  10. [WARNING]
  11. ====
  12. Method Security also supports Kotlin coroutines, though only to a limited degree.
  13. When intercepting coroutines, only the first interceptor participates.
  14. If any other interceptors are present and come after Spring Security's method security interceptor, they will be skipped.
  15. ====
  16. ====
  17. .Java
  18. [source,java,role="primary"]
  19. ----
  20. Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
  21. Mono<String> messageByUsername = ReactiveSecurityContextHolder.getContext()
  22. .map(SecurityContext::getAuthentication)
  23. .map(Authentication::getName)
  24. .flatMap(this::findMessageByUsername)
  25. // In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
  26. .subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication));
  27. StepVerifier.create(messageByUsername)
  28. .expectNext("Hi user")
  29. .verifyComplete();
  30. ----
  31. .Kotlin
  32. [source,kotlin,role="secondary"]
  33. ----
  34. val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
  35. val messageByUsername: Mono<String> = ReactiveSecurityContextHolder.getContext()
  36. .map(SecurityContext::getAuthentication)
  37. .map(Authentication::getName)
  38. .flatMap(this::findMessageByUsername) // In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
  39. .subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication))
  40. StepVerifier.create(messageByUsername)
  41. .expectNext("Hi user")
  42. .verifyComplete()
  43. ----
  44. ====
  45. with `this::findMessageByUsername` defined as:
  46. ====
  47. .Java
  48. [source,java,role="primary"]
  49. ----
  50. Mono<String> findMessageByUsername(String username) {
  51. return Mono.just("Hi " + username);
  52. }
  53. ----
  54. .Kotlin
  55. [source,kotlin,role="secondary"]
  56. ----
  57. fun findMessageByUsername(username: String): Mono<String> {
  58. return Mono.just("Hi $username")
  59. }
  60. ----
  61. ====
  62. Below is a minimal method security configuration when using method security in reactive applications.
  63. ====
  64. .Java
  65. [source,java,role="primary"]
  66. ----
  67. @EnableReactiveMethodSecurity
  68. public class SecurityConfig {
  69. @Bean
  70. public MapReactiveUserDetailsService userDetailsService() {
  71. User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
  72. UserDetails rob = userBuilder.username("rob")
  73. .password("rob")
  74. .roles("USER")
  75. .build();
  76. UserDetails admin = userBuilder.username("admin")
  77. .password("admin")
  78. .roles("USER","ADMIN")
  79. .build();
  80. return new MapReactiveUserDetailsService(rob, admin);
  81. }
  82. }
  83. ----
  84. .Kotlin
  85. [source,kotlin,role="secondary"]
  86. ----
  87. @EnableReactiveMethodSecurity
  88. class SecurityConfig {
  89. @Bean
  90. fun userDetailsService(): MapReactiveUserDetailsService {
  91. val userBuilder: User.UserBuilder = User.withDefaultPasswordEncoder()
  92. val rob = userBuilder.username("rob")
  93. .password("rob")
  94. .roles("USER")
  95. .build()
  96. val admin = userBuilder.username("admin")
  97. .password("admin")
  98. .roles("USER", "ADMIN")
  99. .build()
  100. return MapReactiveUserDetailsService(rob, admin)
  101. }
  102. }
  103. ----
  104. ====
  105. Consider the following class:
  106. ====
  107. .Java
  108. [source,java,role="primary"]
  109. ----
  110. @Component
  111. public class HelloWorldMessageService {
  112. @PreAuthorize("hasRole('ADMIN')")
  113. public Mono<String> findMessage() {
  114. return Mono.just("Hello World!");
  115. }
  116. }
  117. ----
  118. .Kotlin
  119. [source,kotlin,role="secondary"]
  120. ----
  121. @Component
  122. class HelloWorldMessageService {
  123. @PreAuthorize("hasRole('ADMIN')")
  124. fun findMessage(): Mono<String> {
  125. return Mono.just("Hello World!")
  126. }
  127. }
  128. ----
  129. ====
  130. Or, the following class using Kotlin coroutines:
  131. ====
  132. .Kotlin
  133. [source,kotlin,role="primary"]
  134. ----
  135. @Component
  136. class HelloWorldMessageService {
  137. @PreAuthorize("hasRole('ADMIN')")
  138. suspend fun findMessage(): String {
  139. delay(10)
  140. return "Hello World!"
  141. }
  142. }
  143. ----
  144. ====
  145. Combined with our configuration above, `@PreAuthorize("hasRole('ADMIN')")` will ensure that `findByMessage` is only invoked by a user with the role `ADMIN`.
  146. It is important to note that any of the expressions in standard method security work for `@EnableReactiveMethodSecurity`.
  147. However, at this time we only support return type of `Boolean` or `boolean` of the expression.
  148. This means that the expression must not block.
  149. 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.
  150. ====
  151. .Java
  152. [source,java,role="primary"]
  153. ----
  154. @EnableWebFluxSecurity
  155. @EnableReactiveMethodSecurity
  156. public class SecurityConfig {
  157. @Bean
  158. SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
  159. return http
  160. // Demonstrate that method security works
  161. // Best practice to use both for defense in depth
  162. .authorizeExchange(exchanges -> exchanges
  163. .anyExchange().permitAll()
  164. )
  165. .httpBasic(withDefaults())
  166. .build();
  167. }
  168. @Bean
  169. MapReactiveUserDetailsService userDetailsService() {
  170. User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
  171. UserDetails rob = userBuilder.username("rob")
  172. .password("rob")
  173. .roles("USER")
  174. .build();
  175. UserDetails admin = userBuilder.username("admin")
  176. .password("admin")
  177. .roles("USER","ADMIN")
  178. .build();
  179. return new MapReactiveUserDetailsService(rob, admin);
  180. }
  181. }
  182. ----
  183. .Kotlin
  184. [source,kotlin,role="secondary"]
  185. ----
  186. @EnableWebFluxSecurity
  187. @EnableReactiveMethodSecurity
  188. class SecurityConfig {
  189. @Bean
  190. open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  191. return http {
  192. authorizeExchange {
  193. authorize(anyExchange, permitAll)
  194. }
  195. httpBasic { }
  196. }
  197. }
  198. @Bean
  199. fun userDetailsService(): MapReactiveUserDetailsService {
  200. val userBuilder: User.UserBuilder = User.withDefaultPasswordEncoder()
  201. val rob = userBuilder.username("rob")
  202. .password("rob")
  203. .roles("USER")
  204. .build()
  205. val admin = userBuilder.username("admin")
  206. .password("admin")
  207. .roles("USER", "ADMIN")
  208. .build()
  209. return MapReactiveUserDetailsService(rob, admin)
  210. }
  211. }
  212. ----
  213. ====
  214. You can find a complete sample in {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method]