You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security为何忽略我的登录请求(Vaadin、Postgres环境)

Vaadin LoginForm 集成 Spring Boot Security 登录失败无报错排查与解决

我最近在做Vaadin和Spring Boot Security的集成,用LoginForm.action("login")把登录请求转发给Spring Security处理,但遇到了一个头疼的问题:

问题现象

  • 用户注册正常:通过UserService.registerUser方法注册的用户能正常存入数据库,密码也正确加密了
  • 登录时Hibernate会执行查询用户的SQL,但提交登录后直接被重定向到localhost:8080/login?error
  • 控制台没有任何明确的错误或异常信息,数据库里的用户数据看起来完全正常,但就是登录失败

操作流程

  1. 注册用户:调用registerUser方法,控制台输出Hibernate的查询和插入SQL,数据库确认用户已保存,密码加密
  2. 尝试登录:在Vaadin登录视图输入注册的账号密码,控制台输出Hibernate查询用户的SQL,但登录失败,跳转到带error参数的登录页

相关代码

WebSecurityConfig.class

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends VaadinWebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        setLoginView(http, Login.class);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/images/**");
        super.configure(web);
    }
}

Login.class(Vaadin视图)

@Route(value = "login")
@AnonymousAllowed
public class Login extends VerticalLayout implements BeforeEnterObserver {
    LoginForm loginForm;

    public Login() {
        loginForm = new LoginForm();
        loginForm.setId("login-form");
        loginForm.setAction("login");
        add(loginForm);
        setAlignItems(Alignment.CENTER);
        setJustifyContentMode(JustifyContentMode.CENTER);
        setSizeFull();
    }

    @Override
    public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
        // inform the user about an authentication error
        if(beforeEnterEvent.getLocation()
                .getQueryParameters()
                .getParameters()
                .containsKey("error")) {
            loginForm.setError(true);
        }
    }
}

UserService.class

@Service
public class UserService implements UserDetailsService {
    private final UserRepository userRepository;
    @Lazy
    private final PasswordEncoder passwordEncoder;
    private final static String USER_NOT_FOUND = "User with name %s does not exist!";

    @Autowired
    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    public void registerUser(User user) throws UsernameAlreadyExistsException {
        try {
            loadUserByUsername(user.getUsername());
            throw new UsernameAlreadyExistsException();
        } catch(UsernameNotFoundException e) {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
            userRepository.save(user);
        }
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println(username);
        return userRepository.findByUsername(username).orElseThrow(() -> {
            System.out.println("Username not found " + username);
            return new UsernameNotFoundException(String.format(USER_NOT_FOUND, username));
        });
    }
}

问题根源与解决方案

折腾了一整天,发完问题不到10分钟就找到了答案!

问题原因

我的User类实现了UserDetails接口,但**isAccountNonExpired()和isCredentialsNonExpired()这两个方法的默认实现返回了false**。Spring Security会检查这两个状态,只要其中一个是false,就会判定账号或凭证已过期,直接拒绝登录,而且默认情况下不会输出明显的错误日志。

排查技巧

一开始没有错误日志很难定位,直到我添加了Spring Security的调试日志配置:

logging.level.org.springframework.security=DEBUG

开启调试日志后,就能看到Spring Security判定账号过期的详细错误信息了。

解决方法

在User类中重写这两个方法,让它们返回true(如果没有特殊的过期逻辑的话):

@Override
public boolean isAccountNonExpired() {
    return true;
}

@Override
public boolean isCredentialsNonExpired() {
    return true;
}

同时也要确保isAccountNonLocked()和isEnabled()方法的返回值是true(除非你有自定义的账号锁定/禁用需求)。


内容的提问来源于stack exchange,提问作者slothinflippycar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 17:32:38