Spring Security getAuthenticationManager() 在自定义过滤器中返回 null

Posted

技术标签:

【中文标题】Spring Security getAuthenticationManager() 在自定义过滤器中返回 null【英文标题】:Spring Security getAuthenticationManager() returns null within custom filter 【发布时间】:2019-01-29 21:21:54 【问题描述】:

我正在尝试在 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;
    

MyAuthenticationProvider

@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);
    

【问题讨论】:

如果在构造函数中添加setAuthenticationManager(new NoOpAuthenticationManager()); 会怎样? 我认为 OP 希望他的 authenticate 获得荣誉。 添加了一个答案,希望不会太密集 【参考方案1】:

为什么您会看到 NullPointerException

您看到的是NullPointerException,因为您没有将AuthenticationManager 连接到您的过滤器中。根据AbstractAuthenticationProcessingFilter的javadocs@

过滤器要求您设置 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)

ProviderManagerAuthenticationProvider 的集合,并且将尝试使用它管理的每个AuthenticationProviderauthenticate()。 (这就是public boolean supports(Class&lt;?&gt; authentication)AuthenticationProvider合约中极其重要的地方)

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

酷。现在我的 AuthenticationManager 在哪里?

WebSecurityConfigurerAdapter中的this.authenticationManager()可以抓取configure()方法构建的AuthenticationManager

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

建议

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

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

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

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

将所有内容放在一起

...
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));
    

【讨论】:

附加参考点:AuthenticationManager vs AuthenticationProvider 非常感谢!我在阅读文档时显然没有给予足够的关注,但您的回答可能仍然是必要的,因为我不知道将所有内容放在一起的正确位置。正如我在文档中看到的,ProviderManager 是 AuthenticationManager 的唯一实现;我的示例编写我自己的 AuthenticationManager 实现是否有意义,还是相当不寻常?顺便说一句,我总是感谢您写的其他建议;它有助于建立更好的上下文知识,因此值得称赞。 考虑到您只有一个AuthenticationProvider,您可以选择提供一个AuthenticationManager 的实现。 ProviderManager 是通过不同的策略来工作 我明白了,不过似乎没有太多必要;坦克你!【参考方案2】:

您应该将身份验证管理器设置为您的过滤器。 您可以将此方法添加到您的配置类中

 @Bean 
    public MyAuthenticationFilter myAuthenticationFilter() 
        MyAuthenticationFilter res = new MyAuthenticationFilter();
        try 
            res.setAuthenticationManager(authenticationManagerBean());
         catch (Exception e) 
            // TODO Auto-generated catch block
            e.printStackTrace();
        
        return res;
    

并用

修改配置方法

http.addFilterBefore(myAuthenticationFilter(), BasicAuthenticationFilter.class)....

【讨论】:

以上是关于Spring Security getAuthenticationManager() 在自定义过滤器中返回 null的主要内容,如果未能解决你的问题,请参考以下文章

Spring mvc / security:从spring security中排除登录页面

Spring Security:2.4 Getting Spring Security

没有 JSP 的 Spring Security /j_spring_security_check

Spring-Security

Spring Security 登录错误:HTTP 状态 404 - /j_spring_security_check

未调用 Spring Security j_spring_security_check