Spring Boot + Spring Security + Thymeleaf Form Login Example

摘要: This tutorial demonstrates how to configure spring-boot, spring-security and thymeleaf with form-login. We secure our web application using spring security form-login. We create a reusable Thymeleaf layout which we can use to create our secured and unsecured pages. When a user accesses a protected resource with insufficient rights we redirect the user to an access-denied page. Finally we create a login page where the user is able to login to the application. And we create a logout link.

This tutorial demonstrates how to configure spring-boot, spring-security and thymeleaf with form-login. We secure our web application using spring security form-login. We create a reusable Thymeleaf layout which we can use to create our secured and unsecured pages. When a user accesses a protected resource with insufficient rights we redirect the user to an access-denied page. Finally we create a login page where the user is able to login to the application. And we create a logout link.

Project Structure

Let’s start by looking at the project structure.

Maven Dependencies

We use Apache Maven to manage our project dependencies. Make sure the following dependencies reside on the class-path. We are upgrading Thymeleaf to version 3 using the following Maven properties.

  • thymeleaf.version – specifies the Thymeleaf version.
  • thymeleaf-layout-dialect.version – specifies the layout templating dialect.
  • thymeleaf-extras-springsecurity4.version – specifies the spring security integration.
<?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
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.memorynotfound.spring.security.formlogin</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <url>http://memorynotfound.com</url>
    <name>Spring Security - ${project.artifactId}</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>

        <!-- upgrade to thymeleaf version 3 -->
        <thymeleaf.version>3.0.8.RELEASE</thymeleaf.version>
        <thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
        <thymeleaf-extras-springsecurity4.version>3.0.2.RELEASE</thymeleaf-extras-springsecurity4.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity4</artifactId>
        </dependency>

        <!-- bootstrap and jquery -->
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>3.3.7</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Spring Security Configuration

SecurityConfig extends WebSecurityConfigurerAdapter. This allows us to override the configure(HttpSecurity http) and configure(AuthenticationManagerBuilder auth) methods. In the first method, we configure the HttpSecurity. This allows us to configure static resources, form authentication login and logout configurations. We also can add a custom AccessDeniedHandler. In the second method we configure some default InMemory users to use in this example.

package com.memorynotfound.spring.security.config;

import com.memorynotfound.spring.security.web.LoggingAccessDeniedHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private LoggingAccessDeniedHandler accessDeniedHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .antMatchers(
                            "/",
                            "/js/**",
                            "/css/**",
                            "/img/**",
                            "/webjars/**").permitAll()
                    .antMatchers("/user/**").hasRole("USER")
                    .anyRequest().authenticated()
                .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()
                .and()
                .logout()
                    .invalidateHttpSession(true)
                    .clearAuthentication(true)
                    .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                    .logoutSuccessUrl("/login?logout")
                    .permitAll()
                .and()
                .exceptionHandling()
                    .accessDeniedHandler(accessDeniedHandler);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user").password("password").roles("USER")
            .and()
                .withUser("manager").password("password").roles("MANAGER");
    }

}

We create a custom 403: Access Denied Handler. In this handler we log the user who is trying to access a protected resource which he doesn’t have sufficient rights and redirect the request to /access-denied.

package com.memorynotfound.spring.security.web;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class LoggingAccessDeniedHandler implements AccessDeniedHandler {

    private static Logger log = LoggerFactory.getLogger(LoggingAccessDeniedHandler.class);

    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException ex) throws IOException, ServletException {

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        if (auth != null) {
            log.info(auth.getName()
                    + " was trying to access protected resource: "
                    + request.getRequestURI());
        }

        response.sendRedirect(request.getContextPath() + "/access-denied");

    }
}

Spring Boot

We start the application using Spring Boot.

package com.memorynotfound.spring.security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Run {

    public static void main(String[] args) {
        SpringApplication.run(Run.class, args);
    }
}

Contains the mappings to the views used by the ThymeleafViewResolver.

