security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Security with Spring Security in Vaadin 25

Vaadin 25 中使用 Spring Security 实现安全防护

Use the Vaadin MCP tools (
search_vaadin_docs
,
get_component_java_api
,
get_component_styling
) to look up the latest documentation whenever uncertain about a specific API detail. Always set
vaadin_version
to
"25"
and
ui_language
to
"java"
.
当对特定API细节不确定时,使用Vaadin MCP工具(
search_vaadin_docs
get_component_java_api
get_component_styling
)查阅最新文档。请始终将
vaadin_version
设置为
"25"
ui_language
设置为
"java"

When to Use This Skill vs. Others

何时使用此技能而非其他技能

This skill covers: Spring Security configuration with
VaadinSecurityConfigurer
, login views with
LoginForm
, view access control annotations (
@AnonymousAllowed
,
@PermitAll
,
@RolesAllowed
,
@DenyAll
),
AuthenticationContext
, logout handling, and OAuth2/OpenID Connect integration with providers like Google, Keycloak, GitHub, and Okta.
Use
views-and-navigation
instead
when the question is about
@Route
,
@Layout
,
AppLayout
,
SideNav
, or URL parameters. This skill covers how to secure views, not how to create or navigate between them.
Use
client-side-views
instead
when securing React/Hilla views with
ViewConfig.loginRequired
and
ViewConfig.rolesAllowed
. This skill covers Java/Flow view security, though the annotation-based approach also applies to
@BrowserCallable
endpoints.
此技能涵盖: 使用
VaadinSecurityConfigurer
配置Spring Security、使用
LoginForm
实现登录视图、视图访问控制注解(
@AnonymousAllowed
@PermitAll
@RolesAllowed
@DenyAll
)、
AuthenticationContext
、登出处理,以及与Google、Keycloak、GitHub、Okta等提供商集成OAuth2/OpenID Connect。
当问题涉及
@Route
@Layout
AppLayout
SideNav
或URL参数时,请改用
views-and-navigation
技能
。此技能仅涵盖如何保护视图,而非如何创建或导航视图。
当使用
ViewConfig.loginRequired
ViewConfig.rolesAllowed
保护React/Hilla视图时,请改用
client-side-views
技能
。此技能涵盖Java/Flow视图的安全防护,不过基于注解的方法也适用于
@BrowserCallable
端点。

Setting Up Spring Security

配置Spring Security

Add the Spring Security starter dependency:
xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
Create a security configuration class that uses
VaadinSecurityConfigurer
:
java
@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
            configurer.loginView(LoginView.class);
        });
        return http.build();
    }

    @Bean
    public UserDetailsManager userDetailsManager() {
        // WARNING: In-memory users for development only.
        // Use JDBC, LDAP, or OAuth2 in production.
        var user = User.withUsername("user")
                .password("{noop}user")
                .roles("USER")
                .build();
        var admin = User.withUsername("admin")
                .password("{noop}admin")
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}
VaadinSecurityConfigurer.vaadin()
automatically handles:
  • CSRF — enabled with exemptions for Vaadin internal requests
  • Logout — configured with Vaadin-aware logout handlers
  • Request cache — Vaadin-specific request cache for redirect after login
  • Exception handling — proper error responses for Vaadin requests
  • Authorized requests — permits Vaadin framework requests (client engine, push, etc.)
  • Navigation access control — enforces view access annotations
添加Spring Security starter依赖:
xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
创建使用
VaadinSecurityConfigurer
的安全配置类:
java
@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
            configurer.loginView(LoginView.class);
        });
        return http.build();
    }

    @Bean
    public UserDetailsManager userDetailsManager() {
        // 警告:仅在开发环境使用内存用户。
        // 生产环境请使用JDBC、LDAP或OAuth2。
        var user = User.withUsername("user")
                .password("{noop}user")
                .roles("USER")
                .build();
        var admin = User.withUsername("admin")
                .password("{noop}admin")
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}
VaadinSecurityConfigurer.vaadin()
会自动处理以下内容:
  • CSRF —— 启用并豁免Vaadin内部请求
  • 登出 —— 配置Vaadin感知的登出处理器
  • 请求缓存 —— 用于登录后重定向的Vaadin特定请求缓存
  • 异常处理 —— 为Vaadin请求返回正确的错误响应
  • 授权请求 —— 允许Vaadin框架请求(客户端引擎、推送等)
  • 导航访问控制 —— 强制执行视图访问注解

Login View

登录视图

Use the built-in
LoginForm
component to create a login page. It provides a form with username and password fields, is compatible with password managers, and handles CSRF tokens automatically.
java
@Route(value = "login", autoLayout = false)
@PageTitle("Login")
@AnonymousAllowed
public class LoginView extends Main implements BeforeEnterObserver {

    private final LoginForm login;

    public LoginView() {
        login = new LoginForm();
        login.setAction("login");

        addClassNames(LumoUtility.Display.FLEX,
                LumoUtility.JustifyContent.CENTER,
                LumoUtility.AlignItems.CENTER);
        setSizeFull();
        add(login);
    }

    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        if (event.getLocation()
                .getQueryParameters()
                .getParameters()
                .containsKey("error")) {
            login.setError(true);
        }
    }
}
Key points:
  • autoLayout = false
    — prevents the login view from rendering inside the application's main layout (e.g.,
    AppLayout
    with navigation menu)
  • @AnonymousAllowed
    — required so unauthenticated users can access the page
  • login.setAction("login")
    — makes the form POST to Spring Security's
    /login
    endpoint
  • BeforeEnterObserver
    — checks for the
    ?error
    query parameter that Spring Security adds after a failed login attempt
  • Register in SecurityConfig
    configurer.loginView(LoginView.class)
    tells VaadinSecurityConfigurer which view is the login page
使用内置的
LoginForm
组件创建登录页面。它提供带有用户名和密码字段的表单,兼容密码管理器,并自动处理CSRF令牌。
java
@Route(value = "login", autoLayout = false)
@PageTitle("Login")
@AnonymousAllowed
public class LoginView extends Main implements BeforeEnterObserver {

    private final LoginForm login;

