Как отображать кнопку выхода из сайта только для аутентифицированных пользователей в Thymleaf?

Я попробовал это, но кнопка всё равно отображается всем.

<form th:if="${#authentication.isAuthenticated()}" th:action="@{/logout}" method="post">
    <h3><input type="submit" value="Log out" /></h3>
</form>

Почему так происходит, и как это сделать правильно?

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>arkadisahakyan</groupId>
    <artifactId>authentication-with-spring</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>authentication-with-spring</name>
    <description>Demo project</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity6</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

WebSecurityConfig.java

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filter(HttpSecurity http, SecurityContextRepository securityContextRepository, CustomAccessDeniedHandler accessDeniedHandler,
                                      CustomAuthenticationFailureHandler authenticationFailureHandler, RememberMeServices rememberMeServices) throws Exception {
        return http
                .addFilterAfter(new CsrfTokenLogger(), CsrfFilter.class)
                .authorizeHttpRequests((requests) -> requests
                        .requestMatchers("/", "/home", "/login", "/register", "/logout").permitAll()
                        .requestMatchers(HttpMethod.POST, "/login", "/register", "/logout").permitAll()
                        .requestMatchers("/user").hasAnyAuthority("USER", "ADMIN")
                        .requestMatchers("/admin").hasAnyAuthority("ADMIN")
                        .anyRequest().authenticated()
                )
                .formLogin((form) -> form.loginPage("/login").failureHandler(authenticationFailureHandler).securityContextRepository(securityContextRepository).permitAll())
                .logout((logout) -> logout.logoutUrl("/logout").logoutSuccessUrl("/").invalidateHttpSession(true).deleteCookies("JSESSIONID").permitAll())
                .rememberMe((remember) -> remember.rememberMeServices(rememberMeServices))
                .securityContext(securityContext -> securityContext.securityContextRepository(securityContextRepository))
                .exceptionHandling((exception) -> exception.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).accessDeniedHandler(accessDeniedHandler))
                .build();
    }

    @Bean
    public RememberMeServices rememberMeServices(UserDetailsService userDetailsService) {
        TokenBasedRememberMeServices.RememberMeTokenAlgorithm encodingAlgorithm = TokenBasedRememberMeServices.RememberMeTokenAlgorithm.SHA256;
        TokenBasedRememberMeServices rememberMe = new TokenBasedRememberMeServices("secretKeyForRememberMeToken", userDetailsService, encodingAlgorithm);
        rememberMe.setMatchingAlgorithm(TokenBasedRememberMeServices.RememberMeTokenAlgorithm.MD5);
        return rememberMe;
    }

    @Bean
    public SecurityContextRepository securityContextRepository() {
        return new DelegatingSecurityContextRepository(
                new RequestAttributeSecurityContextRepository(),
                new HttpSessionSecurityContextRepository()
        );
    }

    @Bean
    public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder);
        return new ProviderManager(authenticationProvider);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

HomeController.java

@Controller
@RequestMapping(value = "/")
public class HomeController {

    @GetMapping(value = {"/", "home"})
    public String home(Authentication authentication) {
        return "home";
    }
}

home.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
    <h1>Home Page</h1>
    <h3>
        Available URLs:
        <a title="You should be authenticated" href="/user">/user</a>,
        <a title="You should be an admin" href="/admin">/admin</a>,
        <a title="Log in to the website" href="/login">/login</a>,
        <a title="Create an account" href="/register">/register</a>,
        <a title="Log out from the website" href="/logout">/logout</a>
    </h3>
    <form th:if="${#authentication.isAuthenticated()}" th:action="@{/logout}" method="post">
        <h3><input type="submit" value="Log out" /></h3>
    </form>
</body>
</html>

Ответы (1 шт):

Автор решения: Byb

Прежде всего необходимо подключить следующую зависимость:

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>

Какую цифру ставить в конец (6, 5, 4 и так далее) зависит от вашей версии Spring Boot. Так как она скорее всего наиболее новая, то подойдёт thymeleaf-extras-springsecurity6.

Помимо if есть ещё и другой подход: использовать атрибут sec:authorize. Например:

<form sec:authorize="isAuthenticated()" th:action="@{/logout}" method="post">
    <h3><input type="submit" value="Log out" /></h3>
</form>

Точно так же sec:authorize очень удобно использовать, когда вы проводите проверку по ролям и правам. Например, дать доступ только пользователю, имеющему роль ADMIN, можно так:

<form sec:authorize="hasRole('ADMIN')" th:action="@{/logout}" method="post">
    <h3><input type="submit" value="Log out" /></h3>
</form>

А чтобы среда разработки не ругалась на неопознанный атрибут, нужно в документ подключить пространство имён:

xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity6"

Цифра здесь на конце, опять же, зависит от используемой вами версии.


Помимо этого, чтобы Thymeleaf мог работать с аутентификациями, ролями и правами, в .properties-файл нужно прописать следующую строку:

spring.thymeleaf.extras.springsecurity6.enabled=true

Цифра, как и прежде, зависит от версии.

→ Ссылка