Rumah  >  Artikel  >  Java  >  Log Masuk Borang dengan Spring Boot dan Thymeleaf

Log Masuk Borang dengan Spring Boot dan Thymeleaf

WBOY
WBOYasal
2024-07-25 19:39:43544semak imbas

Log masuk berasaskan borang selalunya pilihan pertama untuk melindungi bahagian hadapan web aplikasi Spring Boot. Ia memastikan bahawa kawasan tertentu aplikasi kami hanya boleh diakses apabila pengguna telah mengesahkan dirinya dengan nama pengguna dan kata laluan, dan status ini disimpan dalam sesi. Apakah langkah yang diperlukan untuk menambah log masuk berasaskan borang pada aplikasi Spring Boot kami?

Mulakan dengan aplikasi mudah

Pertama, kami menggunakan Bootify Builder untuk mencipta aplikasi Spring Boot yang ringkas dalam versi semasa 3.3.2 - untuk melakukan ini, kami hanya klik pada projek terbuka. Di sana kami memilih Thymeleaf + Bootstrap sebagai tindanan hadapan - Thymeleaf ialah enjin templat Spring Boot yang paling banyak digunakan dan membenarkan pemaparan sebelah pelayan. Bootstrap akan disepadukan ke dalam aplikasi kami sebagai WebJar. Pilih mana-mana pangkalan data yang anda ingin sambungkan - pangkalan data terbenam akan berfungsi buat masa ini juga.

Dalam Tab Entiti kami mencipta jadual Pengguna serta TodoList, dan menyambungkannya dengan hubungan N:1. Untuk TodoList kami mengaktifkan pilihan CRUD untuk bahagian hadapan - ini akan menjadi kawasan yang kami selamatkan dengan Spring Security selepas itu.

Form Login with Spring Boot and Thymeleaf
  Pratonton skema pangkalan data kami yang sangat mudah

Kini kami sudah boleh memuat turun aplikasi yang telah siap dan mengimportnya ke dalam IDE kegemaran kami.

Form Login with Spring Boot and Thymeleaf
  Versi pertama aplikasi kami dalam IntelliJ

Konfigurasi Keselamatan Musim Bunga

Log masuk berasaskan borang disediakan dengan bantuan Spring Security. Oleh itu, kami terlebih dahulu memerlukan kebergantungan yang berkaitan, yang kami tambahkan pada build.gradle atau pom.xml kami masing-masing.

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

Modul spring-boot-starter-security menyepadukan Spring Security. Dengan thymeleaf-extras-springsecurity6 sedikit pembantu disertakan yang menyediakan keadaan pengesahan dalam templat Thymeleaf kami - lebih lanjut mengenainya kemudian.

Dengan ini kami boleh sudah menyediakan konfigurasi keselamatan pusat - di sini terus dalam versi terakhir kami.

@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();
    }

}

  Konfigurasi kami untuk log masuk borang

Secara teorinya, Spring Security boleh menyediakan borang log masuk dengan kurang konfigurasi, tetapi ini akan kekurangan reka bentuk dan beberapa mesej yang ingin kami sampaikan kepada pengguna. Tetapi mari kita pergi melalui konfigurasi dahulu.

BCryptPasswordEncoder adalah standard pada masa kini untuk menyimpan cincang kata laluan bersama dengan garam individunya dalam satu medan. Jika kita tidak mempunyai keperluan warisan, kita harus menggunakan yang ini. Selain itu, kami menyediakan AuthenticationManager sebagai kacang untuk dapat menyepadukannya ke dalam perkhidmatan lain.

Sebagai kacang ketiga, kami mencipta SecurityFilterChain kerana ini adalah pendekatan yang diperlukan sejak Spring 3.0. Kita harus mengkonfigurasi kedua-dua CORS dan CSRF dengan betul untuk menutup vektor serangan yang sepadan. Konfigurasi lalai biasanya mencukupi untuk ini.

Di kelas konfigurasi kami, kami telah meletakkan anotasi @EnableMethodSecurity dan akan melindungi titik akhir pengawal yang diingini dengan @PreAuthorize(...) kemudian hari. Oleh itu kami membenarkan akses kepada keseluruhan aplikasi dengan permitAll(). Tanpa keselamatan berasaskan anotasi, kita harus mengkonfigurasi laluan ke sumber yang dilindungi di tempat ini juga.

Kaedah formLogin() dan logout() disesuaikan untuk pengawal kami yang seterusnya supaya kami boleh sentiasa memaparkan mesej yang sesuai kepada pengguna. Spring Security secara automatik menyediakan titik akhir untuk log masuk di mana nama pengguna dan kata laluan boleh diserahkan melalui permintaan POST. Di sini kami menukar nama medan nama pengguna kepada "e-mel". Log keluar diubah suai untuk mengubah hala semula ke halaman log masuk dengan parameter selepas itu.

Memuatkan pengguna daripada pangkalan data kami

Untuk memuatkan pengguna daripada jadual yang telah dibuat, kami perlu menyediakan pelaksanaan UserDetailsService sebagai kacang - ia akan secara automatik ditemui dan digunakan sebagai sumber pengguna.

@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);
    }

}

  Pelaksanaan UserDetailsService secara automatik digunakan sebagai sumber pengguna

Dalam repositori kami, kami harus menambah kaedah User findByEmailIgnoreCase(String email) untuk melaksanakan pertanyaan carian terhadap pangkalan data - mengabaikan huruf besar/kecil membolehkan pengguna melakukan kesilapan kecil semasa menulis e-mel mereka. Peranan di sini sentiasa ROLE_USER untuk setiap pengguna. Memandangkan kami tidak mempunyai titik akhir pendaftaran yang tersedia pada ketika ini, kami boleh menambah pemuat data ringkas bersama-sama dengan aplikasi kami buat masa ini. Profil "tempatan" diperlukan untuk menjadi aktif.

@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);
    }

}

  Kelas pembantu untuk memulakan pengguna pertama secara setempat

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

Atas ialah kandungan terperinci Log Masuk Borang dengan Spring Boot dan Thymeleaf. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn