A Spring Boot Thymeleaf example, uses Spring Security to protect path
/admin
and /user
Technologies used :
- Spring Boot 1.5.3.RELEASE
- Spring 4.3.8.RELEASE
- Spring Security 4.2.2
- Thymeleaf 2.1.5.RELEASE
- Thymeleaf extras Spring Security4 2.1.3
- Tomcat Embed 8.5.14
- Maven 3
- Java 8
1. Project Directory
2. Project Dependencies
Declares
spring-boot-starter-security
, it will get anything you need to develop a Spring Boot + Spring Security
web application.pom.xml
<?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>
<artifactId>spring-boot-web-spring-security</artifactId>
<packaging>jar</packaging>
<name>Spring Boot Web Spring Security</name>
<description>Spring Boot Web Spring Security Example</description>
<url>https://www.mycareerrepublic.com</url>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- do you like thymeleaf? -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- optional, it brings userful tags to display spring security stuff -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
<!-- hot swapping, disable cache for template, enable live reload -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- Optional, for bootstrap -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Package as an executable jar/war -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Display project dependencies :
$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Spring Boot Web Spring Security 1.0
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.10:tree (default-cli) @ spring-boot-web-spring-security ---
[INFO] org.springframework.boot:spring-boot-web-spring-security:jar:1.0
[INFO] +- org.springframework.boot:spring-boot-starter-thymeleaf:jar:1.5.3.RELEASE:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:1.5.3.RELEASE:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:1.5.3.RELEASE:compile
[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.1.11:compile
[INFO] | | | | \- ch.qos.logback:logback-core:jar:1.1.11:compile
[INFO] | | | +- org.slf4j:jcl-over-slf4j:jar:1.7.25:compile
[INFO] | | | +- org.slf4j:jul-to-slf4j:jar:1.7.25:compile
[INFO] | | | \- org.slf4j:log4j-over-slf4j:jar:1.7.25:compile
[INFO] | | +- org.springframework:spring-core:jar:4.3.8.RELEASE:compile
[INFO] | | \- org.yaml:snakeyaml:jar:1.17:runtime
[INFO] | +- org.springframework.boot:spring-boot-starter-web:jar:1.5.3.RELEASE:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.5.3.RELEASE:compile
[INFO] | | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:8.5.14:compile
[INFO] | | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:8.5.14:compile
[INFO] | | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.5.14:compile
[INFO] | | +- org.hibernate:hibernate-validator:jar:5.3.5.Final:compile
[INFO] | | | +- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] | | | +- org.jboss.logging:jboss-logging:jar:3.3.1.Final:compile
[INFO] | | | \- com.fasterxml:classmate:jar:1.3.3:compile
[INFO] | | +- com.fasterxml.jackson.core:jackson-databind:jar:2.8.8:compile
[INFO] | | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.8.0:compile
[INFO] | | | \- com.fasterxml.jackson.core:jackson-core:jar:2.8.8:compile
[INFO] | | +- org.springframework:spring-web:jar:4.3.8.RELEASE:compile
[INFO] | | \- org.springframework:spring-webmvc:jar:4.3.8.RELEASE:compile
[INFO] | +- org.thymeleaf:thymeleaf-spring4:jar:2.1.5.RELEASE:compile
[INFO] | | \- org.thymeleaf:thymeleaf:jar:2.1.5.RELEASE:compile
[INFO] | | +- ognl:ognl:jar:3.0.8:compile
[INFO] | | +- org.javassist:javassist:jar:3.21.0-GA:compile
[INFO] | | \- org.unbescape:unbescape:jar:1.1.0.RELEASE:compile
[INFO] | \- nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:jar:1.4.0:compile
[INFO] | \- org.codehaus.groovy:groovy:jar:2.4.10:compile
[INFO] +- org.springframework.boot:spring-boot-starter-security:jar:1.5.3.RELEASE:compile
[INFO] | +- org.springframework:spring-aop:jar:4.3.8.RELEASE:compile
[INFO] | | \- org.springframework:spring-beans:jar:4.3.8.RELEASE:compile
[INFO] | +- org.springframework.security:spring-security-config:jar:4.2.2.RELEASE:compile
[INFO] | | +- org.springframework.security:spring-security-core:jar:4.2.2.RELEASE:compile
[INFO] | | \- org.springframework:spring-context:jar:4.3.8.RELEASE:compile
[INFO] | \- org.springframework.security:spring-security-web:jar:4.2.2.RELEASE:compile
[INFO] | \- org.springframework:spring-expression:jar:4.3.8.RELEASE:compile
[INFO] +- org.thymeleaf.extras:thymeleaf-extras-springsecurity4:jar:2.1.3.RELEASE:compile
[INFO] | \- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] +- org.springframework.boot:spring-boot-devtools:jar:1.5.3.RELEASE:compile
[INFO] | +- org.springframework.boot:spring-boot:jar:1.5.3.RELEASE:compile
[INFO] | \- org.springframework.boot:spring-boot-autoconfigure:jar:1.5.3.RELEASE:compile
[INFO] \- org.webjars:bootstrap:jar:3.3.7:compile
[INFO] \- org.webjars:jquery:jar:1.11.1:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.072 s
[INFO] Finished at: 2017-05-04T10:13:05+08:00
[INFO] Final Memory: 19M/309M
[INFO] ------------------------------------------------------------------------
3.1 Extends
WebSecurityConfigurerAdapter
, and defined the security rules in the configure
method.For user “admin” :
- Able to access
/admin
page - Unable to access
/user
page, redirect to 403 access denied page.
For user “user” :
- able to access
/user
page - unable to access
/admin
page, redirect to 403 access denied page.
SpringSecurityConfig.java
package com.mycareerrepublic.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.AccessDeniedHandler;
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AccessDeniedHandler accessDeniedHandler;
// roles admin allow to access /admin/**
// roles user allow to access /user/**
// custom 403 access denied handler
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home", "/about").permitAll()
.antMatchers("/admin/**").hasAnyRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
// create two users, admin and user
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER")
.and()
.withUser("admin").password("password").roles("ADMIN");
}
}
3.2 Custom 403 Access denied handler, logs the request and redirect to
/403
WelcomeController.java
package com.mycareerrepublic.error;
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;
// handle 403 page
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
private static Logger logger = LoggerFactory.getLogger(MyAccessDeniedHandler.class);
@Override
public void handle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AccessDeniedException e) throws IOException, ServletException {
Authentication auth
= SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
logger.info("User '" + auth.getName()
+ "' attempted to access the protected URL: "
+ httpServletRequest.getRequestURI());
}
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/403");
}
}
4. Spring Boot
4.1 A controller class, to define the http request and view name.
DefaultController.java
package com.mycareerrepublic.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class DefaultController {
@GetMapping("/")
public String home1() {
return "/home";
}
@GetMapping("/home")
public String home() {
return "/home";
}
@GetMapping("/admin")
public String admin() {
return "/admin";
}
@GetMapping("/user")
public String user() {
return "/user";
}
@GetMapping("/about")
public String about() {
return "/about";
}
@GetMapping("/login")
public String login() {
return "/login";
}
@GetMapping("/403")
public String error403() {
return "/error/403";
}
}
4.2 Start Spring Boot application.
DefaultController.java
package com.mycareerrepublic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootWebApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(SpringBootWebApplication.class, args);
}
}
5. Thymeleaf + Resources + Static files
5.1 For Thymeleaf files, put in
src/main/resources/templates/
folder.5.2 Thymeleaf fragments, for template layout – header.
src/main/resources/templates/fragments/header.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:fragment="header-css">
<!-- this is header-css -->
<link rel="stylesheet" type="text/css"
href="webjars/bootstrap/3.3.7/css/bootstrap.min.css" />
<link rel="stylesheet" th:href="@{/css/main.css}"
href="../../css/main.css" />
</div>
</head>
<body>
<div th:fragment="header">
<!-- this is header -->
<nav class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" th:href="@{/}">Spring Boot</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>
</div>
</body>
</html>
5.3 Thymeleaf fragments, for template layout – footer. Review the
sec
tag, it is a useful tag to display the Spring Security stuff, refer to this Thymeleaf extra Spring Security for detail.src/main/resources/templates/fragments/footer.html
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
</head>
<body>
<div th:fragment="footer">
<div class="container">
<footer>
<!-- this is footer -->
© 2017 mycareerrepublic.com
<span sec:authorize="isAuthenticated()">
| Logged user: <span sec:authentication="name"></span> |
Roles: <span sec:authentication="principal.authorities"></span> |
<a th:href="@{/logout}">Sign Out</a>
</span>
<script type="text/javascript"
src="webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</footer>
</div>
</div>
</body>
</html>
5.4 List of the Thymeleaf files, nothing special, self-explanatory.
home ~
src/main/resources/templates/home.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Boot Thymeleaf + Spring Security</title>
<div th:replace="fragments/header :: header-css"/>
</head>
<body>
<div th:replace="fragments/header :: header"/>
<div class="container">
<div class="starter-template">
<h1>Spring Boot Web Thymeleaf + Spring Security</h1>
<h2>1. Visit <a th:href="@{/admin}">Admin page (Spring Security protected, Need Admin Role)</a></h2>
<h2>2. Visit <a th:href="@{/user}">User page (Spring Security protected, Need User Role)</a></h2>
<h2>3. Visit <a th:href="@{/about}">Normal page</a></h2>
</div>
</div>
<!-- /.container -->
<div th:replace="fragments/footer :: footer"/>
</body>
</html>
admin ~
src/main/resources/templates/admin.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="fragments/header :: header-css"/>
</head>
<body>
<div th:replace="fragments/header :: header"/>
<div class="container">
<div class="starter-template">
<h1>Admin page (Spring Security protected)</h1>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</div>
</div>
<!-- /.container -->
<div th:replace="fragments/footer :: footer"/>
</body>
</html>
user ~
src/main/resources/templates/user.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="fragments/header :: header-css"/>
</head>
<body>
<div th:replace="fragments/header :: header"/>
<div class="container">
<div class="starter-template">
<h1>User page (Spring Security protected)</h1>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</div>
</div>
<!-- /.container -->
<div th:replace="fragments/footer :: footer"/>
</body>
</html>
about ~
src/main/resources/templates/about.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="fragments/header :: header-css"/>
</head>
<body>
<div th:replace="fragments/header :: header"/>
<div class="container">
<div class="starter-template">
<h1>Normal page (No need login)</h1>
</div>
</div>
<!-- /.container -->
<div th:replace="fragments/footer :: footer"/>
</body>
</html>
login ~
src/main/resources/templates/login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
>
<head>
<title>Spring Security Example </title>
<div th:replace="fragments/header :: header-css"/>
</head>
<body>
<div th:replace="fragments/header :: header"/>
<div class="container">
<div class="row" style="margin-top:20px">
<div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3">
<form th:action="@{/login}" method="post">
<fieldset>
<h1>Please Sign In</h1>
<div th:if="${param.error}">
<div class="alert alert-danger">
Invalid username and password.
</div>
</div>
<div th:if="${param.logout}">
<div class="alert alert-info">
You have been logged out.
</div>
</div>
<div class="form-group">
<input type="text" name="username" id="username" class="form-control input-lg"
placeholder="UserName" required="true" autofocus="true"/>
</div>
<div class="form-group">
<input type="password" name="password" id="password" class="form-control input-lg"
placeholder="Password" required="true"/>
</div>
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<input type="submit" class="btn btn-lg btn-primary btn-block" value="Sign In"/>
</div>
<div class="col-xs-6 col-sm-6 col-md-6">
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
<div th:replace="fragments/footer :: footer"/>
</body>
</html>
403 ~
src/main/resources/templates/error/403.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<div th:replace="fragments/header :: header-css"/>
</head>
<body>
<div th:replace="fragments/header :: header"/>
<div class="container">
<div class="starter-template">
<h1>403 - Access is denied</h1>
<div th:inline="text">Hello '[[${#httpServletRequest.remoteUser}]]',
you do not have permission to access this page.</div>
</div>
</div>
<!-- /.container -->
<div th:replace="fragments/footer :: footer"/>
</body>
</html>
5.5 For static files like CSS or Javascript, put in
/src/main/resources/static/
/src/main/resources/static/css/main.css
h1{
color:#0000FF;
}
h2{
color:#FF0000;
}
footer{
margin-top:60px;
}
Note
Read this Spring Boot Serving static content to understand the resource mapping.
Read this Spring Boot Serving static content to understand the resource mapping.
6. Demo
6.1 Start the Spring Boot web app. This
/admin/**
is protected, you need login as admin to access it.Terminal
$ mvn spring-boot:run
//...
6.2 Access
http://localhost:8080
0 comments:
Post a Comment