Thymeleaf und Spring Security

Dies ist das zweite Kapitel der Tutorial-Beitragsreihe zu Spring Security. Dieser Beitrag beschreibt den Einsatz von Thymeleaf und Bootstrap sowie den Einbau der Logout-Funktionalität. Den Source Code zu diesem Tutorial findet ihr auf dem Micromata Github Bereich.

Einen Überblick über das gesamte Tutorial, bereits veröffentlichte Kapitel sowie den Ausblick auf kommende Kapitel ist hier zu finden. Falls ihr Fragen zum Tutorial oder Source Code habt, meldet euch einfach über das LABS Kontaktformular oder wendet euch direkt über den Micromata Github Bereich an mich, Jürgen Fast (Micromata).

Vorbereitung

Als Basis dient das Projekt aus dem Beitrag Einstieg in Spring Security. Hier wird nun die Dependency für Thymeleaf hinzugefügt.

  
  
  

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Anpassung des Controllers

Damit keine festen Strings, sondern mit Thymeleaf erstellte Template-Dateien gerendert werden, muss der Controller angepasst werden.

package de.micromata.spring.security.example;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class DefaultController {
    
    @RequestMapping("/")
    public String index() {
        return "index";
    }

    @RequestMapping("/login")
    public String login(Model model) {
        return "login";
    }

    @RequestMapping("/noSecurity")
    public String noSecurity() {
        return "noSecurity";
    }

}

Der Controller erhält nun @Controller als Annotation. Der Unterschied zu @RestController ist, dass @RestController eine Kombination der Annotationen @Controller und @ResponseBody darstellt. Durch die Annotation @ResponseBody wird der String, den ein Controller als Rückgabewert übergibt, direkt gerendert. Die Annotation @Controller sorgt dafür, dass der Rückgabewert als Name des Templates, welches gerendert werden soll, interpretiert wird. Wird zum Beispiel /noSecurity aufgerufen, so wird die Datei /resources/templates/noSecurity.html gerendert. Außerdem wurde ein Mapping für /login eingefügt, um ein eigenes Template für die Login-Seite zu erstellen.

Die Templates

Für den Default Controller werden die Templates index.htmllogin.html und noSecurity.html unter  /resources/templates/ angelegt. Diese wiederum binden das Fragment  headerAndNav.html  ein, welches unter  /resources/templates/fragments/headerAndNav.html  zu finden ist.

headerAndNav.html:

<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
    <head th:fragment="header">
        <title tiles:fragment="title">Spring Ecurity Example</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous"/>
    </head>
    <body>
        <nav class="navbar navbar-inverse" th:fragment="navbar">
            <div class="container-fluid" >
                <div class="navbar-inner" th:with="currentUser=${#httpServletRequest.userPrincipal?.name}">
                    <a class="navbar-brand" th:href="@{/}">Home</a>
                    <a class="navbar-brand" th:href="@{/noSecurity}">No Security</a>
                    <div th:if="${currentUser != null}">
                        <form class="navbar-form navbar-right" th:action="@{/logout}" method="post">
                            <input type="submit" class="btn btn-primary" value="Logout" />
                        </form>
                        <p class="navbar-text navbar-right" th:text="${currentUser}">
                            example_user
                        </p>
                    </div>
                </div>
            </div>`
        </nav>
    </body>
</html>

Das Fragment headerAndNav.html bindet unter anderem Bootstrap im Header ein. Der Header wird hierbei mit th:fragment als Thymeleaf-Fragment mit dem Namen header gekennzeichnet. Im Body wird eine Navigationsbar angelegt, die ebenfalls als Thymeleaf-Fragment gekennzeichnet wird und den Namen navbar enthält. Die folgenden Templates binden den Header und die Navigationsbar über th:replace ein.

index.html:

<html>
    <head th:replace="fragments/headerAndNav :: header"/>
    <body>
        <div th:replace="fragments/headerAndNav :: navbar"/>
        <div class="container">
            You can only see this, if you are logged in!
        </div>
    </body>
</html>

Dies ist das Template zum Mapping /.

noSecurity.html:

<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
    <head th:replace="fragments/headerAndNav :: header"/>
    <body>
        <div th:replace="fragments/headerAndNav :: navbar"/>
        <div class="container">
            Everybody can see this!
        </div>
    </body>
</html>

Dies ist das Template zum Mapping /noSecurity.

login.html:

<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
    <head th:replace="fragments/headerAndNav :: header"/>
    <body>
        <div th:replace="fragments/headerAndNav :: navbar"/>
        <div class="container">
            <form name="f" th:action="@{/login}" method="post">
                <fieldset>
                    <legend>Please Login</legend>
                    <div th:if="${param.error}" class="alert alert-error">
                        Invalid username and password.
                    </div>
                    <div th:if="${param.logout}" class="alert alert-success">
                        You have been logged out.
                    </div>
                    <div class="form-group">
                        <label for="username">Username</label>
                        <input type="text" id="username" name="username"/>
                    </div>
                    <div class="form-group">
                        <label for="password">Password</label>
                        <input type="password" id="password" name="password"/>
                    </div>
                    <button type="submit" class="btn btn-primary">Login</button>

                </fieldset>
            </form>
        </div>
    </body>
</html>

Das Template login.html ist das Template der Login-Seite. Es wird sowohl beim Logout als auch bei einem fehlerhaften Login-Versuch eine Nachricht ausgegeben. Dies kann mit th:if="${param.error}" und th:if="${param.logout}" abgefragt werden.

Anpassung der Security Konfiguration

Die Methode configure(HttpSecurity http) der Elternklasse von WebSecurityConfig (siehe dazu auch den Beitrag Einstieg in Spring Security) wird im Folgenden angepasst.

Vorher

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/noSecurity").permitAll()
        .anyRequest().authenticated()
        .and().formLogin().permitAll();
}

Nachher

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests().antMatchers("/noSecurity").permitAll()
    .anyRequest().authenticated()
    .and().formLogin().loginPage("/login").permitAll()
    .and().logout().permitAll();
}

Durch loginPage("/login") wird festgelegt, dass die Login-Seite unter /login aufrufbar ist. Ein nicht eingeloggter User wird auf diese Seite weitergeleitet. Mit .and().logout().permitAll() wird festgelegt, dass der Aufruf des Logouts von jeder Seite aus möglich ist.

Autor

Jürgen Fast arbeitet seit 2014 bei der Micromata GmbH als Softwareentwickler in verschiedenen Projekten der Logistik- und Automobilbranche. Sein Schwerpunkt liegt in der Entwicklung von javabasierten Webapplikationen.