首页 > 解决方案 > Spring Security getAuthenticationManager() 在自定义过滤器中返回 null

问题描述

我正在尝试在 Spring 中实现一个非常简单的自定义身份验证过程示例,以更好地理解该概念。

我以为我现在已经准备好了所有东西,但是发送一个请求来测试我实现的结果会导致 NullPointerException,这可以追溯到 this.getAuthenticationManager()在我的自定义过滤器中返回 null。但我不明白为什么。不幸的是,非常相似的现有问题并没有真正帮助我。所以我会感谢你的帮助;这是我认为最相关的课程,请随时询问是否需要更多课程。

MyAuthenticationFilter(基于 UsernamePasswordAuthenticationFilter 的源代码),最后一行发生错误:

public class MyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public MyAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            if (!request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException(
                        "Authentication method not supported: " + request.getMethod());
            }

            String username = request.getParameter("username");
            String password = request.getParameter("password");
            String secondSecret = request.getParameter("secondSecret");

            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();

            MyAuthenticationToken authRequest = new MyAuthenticationToken(username, new MyCredentials(password, secondSecret));

            return this.getAuthenticationManager().authenticate(authRequest);
    }
}

我的配置类:

@Configuration
@EnableWebSecurity
@EnableWebMvc
@ComponentScan
public class AppConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    MyAuthenticationProvider myAuthenticationProvider;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(new MyAuthenticationFilter(), BasicAuthenticationFilter.class)
                .authorizeRequests().antMatchers("/**")
                    .hasAnyRole()
                    .anyRequest()
                    .authenticated()
                    .and()
                .csrf().disable();
    }

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}

我的身份验证提供者

@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        MyAuthenticationToken myAuthenticationToken = (MyAuthenticationToken) authentication;
        MyCredentials credentials = (MyCredentials) myAuthenticationToken.getCredentials();
        if (credentials.getPassword().equals("sesamOeffneDich") && credentials.getSecondSecret().equals(MyAuthenticationToken.SECOND_SECRET)) {
            myAuthenticationToken.setAuthenticated(true);
            return myAuthenticationToken;
        } else {
            throw new BadCredentialsException("Bad credentials supplied!");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return MyAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

标签: javaspringspring-security

解决方案


为什么您会看到 NullPointerException

您看到 a 是NullPointerException因为您的过滤器没有AuthenticationManager接线。根据javadocsAbstractAuthenticationProcessingFilter

过滤器要求您设置 authenticationManager 属性。需要一个 AuthenticationManager 来处理由实现类创建的身份验证请求令牌

在这种情况下,我确实对为什么authenticationManager没有为这个抽象过滤器的构造函数参数进行切割而摸不着头脑。我建议在您的构造函数中为您的自定义过滤器强制执行此操作。

public MyAuthenticationFilter(AuthenticationManager authenticationManager) {
    super(new AntPathRequestMatcher("/login", "POST"));
    this.setAuthenticationManager(authenticationManager);
}

AuthenticationManager 或 AuthenticationProvider

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

AuthenticationManagerBuilder创建一个ProviderManager (an AuthenticationManager).

ProviderManager是一个集合,AuthenticationProvider并将尝试使用它管理authenticate()的每一个。AuthenticationProvider(这是合同public boolean supports(Class<?> authentication)中极其重要的地方)AuthenticationProvider

在您的配置中,您创建了一个ProviderManager仅包含您的自定义Authentication Provider

凉爽的。现在我的 AuthenticationManager 在哪里?

可以AuthenticationManager通过在configure()_ this.authenticationManager()WebSecurityConfigurerAdapter

@Bean
public AuthenticationManager authenticationManager throws Exception() {
    this.authenticationManager();
}

建议

创建你自己的ProviderManager确实有它的好处,因为它是明确的并且在你的控制范围内。

@Bean
public AuthenticationManager authenticationManager() {
    return new ProviderManager(Arrays.asList(myAuthenticationProvider));
}

这将使您可以灵活地放置AuthenticationManagerbean 并避免:

  • bean 配置中潜在的循环依赖问题
  • Exception由于调用而冒泡检查this.authenticationManager()

把它们放在一起

...
public class AppConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    MyAuthenticationProvider myAuthenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(new MyAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class)
        ...
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(Arrays.asList(myAuthenticationProvider));
    }
}

推荐阅读