package com.memorynotfound.spring.security.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    @GetMapping("/")
    public String root() {
        return "index";
    }

    @GetMapping("/user")
    public String userIndex() {
        return "user/index";
    }

    @GetMapping("/login")
    public String login() {
        return "login";
    }

    @GetMapping("/access-denied")
    public String accessDenied() {
        return "/error/access-denied";
    }

}

Configure Thymeleaf with Spring Boot

We configure Thymeleaf and Spring Boot using the application.yml file located in the src/main/resources folder.

server:
  port: 8080

spring:
  thymeleaf:
    cache: false
    check-template: true
    check-template-location: true
    content-type: text/html
    enabled: true
    encoding: UTF-8
    mode: HTML
    prefix: classpath:/templates/
    suffix: .html
    # excluded-view-names:
    # template-resolver-order:
    # view-names:

logging:
  level:
    root: WARN
    com.memorynotfound: DEBUG
    org.springframework.web: INFO
    org.springframework.security: INFO

Thymeleaf Templating Configuration

All Thymeleaf templates are located in the src/main/resources/templates/ folder.

Creating the Thymeleaf Layout Template

First, we create the layout template. This layout is used by each page. The layout is located at src/main/resources/templates/fragments/layout.html.

<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>

    <link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.min.css}"/>
    <link rel="stylesheet" type="text/css" th:href="@{/css/main.css}"/>

    <title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Spring Security Thymeleaf</title>
</head>
<body>
    <th:block th:replace="fragments/header :: header"/>
    <div class="container">
        <th:block layout:fragment="content"/>
    </div>
    <th:block th:replace="fragments/footer :: footer"/>
</body>
</html>

Creating the header

Next, we create a header. The header is located at src/main/resources/templates/fragments/header.html.

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="header">
    <nav class="navbar navbar-inverse navbar-static-top">
        <div class="container">
            <div class="navbar-header">
                <a class="navbar-brand" th:href="@{/}">
                    <img th:src="@{/img/logo.png}" alt="memorynotfound logo"/>
                </a>
            </div>
            <div id="navbar" class="collapse navbar-collapse">
                <ul class="nav navbar-nav">
                    <li class="active">
                        <a th:href="@{/}">Home</a>
                    </li>
                </ul>
            </div>
        </div>
    </nav>
</th:block>
</body>
</html>

Creating the footer

Next, we create a footer. The header is located at src/main/resources/templates/fragments/footer.html.

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<body>
<th:block th:fragment="footer">
    <footer>
        <div class="container">
            <p>
                &copy; Memorynotfound.com
                <span sec:authorize="isAuthenticated()" style="display: inline-block;">
                    | Logged user: <span sec:authentication="name"></span> |
                    Roles: <span sec:authentication="principal.authorities"></span> |
                    <a th:href="@{/logout}">Sign Out</a>
                </span>
            </p>
        </div>
    </footer>

    <script type="text/javascript" th:src="@{/webjars/jquery/3.2.1/jquery.min.js/}"></script>
    <script type="text/javascript" th:src="@{/webjars/bootstrap/3.3.7/js/bootstrap.min.js}"></script>

</th:block>
</body>
</html>

Creating the Home Page

Next, we create the Home Page. The home page uses the previous layout and overrides the content fragment. The home page is located at src/main/resources/templates/index.html.

<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:th="http://www.thymeleaf.org"
      layout:decorate="~{fragments/layout}">
<head>
    <title>Index</title>
</head>
<body>
<div layout:fragment="content" th:remove="tag">
    <h1>Hello Spring Security</h1>
    <p>This is an unsecured page, but you can access the secured pages after authenticating.</p>
    <ul>
        <li>Go to the <a href="/user" th:href="@{/user}">secured pages</a></li>
    </ul>
</div>
</body>
</html>

Creating the Login Page

Next, we create the Login Page. The login page uses the previous layout and overrides the content fragment. The login page is located at src/main/resources/templates/login.html.

<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:th="http://www.thymeleaf.org"
      layout:decorate="~{fragments/layout}">
<head>
    <title>Login</title>
</head>
<body>
<div layout:fragment="content" th:remove="tag">

    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h1>Login page</h1>
            <form th:action="@{/login}" method="post">
                <div th:if="${param.error}">
                    <div class="alert alert-danger">
                        Invalid username or password.
                    </div>
                </div>
                <div th:if="${param.logout}">
                    <div class="alert alert-info">
                        You have been logged out.
                    </div>
                </div>
                <div class="form-group">
                    <label for="username">Username</label>:
                    <input type="text"
                           id="username"
                           name="username"
                           class="form-control"
                           autofocus="autofocus"
                           placeholder="Username">
                </div>
                <div class="form-group">
                    <label for="password">Password</label>:
                    <input type="password"
                           id="password"
                           name="password"
                           class="form-control"
                           placeholder="Password">
                </div>
                <div class="form-group">
                    <div class="row">
                        <div class="col-sm-6 col-sm-offset-3">
                            <input type="submit"
                                   name="login-submit"
                                   id="login-submit"
                                   class="form-control btn btn-info"
                                   value="Log In">
                        </div>
                    </div>
                </div>
            </form>
        </div>
    </div>

    <p><a href="/" th:href="@{/}">Back to home page</a></p>

</div>
</body>
</html>

Creating the Secured User Page

Next, we create the Secured User Page. This page is secured, and only visible for users who have the ROLE_USER. The user page is located at src/main/resources/templates/user/index.html.

<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:th="http://www.thymeleaf.org"
      layout:decorate="~{fragments/layout}">
<head>
    <title>Secured</title>
</head>
<body>
<div layout:fragment="content" th:remove="tag">
    <h1>This is a secured page!</h1>
    <p><a href="/" th:href="@{/}">Back to home page</a></p>
</div>
</body>
</html>

Creating the Access Denied Page

Finally, we create the Access Denied Page. Uses who have insufficient rights will be redirected to this page. The access denied page is located at src/main/resources/templates/error/access-denied.html.

<!DOCTYPE html>
<html xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      xmlns:th="http://www.thymeleaf.org"
      layout:decorate="~{fragments/layout}">
<head>
    <title>Access Denied</title>
</head>
<body>
<div layout:fragment="content" th:remove="tag">
    <h1>Access denied</h1>
    <p><a href="/" th:href="@{/}">Back to home page</a></p>
</div>
</body>
</html>

Creating the Stylesheet

We use the following stylesheet which is located at src/main/resources/static/css/main.css.

h1 {
    color: #78ab46;
}

footer {
    position: fixed;
    height: 50px;
    bottom: 0;
    width: 100%;
    background-color: #ccc
}

footer p {
    padding: 15px;
}

Demo

Start the Spring Boot Web Application.

mvn spring-boot:run

Go to http://localhost:8080/.

Go to http://localhost:8080/user and is redirected to http://localhost:8080/login.

Invalid username or password http://localhost:8080/login?error.

Successful login http://localhost:8080/login redirected to http://localhost:8080.

Logged in with user manager, access page http://localhost:8080/user redirected to http://localhost:8080/access-denied.

Sucessful logout, redirected to http://localhost:8080/login?logout.

Download

上一篇: Spring Security Basic Authentication Configuration Example
下一篇: Spring Kafka Forwarding Listener Results using @SendTo
 评论 ( What Do You Think )
名称
邮箱
网址
评论
验证
   
 

 


  • 微信公众号

  • 我的微信

站点声明:

1、一号门博客CMS,由Python, MySQL, Nginx, Wsgi 强力驱动

2、部分文章或者资源来源于互联网, 有时候很难判断是否侵权, 若有侵权, 请联系邮箱:summer@yihaomen.com, 同时欢迎大家注册用户,主动发布无版权争议的 文章/资源.

3、鄂ICP备14001754号-3, 鄂公网安备 42280202422812号