Scratch the edge using Spring WebFlux (Part 2)
Tags: Spring Boot, Kafka, Spring, Mongodb, Web Development

Photo by Jonathan Cooper on Unsplash

We know Bob from the previous story and he wants to use his service right now, and he could do it but wait! this is his exclusive service, is he alone right now?

He and the whole world can use this service forever. let’s limit the alien’s access by Spring Security.

First thing first, we need a model for our users (lovely Bob is alone) so let’s create one as the follow in kotlin>com>{something you know}>{project name, that also you know}>model

@Document

data class User(

@Id

val id : ObjectId,

@Indexed(unique = true)

val name: String,

val pass: String,

val role: String

) : UserDetails {



override fun getAuthorities(): MutableCollection<out GrantedAuthority> {

return mutableListOf(SimpleGrantedAuthority(role))

}



override fun getPassword(): String {

return pass

}



override fun getUsername(): String {

return name

}



override fun isAccountNonExpired() = true



override fun isAccountNonLocked() = true



override fun isCredentialsNonExpired() = true



override fun isEnabled() = true

}

You can see two elegant things in one frame, the flexibility of Spring Security and the conciseness of Kotlin (by comparing two different types of method).

# Note that you can’t use `username` and `password` fields in this special data model because of some getter and setter conflicts.

And in kotlin>com>{something you know}>{project name, that also you know}>repository

interface UserRepository : ReactiveMongoRepository<User,ObjectId>{

fun findByName(name: String) : Mono<User>

}

There is a problem before continuing, Bob is too lazy. He uses the same password everywhere then, we have to protect his password by saving an encrypted version of his password, following class will help us

@Configuration

class PasswordEncoderConfig {



@Bean

fun getEncoder(): PasswordEncoder {

return BCryptPasswordEncoder()

}

}

Now, we need an authentication manager, it simply checks users claim and let them continue if true, so create `AuthenticationManager` as follow in kotlin>com>{something you know}>{project name, that also you know}>security

@Component

class AuthenticationManager(private val encoder: PasswordEncoder,

private val userRepository: UserRepository) : ReactiveAuthenticationManager { override fun authenticate(authentication: Authentication): Mono<Authentication> {

val pass = authentication.credentials.toString()

val name = authentication.principal.toString()



return userRepository.findByName(name).filter { user ->

encoder.matches(pass, user.password)

}.map { user ->

UsernamePasswordAuthenticationToken(user.name, user.pass, user.authorities)

}

}

}

Let’s prepare and play with our authentication manager in Spring Security as follow in kotlin>com>{something you know}>{project name, that also you know}>security

@Component

class SecurityContextChain(private val authenticationManager: AuthenticationManager) : ServerSecurityContextRepository {

override fun save(exchange: ServerWebExchange?, context: SecurityContext?): Mono<Void> {

throw UnsupportedOperationException("Not Supported yet ...")

}



override fun load(exchange: ServerWebExchange): Mono<SecurityContext> {

val request = exchange.request

val authHeader = request.headers.getFirst(HttpHeaders.AUTHORIZATION)

return if (authHeader != null && authHeader.length > 6 && authHeader.startsWith("Basic ")) {

// split token part

val token = authHeader.substring(6)

// decode the token

val bytes = Base64.getDecoder().decode(token)

// convert decoded token to Sting

val credentials = String(bytes, StandardCharsets.UTF_8)

// split username and password

val values = credentials.split(Regex(":"), 2)



val auth = UsernamePasswordAuthenticationToken(values[0],values[1])



authenticationManager.authenticate(auth).map { authentication ->

SecurityContextImpl(authentication)

}

} else {

Mono.empty()

}

}

}

It gets the auth header from the request, prepare it (check, decode, split) and pass it to our Authentication Manager.

Now we need to configure Spring Security as follow in kotlin>com>{something you know}>{project name, that also you know}>security

@EnableWebFluxSecurity

@EnableReactiveMethodSecurity

class SecurityConfig(private val authenticationManager: ReactiveAuthenticationManager,

private val securityContextRepository: ServerSecurityContextRepository) {

@Bean

fun setSecurityConfigure(http : ServerHttpSecurity) : SecurityWebFilterChain{

return http

.formLogin()1️⃣

.disable()

.authenticationManager(authenticationManager)2️⃣

.securityContextRepository(securityContextRepository)

.authorizeExchange()

.anyExchange()3️⃣

.authenticated()

.and()

.build()



}

}

We’ve disabled the login page (create by default by Spring Security) Introduced our auth staffs And “Please, secure all of my endpoints” we’ve told Spring Security

It’s done, our application is secure now, but anyone can not use it even Bob! we need to include Bob as our users as follow in kotlin>com>{something you know}>{project name, that also you know}>extra

@Component

class InitBean(private val userRepository: UserRepository,

private val passwordEncoder: PasswordEncoder) : CommandLineRunner {



private val userName = "Bob"

private val password = "BobLikesBobWithDoubleO"



override fun run(vararg args: String?) {

userRepository.findByName(userName).switchIfEmpty {

val user = User(ObjectId.get(), userName, passwordEncoder.encode(password),"USER")

userRepository.save(user)

}.subscribe()

}



}

Now Bob can request and enjoy his response with his username and password.

# You can find source code on Github

# You can find the previous story, here.