Как отображать кнопку выхода из сайта только для аутентифицированных пользователей в 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 шт):
Прежде всего необходимо подключить следующую зависимость:
<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
Цифра, как и прежде, зависит от версии.