Vaadin23自定义登录表单对接原有安全机制实现问询
自定义Vaadin23登录表单对接现有Spring Security机制
需求背景
基于Vaadin23开发的应用目前使用默认LoginOverlay登录表单,现需替换为自定义表单,要求包含:
- 加载用户列表的下拉选择框
- 仅支持数字输入的PIN码密码字段
- 登录按钮
同时需完全复用现有Spring Security安全机制,无需修改标准登录组件。
解决方案实现
1. TypeScript自定义登录表单组件
创建custom-login-form.ts,实现表单布局、数字输入限制及登录事件触发:
import { html, LitElement } from 'lit'; import { customElement, query } from 'lit/decorators.js'; import '@vaadin/vertical-layout'; import '@vaadin/form-layout'; import '@vaadin/select'; import '@vaadin/password-field'; import '@vaadin/button'; @customElement('custom-login-form') export class CustomLoginForm extends LitElement { @query('#username-select') usernameSelect!: HTMLSelectElement; @query('#password-field') passwordField!: HTMLInputElement; // 接收后端传递的用户列表 set users(users: Array<{ value: string; label: string }>) { this.usernameSelect.innerHTML = users.map(user => `<vaadin-item value="${user.value}">${user.label}</vaadin-item>` ).join(''); } render() { return html` <vaadin-vertical-layout style="width: 100%; height: 100%;" class="content"> <vaadin-vertical-layout theme="spacing" style="width: 100%; max-width: 400px; margin: auto;"> <vaadin-form-layout id="login-form"> <vaadin-select label="用户" id="username-select" required></vaadin-select> <vaadin-password-field label="PIN码" id="password-field" required inputmode="numeric" pattern="[0-9]*" ></vaadin-password-field> <vaadin-button theme="primary" tabindex="0" id="login-button" @click="${this.handleLogin}" > 登录 </vaadin-button> </vaadin-form-layout> </vaadin-vertical-layout> </vaadin-vertical-layout> `; } private handleLogin() { const username = this.usernameSelect.value; const password = this.passwordField.value; if (!username || !password) { this.dispatchEvent(new CustomEvent('login-error', { detail: '请填写完整信息' })); return; } // 向Java端触发登录请求事件 this.dispatchEvent(new CustomEvent('login-request', { detail: { username, password }, bubbles: true, composed: true })); } } declare global { interface HTMLElementTagNameMap { 'custom-login-form': CustomLoginForm; } }
2. Java登录视图类
创建CustomLoginView.java,对接前端组件并复用现有安全机制:
@Route("login") @PageTitle("登录") public class CustomLoginView extends VerticalLayout implements BeforeEnterObserver { private final AuthenticatedUser authenticatedUser; private final UserRepository userRepository; public CustomLoginView(AuthenticatedUser authenticatedUser, UserRepository userRepository) { this.authenticatedUser = authenticatedUser; this.userRepository = userRepository; // 初始化自定义表单组件 CustomLoginForm loginForm = new CustomLoginForm(); // 加载系统用户列表并传递给前端 List<SelectItem> userOptions = userRepository.findAll().stream() .map(user -> new SelectItem(user.getUsername(), user.getUsername())) .collect(Collectors.toList()); loginForm.setUsers(userOptions); // 监听前端登录请求,执行认证逻辑 loginForm.addLoginRequestListener(event -> { try { // 复用Vaadin AuthenticationContext执行Spring Security认证 authenticatedUser.getAuthenticationContext().login( UsernamePasswordAuthenticationToken.unauthenticated( event.getUsername(), event.getPassword() ) ); } catch (AuthenticationException e) { // 认证失败提示 Notification.show("登录失败:用户名或PIN码错误", 3000, Notification.Position.TOP_CENTER); } }); // 布局居中配置 setSizeFull(); setJustifyContentMode(JustifyContentMode.CENTER); setAlignItems(Alignment.CENTER); add(loginForm); } @Override public void beforeEnter(BeforeEnterEvent event) { // 已登录用户访问登录页自动重定向主页 if (authenticatedUser.get().isPresent()) { event.forwardTo(""); } } // 用于传递用户选项的DTO public static class SelectItem { private String value; private String label; public SelectItem(String value, String label) { this.value = value; this.label = label; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } } // 登录请求事件定义 @DomEvent("login-request") public static class LoginRequestEvent extends ComponentEvent<CustomLoginForm> { private final String username; private final String password; public LoginRequestEvent(CustomLoginForm source, boolean fromClient, @EventData("event.detail.username") String username, @EventData("event.detail.password") String password) { super(source, fromClient); this.username = username; this.password = password; } public String getUsername() { return username; } public String getPassword() { return password; } } }
3. 更新安全配置
修改SecurityConfiguration.java,将登录视图替换为自定义视图:
@EnableWebSecurity @Configuration public class SecurityConfiguration extends VaadinWebSecurity { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().requestMatchers(new AntPathRequestMatcher("/images/*.png")).permitAll(); super.configure(http); // 替换为自定义登录视图 setLoginView(http, CustomLoginView.class); } }
关键说明
- 数字输入限制:通过
inputmode="numeric"和pattern="[0-9]*"确保密码框仅接受数字输入 - 安全机制复用:完全依赖现有
UserDetailsServiceImpl、PasswordEncoder及AuthenticatedUser组件,无需修改原有认证逻辑 - 认证流程:通过Vaadin
AuthenticationContext执行登录,自动处理会话创建、权限校验及重定向逻辑 - 用户体验:表单验证、错误提示及已登录用户重定向逻辑保持原有应用行为
内容的提问来源于stack exchange,提问作者bc_tom
相关产品推荐
相关产品推荐

