0

I am trying for reactive security and the unauthenticated calls are not going to auth manager.

@Configuration
@EnableWebFluxSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig{
    @Autowired
    private WebAuthenticationManager authenticationManager;

    @Autowired
    private ServerSecurityContextRepository securityContextRepository;

    private static final String[] AUTH_WHITELIST = {
            "/login/**",
            "/logout/**",
            "/authorize/**",
            "/favicon.ico",
    };

    @Bean
    public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http) {
        return http.exceptionHandling().authenticationEntryPoint((swe, e) -> {
            return Mono.fromRunnable(() -> {
                swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            });
        }).accessDeniedHandler((swe, e) -> {
            return Mono.fromRunnable(() -> {
                swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            });
        }).and().csrf().disable()
                .formLogin().disable()
                .httpBasic().disable()
                .authenticationManager(authenticationManager)
                .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
                .authorizeExchange().pathMatchers(HttpMethod.OPTIONS).permitAll()
                .pathMatchers(AUTH_WHITELIST).permitAll()
                .anyExchange().authenticated().and().build();
    }

    @Bean
    public PBKDF2Encoder passwordEncoder() {
        return new PBKDF2Encoder();
    }


}

WebAuthentication Manager,

@Component
public class WebAuthenticationManager implements ReactiveAuthenticationManager {

    @Autowired
    private JWTUtil jwtUtil;

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        String authToken = authentication.getCredentials().toString();

        String username;
        try {
            username = jwtUtil.getUsernameFromToken(authToken);
        } catch (Exception e) {
            username = null;
        }
        if (username != null && jwtUtil.validateToken(authToken)) {
            Claims claims = jwtUtil.getAllClaimsFromToken(authToken);
            List<String> rolesMap = claims.get("role", List.class);
            List<Role> roles = new ArrayList<>();
            for (String rolemap : rolesMap) {
                roles.add(Role.valueOf(rolemap));
            }
            UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                username,
                null,
                roles.stream().map(authority -> new SimpleGrantedAuthority(authority.name())).collect(Collectors.toList())
            );
            return Mono.just(auth);
        } else {
            return Mono.empty();
        }
    }
}

Here, I have registered my WebAuthentication manager in Securityconfig. But, still the unauthenticated calls are not going to the WebAuthenticationManager.

It is expected to go to AuthenticationManager when the protected URL's are hit. For ex,

http://localhost:8080/api/v1/user.

Not sure, why the calls are not going to AuthManager.

In non reactive, we have OncePerRequestFilter and the auth is being taken care over there. Not sure, how to implement the same for reactive.

user1578872
  • 7,808
  • 29
  • 108
  • 206

1 Answers1

1

You disabled all authentication mechanisms hence there is nothing calling your authentication manager. As you mentioned, you can implement authentication flow through filters.

Sample implementation of authentication filter:

    @Bean
    public AuthenticationWebFilter webFilter() {
    AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(authenticationManager);
    authenticationWebFilter.setServerAuthenticationConverter(tokenAuthenticationConverter());
    authenticationWebFilter.setRequiresAuthenticationMatcher(serverWebExchangeMatcher());
    authenticationWebFilter.setSecurityContextRepository(NoOpServerSecurityContextRepository.getInstance());
    return authenticationWebFilter;
}

Then add this filter to ServerHttpSecurity: http.addFilterBefore(webFilter(),SecurityWebFiltersOrder.HTTP_BASIC)

Then finally your authentication manager will be called.


You must provide few additional things to make it working.
Matcher to check if Authorization header is added to request:

    @Bean
    public ServerWebExchangeMatcher serverWebExchangeMatcher() {
    return exchange -> {
        Mono<ServerHttpRequest> request = Mono.just(exchange).map(ServerWebExchange::getRequest);
        return request.map(ServerHttpRequest::getHeaders)
                .filter(h -> h.containsKey(HttpHeaders.AUTHORIZATION))
                .flatMap($ -> ServerWebExchangeMatcher.MatchResult.match())
                .switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
    };
}

Token converter responsible for getting token from request and preparing basic AbstractAuthenticationToken

    @Bean
    public ServerAuthenticationConverter tokenAuthenticationConverter() {
    return exchange -> Mono.justOrEmpty(exchange)
            .map(e -> getTokenFromRequest(e))
            .filter(token -> !StringUtils.isEmpty(token))
            .map(token -> getAuthentication(token));
}

I intentionally omitted implementation of getTokenFromRequest and getAuthentication because there is a lot of examples available.

zfChaos
  • 415
  • 4
  • 13
  • tokenAuthenticationConverter is being invoked twice for each request. Also, How can I get the claims in my rest controller? – user1578872 Jul 15 '20 at 14:30