Как переписать логику утверждения if на реактивный подход в фильтре веб-безопасности?

#java #reactive-programming #spring-webflux #project-reactor

Вопрос:

Я разумно новичок в проекте reactor и web flux, я хочу переписать свой нынешний традиционный блокирующий фильтр безопасности на реактивный, а именно:

текущий фильтр выглядит следующим образом:

 @Component @Slf4j public class WhitelistingFilter extends OncePerRequestFilter {   private static final String SECURITY_PROPERTIES = "security.properties";  private final Properties securityProperties = readConfigurationFile(SECURITY_PROPERTIES);  private final String whitelistingEnabled = securityProperties.getProperty("whitelisting.enabled", FALSE.toString());   private final RedisTemplatelt;String, Objectgt; whitelistingRedisTemplate;  private final AwsCognitoIdTokenProcessor awsCognitoIdTokenProcessor;   public WhitelistingFilter(  @Qualifier("whitelistingRedisTemplate")  RedisTemplatelt;String, Objectgt; whitelistingRedisTemplate,  AwsCognitoIdTokenProcessor awsCognitoIdTokenProcessor) {  this.whitelistingRedisTemplate = whitelistingRedisTemplate;  this.awsCognitoIdTokenProcessor = awsCognitoIdTokenProcessor;  }   @Override  protected boolean shouldNotFilter(@NonNull HttpServletRequest request) {  AntPathMatcher pathMatcher = new AntPathMatcher();  return Stream.of(USER_LOGIN_URL, ADMIN_LOGIN_URL, SIGNUP_BY_ADMIN_URL, SIGNUP_URL, LOGOUT_URL)  .anyMatch(p -gt; pathMatcher.match(p, request.getServletPath())) || whitelistingDisabled();  }   private boolean whitelistingDisabled() {  return FALSE.toString().equalsIgnoreCase(whitelistingEnabled);  }   @Override  protected void doFilterInternal(@NonNull HttpServletRequest httpServletRequest, @NonNull HttpServletResponse httpServletResponse, @NonNull FilterChain filterChain) {  try {  Authentication authentication = awsCognitoIdTokenProcessor.getAuthentication(httpServletRequest);  Optionallt;Stringgt; username = Optional.ofNullable(authentication.getName());  if (username.isPresent() amp;amp; usernameWhitelisted(username.get())) {  log.info("User with username: {} is present in whitelisting", username.get());  filterChain.doFilter(httpServletRequest, httpServletResponse);  } else {  httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);  log.error("Username: {} not whitelisted or empty", username.orElse(""));  }   } catch (Exception e) {  logger.error("Error occurred while checking user in redis whitelisting", e);  SecurityContextHolder.clearContext();  }  }   private boolean usernameWhitelisted(String username) {  return Boolean.TRUE.equals(whitelistingRedisTemplate.hasKey(WHITELISTING_PREFIX   username));  } }  

Новый, неполный, реактивный класс подхода выглядит следующим образом:

 @Component @Slf4j public class WhitelistingFilter implements WebFilter {   private static final String SECURITY_PROPERTIES = "security.properties";  public final Listlt;Stringgt; whitelistedUrls =  List.of(USER_LOGIN_URL, ADMIN_LOGIN_URL, SIGNUP_BY_ADMIN_URL, SIGNUP_URL, LOGOUT_URL);   private final Properties securityProperties = readConfigurationFile(SECURITY_PROPERTIES);  private final String whitelistingEnabled = securityProperties.getProperty("whitelisting.enabled", FALSE.toString());  private final ReactiveRedisOperationslt;String, Objectgt; whitelistingRedisTemplate;  private final AuthenticationManager authenticationManager;   public WhitelistingFilter(  @Qualifier("reactiveWhitelistingRedisTemplate")  ReactiveRedisOperationslt;String, Objectgt; whitelistingRedisTemplate,  AuthenticationManager authenticationManager) {  this.whitelistingRedisTemplate = whitelistingRedisTemplate;  this.authenticationManager = authenticationManager;  }   @Override  public Monolt;Voidgt; filter(ServerWebExchange exchange, WebFilterChain chain) {   Monolt;Stringgt; username =  ReactiveSecurityContextHolder.getContext()  .map(SecurityContext::getAuthentication)  .map(Authentication::getName);  //logic here    }   private Monolt;Booleangt; whitelistingDisabled() {  return Mono.just(FALSE.toString().equalsIgnoreCase(whitelistingEnabled));  }   private Monolt;Booleangt; usernameWhitelisted(Monolt;Stringgt; username) {  return whitelistingRedisTemplate.hasKey(WHITELISTING_PREFIX   username);  } }  

Я изменил методы usernameWhitelisted() и whitelistingDisabled() , чтобы вернуть Моно, но я не могу понять, как проверить, включено ли имя пользователя в белый список и включен ли белый список в реактивном подходе. Я пытался сделать это

 username.flatMap(u -gt; {  if(two conditions here)  })  

но при таком подходе я предоставляю Моно для утверждения if, которое противоречит семантике Java. Я буду благодарен за предложения о том, как переписать код и заставить его работать в реактивном подходе.

Ответ №1:

Наличие реактивного потока или канала не означает, что все должно быть реактивным, вы можете просто исключить моно из логических операторов

Поскольку вы возвращаете Моно типа void и поскольку вам нужно (?) ведение журнала, я думаю, вы могли бы просто добавить здесь пользовательское исключение и перехватить его поверх потока/канала, используя doOnError и там проверьте тип исключения и соответствующую логику (передайте msg исключению, если оно вам понадобится).

 ReactiveSecurityContextHolder.getContext()  .map(SecurityContext::getAuthentication)  .map(Authentication::getName)  .doOnNext(this::doBla)  public void doBla(String username) {  if (!whitelistingDisabled() || !usernameWhitelisted(username)) {  throw new TypeException(ms);  } }  

К вашему сведению, doOnNext выполняется только в том случае, если вы достигнете этой части, это похоже на просмотр потока java.
Функция doOnError также может быть извлечена, поток легче читать, чтобы он был простым и не добавлял в него слишком много

 myStream  ....otherOperators  .filter(bla)  ...  .doOnError(e -gt; {  if ( e instanceOf TypeException) {  // do stuff   }  }