首页 >Java >java教程 >使用 Spring Boot 和 Thymeleaf 进行表单登录

使用 Spring Boot 和 Thymeleaf 进行表单登录

WBOY
WBOY原创
2024-07-25 19:39:43569浏览

基于表单的登录通常是保护 Spring Boot 应用程序 Web 前端的首选。它确保只有用户使用用户名和密码对自己进行身份验证后才能访问应用程序的某些区域,并且此状态存储在会话中。将基于表单的登录添加到我们的 Spring Boot 应用程序需要执行哪些步骤?

从一个简单的应用程序开始

首先,我们使用 Bootify Builder 在当前版本 3.3.2 中创建一个简单的 Spring Boot 应用程序 - 为此,我们只需单击打开项目即可。我们选择 Thymeleaf + Bootstrap 作为前端堆栈 - Thymeleaf 是 Spring Boot 最常用的模板引擎,并允许服务器端渲染。 Bootstrap 将作为 WebJar 集成到我们的应用程序中。选择您想要连接的任何数据库 - 现在也可以使用嵌入式数据库。

实体选项卡中,我们创建表 User 和 TodoList,并使用 N:1 关系将它们连接起来。对于 TodoList,我们激活前端的 CRUD 选项 - 这将是我们随后使用 Spring Security 保护的区域。

Form Login with Spring Boot and Thymeleaf
 我们非常简单的数据库架构的预览

现在我们已经可以下载完成的应用程序并将其导入到我们最喜欢的 IDE 中。

Form Login with Spring Boot and Thymeleaf
  IntelliJ 中我们应用程序的第一个版本

Spring安全的配置

基于表单的登录是在 Spring Security 的帮助下提供的。所以我们首先需要相关的依赖项,我们将其分别添加到我们的build.gradle或pom.xml中。

implementation('org.springframework.boot:spring-boot-starter-security')
implementation('org.thymeleaf.extras:thymeleaf-extras-springsecurity6')

模块 spring-boot-starter-security 集成了 Spring Security。 thymeleaf-extras-springsecurity6 包含一个小助手,它在我们的 Thymeleaf 模板中提供身份验证状态 - 稍后会详细介绍。

有了这个,我们可以已经提供中央安全配置 - 直接在我们的最终版本中。

@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class HttpSecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(
            final AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain configure(final HttpSecurity http) throws Exception {
        return http.cors(withDefaults())
                .csrf(withDefaults())
                .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll())
                .formLogin(form -> form
                    .loginPage("/login")
                    .usernameParameter("email")
                    .failureUrl("/login?loginError=true"))
                .logout(logout -> logout
                    .logoutSuccessUrl("/login?logoutSuccess=true")
                    .deleteCookies("JSESSIONID"))
                .exceptionHandling(exception -> exception
                    .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login?loginRequired=true")))
                .build();
    }

}

  我们的表单登录配置

理论上,Spring Security 可以提供较少配置的登录表单,但这会缺乏设计和我们希望向用户呈现的一些消息。但我们先来看看配置。

BCryptPasswordEncoder 是当今的标准将密码的哈希值及其单独的盐存储在单个字段中。如果我们没有遗留需求,我们应该使用这个。此外,我们提供 AuthenticationManager 作为 bean,以便能够将其集成到其他服务中。

作为第三个 bean,我们创建 SecurityFilterChain,因为这是自 Spring 3.0 以来所需的方法。我们应该正确配置 CORS 和 CSRF 以关闭相应的攻击向量。默认配置通常足以满足此目的。

在我们的配置类中,我们放置了注释@EnableMethodSecurity,稍后将使用@PreAuthorize(...) 保护所需的控制器端点。因此我们允许使用permitAll()访问整个应用程序。如果没有基于注释的安全性,我们也应该在这个地方配置受保护资源的路径。

formLogin() 和 logout() 方法是为我们的后续控制器定制的,以便我们能够始终向用户显示适当的消息。 Spring Security 自动提供一个登录端点,可以通过 POST 请求提交用户名和密码。这里我们将用户名字段的名称更改为“电子邮件”。修改注销为之后带参数重定向回登录页面。

从我们的数据库加载用户

要从已创建的表中加载用户,我们需要以 bean 的形式提供 UserDetailsS​​ervice 的实现 - 它将自动找到并用作用户源

@Service
public class HttpUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public HttpUserDetailsService(final UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public HttpUserDetails loadUserByUsername(final String username) {
        final User user = userRepository.findByEmailIgnoreCase(username);
        if (user == null) {
            log.warn("user not found: {}", username);
            throw new UsernameNotFoundException("User " + username + " not found");
        }
        final List<SimpleGrantedAuthority> roles = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
        return new HttpUserDetails(user.getId(), username, user.getHash(), roles);
    }

}

  UserDetailsS​​ervice 实现自动用作用户源

在我们的存储库中,我们应该添加一个方法 User findByEmailIgnoreCase(String email) 来对数据库执行搜索查询 - 忽略大小写允许用户在编写电子邮件时出现小错误。对于每个用户来说,这里的角色始终是 ROLE_USER。由于我们目前没有可用的注册端点,因此我们现在可以在应用程序中添加一个简单的数据加载器。需要配置文件“本地”才能激活。

