method.adoc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. [[jc-erms]]
  2. = EnableReactiveMethodSecurity
  3. Spring Security supports method security by using https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context], which is set up by `ReactiveSecurityContextHolder`.
  4. The following example shows how to retrieve the currently logged in user's message:
  5. [NOTE]
  6. ====
  7. For this example to work, the return type of the method must be a `org.reactivestreams.Publisher` (that is, a `Mono` or a `Flux`).
  8. This is necessary to integrate with Reactor's `Context`.
  9. ====
  10. [[jc-enable-reactive-method-security-authorization-manager]]
  11. == EnableReactiveMethodSecurity with AuthorizationManager
  12. In Spring Security 5.8, we can enable annotation-based security using the `@EnableReactiveMethodSecurity(useAuthorizationManager=true)` annotation on any `@Configuration` instance.
  13. This improves upon `@EnableReactiveMethodSecurity` in a number of ways. `@EnableReactiveMethodSecurity(useAuthorizationManager=true)`:
  14. 1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters.
  15. This simplifies reuse and customization.
  16. 2. Supports reactive return types including Kotlin coroutines.
  17. 3. Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize
  18. 4. Checks for conflicting annotations to ensure an unambiguous security configuration
  19. 5. Complies with JSR-250
  20. [NOTE]
  21. ====
  22. For earlier versions, please read about similar support with <<jc-enable-reactive-method-security, @EnableReactiveMethodSecurity>>.
  23. ====
  24. For example, the following would enable Spring Security's `@PreAuthorize` annotation:
  25. .Method Security Configuration
  26. [tabs]
  27. ======
  28. Java::
  29. +
  30. [source,java,role="primary"]
  31. ----
  32. @EnableReactiveMethodSecurity(useAuthorizationManager=true)
  33. public class MethodSecurityConfig {
  34. // ...
  35. }
  36. ----
  37. ======
  38. Adding an annotation to a method (on a class or interface) would then limit the access to that method accordingly.
  39. Spring Security's native annotation support defines a set of attributes for the method.
  40. These will be passed to the various method interceptors, like `AuthorizationManagerBeforeReactiveMethodInterceptor`, for it to make the actual decision:
  41. .Method Security Annotation Usage
  42. [tabs]
  43. ======
  44. Java::
  45. +
  46. [source,java,role="primary"]
  47. ----
  48. public interface BankService {
  49. @PreAuthorize("hasRole('USER')")
  50. Mono<Account> readAccount(Long id);
  51. @PreAuthorize("hasRole('USER')")
  52. Flux<Account> findAccounts();
  53. @PreAuthorize("@func.apply(#account)")
  54. Mono<Account> post(Account account, Double amount);
  55. }
  56. ----
  57. ======
  58. In this case `hasRole` refers to the method found in `SecurityExpressionRoot` that gets invoked by the SpEL evaluation engine.
  59. `@bean` refers to a custom component you have defined, where `apply` can return `Boolean` or `Mono<Boolean>` to indicate the authorization decision.
  60. A bean like that might look something like this:
  61. .Method Security Reactive Boolean Expression
  62. [tabs]
  63. ======
  64. Java::
  65. +
  66. [source,java,role="primary"]
  67. ----
  68. @Bean
  69. public Function<Account, Mono<Boolean>> func() {
  70. return (account) -> Mono.defer(() -> Mono.just(account.getId().equals(12)));
  71. }
  72. ----
  73. ======
  74. Method authorization is a combination of before- and after-method authorization.
  75. [NOTE]
  76. ====
  77. Before-method authorization is performed before the method is invoked.
  78. If that authorization denies access, the method is not invoked, and an `AccessDeniedException` is thrown.
  79. After-method authorization is performed after the method is invoked, but before the method returns to the caller.
  80. If that authorization denies access, the value is not returned, and an `AccessDeniedException` is thrown
  81. ====
  82. To recreate what adding `@EnableReactiveMethodSecurity(useAuthorizationManager=true)` does by default, you would publish the following configuration:
  83. .Full Pre-post Method Security Configuration
  84. [tabs]
  85. ======
  86. Java::
  87. +
  88. [source,java,role="primary"]
  89. ----
  90. @Configuration
  91. class MethodSecurityConfig {
  92. @Bean
  93. BeanDefinitionRegistryPostProcessor aopConfig() {
  94. return AopConfigUtils::registerAutoProxyCreatorIfNecessary;
  95. }
  96. @Bean
  97. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  98. PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor() {
  99. return new PreFilterAuthorizationReactiveMethodInterceptor();
  100. }
  101. @Bean
  102. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  103. AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor() {
  104. return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize();
  105. }
  106. @Bean
  107. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  108. AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor() {
  109. return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize();
  110. }
  111. @Bean
  112. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  113. PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor() {
  114. return new PostFilterAuthorizationReactiveMethodInterceptor();
  115. }
  116. }
  117. ----
  118. ======
  119. Notice that Spring Security's method security is built using Spring AOP.
  120. === Customizing Authorization
  121. Spring Security's `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` ship with rich expression-based support.
  122. [[jc-reactive-method-security-custom-granted-authority-defaults]]
  123. Also, for role-based authorization, Spring Security adds a default `ROLE_` prefix, which is uses when evaluating expressions like `hasRole`.
  124. You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
  125. .Custom GrantedAuthorityDefaults
  126. [tabs]
  127. ======
  128. Java::
  129. +
  130. [source,java,role="primary"]
  131. ----
  132. @Bean
  133. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  134. static GrantedAuthorityDefaults grantedAuthorityDefaults() {
  135. return new GrantedAuthorityDefaults("MYPREFIX_");
  136. }
  137. ----
  138. ======
  139. [TIP]
  140. ====
  141. We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes.
  142. Since the `GrantedAuthorityDefaults` bean is part of internal workings of Spring Security, we should also expose it as an infrastructural bean effectively avoiding some warnings related to bean post-processing (see https://github.com/spring-projects/spring-security/issues/14751[gh-14751]).
  143. ====
  144. [[use-programmatic-authorization]]
  145. == Authorizing Methods Programmatically
  146. As you've already seen, there are several ways that you can specify non-trivial authorization rules using xref:servlet/authorization/method-security.adoc#authorization-expressions[Method Security SpEL expressions].
  147. There are a number of ways that you can instead allow your logic to be Java-based instead of SpEL-based.
  148. This gives use access the entire Java language for increased testability and flow control.
  149. === Using a Custom Bean in SpEL
  150. The first way to authorize a method programmatically is a two-step process.
  151. First, declare a bean that has a method that takes a `MethodSecurityExpressionOperations` instance like the following:
  152. [tabs]
  153. ======
  154. Java::
  155. +
  156. [source,java,role="primary"]
  157. ----
  158. @Component("authz")
  159. public class AuthorizationLogic {
  160. public decide(MethodSecurityExpressionOperations operations): Mono<Boolean> {
  161. // ... authorization logic
  162. }
  163. }
  164. ----
  165. Kotlin::
  166. +
  167. [source,kotlin,role="secondary"]
  168. ----
  169. @Component("authz")
  170. open class AuthorizationLogic {
  171. fun decide(val operations: MethodSecurityExpressionOperations): Mono<Boolean> {
  172. // ... authorization logic
  173. }
  174. }
  175. ----
  176. ======
  177. Then, reference that bean in your annotations in the following way:
  178. [tabs]
  179. ======
  180. Java::
  181. +
  182. [source,java,role="primary"]
  183. ----
  184. @Controller
  185. public class MyController {
  186. @PreAuthorize("@authz.decide(#root)")
  187. @GetMapping("/endpoint")
  188. public Mono<String> endpoint() {
  189. // ...
  190. }
  191. }
  192. ----
  193. Kotlin::
  194. +
  195. [source,kotlin,role="secondary"]
  196. ----
  197. @Controller
  198. open class MyController {
  199. @PreAuthorize("@authz.decide(#root)")
  200. @GetMapping("/endpoint")
  201. fun endpoint(): Mono<String> {
  202. // ...
  203. }
  204. }
  205. ----
  206. ======
  207. Spring Security will invoke the given method on that bean for each method invocation.
  208. What's nice about this is all your authorization logic is in a separate class that can be independently unit tested and verified for correctness.
  209. It also has access to the full Java language.
  210. [TIP]
  211. In addition to returning a `Mono<Boolean>`, you can also return `Mono.empty()` to indicate that the code abstains from making a decision.
  212. If you want to include more information about the nature of the decision, you can instead return a custom `AuthorizationDecision` like this:
  213. [tabs]
  214. ======
  215. Java::
  216. +
  217. [source,java,role="primary"]
  218. ----
  219. @Component("authz")
  220. public class AuthorizationLogic {
  221. public Mono<AuthorizationDecision> decide(MethodSecurityExpressionOperations operations) {
  222. // ... authorization logic
  223. return Mono.just(new MyAuthorizationDecision(false, details));
  224. }
  225. }
  226. ----
  227. Kotlin::
  228. +
  229. [source,kotlin,role="secondary"]
  230. ----
  231. @Component("authz")
  232. open class AuthorizationLogic {
  233. fun decide(val operations: MethodSecurityExpressionOperations): Mono<AuthorizationDecision> {
  234. // ... authorization logic
  235. return Mono.just(MyAuthorizationDecision(false, details))
  236. }
  237. }
  238. ----
  239. ======
  240. Or throw a custom `AuthorizationDeniedException` instance.
  241. Note, though, that returning an object is preferred as this doesn't incur the expense of generating a stacktrace.
  242. Then, you can access the custom details when you xref:servlet/authorization/method-security.adoc#fallback-values-authorization-denied[customize how the authorization result is handled].
  243. [[jc-reactive-method-security-custom-authorization-manager]]
  244. [[custom-authorization-managers]]
  245. === Using a Custom Authorization Manager
  246. The second way to authorize a method programmatically is to create a custom xref:servlet/authorization/architecture.adoc#_the_authorizationmanager[`AuthorizationManager`].
  247. First, declare an authorization manager instance, perhaps like this one:
  248. [tabs]
  249. ======
  250. Java::
  251. +
  252. [source,java,role="primary"]
  253. ----
  254. @Component
  255. public class MyPreAuthorizeAuthorizationManager implements ReactiveAuthorizationManager<MethodInvocation> {
  256. @Override
  257. public Mono<AuthorizationDecision> check(Supplier<Authentication> authentication, MethodInvocation invocation) {
  258. // ... authorization logic
  259. }
  260. }
  261. ----
  262. Kotlin::
  263. +
  264. [source,kotlin,role="secondary"]
  265. ----
  266. @Component
  267. class MyPreAuthorizeAuthorizationManager : ReactiveAuthorizationManager<MethodInvocation> {
  268. override fun check(authentication: Supplier<Authentication>, invocation: MethodInvocation): Mono<AuthorizationDecision> {
  269. // ... authorization logic
  270. }
  271. }
  272. ----
  273. ======
  274. Then, publish the method interceptor with a pointcut that corresponds to when you want that `ReactiveAuthorizationManager` to run.
  275. For example, you could replace how `@PreAuthorize` and `@PostAuthorize` work like so:
  276. .Only @PreAuthorize and @PostAuthorize Configuration
  277. [tabs]
  278. ======
  279. Java::
  280. +
  281. [source,java,role="primary"]
  282. ----
  283. @Configuration
  284. @EnableMethodSecurity(prePostEnabled = false)
  285. class MethodSecurityConfig {
  286. @Bean
  287. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  288. Advisor preAuthorize(MyPreAuthorizeAuthorizationManager manager) {
  289. return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(manager);
  290. }
  291. @Bean
  292. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  293. Advisor postAuthorize(MyPostAuthorizeAuthorizationManager manager) {
  294. return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(manager);
  295. }
  296. }
  297. ----
  298. Kotlin::
  299. +
  300. [source,kotlin,role="secondary"]
  301. ----
  302. @Configuration
  303. @EnableMethodSecurity(prePostEnabled = false)
  304. class MethodSecurityConfig {
  305. @Bean
  306. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  307. fun preAuthorize(val manager: MyPreAuthorizeAuthorizationManager) : Advisor {
  308. return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(manager)
  309. }
  310. @Bean
  311. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  312. fun postAuthorize(val manager: MyPostAuthorizeAuthorizationManager) : Advisor {
  313. return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(manager)
  314. }
  315. }
  316. ----
  317. ======
  318. [TIP]
  319. ====
  320. You can place your interceptor in between Spring Security method interceptors using the order constants specified in `AuthorizationInterceptorsOrder`.
  321. ====
  322. [[customizing-expression-handling]]
  323. === Customizing Expression Handling
  324. Or, third, you can customize how each SpEL expression is handled.
  325. To do that, you can expose a custom `MethodSecurityExpressionHandler`, like so:
  326. .Custom MethodSecurityExpressionHandler
  327. [tabs]
  328. ======
  329. Java::
  330. +
  331. [source,java,role="primary"]
  332. ----
  333. @Bean
  334. static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
  335. DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
  336. handler.setRoleHierarchy(roleHierarchy);
  337. return handler;
  338. }
  339. ----
  340. Kotlin::
  341. +
  342. [source,kotlin,role="secondary"]
  343. ----
  344. companion object {
  345. @Bean
  346. fun methodSecurityExpressionHandler(val roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler {
  347. val handler = DefaultMethodSecurityExpressionHandler()
  348. handler.setRoleHierarchy(roleHierarchy)
  349. return handler
  350. }
  351. }
  352. ----
  353. ======
  354. [TIP]
  355. ====
  356. We expose `MethodSecurityExpressionHandler` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
  357. ====
  358. You can also subclass xref:servlet/authorization/method-security.adoc#subclass-defaultmethodsecurityexpressionhandler[`DefaultMessageSecurityExpressionHandler`] to add your own custom authorization expressions beyond the defaults.
  359. == EnableReactiveMethodSecurity
  360. [tabs]
  361. ======
  362. Java::
  363. +
  364. [source,java,role="primary"]
  365. ----
  366. Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
  367. Mono<String> messageByUsername = ReactiveSecurityContextHolder.getContext()
  368. .map(SecurityContext::getAuthentication)
  369. .map(Authentication::getName)
  370. .flatMap(this::findMessageByUsername)
  371. // In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
  372. .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));
  373. StepVerifier.create(messageByUsername)
  374. .expectNext("Hi user")
  375. .verifyComplete();
  376. ----
  377. Kotlin::
  378. +
  379. [source,kotlin,role="secondary"]
  380. ----
  381. val authentication: Authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
  382. val messageByUsername: Mono<String> = ReactiveSecurityContextHolder.getContext()
  383. .map(SecurityContext::getAuthentication)
  384. .map(Authentication::getName)
  385. .flatMap(this::findMessageByUsername) // In a WebFlux application the `subscriberContext` is automatically setup using `ReactorContextWebFilter`
  386. .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
  387. StepVerifier.create(messageByUsername)
  388. .expectNext("Hi user")
  389. .verifyComplete()
  390. ----
  391. ======
  392. Where `this::findMessageByUsername` is defined as:
  393. [tabs]
  394. ======
  395. Java::
  396. +
  397. [source,java,role="primary"]
  398. ----
  399. Mono<String> findMessageByUsername(String username) {
  400. return Mono.just("Hi " + username);
  401. }
  402. ----
  403. Kotlin::
  404. +
  405. [source,kotlin,role="secondary"]
  406. ----
  407. fun findMessageByUsername(username: String): Mono<String> {
  408. return Mono.just("Hi $username")
  409. }
  410. ----
  411. ======
  412. The following minimal method security configures method security in reactive applications:
  413. [tabs]
  414. ======
  415. Java::
  416. +
  417. [source,java,role="primary"]
  418. ----
  419. @Configuration
  420. @EnableReactiveMethodSecurity
  421. public class SecurityConfig {
  422. @Bean
  423. public MapReactiveUserDetailsService userDetailsService() {
  424. User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
  425. UserDetails rob = userBuilder.username("rob")
  426. .password("rob")
  427. .roles("USER")
  428. .build();
  429. UserDetails admin = userBuilder.username("admin")
  430. .password("admin")
  431. .roles("USER","ADMIN")
  432. .build();
  433. return new MapReactiveUserDetailsService(rob, admin);
  434. }
  435. }
  436. ----
  437. Kotlin::
  438. +
  439. [source,kotlin,role="secondary"]
  440. ----
  441. @Configuration
  442. @EnableReactiveMethodSecurity
  443. class SecurityConfig {
  444. @Bean
  445. fun userDetailsService(): MapReactiveUserDetailsService {
  446. val userBuilder: User.UserBuilder = User.withDefaultPasswordEncoder()
  447. val rob = userBuilder.username("rob")
  448. .password("rob")
  449. .roles("USER")
  450. .build()
  451. val admin = userBuilder.username("admin")
  452. .password("admin")
  453. .roles("USER", "ADMIN")
  454. .build()
  455. return MapReactiveUserDetailsService(rob, admin)
  456. }
  457. }
  458. ----
  459. ======
  460. Consider the following class:
  461. [tabs]
  462. ======
  463. Java::
  464. +
  465. [source,java,role="primary"]
  466. ----
  467. @Component
  468. public class HelloWorldMessageService {
  469. @PreAuthorize("hasRole('ADMIN')")
  470. public Mono<String> findMessage() {
  471. return Mono.just("Hello World!");
  472. }
  473. }
  474. ----
  475. Kotlin::
  476. +
  477. [source,kotlin,role="secondary"]
  478. ----
  479. @Component
  480. class HelloWorldMessageService {
  481. @PreAuthorize("hasRole('ADMIN')")
  482. fun findMessage(): Mono<String> {
  483. return Mono.just("Hello World!")
  484. }
  485. }
  486. ----
  487. ======
  488. Alternatively, the following class uses Kotlin coroutines:
  489. [tabs]
  490. ======
  491. Kotlin::
  492. +
  493. [source,kotlin,role="primary"]
  494. ----
  495. @Component
  496. class HelloWorldMessageService {
  497. @PreAuthorize("hasRole('ADMIN')")
  498. suspend fun findMessage(): String {
  499. delay(10)
  500. return "Hello World!"
  501. }
  502. }
  503. ----
  504. ======
  505. Combined with our configuration above, `@PreAuthorize("hasRole('ADMIN')")` ensures that `findByMessage` is invoked only by a user with the `ADMIN` role.
  506. Note that any of the expressions in standard method security work for `@EnableReactiveMethodSecurity`.
  507. However, at this time, we support only a return type of `Boolean` or `boolean` of the expression.
  508. This means that the expression must not block.
  509. 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:
  510. [tabs]
  511. ======
  512. Java::
  513. +
  514. [source,java,role="primary"]
  515. ----
  516. @Configuration
  517. @EnableWebFluxSecurity
  518. @EnableReactiveMethodSecurity
  519. public class SecurityConfig {
  520. @Bean
  521. SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
  522. return http
  523. // Demonstrate that method security works
  524. // Best practice to use both for defense in depth
  525. .authorizeExchange((authorize) -> authorize
  526. .anyExchange().permitAll()
  527. )
  528. .httpBasic(withDefaults())
  529. .build();
  530. }
  531. @Bean
  532. MapReactiveUserDetailsService userDetailsService() {
  533. User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
  534. UserDetails rob = userBuilder.username("rob")
  535. .password("rob")
  536. .roles("USER")
  537. .build();
  538. UserDetails admin = userBuilder.username("admin")
  539. .password("admin")
  540. .roles("USER","ADMIN")
  541. .build();
  542. return new MapReactiveUserDetailsService(rob, admin);
  543. }
  544. }
  545. ----
  546. Kotlin::
  547. +
  548. [source,kotlin,role="secondary"]
  549. ----
  550. @Configuration
  551. @EnableWebFluxSecurity
  552. @EnableReactiveMethodSecurity
  553. class SecurityConfig {
  554. @Bean
  555. open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
  556. return http {
  557. authorizeExchange {
  558. authorize(anyExchange, permitAll)
  559. }
  560. httpBasic { }
  561. }
  562. }
  563. @Bean
  564. fun userDetailsService(): MapReactiveUserDetailsService {
  565. val userBuilder: User.UserBuilder = User.withDefaultPasswordEncoder()
  566. val rob = userBuilder.username("rob")
  567. .password("rob")
  568. .roles("USER")
  569. .build()
  570. val admin = userBuilder.username("admin")
  571. .password("admin")
  572. .roles("USER", "ADMIN")
  573. .build()
  574. return MapReactiveUserDetailsService(rob, admin)
  575. }
  576. }
  577. ----
  578. ======
  579. You can find a complete sample in {gh-samples-url}/reactive/webflux/java/method[hellowebflux-method].