spring-security 在React式编程中登录后应调用Spring安全过滤器

juud5qan  于 2022-11-11  发布在  Spring
关注(0)|答案(1)|浏览(242)

[CustomerWebFilter类]在筛选器类中,我需要重新检查不需要任何身份验证或授权的url。我不希望对指定的url(如登录等)调用筛选器方法。筛选器应仅在成功登录后调用。
[使用ServerHttpSecurity配置安全类]这里我已经放置了http.addFilterAfter(customerWebFilter,SecurityWebFiltersOrder.AUTHORIZATION);
P.S.下面描述的流程是工作,但我想即兴发挥。

@Log4j2
@Component
@RequiredArgsConstructor
public class CustomerWebFilter implements WebFilter {

    private final AccountRoleMenuViewEntityService accountRoleMenuViewEntityService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        final List<String> ALLOWED_ENDPOINTS = Arrays.asList("/assets/", "/bootstrap/", "/plugins/");
        final List<String> ALLOWED_URL = Arrays.asList("/login", "/logout",
                "/forgotpassword", "/sendcode", "/verifycode", "/newpassword", "/razorpay-webhook");

        if (ALLOWED_ENDPOINTS.parallelStream().anyMatch(allowedWords ->
                StringUtils.startsWith(request.getPath().pathWithinApplication().value(), allowedWords))) {
            return chain.filter(exchange);
        }
        if (ALLOWED_URL.parallelStream().anyMatch(allowedWords ->
                StringUtils.equalsIgnoreCase(request.getPath().pathWithinApplication().value(), allowedWords))) {
            return chain.filter(exchange);
        }

        log.debug("Inside filter request: {} ", request.getPath().pathWithinApplication());
        String allowedPrefix = request.getPath().pathWithinApplication().value();

        if (allowedPrefix != "/" && allowedPrefix.length() > 1) {
            String[] urlComponents = request.getPath().pathWithinApplication().value().split("/");
            allowedPrefix = "/" + urlComponents[1] + "/" + urlComponents[2];
        }
        AtomicReference<String> value = new AtomicReference<>(allowedPrefix);

        return exchange.getPrincipal()
                .flatMap(principal -> {
                    log.debug("principal.getName(): {}", principal.getName());

                    AtomicInteger runCount = new AtomicInteger(0);

                    if (value.get().equalsIgnoreCase("/")) {
                        value.set("/customer/dashboard");
                    }
                    return accountRoleMenuViewEntityService.findByUsername(principal.getName())
                            .flatMap(accountRoleMenuView -> {
                                log.debug("controller: {}", accountRoleMenuView.getController());
                                if (accountRoleMenuView.getController().contains("/")) {
                                    String[] controllerComponents = accountRoleMenuView.getController().split("/");
                                    String controllerPrefix = "/" + controllerComponents[1] + "/" + controllerComponents[2];
                                    log.debug("controllerPrefix: {} value: {}", controllerPrefix, value.get());
                                    if (controllerPrefix.equalsIgnoreCase(value.get())) {
                                        runCount.getAndIncrement();
                                    }
                                }
                                return Mono.just(runCount);
                            }).count()
                            .flatMap(count -> {
                                if (runCount.get() == 0) {
                                    response.setStatusCode(HttpStatus.FORBIDDEN);
                                }
                                return chain.filter(exchange);
                            });
                });
    }
}

    @Configuration
    @EnableWebFluxSecurity
    @RequiredArgsConstructor
    public class ConfigurationSecurity {

        private final CustomerWebFilter customerWebFilter;
        private final RememberMeSuccessHandler rememberMeSuccessHandler;

        @Bean
        public SecurityWebFilterChain configure(ServerHttpSecurity http) {
            http.csrf().disable();
            http.headers().frameOptions().disable();
            http.authorizeExchange().pathMatchers("/login", "/logout", "/assets/**", "/bootstrap/**", "/plugins/**",
                "/forgotpassword", "/sendcode", "/verifycode", "/newpassword", "/razorpay-webhook").permitAll();
            http.authorizeExchange().pathMatchers("/customer/**").hasRole("CUSTOMER");
            http.authorizeExchange().anyExchange().authenticated();
            http.addFilterAfter(customerWebFilter, SecurityWebFiltersOrder.AUTHORIZATION);

            http.formLogin()
                .loginPage("/login")
                .authenticationSuccessHandler(rememberMeSuccessHandler)
                .authenticationFailureHandler(new AuthenticationFailureHandler())
                .and()
                .exceptionHandling()
                .accessDeniedHandler(new AccessDeniedHandler())
                .and()
                .logout()
                .logoutUrl("/logout")
                .requiresLogout(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/logout"))
                .and()
                .httpBasic();
            return http.build();
        }

        @Bean
        ReactiveAuthenticationManager reactiveAuthenticationManager(AccountDataService accountDataService) {
            return new UserDetailsRepositoryReactiveAuthenticationManager(accountDataService);
        }
        @Bean
        public PasswordEncoder encoder() {
            return new BCryptPasswordEncoder();
        }
    }
oprakyz7

oprakyz71#

我认为可能对过滤器链的工作原理有误解。
通过注册一个SecurityWebFilterChain的bean,您已经创建了一个带有特定ServerHttpSecurity对象的过滤器链,该对象应用了为该链定义的规则,并且这些规则在默认情况下 * 应用于每个请求,但是可以使用securityMatcher(ServerWebExchangeMatcher)进行限制(您可以在此处查看有关此方法的文档)。
当安全上下文保持器中存在Authentication对象时,在ServerHttpSecurity.authorizeExchange()调用之后定义的规则将在过滤器链之后应用,可以检查角色的存在,以决定是否允许访问特定端点。
因此,如果您希望在应用程序中为不同的url设置不同的安全过滤器链,您有两种选择:
1.定义多个SecurityWebFilterChain Bean:一个用于需要身份验证和自定义筛选器的路径,另一个用于不需要身份验证和自定义筛选器的路径。您可以在此处查看有关多个筛选器链支持的文档和示例,以及securityMatcher方法的用法。
1.在过滤器内创建规则以定义是否必须应用该规则。
我想你已经实现了第二种方法。改进建议--提取一个类似shouldFilter的方法,使用ServerWebExchangeMatcher将两个String数组合并为一个,并使用相同的方法比较交换路径与匹配器,包括带有/**通配符的路径,例如:

public class CustomerWebFilter implements WebFilter {

    private final String[] ALLOWED_URL = {"/assets/", "/bootstrap/", "/plugins/", "/login", "/logout",
            "/forgotpassword", "/sendcode", "/verifycode", "/newpassword", "/razorpay-webhook"};

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return exchangeForFiltering(exchange, chain)
                .flatMap(webExchange -> {

                    // your code here

                    return chain.filter(exchange);
                });
    }

    private Mono<ServerWebExchange> exchangeForFiltering(ServerWebExchange exchange, WebFilterChain chain) {
        return Mono.just(exchange)
                .filterWhen(this::shouldFilter)
                .switchIfEmpty(chain.filter(exchange).then(Mono.empty()));
    }

    private Mono<Boolean> shouldFilter(ServerWebExchange exchange) {
        return new NegatedServerWebExchangeMatcher(ServerWebExchangeMatchers.pathMatchers(ALLOWED_URL))
            .matches(exchange).map(ServerWebExchangeMatcher.MatchResult::isMatch);
    }
}

相关问题