Spring 安全性:为什么我的自定义 AccessDecisionVoter 没有被调用

Spring Security: Why is my custom AccessDecisionVoter not invoked

我正在尝试使用自定义 AccessDecisionVoter 进行 URL 授权。我没有收到任何错误,调试显示我的选民在启动时被选中。但是,在运行时,不会调用 vote 方法,因此允许每个经过身份验证的用户完全访问。

请注意,我不需要方法安全性。我也没有使用 XML 配置。这排除了互联网上关于该主题的所有示例。

@Configuration
@EnableWebSecurity
@EnableWebMvc
@ComponentScan
@Order(-10)
public class HttpSecurityConfig extends WebSecurityConfigurerAdapter {
    @Value("${trusted_ports}")
    private List<Integer> trustedPorts;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private ServiceIdAwareVoter serviceIdAwareVoter;

    RequestMatcher requestMatcher = new OrRequestMatcher(
        // @formatter:off
        new AntPathRequestMatcher("/**", GET.name()),
        new AntPathRequestMatcher("/**", POST.name()), 
        new AntPathRequestMatcher("/**", DELETE.name()),
        new AntPathRequestMatcher("/**", PATCH.name()), 
        new AntPathRequestMatcher("/**", PUT.name())
        // @formatter:on
    );

    @Override
    protected UserDetailsService userDetailsService() {
        return userDetailsService;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(preAuthProvider());
        auth.authenticationProvider(authProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.
            httpBasic().and().
            authorizeRequests().anyRequest().fullyAuthenticated().
            accessDecisionManager(accessDecisionManager()).and().
            csrf().disable().
            logout().disable().
            exceptionHandling().and().
            sessionManagement().sessionCreationPolicy(STATELESS).and().
            anonymous().disable().
            addFilterAfter(preAuthFilter(), X509AuthenticationFilter.class).
            addFilter(authFilter());
        // @formatter:on
    }

    AccessDecisionManager accessDecisionManager() {
        return new UnanimousBased(ImmutableList.of(serviceIdAwareVoter));
    }

    Filter preAuthFilter() throws Exception {
        PreAuthenticationFilter preAuthFilter = new PreAuthenticationFilter(trustedPorts);

        preAuthFilter.setAuthenticationManager(super.authenticationManager());

        return preAuthFilter;
    }

    PreAuthenticatedAuthenticationProvider preAuthProvider() {
        PreAuthenticatedAuthenticationProvider preAuthProvider = new PreAuthenticatedAuthenticationProvider();
        UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper = new UserDetailsByNameServiceWrapper<>();

        userDetailsServiceWrapper.setUserDetailsService(userDetailsService());

        preAuthProvider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper);

        return preAuthProvider;
    }

    Filter authFilter() throws Exception {
        AppIdAppKeyAuthenticationFilter authFilter = new AppIdAppKeyAuthenticationFilter(requestMatcher);
        authFilter.setAuthenticationFailureHandler(new ExceptionStoringAuthenticationFailureHandler());
        authFilter.setAuthenticationSuccessHandler(new UrlForwardingAuthenticationSuccessHandler());

        authFilter.setAuthenticationManager(authenticationManagerBean());

        return authFilter;
    }

    AuthenticationProvider authProvider() {
        AppIdAppKeyAuthenticationProvider authProvider = new AppIdAppKeyAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());

        return authProvider;
    }

你的配置表明你允许访问完全认证的用户:

authorizeRequests().anyRequest().fullyAuthenticated().

您是在告诉 Spring 安全部门授予对任何请求的访问权限,只要它们经过完全身份验证即可。你的目标是什么?你是如何尝试通过 role/permission 来限制访问的?我猜这是您在自定义选民 bean 中口授的内容?

通常,当您的安全级别存在冲突时,选民 bean 就会发挥作用,例如,在这里您说所有请求都具有完全访问权限,但是如果您的代码遇到具有方法级别安全性的方法(不是很 real-world例子):

@PreAuthrorize("permitNone")
public void someMethod{
   ...
}

您将让选民参与进来,因为您的 java 安全配置说 "grant access to everyone"(投票赞成访问)但此方法注释是 "grant access to no one"(投票反对访问)。

在您的情况下,没有什么可投票的,您授予所有人访问权限。

背景:

经过几个小时的调试,我找到了问题的根源,真是深奥。部分原因是 Spring 安全性 Java 配置的文档非常少(为此我打开了一个 JIRA ticket)。他们的以及大多数在线示例是 XML 配置中的 copy-pasted 而世界可能从 2010 年开始就停止使用 Spring XML 配置。另一部分是由于以下事实REST 服务安全性是 Spring 安全设计的事后考虑,他们不支持 first-class 保护没有登录页面、错误页面和普通视图层的应用程序。最后但并非最不重要的一点是,我的应用程序中有几个(错误的)配置,它们全部组合在一起并创造了一个 mind-boggling 复杂性的完美风暴。

技术背景:

使用 authorizeRequests() 配置 ExpressionUrlAuthorizationConfigurer,最终设置 UnanimousBased AccessDecisionManagerWebExpressionVoter。如果身份验证成功,则从 FilterSecurityInterceptor 调用此 AccessDecisionManager(显然,如果用户首先未通过身份验证,则授权没有意义)。

问题:

  • 在我的 AbstractAnnotationConfigDispatcherServletInitializer 子类中,它基本上是 web.xml 的 Java 版本,我将过滤器配置为不拦截转发请求。我不打算在这里讨论 为什么 。对于感兴趣的,这里有一个如何完成的例子:

    private Dynamic registerCorsFilter(ServletContext ctx) {
        Dynamic registration = ctx.addFilter("CorsFilter", CorsFilter.class);
    
        registration.addMappingForUrlPatterns(getDispatcherTypes(), false, "/*");    
        return registration;
    }
    
    private EnumSet<DispatcherType> getDispatcherTypes() {
        return (isAsyncSupported() ? EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC)
      : EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
    }
    

如果您从调度程序类型集中取出 DispatcherType.FORWARD,则已注册的过滤器不会针对此类请求启动。

  • 我的问题中显示的 authFilterUsernamePasswordAuthenticationFilter 扩展而来,并且有一个 AuthenticationSuccessHandler 在成功验证后将请求转发到目的地 URL。默认 Spring 实现使用 SavedRequestAwareAuthenticationSuccessHandler 重定向到网页,这在 REST 应用程序的上下文中是不需要的。
  • 由于上述 2 个原因,FilterSecurityInterceptor 在成功验证后未被调用,这反过来又跳过了导致我原来的问题的授权链 post。

修复:

  • 从 Web 应用初始化程序中删除自定义调度程序配置。
  • 不要从 AuthenticationSuccessHandler 转发或重定向。顺其自然吧。
  • 自定义投票器有一个 vote 方法,如下所示:

    public int vote(Authentication authentication, FilterInvocation fi,
            Collection<ConfigAttribute> attributes) {
    }
    

在我的例子中attributes,如我原来的post所示,是字符串表达式fullyAuthenticated。我没有使用它进行授权,因为我已经知道用户已经通过身份验证流程中的各种过滤器进行了身份验证。

我希望这可以作为所有那些因 Spring 安全性 Java 配置中缺少文档而受苦的灵魂的文档。