    public LoginView() {
        login = new LoginForm();
        login.setAction("login");

        addClassNames(LumoUtility.Display.FLEX,
                LumoUtility.JustifyContent.CENTER,
                LumoUtility.AlignItems.CENTER);
        setSizeFull();
        add(login);
    }

    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        if (event.getLocation()
                .getQueryParameters()
                .getParameters()
                .containsKey("error")) {
            login.setError(true);
        }
    }
}
关键点:
  • autoLayout = false
    —— 防止登录视图渲染在应用的主布局内(例如带有导航菜单的
    AppLayout
  • @AnonymousAllowed
    —— 必须添加,以便未认证用户可以访问该页面
  • login.setAction("login")
    —— 使表单POST到Spring Security的
    /login
    端点
  • BeforeEnterObserver
    —— 检查Spring Security在登录失败后添加的
    ?error
    查询参数
  • 在SecurityConfig中注册 ——
    configurer.loginView(LoginView.class)
    告诉VaadinSecurityConfigurer哪个视图是登录页面

View Access Control

视图访问控制

Control who can access each view using Jakarta and Vaadin security annotations on the view class:
AnnotationAccess LevelTypical Use
@AnonymousAllowed
Anyone (no login required)Login view, public landing page
@PermitAll
Any authenticated userDashboard, user profile
@RolesAllowed("ADMIN")
Users with specified role(s)Admin panel, user management
@DenyAll
NobodyDefault when no annotation is present
Note on
@PermitAll
:
Vaadin's use of
@PermitAll
differs from the Jakarta Security standard. In standard Jakarta Security,
@PermitAll
means "anyone, including unauthenticated users" — similar to Vaadin's
@AnonymousAllowed
. In Vaadin,
@PermitAll
means "any authenticated user." Developers familiar with standard Jakarta security may be confused when access is denied to unauthenticated users on a view they explicitly "permitted all" — use
@AnonymousAllowed
for truly public views.
java
@Route("public")
@AnonymousAllowed
public class PublicView extends VerticalLayout { }

@Route("dashboard")
@PermitAll
public class DashboardView extends VerticalLayout { }

@Route("admin")
@RolesAllowed("ADMIN")
public class AdminView extends VerticalLayout { }
在视图类上使用Jakarta和Vaadin安全注解控制谁可以访问每个视图:
注解访问级别典型用途
@AnonymousAllowed
任何人(无需登录)登录视图、公共着陆页
@PermitAll
任何已认证用户仪表盘、用户资料页
@RolesAllowed("ADMIN")
拥有指定角色的用户管理面板、用户管理页
@DenyAll
无人可访问未添加注解时的默认设置
关于
@PermitAll
的注意事项:
Vaadin对
@PermitAll
的使用与Jakarta Security标准不同。在标准Jakarta Security中,
@PermitAll
表示“任何人,包括未认证用户”——与Vaadin的
@AnonymousAllowed
类似。而在Vaadin中,
@PermitAll
表示“任何已认证用户”。熟悉标准Jakarta Security的开发者可能会困惑,为什么他们明确“允许所有”的视图会拒绝未认证用户访问——对于真正的公共视图,请使用
@AnonymousAllowed
java
@Route("public")
@AnonymousAllowed
public class PublicView extends VerticalLayout { }

@Route("dashboard")
@PermitAll
public class DashboardView extends VerticalLayout { }

@Route("admin")
@RolesAllowed("ADMIN")
public class AdminView extends VerticalLayout { }

Annotation Resolution Rules

注解解析规则

  • No annotation on a view
    @DenyAll
    applies (access denied by default)
  • Superclass annotations — inherited from the closest annotated parent class
  • Child class annotated — overrides parent class annotations
  • Interfaces — annotations on interfaces are not checked
  • Layouts are checked independently — both the layout and the view must grant access. A view with
    @PermitAll
    inside a layout with no annotation (default
    @DenyAll
    ) is inaccessible
  • Override priority when multiple annotations exist
    @DenyAll
    >
    @AnonymousAllowed
    >
    @RolesAllowed
    >
    @PermitAll
  • 视图未添加注解 —— 应用
    @DenyAll
    (默认拒绝访问)
  • 父类注解 —— 继承自最近的已注解父类
  • 子类添加注解 —— 覆盖父类注解
  • 接口 —— 不检查接口上的注解
  • 布局独立检查 —— 布局和视图都必须授予访问权限。带有
    @PermitAll
    的视图若位于未添加注解的布局(默认
    @DenyAll
    )内,则无法访问
  • 多个注解存在时的优先级 ——
    @DenyAll
    >
    @AnonymousAllowed
    >
    @RolesAllowed
    >
    @PermitAll

Role Constants

角色常量

Define role names as constants to avoid typos in
@RolesAllowed
annotations:
java
public final class Roles {
    public static final String ADMIN = "ADMIN";
    public static final String USER = "USER";

    private Roles() {
    }
}

// Usage:
@RolesAllowed(Roles.ADMIN)
public class AdminView extends VerticalLayout { }
将角色名称定义为常量,避免在
@RolesAllowed
注解中出现拼写错误:
java
public final class Roles {
    public static final String ADMIN = "ADMIN";
    public static final String USER = "USER";

    private Roles() {
    }
}

// 使用方式:
@RolesAllowed(Roles.ADMIN)
public class AdminView extends VerticalLayout { }

Programmatic Access Checks

程序化访问检查

Inject
AuthenticationContext
to check roles or get user information within a view:
java
@Route("settings")
@PermitAll
public class SettingsView extends VerticalLayout {

    public SettingsView(AuthenticationContext authContext) {
        authContext.getAuthenticatedUser(UserDetails.class)
                .ifPresent(user -> add(new H2("Welcome " + user.getUsername())));

        if (authContext.hasRole(Roles.ADMIN)) {
            add(new Button("Admin Settings", event -> {
                // show admin-only settings
            }));
        }
    }
}
注入
AuthenticationContext
以在视图内检查角色或获取用户信息:
java
@Route("settings")
@PermitAll
public class SettingsView extends VerticalLayout {

    public SettingsView(AuthenticationContext authContext) {
        authContext.getAuthenticatedUser(UserDetails.class)
                .ifPresent(user -> add(new H2("Welcome " + user.getUsername())));

        if (authContext.hasRole(Roles.ADMIN)) {
            add(new Button("Admin Settings", event -> {
                // 显示仅管理员可见的设置
            }));
        }
    }
}

Logout

登出

AuthenticationContext (Recommended)

AuthenticationContext(推荐方式)

The simplest and most reliable way to log out.
AuthenticationContext
handles session invalidation and redirect automatically. Add a logout button to your
MainLayout
:
java
public class MainLayout extends AppLayout {

    private final transient AuthenticationContext authContext;

    public MainLayout(AuthenticationContext authContext) {
        this.authContext = authContext;

        var title = new H1("My App");
        title.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE);
        var logout = new Button("Logout", event -> authContext.logout());

        var header = new HorizontalLayout(title, logout);
        header.setWidthFull();
        header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
        header.setAlignItems(FlexComponent.Alignment.CENTER);
        header.addClassNames(LumoUtility.Padding.Horizontal.MEDIUM);

        addToNavbar(header);
    }
}
AuthenticationContext
must be declared
transient
because it is not
Serializable
.
By default, logout redirects to
/
. To customize the post-logout redirect, specify a second parameter when registering the login view:
java
configurer.loginView(LoginView.class, "/goodbye");
最简单且最可靠的登出方式。
AuthenticationContext
会自动处理会话失效和重定向。在
MainLayout
中添加登出按钮:
java
public class MainLayout extends AppLayout {

