DEV Community

sayf21
sayf21

Posted on

Security, Cloud with JWT and WebFlux

Spring Security is a pretty cool thing, on the subject of which there are many guides, articles on various platforms. But the problem is that a lot of these videos are limited to monolithic architecture. In this article I want to talk about my personal experience of using it for microservices. This is an exceptionally personal experience that I would like to share, and maybe it will be useful to someone.

Image description

This article will cover the following:

  • Mechanism for registering and issuing JWT tokens to users (briefly)
  • Authorization mechanism (briefly)
  • Security applications based on user roles

Applied technologies:

  • Spring Boot
  • Spring Cloud
  • Spring Security
  • JWT
  • WebFlux

The mechanics of queries, I think, are clear to many. If not, the picture below will briefly explain everything.

Image description

A request is received from the user. It is redirected to the port of the deployed Gateway, the name of the microservice is substituted, and then the usual end points of the specified microservice go. For example: localhost:8888/microserviceName/users.

*Let's move on to the most interesting!
*

I suggest a little run through the microservice of registration, storing users in the database and issuing JWT tokens. Suppose there is a certain Person entity, which contains Id, username, password, role.

Method of creating a user from User Service:

public AuthResponse createPerson(PersonDto dto) {
        Person personEntity = mapper.dtoToPerson(dto);
        personEntity.setRole(Role.USER);
        personEntity.setPassword(BCrypt.hashpw(dto.getPassword(), BCrypt.gensalt()));

        repository.save(personEntity);
        return getAuthResponse(personEntity);
    }

private AuthResponse getAuthResponse(Person personEntity) {
        String accessToken = jwt.generate(personEntity, accessType);
        String refreshToken = jwt.generate(personEntity, refreshType);
        return new AuthResponse(accessToken, refreshToken, personEntity.getMRID());
    }
Enter fullscreen mode Exit fullscreen mode

Note that in line 4 we hash the password and store it in the database in encrypted form. There are a lot of useful articles and videos on the topic of generating JWT tokens on the Internet. This article is mostly devoted to the Security of our application.

Now let's get to the most interesting part. Api Gateway! Let's dwell on it in more detail.

Required dependencies:

    implementation 'org.springframework.boot:spring-boot-starter-security:2.6.8'
    implementation 'org.springframework.boot:spring-boot-starter-webflux:2.6.8'
    implementation 'org.springframework:spring-webmvc:5.3.22'

    implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.1'
Enter fullscreen mode Exit fullscreen mode

Security config:

@EnableWebFluxSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final AuthenticationManager authenticationManager;
    private final SecurityContextRepository securityContextRepository;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
                .csrf()
                .disable()
                .authenticationManager(authenticationManager)
                .securityContextRepository(securityContextRepository)
                .authorizeExchange()
                .pathMatchers("/microservice1/users").permitAll()
                .pathMatchers("/microservice2/emails").authenticated()
                .pathMatchers("/microservice3/persons").hasAuthority("ADMIN")
                .anyExchange()
                .permitAll()
                .and()
                .httpBasic()
                .disable()
                .formLogin()
                .disable();
        return http.build();
    }
}
Enter fullscreen mode Exit fullscreen mode

Note that we are no longer using @EnableWebSecurity, but @EnableWebFluxSecurity. This annotation is necessary, it allows us to implement Security in Gateway, reactively running through microservices.

As we know, inheritance of WebSecurityConfigurerAdapter is deprecated. Therefore, we implement the Security Web Filter Chain and describe the required functionality in it.

There are two important things in 5,6 lines, namely: authentication Manager, security Context Repository.

To begin with, consider the Security Context Repository:

@Component
@RequiredArgsConstructor
public class SecurityContextRepository implements ServerSecurityContextRepository {

    private final AuthenticationManager authenticationManager;

    @Override
    public Mono<Void> save(ServerWebExchange swe, SecurityContext sc) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Mono<SecurityContext> load(ServerWebExchange swe) {
        Mono<String> stringMono = Mono.justOrEmpty(swe.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
        return stringMono.flatMap(this::getSecurityContext);
    }

    private Mono<? extends SecurityContext> getSecurityContext(String token) {
        Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
        return authenticationManager.authenticate(auth).map(SecurityContextImpl::new);
    }
}
Enter fullscreen mode Exit fullscreen mode

To briefly answer what is happening here, we take the Authorization header from the request and send it to the authenticate method from the AuthenticationManager.

And here is the Authentication Manager:

@Lazy
@Component
@RequiredArgsConstructor
public class AuthenticationManager implements ReactiveAuthenticationManager {

    private final Builder webClient;

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        String jwtToken = authentication.getCredentials().toString();
        return tokenValidate(jwtToken)
                .bodyToMono(UserAuthorities.class)
                .map(this::getAuthorities);
    }

    private UsernamePasswordAuthenticationToken getAuthorities(UserAuthorities userAuthorities) {
        return new UsernamePasswordAuthenticationToken(
                userAuthorities.getUsername(), null,
                userAuthorities.getAuthorities().stream()
                        .map(SimpleGrantedAuthority::new)
                        .collect(Collectors.toList()));
    }

    private ResponseSpec tokenValidate(String token) {
        return webClient.build()
                .get()
                .uri(uriBuilder -> uriBuilder.host("registration").path("/token/auth").queryParam("token", token).build())
                .retrieve()
                .onStatus(HttpStatus.FORBIDDEN::equals, response -> Mono.error(new IllegalStateException("Token is not valid")));
    }
}
Enter fullscreen mode Exit fullscreen mode

In the tokenValidate method, we go to the registration microservice, to the endpoint token/auth. The JWT token verification functionality should be implemented in it. In it, you must take all claims from the JWT token and write them to the DTO. It looks something like this:

public UserAuthorizationInfo getUserInfoFromToken(String token) {
        // здесь должна быть валидация вашего токена

        Claims allClaimsFromToken = jwt.getAllClaimsFromToken(token);
        UserAuthorizationInfo userInfo = new UserAuthorizationInfo();

        userInfo.setPersonId(allClaimsFromToken.get("id").toString());
        userInfo.setUsername(allClaimsFromToken.getSubject());

        List<String> authorities = new ArrayList<>();
        authorities.add(allClaimsFromToken.get(ROLES).toString());
        userInfo.setAuthorities(authorities);
        return userInfo;
    }
Enter fullscreen mode Exit fullscreen mode

Next we get User Authorities containing username and Collection authorities. And upon receipt of the request from the header, the username and role are assigned to the authorization. Now we can just specify which endpoints are available to whom in the Security Config from the Gateway Api and everything will work fine, and by the way, pretty fast, reactivity after all :)

Oldest comments (1)

Collapse
 
chandanws profile image
Chandan

Can you share this code repository?