With the release of Spring Security 5, one of the new features is the WebFlux for securing reactive applications. In this article, we will be discussing about securing REST endpoints exposed through reactive applications.

At first, we will make configuration to use basic authentication httpBasic() to secure the reactive REST endpoints and then in the next article we have extended this example to provide token-based custom authentication using JWT. The authorization process will be role-based and we will be using method based reactive security using @PreAuthorize .

To secure non-reactive endpoints, you can visit my another article here.

You can visit Spring Security Tutorial for all the list of tutorials.

What is Basic Authentication

Basic authentication is a standard HTTP header with the user and password encoded in base64 : Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==. The username and password is encoded in the format username:password. This is one of the simplest technique to protect the REST resources because it does not require cookies. session identifiers or any login pages.

Project Setup

Head over to Spring Initiliazer to download a sample spring boot application with below artifacts. We will be using spring boot 2.1.5.RELEASE.

Below is the pom file.

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies>

Spring Boot Web Flux Security Configuration

We have defined SecurityWebFilterChain and MapReactiveUserDetailsService bean to configure a basic flux security. The class must be annotated with @EnableWebFluxSecurity to enable the flux security for a web app. To get started, we have used in-memory user user details for basic authentication.

@Configuration @EnableWebFluxSecurity public class SecurityConfig { @Bean public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) { return http .csrf().disable() .authorizeExchange() .pathMatchers("/").permitAll() .anyExchange().authenticated() .and() .httpBasic() .and() .formLogin().disable() .build(); } @Bean public MapReactiveUserDetailsService userDetailsService() { UserDetails user = User.builder() .username("user") .password("{noop}user") .roles("USER") .build(); UserDetails admin = User.builder() .username("admin") .password("{noop}admin") .roles("ADMIN") .build(); return new MapReactiveUserDetailsService(user, admin); } }

Above configuration allows the context path(/) to be publicly accessible without any authentication whereas other REST endpoints require basic authentication to be accessible.

With httpBasic() enables the basic authentication

Password is prefixed with {noop} to indicate to DelegatingPasswordEncoder that NoOpPasswordEncoder should be used.

As we have not explicitly configured role based access, all the secured endpoints is accessible by any role. We will enable method based security in coming sections.

DB Configurations for MapReactiveUserDetailsService

Spring supports reactive repository with Mongo DB. Hence, let us first add the maven dependency to enable it.

You caan follow this article for different ways to configure MongoDB with spring boot.

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>

You can follow this article to learn about CRUD implementation with Spring Boot and MongoDB.

Now, we can define our repository and implementation of ReactiveUserDetailsService.

public interface UserRepository extends MongoRepository { Mono findByUsername(String username); }

@Service public class UserDetailsService implements ReactiveUserDetailsService { @Autowired private UserRepository userRepository; @Override public Mono findByUsername(String username) { return userRepository.findByUsername(username).switchIfEmpty(Mono.defer(() -> Mono.error(new UsernameNotFoundException("User Not Found")))).map(User::toAuthUser); } }

@Document public class User { private String id; private String username; private String password; private String role; getters and setters public AuthenticatedUser toAuthUser() { // returns a AuthenticatedUser object }

public class AuthenticatedUser implements UserDetails { private String username; private Collection extends GrantedAuthority> authorities; public AuthenticatedUser(String username, Collection extends GrantedAuthority> authorities){ this.username = username; this.authorities = authorities; } @Override public Collection extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public String getPassword() { return null; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }

Reactive REST Endpoints Implementation

Below is the sample controller that exposes 3 REST endpoints each for no auth required, accessible with USER role("/user") and accessible with ADMIN role("/admin").

As we have not explicitly configured role based access, all the secured endpoints is accessible by any role. We will enable method based security in coming sections.

@RestController public class UserController { @GetMapping("/") public Mono welcome() { return Mono.just("Welcome !"); } @GetMapping("/user") public Mono greetUser(Mono principal) { return principal .map(Principal::getName) .map(name -> String.format("Hello, %s", name)); } @GetMapping("/admin") public Mono greetAdmin(Mono principal) { return principal .map(Principal::getName) .map(name -> String.format("Hello, %s", name)); } }

Now, if we try to access the URL http://localhost:8080/admin without Basic Auth, the response status will be a 401 Unauthorized. The important thing to note here is the default response header added by Spring Security.

Spring Security Default Headers

The default for Spring Security is to include the following headers:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1; mode=block Referrer-Policy ?no-referrer

Enable Method Level Security

Enabling method level security is extremely easy to configure. We need to annotate our SecurityConfig class with @EnableReactiveMethodSecurity and then we can use it at method level in our controller.

Now the endpoint /user is accessible with user having role USER .

@PreAuthorize("hasRole('USER')") @GetMapping("/user") public Mono greetUser(Mono principal) { return principal .map(Principal::getName) .map(name -> String.format("Hello, %s", name)); } @PreAuthorize("hasRole('ADMIN')") @GetMapping("/admin") public Mono greetAdmin(Mono principal) { return principal .map(Principal::getName) .map(name -> String.format("Hello, %s", name)); }

Exception Handling

We can define our custom authentication entry point for exception handling. Below is a sample to handle exceptions with reactive ExceptionhandlingSpec in our SecurityConfig.java under securityFilterChain

... .and() .exceptionHandling() .authenticationEntryPoint((swe, e) -> Mono.fromRunnable(() -> { swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); })).accessDeniedHandler((swe, e) -> Mono.fromRunnable(() -> { swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN); }))

Conclusion

In this example, we created reactive REST endpoints and used spring web flux rest authentication to secure those endpoints. Here, we used basic authentication to secure these endpoints. In the next article, we will create custom token based authentication and authorization using JWT.