    private final transient AuthenticationContext authContext;

    public MainLayout(AuthenticationContext authContext) {
        this.authContext = authContext;

        var title = new H1("My App");
        title.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE);
        var logout = new Button("Logout", event -> authContext.logout());

        var header = new HorizontalLayout(title, logout);
        header.setWidthFull();
        header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
        header.setAlignItems(FlexComponent.Alignment.CENTER);
        header.addClassNames(LumoUtility.Padding.Horizontal.MEDIUM);

        addToNavbar(header);
    }
}
AuthenticationContext
必须声明为
transient
,因为它不实现
Serializable
接口。
默认情况下,登出后会重定向到
/
。要自定义登出后的重定向地址,请在注册登录视图时指定第二个参数:
java
configurer.loginView(LoginView.class, "/goodbye");

SecurityContextLogoutHandler (Alternative)

SecurityContextLogoutHandler(替代方式)

When
AuthenticationContext
is not available, use
SecurityContextLogoutHandler
directly. The redirect must happen before the handler invalidates the session:
java
public void logout() {
    UI.getCurrent().getPage().setLocation("/");
    var logoutHandler = new SecurityContextLogoutHandler();
    logoutHandler.logout(
            VaadinServletRequest.getCurrent().getHttpServletRequest(),
            null, null);
}
当无法使用
AuthenticationContext
时,可直接使用
SecurityContextLogoutHandler
。必须在处理器失效会话前执行重定向:
java
public void logout() {
    UI.getCurrent().getPage().setLocation("/");
    var logoutHandler = new SecurityContextLogoutHandler();
    logoutHandler.logout(
            VaadinServletRequest.getCurrent().getHttpServletRequest(),
            null, null);
}

OAuth2 / OpenID Connect

OAuth2 / OpenID Connect

To authenticate users via an external identity provider (Google, GitHub, Keycloak, Okta, Azure AD), use Spring Security's OAuth2 client support.
要通过外部身份提供商(Google、GitHub、Keycloak、Okta、Azure AD)认证用户,请使用Spring Security的OAuth2客户端支持。

Dependencies

依赖

Add the OAuth2 client starter alongside Spring Security:
xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
在Spring Security之外添加OAuth2 client starter依赖:
xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

Google (Common Provider)

Google(常见提供商)

Google is a Spring Security "common provider", so minimal configuration is needed. Only the client ID and secret are required:
properties
spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.scope=openid,profile,email
Google是Spring Security的“常见提供商”,因此只需最少配置。仅需客户端ID和密钥:
properties
spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.scope=openid,profile,email

Keycloak (Custom Provider)

Keycloak(自定义提供商)

Providers that are not built into Spring Security require full configuration including the issuer URI:
properties
spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.client-id=my-client-id
spring.security.oauth2.client.registration.keycloak.client-secret=${KEYCLOAK_CLIENT_SECRET}
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid,profile

spring.security.oauth2.client.provider.keycloak.issuer-uri=http://keycloak.local:8180/realms/my-app
未内置到Spring Security的提供商需要完整配置,包括颁发者URI:
properties
spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.client-id=my-client-id
spring.security.oauth2.client.registration.keycloak.client-secret=${KEYCLOAK_CLIENT_SECRET}
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid,profile

spring.security.oauth2.client.provider.keycloak.issuer-uri=http://keycloak.local:8180/realms/my-app

SecurityConfig for OAuth2

OAuth2的SecurityConfig配置

