method.adoc 6.3 KB

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