#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 } }