Spring Webflux Rest Basic Authentication

Spring Webflux Rest Basic Authentication thumbnail
49K
By Dhiraj 06 June, 2019

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.

pom.xml
<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.

UserRepository.java
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);
    }

}
User.java
@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
        
    }
AuthenticatedUser.java
public class AuthenticatedUser implements UserDetails {
    private String username;
    private Collection authorities;

    public AuthenticatedUser(String username, Collection authorities){
        this.username = username;
        this.authorities = authorities;
    }

    @Override
    public Collection 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.

UserController.java
@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.

Share

If You Appreciate This, You Can Consider:

We are thankful for your never ending support.

About The Author

author-image
A technology savvy professional with an exceptional capacity to analyze, solve problems and multi-task. Technical expertise in highly scalable distributed systems, self-healing systems, and service-oriented architecture. Technical Skills: Java/J2EE, Spring, Hibernate, Reactive Programming, Microservices, Hystrix, Rest APIs, Java 8, Kafka, Kibana, Elasticsearch, etc.

Further Reading on Spring Security