Heim > Java > javaLernprogramm > Hauptteil

Formularanmeldung mit Spring Boot und Thymeleaf

WBOY
Freigeben: 2024-07-25 19:39:43
Original
558 Leute haben es durchsucht

Die formularbasierte Anmeldung ist oft die erste Wahl, um das Web-Frontend einer Spring Boot-Anwendung zu schützen. Dadurch wird sichergestellt, dass bestimmte Bereiche unserer Anwendung nur dann zugänglich sind, wenn sich ein Benutzer mit einem Benutzernamen und einem Passwort authentifiziert hat und dieser Status in der Sitzung gespeichert wird. Welche Schritte sind erforderlich, um eine formularbasierte Anmeldung zu unserer Spring Boot-Anwendung hinzuzufügen?

Beginnen Sie mit einer einfachen Bewerbung

Zuerst erstellen wir mit dem Bootify Builder eine einfache Spring Boot-Anwendung in der aktuellen Version 3.3.2 – dazu klicken wir einfach auf Projekt öffnen. Dort wählen wir Thymeleaf + Bootstrap als Frontend-Stack aus – Thymeleaf ist die am häufigsten verwendete Template-Engine von Spring Boot und ermöglicht serverseitiges Rendern. Bootstrap wird als WebJar in unsere App integriert. Wählen Sie eine beliebige Datenbank aus, mit der Sie eine Verbindung herstellen möchten. Eine eingebettete Datenbank reicht vorerst auch aus.

Im Entities Tab erstellen wir die Tabellen User sowie TodoList und verbinden sie mit einer N:1-Relation. Für die TodoList aktivieren wir die CRUD-Option für das Frontend – das wird der Bereich sein, den wir anschließend mit Spring Security absichern.

Form Login with Spring Boot and Thymeleaf
 Vorschau unseres sehr einfachen Datenbankschemas

Jetzt können wir bereits die fertige Anwendung herunterladen und in unsere Lieblings-IDE importieren.

Form Login with Spring Boot and Thymeleaf
 Die erste Version unserer Anwendung in IntelliJ

Konfiguration von Spring Security

Die formularbasierte Anmeldung erfolgt mit Hilfe von Spring Security. Wir benötigen also zunächst die relevanten Abhängigkeiten, die wir zu unserem build.gradle bzw. pom.xml hinzufügen.

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

Das Modul spring-boot-starter-security integriert Spring Security. Mit thymeleaf-extras-springsecurity6 ist ein kleiner Helfer enthalten, der den Authentifizierungsstatus in unseren Thymeleaf-Vorlagen bereitstellt – dazu später mehr.

Damit können wir bereits die zentrale Sicherheitskonfiguration bereitstellen – hier direkt in unserer finalen Version.

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

}
Nach dem Login kopieren

  Unsere Konfiguration für den Formular-Login

Theoretisch kann Spring Security das Anmeldeformular mit weniger Konfiguration bereitstellen, aber dafür würden das Design und einige Nachrichten fehlen, die wir dem Benutzer präsentieren möchten. Aber gehen wir zuerst die Konfiguration durch.

BCryptPasswordEncoder ist heutzutage Standard, um den Hash eines Passworts zusammen mit seinem individuellen Salt in einem einzigen Feld zu speichern. Wenn wir keine Legacy-Anforderung haben, sollten wir diese verwenden. Außerdem stellen wir den AuthenticationManager als Bean zur Verfügung, um ihn in andere Dienste integrieren zu können.

Als dritte Bean erstellen wir die SecurityFilterChain, da dies seit Spring 3.0 der erforderliche Ansatz ist. Wir sollten sowohl CORS als auch CSRF richtig konfigurieren, um die entsprechenden Angriffsvektoren zu schließen. Hierfür reicht in der Regel die Standardkonfiguration aus.

In unserer Konfigurationsklasse haben wir die Annotation @EnableMethodSecurity platziert und werden die gewünschten Controller-Endpunkte später mit @PreAuthorize(...) schützen. Deshalb erlauben wir den Zugriff auf die gesamte Anwendung mit erlaubenAll(). Ohne die annotationsbasierte Sicherheit sollten wir auch an dieser Stelle die Pfade zu den geschützten Ressourcen konfigurieren.

Die Methoden formLogin() und logout() sind für unseren nachfolgenden Controller angepasst, sodass wir dem Benutzer immer eine entsprechende Meldung anzeigen können. Spring Security stellt automatisch einen Endpunkt für die Anmeldung bereit, an den Benutzername und Passwort per POST-Anfrage übermittelt werden können. Hier ändern wir den Namen des Benutzernamenfelds in „E-Mail“. Die Abmeldung wird so geändert, dass sie anschließend mit einem Parameter zur Anmeldeseite zurückgeleitet wird.

Laden von Benutzern aus unserer Datenbank

Um die Benutzer aus der bereits erstellten Tabelle zu laden, müssen wir eine Implementierung von UserDetailsService als Bean bereitstellen – diese wird automatisch gefunden und als Benutzerquelle verwendet.

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

}
Nach dem Login kopieren

  UserDetailsService-Implementierung wird automatisch als Benutzerquelle verwendet

In unserem Repository sollten wir eine Methode User findByEmailIgnoreCase(String email) hinzufügen, um eine Suchabfrage in der Datenbank auszuführen – das Ignorieren von Groß-/Kleinschreibung ermöglicht es dem Benutzer, beim Schreiben seiner E-Mail kleine Fehler zu machen. Die Rolle ist hier immer ROLE_USER für jeden Benutzer. Da uns derzeit kein Registrierungsendpunkt zur Verfügung steht, können wir vorerst einen einfachen Datenlader zusammen mit unserer Anwendung hinzufügen. Damit es aktiv wird, ist das Profil „local“ erforderlich.

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

}
Nach dem Login kopieren

 Hilfsklasse zum lokalen Initialisieren eines ersten Benutzers

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";
    }

}
Nach dem Login kopieren

  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;

}
Nach dem Login kopieren

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>
Nach dem Login kopieren

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();
}
Nach dem Login kopieren

_  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
Nach dem Login kopieren

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>
Nach dem Login kopieren

  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

Das obige ist der detaillierte Inhalt vonFormularanmeldung mit Spring Boot und Thymeleaf. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage