当刚刚创建另一个实现 AuthenticationProvider 的类时,AuthenticationManager.authenticate() 会抛出 java.lang.StackOverflowError: null

ker*_*gdy 3 java spring-security basic-authentication jwt spring-boot

在我的项目中,我有两个不同的身份验证路径:第一个用于普通用户登录,第二个用于雇主登录。因此,我首先使用 AuthenticationProvider 为用户创建一个身份验证过滤器,并且它工作正常。对于第二次身份验证,我创建了其身份验证过滤器,当我为雇主创建另一个身份验证提供程序时,这就是问题所在。当我尝试使用authenticationmanager.authenticate(userPassAuthToken) .\xc2\xa0中的AuthenticationManager实例来验证用户的用户名和密码时,出现java.lang.StackOverflowError: null

\n

我不明白当我创建另一个身份验证提供程序时发生了什么,也不明白为什么身份验证管理器会抛出此异常。我认为身份验证管理器对使用哪个提供程序感到困惑。

\n

首先,我使用其过滤器和身份验证提供程序为普通用户创建第一个身份验证,如以下代码所示:

\n
    \n
  1. 用户过滤器类
  2. \n
\n
@Component\npublic class UserPassAuthFilter extends OncePerRequestFilter {\n\n    @Autowired\n    @Lazy\n    private AuthenticationManager authManager;\n    @Autowired\n    private AuthPath authPath;\n    @Autowired\n    AuthFilterUtil authFilterUtil;\n    @Autowired\n    private UserMapper userMapper;\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {\n\n        char[] username=request.getHeader("username").toCharArray();\n        char[] password=request.getHeader("password").toCharArray();\n\n        UserPassAuthToken authentication= new UserPassAuthToken(String.valueOf(username),String.valueOf(password));\n        UserPassAuthToken currentAuth =(UserPassAuthToken)authManager.authenticate(authentication);\n        if (!currentAuth.isAuthenticated()){\n            authFilterUtil.onFailureAuth(response,request,null);\n        }\n        else {\n            SecurityContextHolder.getContext().setAuthentication(currentAuth);\n            if(request.getServletPath().equals(authPath.getUserPasswordFilter_pathShouldDo().get(0))) {\n                UserDto responseBody = userMapper.mapUserToDto(currentAuth.getUsersDetails().getUsers());\n                responseBody.setToken(authFilterUtil.prepareTokenToAuthResponse(\n                        currentAuth.getAuthorities(),\n                        currentAuth.getName().toCharArray()\n                ));\n                authFilterUtil.onSuccessfulAuth(response, responseBody);\n            }\n            filterChain.doFilter(request,response);\n        }\n    }\n\n    @Override\n    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {\n\n        return !authPath.getUserPasswordFilter_pathShouldDo().contains(request.getServletPath());\n    }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 用户身份验证令牌类
  2. \n
\n
public class UserPassAuthToken extends UsernamePasswordAuthenticationToken {\n    @Getter\n    @Setter\n    private UsersDetails usersDetails;\n\n    public UserPassAuthToken(Object principal, Object credentials) {\n        super(principal, credentials);\n    }\n\n    public UserPassAuthToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {\n        super(principal, credentials, authorities);\n    }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 用户认证提供者类
  2. \n
\n
@Component\npublic class UserPassAuthProvider implements AuthenticationProvider {\n\n    @Autowired\n    UserService userDetailsService;\n    @Autowired\n    PasswordEncoder passwordEncoder;\n\n    @Override\n    public UserPassAuthToken authenticate(Authentication authentication) throws AuthenticationException {\n        String username =authentication.getName();\n        String password=(String)authentication.getCredentials();\n\n        UserPassAuthToken userPassAuthToken;\n        UsersDetails   user=userDetailsService.loadUserByUsername(username);\n        if (user !=null && password != null && passwordEncoder.matches(password,user.getPassword())){\n            userPassAuthToken=new UserPassAuthToken(username,user.getPassword(),user.getAuthorities());\n            userPassAuthToken.setUsersDetails(user);\n        }else {\n            userPassAuthToken = new UserPassAuthToken(username, password);\n            userPassAuthToken.setAuthenticated(false);\n        }\n        userPassAuthToken = new UserPassAuthToken(username, password);\n        userPassAuthToken.setAuthenticated(false);\n        return userPassAuthToken;\n    }\n\n    @Override\n    public boolean supports(Class<?> authType) {\n        return UserPassAuthToken.class.equals(authType);\n    }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 雇主身份验证提供者类别
  2. \n
\n
@Component\npublic class EmployerAuthProvider implements AuthenticationProvider {\n\n    @Autowired\n    EmployerService employerService;\n    @Autowired\n    PasswordEncoder passwordEncoder;\n\n    @Override\n    public EmployerPassAuthToken authenticate(Authentication authentication) throws AuthenticationException {\n        char[]username =authentication.getName().toCharArray();\n        char[] password=(char[]) authentication.getCredentials();\n        EmployerPassAuthToken employerPassAuthToken;\n        UsersDetails employer=employerService.loadUserByUsername(String.valueOf(username));\n        if (employer !=null && password != null && passwordEncoder.matches(String.valueOf(password),employer.getPassword())){\n            employerPassAuthToken=new EmployerPassAuthToken(username,employer.getPassword(),employer.getAuthorities());\n            employerPassAuthToken.setUsersDetails(employer);\n        }else {\n            employerPassAuthToken = new EmployerPassAuthToken(username, password);\n            employerPassAuthToken.setAuthenticated(false);\n        }\n        return employerPassAuthToken;\n    }\n\n    @Override\n    public boolean supports(Class<?> authType) {\n        return EmployerPassAuthToken.class.equals(authType);\n    }\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 安全配置类
  2. \n
\n
@Configuration\n@EnableWebSecurity\n@EnableGlobalMethodSecurity(prePostEnabled = true)\n\npublic class SecConfig {\n\n    @Autowired\n    UserPassAuthProvider userPassAuthProvider;\n    @Autowired\n    UserPassAuthFilter userPassAuthFilter;\n    @Autowired\n    TokenAuthFilter tokenAuthFilter;\n    @Autowired\n    AuthPathPrivilege userPrivilege;\n    @Autowired\n    AuthPath authPath;\n    @Bean\n    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http.csrf().disable()\n                .authorizeRequests()\n                .antMatchers(authPath.getAllNotAuthPath().iterator().next()).permitAll()\n                .antMatchers(authPath.getAllAuthPath().iterator().next()).authenticated()\n                .antMatchers(userPrivilege.getSuper_adminPrivilegePath().iterator().next()).hasAnyAuthority("SUPER_ADMIN")\n                .antMatchers(userPrivilege.getAdminPrivilegePath().iterator().next()).hasAnyAuthority("ADMIN")\n                .antMatchers(userPrivilege.getManagerPrivilegePath().iterator().next()).hasAnyAuthority("MANAGER")\n                .antMatchers(userPrivilege.getUserPrivilegePath().iterator().next()).hasAnyAuthority("USER")\n                .and().httpBasic()\n                .and().sessionManagement().sessionCreationPolicy(STATELESS)\n                .and().authenticationProvider(userPassAuthProvider)\n                .addFilterAt(userPassAuthFilter , BasicAuthenticationFilter.class)\n                .addFilterAfter(tokenAuthFilter , BasicAuthenticationFilter.class);\n\n        return http.build();\n    }\n\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder();\n    }\n\n    @Bean\n    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {\n        return authenticationConfiguration.getAuthenticationManager();\n    }\n\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

**笔记: **

\n
    \n
  1. 还使用 Intellij idea 调试它卡在用户过滤器类中的同一行代码authManager.authenticate(authentication)
  2. \n
  3. 创建雇主提供商后,应用程序未输入用户身份验证提供商。
  4. \n
\n

Mar*_*gio 10

除了 Eleftheria 的评论之外:

您要求 Spring SecurityAuthenticationManager通过调用 为您提供一个 bean authenticationConfiguration.getAuthenticationManager()

  1. 调用AuthenticationConfigurationAuthenticationManagerBuilder创建AuthenticationManager,并且仅AuthenticationManager只有一个 AuthenticationProvider UserDetailsService配置一个时才创建 。如果无法创建它,则会提供一个延迟初始化 bean

  2. 当应用程序第一次尝试使用时AuthenticationManager,将触发延迟初始化 bean,调用创建 bean 的方法,在您的情况下是:

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
}
Run Code Online (Sandbox Code Playgroud)
  1. 该方法依次调用AuthenticationConfiguration#getAuthenticationManager它将再次返回一个延迟初始化 bean,因为AuthenticationManager由于 <1> 而无法创建 Bean,并且循环继续。

那么,为什么 Spring Security 没有AuthenticationManager在您的情况下初始化呢?因为你有 2AuthenticationProvider个,Spring Security 无法知道你想要如何组合它们,这就是 Spring Security 退缩并让你处理AuthenticationManager.

你如何解决它?

由于 Spring Security 无法为您创建 bean,因此您必须AuthenticationManager自己创建 bean。以下是在您的场景中执行此操作的方法:

@Bean
public AuthenticationManager authenticationManager(List<AuthenticationProvider> myAuthenticationProviders) {
    return new ProviderManager(myAuthenticationProviders);
}
Run Code Online (Sandbox Code Playgroud)

对于想要使用 a 的人UserDetailsService,您可以创建 aDaoAuthenticationProvider并设置必要的字段。