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

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组件,无需修改原有认证逻辑
  • 认证流程:通过VaadinAuthenticationContext执行登录,自动处理会话创建、权限校验及重定向逻辑
  • 用户体验:表单验证、错误提示及已登录用户重定向逻辑保持原有应用行为

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 05:37:10