@Component
@Profile("local")
public class UserLoader implements ApplicationRunner {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public UserLoader(final UserRepository userRepository,
                      final PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void run(final ApplicationArguments args) {
        if (userRepository.count() != 0) {
            return;
        }
        final User user = new User();
        user.setEmail("test@test.com");
        user.setHash(passwordEncoder.encode("testtest"));
        userRepository.save(user);
    }

}

  用于在本地初始化第一个用户的辅助类

Add login controller

With this we can already add the LoginController. Since the POST endpoint is automatically provided by Spring Security, a GET endpoint is sufficient here to show the template to the user.

@Controller
public class AuthenticationController {

    @GetMapping("/login")
    public String login(@RequestParam(name = "loginRequired", required = false) final Boolean loginRequired,
            @RequestParam(name = "loginError", required = false) final Boolean loginError,
            @RequestParam(name = "logoutSuccess", required = false) final Boolean logoutSuccess,
            final Model model) {
        model.addAttribute("authentication", new AuthenticationRequest());
        if (loginRequired == Boolean.TRUE) {
            model.addAttribute(WebUtils.MSG_INFO, WebUtils.getMessage("authentication.login.required"));
        }
        if (loginError == Boolean.TRUE) {
            model.addAttribute(WebUtils.MSG_ERROR, WebUtils.getMessage("authentication.login.error"));
        }
        if (logoutSuccess == Boolean.TRUE) {
            model.addAttribute(WebUtils.MSG_INFO, WebUtils.getMessage("authentication.logout.success"));
        }
        return "authentication/login";
    }

}

  Backend for rendering the login page

The request parameters that we had already specified in our security configuration are converted to corresponding messages here. In our simple application from Bootify the corresponding helpers are already included. Here we also need the AuthenticationRequest object with getters and setters.

public class AuthenticationRequest {

    @NotNull
    @Size(max = 255)
    private String email;

    @NotNull
    @Size(max = 255)
    private String password;

}

The corresponding template for our controller could then look like this.

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
        layout:decorate="~{layout}">
    <head>
        <title>[[#{authentication.login.headline}]]</title>
    </head>
    <body>
        <div layout:fragment="content">
            <h1 class="mb-4">[[#{authentication.login.headline}]]</h1>
            <div th:replace="~{fragments/forms::globalErrors('authentication')}" />
            <form th:action="${requestUri}" method="post">
                <div th:replace="~{fragments/forms::inputRow(object='authentication', field='email')}" />
                <div th:replace="~{fragments/forms::inputRow(object='authentication', field='password', type='password')}" />
                <input type="submit" th:value="#{authentication.login.headline}" class="btn btn-primary mt-4" />
            </form>
        </div>
    </body>
</html>

As Thymeleaf doesn't allow direct access to request object anymore, we're providing the requestUri in the model.

@ModelAttribute("requestUri")
String getRequestServletPath(final HttpServletRequest request) {
    return request.getRequestURI();
}

_  Providing the requestUri - as part of the AuthenticationController or a general ControllerAdvice _

With this template we send a POST request to the /login endpoint. The INFO or ERROR messages are automatically displayed by the layout. All used messages have to be present in our messages.properties.

authentication.login.headline=Login
authentication.email.label=Email
authentication.password.label=Password
authentication.login.required=Please login to access this area.
authentication.login.error=Your login was not successful - please try again.
authentication.logout.success=Your logout was successful.
navigation.login=Login
navigation.logout=Logout

Last we can extend our layout.html. With this we also always show a login / logout link in the header. Spring Security also automatically provides a /logout endpoint, but we have to address it via POST.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
            xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
    <!-- ... -->    
    <a sec:authorize="!isAuthenticated()" th:href="@{/login}" class="nav-link">[[#{navigation.login}]]</a>
    <form sec:authorize="isAuthenticated()" th:action="@{/logout}" method="post" class="nav-link">
        <input th:value="#{navigation.logout}" type="submit" class="unset" />
    </form>
    <!-- ... -->
</html>

  Adding login / logout links to our layout

In the html tag we've extended the namespace to use the helpers from the thymeleaf-extras-springsecurity6 module. As a final step we only need to add the annotation @PreAuthorize("hasAuthority('ROLE_USER')") at our TodoListController.

Starting our application

With this we have all needed pieces of our puzzle together! Now we start our application and when we want to see the todo lists, we should be redirected to the login page. Here we can log in with test@test.com / testtest.

Form Login with Spring Boot and Thymeleaf
  Automatic redirect to the login

In the Free plan of Bootify, Spring Boot prototypes with its own database schema, REST API and frontend can be generated. In the Professional plan, among other things, Spring Security with the form-based login is available to generate the setup described here - exactly matching the created database and the selected settings. A registration endpoint and role source can be specified as well.

» See Features and Pricing

以上是使用 Spring Boot 和 Thymeleaf 进行表单登录的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn