Сегодня дополним наш предыдущий урок Spring Boot и Thymeleaf JAR добавив Spring Sequrity.
Итак, собираем SPRING BOOT + SPRING SECURITY приложение в связке с встроенным сервером приложений Tomcat и движком шаблонов Thymeleaf в качестве исполняемого JAR-файла.
содержание
Технологии:
- Spring Boot 1.5.9.RELEASE
- Spring 4.3.13.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. Структура каталогов
2. Зависимости
Объявляем зависимость spring-boot-starter-security, она даст нам все необходимые инструменты для Spring Boot + Spring Security приложения.
Я как обычно использую Spring Initializr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
<?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>ru.leodev.examples.springboot</groupId> <artifactId>spring-boot-web-spring-security</artifactId> <version>0.0.1</version> <packaging>jar</packaging> <name>Spring Boot Web Spring Security</name> <description>Spring Boot Web Spring Security Example</description> <url> http://leodev.ru/blog/news/spring-boot-spri…hymeleaf-example/</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- наш старый добрый thymeleaf шаблонизатор --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- опционально, библиотека добавит полезные теги для SPRING SECURITY в Thymeleaf шаблонах --> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency> <!-- содержит в себе необходимые библиотеки для создания ВЕБ приложения --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- горячая подмена, выключенный кеш для шаблонов, включенный live reload --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <scope>runtime</scope> </dependency> <!-- опционально, подгружаем bootstrap --> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.7</version> </dependency> </dependencies> <build> <!-- так же мы можем указать жесткое имя при сборке приложения, что бы maven не дописывал версию например или сразу подготовить для деплоя на сервер --> <finalName>spring-boot-web-spring-security</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> |
3. Spring Security
3.1 Наследуемся от WebSecurityConfigurerAdapter, и переопределяем правила безопасности в void configure(HttpSecurity http)
Подобные конфиги я обычно кладу в пакет config, в Spring MVC их было штуки 4
Для пользователя «admin»:
Разрешен доступ к странице /admin
Запрещен доступ к странице /user , будет перенаправлять на 403 access deniedДля пользователя «user»:
Разрешен доступ к странице /user
Запрещен доступ к странице /admin, будет перенаправлять на 403 access denied
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
package ru.leodev.examples.springboot.springbootwebspringsecurity.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; // роль admin всегда есть доступ к /admin/** // роль user всегда есть доступ к /user/** // Наш кастомный "403 access denied" обработчик @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/", "/index", "/about").permitAll() .antMatchers("/admin/**").hasAnyRole("ADMIN") .antMatchers("/user/**").hasAnyRole("USER") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll() .and() .exceptionHandling().accessDeniedHandler(accessDeniedHandler); } // создаем пользоватлелей, admin и user @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("user").password("password").roles("USER") .and() .withUser("admin").password("password").roles("ADMIN"); } } |
[stextbox id=’warning’ caption=’configure(HttpSecurity)’ collapsing=»true»]
Метод configure(HttpSecurity) определяет, какие URL пути должны быть защищены, а какие нет. В частности, «/» и «/home» настроены без требования к авторизации. Ко всем остальным путям должна быть произведена аутентификация.
Когда пользователь успешно войдет в систему, он будет перенаправлен на предыдущую запрашиваемую страницу, требующую авторизацию.
[/stextbox]
[stextbox id=’warning’ caption=’configure(AuthenticationManagerBuilder)’ collapsing=»true»]метод configure(AuthenticationManagerBuilder), то он создает в памяти хранилище пользователей, в нашем случае их 2 user и admin[/stextbox]
3.2 Наш кастомный 403 Access denied обработчик, логирует запрос и перенаправляет на /403
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package ru.leodev.examples.springboot.springbootwebspringsecurity.controller; 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; /** * обрабатывает 403 ошибку перенаправляя в случае ее вызова на /403 страницу */ @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 Добавим контроллер, определим в нем имена вьюх и http запросы
[stextbox id=’info’ caption=’Почему @GetMapping?’ collapsing=»true» collapsed=»true» shadow=»false»]
В контроллере мы используем @GetMapping, по сути он эквивалентен @RequestMapping(method = RequestMethod.GET) но более лаконичен.
Так же бывают такие варианты:
@GetMapping,
@PostMapping,
@PutMapping,
@DeleteMapping,
@PatchMapping
[/stextbox]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package ru.leodev.examples.springboot.springbootwebspringsecurity.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class MainController { //Мы могли бы расписать эти 2 маппинга отдельно, но смысла дублировать одинаковый код нет. // этот метод будет слушать запросы на "/" и "/index" @GetMapping(value = {"/", "/index"}) public String index() { return "/index"; } @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 Наша точка входа — сам Spring Boot application, если вы создавали проект в ручную
1 2 3 4 5 6 7 8 9 10 11 12 |
package ru.leodev.examples.springboot.springbootwebspringsecurity; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringBootWebSpringSecurityApplication { public static void main(String[] args) { SpringApplication.run(SpringBootWebSpringSecurityApplication.class, args); } } |
5. Thymeleaf + Resources + статические файлы
5.1 Для Thymeleaf файлов, создаем каталог в src/main/resources/templates/
5.2 Thymeleaf фрагменты, для шаблонов – header
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<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 фрагменты, для макета шаблона – footer. Обратите внимание на тег sec, он очень полезен для отображения информации Spring Security в шаблонах, подробнее Thymeleaf extra Spring Security
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<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> <!-- FOOTER --> <!-- Благодаря дополнительной библиотеке которую мы опционально подключали в pom мы моежм использовать вспомогательные теги и без особых трудностей выводить имя пользователя на странице ВАЖНО! Старайтесь писать на английском, даже если не имеете подобного опыта(переводчик), это сэкономит вам нервы(проблемы с кодировками, фильтры) и даст бесценный опыт в будущем --> © 2018 leodev.ru <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 Перечисляем Thymeleaf файлы и добавляем ссылки на страницы для более удобной навигации.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<!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. Посетите <a th:href="@{/admin}">страницу Admin (Защищено с помошью Spring Security, нужны права Администратора(Admin Role))</a></h2> <h2>2. Посетите <a th:href="@{/user}">страницу User (Защищено с помошью Spring Security, нужны права пользователя(User Role))</a></h2> <h2>3. Посетите <a th:href="@{/about}">Открытую страницу(доступна всем)</a></h2> </div> </div> <!-- /.container --> <div th:replace="fragments/footer :: footer"/> </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<!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> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<!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> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<!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> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
<!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> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<!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> |
4.2 Статические файлы, такие как стили CSS или Javascript кладем в /src/main/resources/static/
1 2 3 4 5 6 7 8 9 10 11 |
h1{ color: #4a2bff; } h2{ color: #ff171c; } footer{ margin-top:60px; } |
[stextbox id=’black’]Прочтите эту статью о Spring Boot Serving static content для общего понимания работы с ресурсами в Spring[/stextbox]
6. Запуск приложения
6.1 Запускаем приложение. Путь /admin/**
защищен ,нам нужно войти в систему как админ что бы получить к нему доступ
1 |
mvn spring-boot:run |
6.2 Смотрим результат http://localhost:8080
6.3 Попытка доступа к http://localhost:8080/admin, автоматически отправляет нас на страницу авторизации http://localhost:8080/login
6.4 Не верный логин или пароль так же обрабатывается без нашего участия http://localhost:8080/login
6.5 При правильной авторизации нас автоматически направляет на страницу Админа http://localhost:8080/admin, к тому же мы избавились от дубликатов в написании header и footer благодаря фрагментам thymeleaf
6.6 А доступ к http://localhost:8080/user, по прежнему редиректит нас на 403 страницу http://localhost:8080/403 как мы и описывали в конфигурации Spring Security
6.7 Кликнув в низу страницы на ссылку sign out(выход), нас перенаправляет на http://localhost:8080/login?logout
Как видим и здесь Spring Security позаботился о нас 😉
Ссылки
- http://www.mkyong.com/
- Securing a Web Application
- Spring Security Reference
- Spring Boot Security features
- Spring Boot Hello World Example – Thymeleaf
- Spring Security Hello World Annotation Example
- Thymeleaf – Spring Security integration basics
- Thymeleaf extra – Spring Security integration basics
- Thymeleaf – Standard URL Syntax
- Spring Boot + Spring MVC + Spring Security + MySQL
- Spring Boot – Static content
- Spring MVC – Inlucde CSS file
Не могли бы Вы дать ссылку на готовый проект?
https://bitbucket.org/tresvircore/spring-boot-examples-ld/src/master/
По моему spring-boot-web-spring-security