Replace
loginView()
with
oauth2LoginPage()
, pointing to the provider's authorization URI:
java
@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
            configurer.oauth2LoginPage(
                    "/oauth2/authorization/google",
                    "{baseUrl}");
        });
        return http.build();
    }
}
The
{baseUrl}
template variable resolves to the application's base URL and is used as the post-logout redirect URI. Other supported variables:
{baseScheme}
,
{baseHost}
,
{basePort}
,
{basePath}
.
When using OAuth2, no
LoginView
class is needed — the identity provider handles the login UI. Users are redirected to the provider's login page and back to the application after authentication.
loginView()
替换为
oauth2LoginPage()
,指向提供商的授权URI:
java
@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
            configurer.oauth2LoginPage(
                    "/oauth2/authorization/google",
                    "{baseUrl}");
        });
        return http.build();
    }
}
{baseUrl}
模板变量会解析为应用的基础URL,并用作登出后的重定向URI。其他支持的变量:
{baseScheme}
{baseHost}
{basePort}
{basePath}
使用OAuth2时,无需创建
LoginView
类——身份提供商会处理登录UI。用户会被重定向到提供商的登录页面,认证完成后返回应用。

OAuth2 Logout

OAuth2登出

For OAuth2 applications, use
AuthenticationContext.logout()
the same way as with form login. The
VaadinSecurityConfigurer
handles the OAuth2-specific logout flow.
对于OAuth2应用,使用
AuthenticationContext.logout()
的方式与表单登录相同。
VaadinSecurityConfigurer
会处理OAuth2特定的登出流程。

Other Providers

其他提供商

The same pattern works for GitHub, Okta, and Azure AD. The only differences are:
  • application.properties
    — the registration and provider keys change per provider
  • oauth2LoginPage()
    URL
    — use
    /oauth2/authorization/{registrationId}
    where
    {registrationId}
    matches the key in
    application.properties
  • Common providers (Google, GitHub, Facebook, Okta) need only
    client-id
    ,
    client-secret
    , and
    scope
  • Custom providers (Keycloak, Azure AD) also need
    issuer-uri
    and
    authorization-grant-type
相同模式适用于GitHub、Okta和Azure AD。唯一的区别在于:
  • application.properties
    —— 注册和提供商密钥因提供商而异
  • oauth2LoginPage()
    URL
    —— 使用
    /oauth2/authorization/{registrationId}
    ,其中
    {registrationId}
    application.properties
    中的密钥匹配
  • 常见提供商(Google、GitHub、Facebook、Okta)仅需
    client-id
    client-secret
    scope
  • 自定义提供商(Keycloak、Azure AD)还需要
    issuer-uri
    authorization-grant-type

Best Practices

最佳实践

  1. Never hard-code credentials — use environment variables or a secrets manager. The
    UserDetailsManager
    with
    {noop}
    passwords is for development only.
  2. Annotate every view and layout — views without an access annotation default to
    @DenyAll
    . Layouts are checked independently; both must grant access.
  3. Use
    AuthenticationContext
    for logout and user info
    — it integrates with Spring Security and handles session cleanup correctly. Declare the field
    transient
    .
  4. Use
    autoLayout = false
    on the login view
    — the login view should not render inside the application's main layout.
  5. Prefer
    VaadinSecurityConfigurer
    over manual Spring Security config
    — it handles CSRF, logout, request caching, and exception handling for Vaadin automatically.
  6. Externalize OAuth2 credentials — use
    application.properties
    with environment variable references (
    ${GOOGLE_CLIENT_SECRET}
    ) or Spring profiles.
  7. Define role constants — create a
    Roles
    class with
    public static final String
    fields to avoid typos in
    @RolesAllowed
    annotations.
  8. Leverage Vaadin's server-side security model — UI state lives on the server, so attackers cannot tamper with it from the browser. Combined with annotation-based access control, this provides defense in depth.
  1. 切勿硬编码凭证 —— 使用环境变量或密钥管理器。带有
    {noop}
    密码的
    UserDetailsManager
    仅用于开发环境。
  2. 为每个视图和布局添加注解 —— 未添加访问注解的视图默认使用
    @DenyAll
    。布局会独立检查;两者都必须授予访问权限。
  3. 使用
    AuthenticationContext
    处理登出和用户信息
    —— 它与Spring Security集成,能正确处理会话清理。请将字段声明为
    transient
  4. 在登录视图上使用
    autoLayout = false
    —— 登录视图不应渲染在应用的主布局内。
  5. 优先使用
    VaadinSecurityConfigurer
    而非手动配置Spring Security
    —— 它会自动处理Vaadin的CSRF、登出、请求缓存和异常处理。
  6. 外部化OAuth2凭证 —— 使用带有环境变量引用(
    ${GOOGLE_CLIENT_SECRET}
    )的
    application.properties
    或Spring配置文件。
  7. 定义角色常量 —— 创建
    Roles
    类,包含
    public static final String
    字段,避免在
    @RolesAllowed
    注解中出现拼写错误。
  8. 利用Vaadin的服务器端安全模型 —— UI状态存储在服务器端,攻击者无法从浏览器篡改。结合基于注解的访问控制,可提供深度防御。

