Spring认证执行了两次,先成功后失败

Posted

技术标签:

【中文标题】Spring认证执行了两次,先成功后失败【英文标题】:Spring authentication performed twice, first succeeds and then fails 【发布时间】:2017-05-11 15:51:30 【问题描述】:

我正在尝试围绕 Spring Security 框架展开思考,并使用自定义 AuthenticationProvider 实现身份验证

当我使用正确的凭据导航到安全 url 和表单登录时,会进行两次登录尝试。第一次成功第二次失败,浏览器停留在登录页面,没有错误信息。

这是我的安全配置。

package training2;

 imports... 

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    @Qualifier("myProvider")
    private AuthenticationProvider provider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.authenticationProvider(provider);
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .permitAll()
            .and()
            .logout()
            .permitAll();
    

    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception 
        // TODO Auto-generated method stub
        return super.userDetailsServiceBean();
    

    @Override
    @Bean
    protected UserDetailsService userDetailsService() 
        return new UserDetailsService() 

            public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException 
                System.out.println("loadUserByUsername");

                return new UserDetails() 

                    private static final long serialVersionUID = -1044116648365271684L;

                    public boolean isEnabled() 
                        // TODO Auto-generated method stub
                        return true;
                    

                    public boolean isCredentialsNonExpired() 
                        // TODO Auto-generated method stub
                        return true;
                    

                    public boolean isAccountNonLocked() 
                        // TODO Auto-generated method stub
                        return true;
                    

                    public boolean isAccountNonExpired() 
                        // TODO Auto-generated method stub
                        return true;
                    

                    public String getUsername() 
                        // TODO Auto-generated method stub
                        return username;
                    

                    public String getPassword() 
                        // TODO Auto-generated method stub
                        return "asdf";
                    

                    public Collection<? extends GrantedAuthority> getAuthorities() 
                        // TODO Auto-generated method stub
                        return null;
                    
                ;
            
        ;
    

    @Bean
    @Qualifier("myProvider")
    public AuthenticationProvider myProvider(final UserDetailsService userDetailsService) 
        return new AuthenticationProvider() 

            public boolean supports(Class<?> authentication) 
                return true;
            

            public Authentication authenticate(Authentication authentication) throws AuthenticationException 
                System.out.println("authenticate");

                UserDetails user = userDetailsService.loadUserByUsername(authentication.getName());
                if (user != null && user.getPassword().equals(authentication.getCredentials())) 
                    System.out.println("authentication ok");
                    return authentication;
                 else 
                    System.out.println("authentication failed");
                    throw new AuthenticationException(null) 
                        private static final long serialVersionUID = -1022654748424786317L;
                    ;
                
            
        ;
    

当尝试使用有效凭据登录时,会在控制台中打印出以下内容

authenticate
loadUserByUsername
authentication ok
authenticate
loadUserByUsername
authentication failed

当尝试使用无效凭据(密码不是 asdf)登录时,身份验证只执行一次,并且会失败。

为什么在表单登录时会进行两次身份验证,第一次成功,然后失败?是配置错误,还是我缺少一些 bean?另外,在使用自定义AuthenticationProvider 实现时,我应该手动管理SecurityContext,还是Spring 仍然在哪里管理它?

我也很困惑,为什么我必须将userDetailsService 方法显式注释为@Bean 才能让Spring 管理,即使WebSecurityConfigurer 声明了这个方法。

【问题讨论】:

您是否有任何其他代码更改了authentication?我可以解释为什么两次,但不知道为什么第二次失败。 【参考方案1】:
.anyRequest().authenticated()

所以它会检查验证是否在FilterSecurityInterceptor 中进行了验证,但是在您的提供程序中返回的验证没有经过验证,FilterSecurityInterceptor 会再次进行authenticate

if (user != null && user.getPassword().equals(authentication.getCredentials())) 
    System.out.println("authentication ok");
    // should authenticated authenticaiton
    //return new UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)

【讨论】:

通过将 setAuthenticated 设置为 true,引发以下异常:java.lang.IllegalArgumentException:无法将此令牌设置为受信任 - 使用采用 GrantedAuthority 列表的构造函数。通过返回新的 UsernameAndPasswordAuthenticationToken 解决了问题,但我不明白逻辑。为什么身份验证仍然需要经过身份验证的标志,为什么会抛出异常? 是的,您应该新建一个经过身份验证的身份验证。因为旧的身份验证包含凭据信息。 在你的项目中认证为UsernamePasswordAuthenticationTokenUsernamePasswordAuthenticationToken覆盖setAuthenticated 方法,并抛出异常。

以上是关于Spring认证执行了两次,先成功后失败的主要内容,如果未能解决你的问题,请参考以下文章

spring中定时器每周执行两次

spring boot 启动时会运行两次

spring boot 启动时会运行两次

spring boot 启动时会运行两次

ViewDidLoad 调用了两次

Spring Boot 中的同一个 Bug,竟然把我坑了两次!