从筛选器访问 UserDetails (Spring)

Accessing UserDetails from Filter (Spring)

规格

一个过滤器,可以通过 userDetailsS​​ervice 调用 loadUserFromUsername(),以便从自定义 UserDetails 实例中检索租户数据库详细信息。

问题

无论过滤器优先级设置为什么,此自定义过滤器都会在安全过滤器之前运行,因此 spring 安全上下文未填充或为空。当我从控制器访问主体对象时,我已经确认填充了此上下文。

尝试次数

我已经将 application.properties 中的 spring 安全顺序设置为 5,并且在注册此过滤器时,我使用了更大和更小的值,但它总是在之前运行。 我知道通用过滤器 bean 应该允许我将它设置为在安全配置之后出现,但我不知道如何将配置和过滤器移动到一个通用过滤器 bean 中。

TenantFilter.java

@Component
public class TenantFilter implements Filter {

    @Autowired
    private TenantStore tenantStore;

    @Autowired
    private UserService userService;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) servletRequest;

        User user = null;
        try {
            user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        } catch (UsernameNotFoundException ignored) {}

        String tenantId = user != null ? user.getSchool().getCode() : "";

        try {
            this.tenantStore.setTenantId(tenantId);
            chain.doFilter(servletRequest, servletResponse);
        } finally {
            // Otherwise when a previously used container thread is used, it will have the old tenant id set and
            // if for some reason this filter is skipped, tenantStore will hold an unreliable value
            this.tenantStore.clear();
        }
    }

    @Override
    public void destroy() {

    }
}

TenantFilterConfig.java

@Configuration
public class TenantFilterConfig {

    @Bean
    public Filter tenantFilter() {
        return new TenantFilter();
    }

    @Bean
    public FilterRegistrationBean tenantFilterRegistration() {
        FilterRegistrationBean result = new FilterRegistrationBean();
        result.setFilter(this.tenantFilter());
        result.setUrlPatterns(Lists.newArrayList("/*"));
        result.setName("Tenant Store Filter");
        result.setOrder(Ordered.LOWEST_PRECEDENCE-1);
        return result;
    }

    @Bean(destroyMethod = "destroy")
    public ThreadLocalTargetSource threadLocalTenantStore() {
        ThreadLocalTargetSource result = new ThreadLocalTargetSource();
        result.setTargetBeanName("tenantStore");
        return result;
    }

    @Primary
    @Bean(name = "proxiedThreadLocalTargetSource")
    public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
        ProxyFactoryBean result = new ProxyFactoryBean();
        result.setTargetSource(threadLocalTargetSource);
        return result;
    }

    @Bean(name = "tenantStore")
    @Scope(scopeName = "prototype")
    public TenantStore tenantStore() {
        return new TenantStore();
    }
}

找到了一种非常有效的不同方式:Aspects!

所使用的切入点表达式意味着它会围绕该项目中控制器包中所有 类 的所有方法调用运行。

租户存储基于更安全的 threadlocal 用法以避免内存泄漏,因为这样它总是被清除(由于 finally 块)

编码愉快!

TenantAspect.java

@Component
@Aspect
public class TenantAspect {

    private final
    TenantStore tenantStore;

    @Autowired
    public TenantAspect(TenantStore tenantStore) {
        this.tenantStore = tenantStore;
    }

    @Around(value = "execution(* com.things.stuff.controller..*(..))")
    public Object assignForController(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        return assignTenant(proceedingJoinPoint);
    }

    private Object assignTenant(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        try {
            User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            if (user != null) tenantStore.setTenantId(user.getSchool().getCode());
        } finally {
            Object retVal;
            retVal = proceedingJoinPoint.proceed();
            tenantStore.clear();
            return retVal;
        }
    }
}