Anti-Patterns

反模式

  1. Forgetting
    autoLayout = false
    on the login view
    — the login form renders inside the app shell with the navigation menu, which is confusing and may cause access control issues.
  2. Forgetting to annotate the layout — a view marked
    @PermitAll
    inside a layout with no annotation (default
    @DenyAll
    ) is inaccessible. Both must independently grant access.
  3. Using
    @Secured
    or
    @PreAuthorize
    on views
    — these Spring Security annotations are not supported on Vaadin views. Use
    @AnonymousAllowed
    ,
    @PermitAll
    ,
    @RolesAllowed
    , or
    @DenyAll
    .
  4. Hard-coding passwords in
    SecurityConfig
    — in-memory
    {noop}
    passwords are for prototyping only. Production applications must use JDBC, LDAP, or OAuth2 authentication.
  5. Calling
    SecurityContextLogoutHandler
    without redirecting first
    — the logout handler invalidates the session, so
    UI.getCurrent().getPage().setLocation()
    must happen before the handler call. Prefer
    AuthenticationContext.logout()
    which handles this correctly.
  6. Using URL-pattern security for view access control — Vaadin uses a single servlet endpoint for all views; the browser URL is updated client-side but all requests go to the same server endpoint. This means URL-pattern rules in
    HttpSecurity
    (e.g.,
    .requestMatchers("/admin/**").hasRole("ADMIN")
    ) do not control access to Vaadin views. Use annotation-based access control (
    @RolesAllowed
    ,
    @PermitAll
    , etc.) exclusively for view security. URL-pattern rules are still appropriate for non-Vaadin endpoints such as REST APIs.
  1. 忘记在登录视图上设置
    autoLayout = false
    —— 登录表单会渲染在应用外壳和导航菜单内,这会造成混淆并可能导致访问控制问题。
  2. 忘记为布局添加注解 —— 带有
    @PermitAll
    的视图若位于未添加注解的布局(默认
    @DenyAll
    )内,则无法访问。两者都必须独立授予访问权限。
  3. 在视图上使用
    @Secured
    @PreAuthorize
    —— 这些Spring Security注解不支持Vaadin视图。请使用
    @AnonymousAllowed
    @PermitAll
    @RolesAllowed
    @DenyAll
  4. SecurityConfig
    中硬编码密码
    —— 内存中的
    {noop}
    密码仅用于原型开发。生产应用必须使用JDBC、LDAP或OAuth2认证。
  5. 未先重定向就调用
    SecurityContextLogoutHandler
    —— 登出处理器会失效会话,因此
    UI.getCurrent().getPage().setLocation()
    必须在调用处理器前执行。优先使用
    AuthenticationContext.logout()
    ,它能正确处理此流程。
  6. 使用URL模式安全控制视图访问 —— Vaadin使用单个Servlet端点处理所有视图;浏览器URL会在客户端更新,但所有请求都发送到同一个服务器端点。这意味着
    HttpSecurity
    中的URL模式规则(例如
    .requestMatchers("/admin/**").hasRole("ADMIN")
    )无法控制Vaadin视图的访问。请仅使用基于注解的访问控制(
    @RolesAllowed
    @PermitAll
    等)进行视图安全防护。URL模式规则仍适用于非Vaadin端点,如REST API。

Detailed Reference

详细参考

For a quick-reference cheatsheet of security annotations, OAuth2 provider configurations, login view checklist, and SecurityConfig templates, see
references/security-patterns.md
.
如需安全注解、OAuth2提供商配置、登录视图检查清单和SecurityConfig模板的速查表,请参阅
references/security-patterns.md