Привет всем!
В этом материале мы постараемся написать приложение защищенное Spring Security с применением Java Config (подхода на основе использования аннотаций и классов для настройки контекста Spring приложения) вместо XML.
Используемые Технологии
- Spring 4.0.5 Release
- Spring Boot 1.0.1
- Spring Security 3.2.3 Release
- Maven
- Tomcat 8
- Servlet Api 3.1
- Java 1.8
Это будет очень простое веб приложение, Hello World на базе Spring MVC и Spring Security. Все настройки мы осуществим используя только Java классы, без единой строчки конфигурации в XML.
Начнем с рассмотрения структуры проекта.
Структура Проекта.
Зависимости Maven (из pom.xml)
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.5.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.0.5.RELEASE</version>
<type>jar</type>
</dependency>
<!-- Add Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<!-- Add Jstl Dependencies -->
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-jstlel</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
Далее мы рассмотрим настройку контроллерa Spring MVC.
Контроллер (AppController.java
)
package com.elennaro.sshwa.controllers;
//Import section ommited...
@Controller
public class AppController {
@RequestMapping(value = {"/", "/helloworld**"}, method = {RequestMethod.GET})
public ModelAndView welcomePage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security Tutorial");
model.addObject("message", "Welcome Page !");
model.setViewName("helloworld");
return model;
}
@RequestMapping(value = "/protected**", method = RequestMethod.GET)
public ModelAndView protectedPage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security 3.2.4 Hello World Tutorial");
model.addObject("message", "This is protected page - Only for Admin Users!");
model.setViewName("protected");
return model;
}
@RequestMapping(value = "/confidential**", method = RequestMethod.GET)
public ModelAndView adminPage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security 3.2.4 Hello World Tutorial");
model.addObject("message", "This is confidential page - Need Super Admin Role!");
model.setViewName("protected");
return model;
}
}
В контроллере будет один незащищенный ресурс и два пути к ресурсам, доступ к которым ограничен ролями администратора (admin) и супер администратора (superadmin). Вот пути к этим ресурсам:
- /helloword будет иметь публичный доступ, к нему не будет применяться никаких ограничений безопасности.
- /protected область с ограниченным доступом, только пользователи обладающие ролью администратора (admin) будут иметь доступ к ней.
- /confidential область с ограниченным доступом, только пользователи обладающие ролью супер администратора будут иметь доступ к ней.
В каждом методе мы создаем и возвращаем Модель(экземпляр класса ModelAndView
). Имя компонента представления так же указано в модели. Чтобы привязать имена компонентов представления (с вашего позволения я буду называть компоненты представления простонародно вьюшками, от англ. View) к конкретным файлам отображения (в нашем случае к файлам JSP), необходимо прописать класс с настройками WebConfig.java
. Описание класса представлено ниже.
Настраиваем Spring MVC (WebConfig.java)
Необходимо указать фреймворку Spring где находятся компоненты представления, и как их отображать. Так же надо привязать настройки безопасности. Все это можно сделать с помощью Java класса с аннотацией @Configuration
(в будущем мы будем называть такие классы конфигурационными).
WebConfig.java
package com.elennaro.sshwa.config.application;
//Import section ommited...
@Configuration
@EnableWebMvc
@ComponentScan({ "com.elennaro.sshwa.config", "com.elennaro.sshwa.controllers" })
//@Import({ AppSecurityConfig.class })
public class WebConfig {
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
Коротко рассмотрим код приведенный выше:
- Объявляем конфигурационный класс (с помощью аннотации
@Configuration
). - С помощью аннотации
@ComponentScan
указываем фреймворку Spring, что компоненты надо искать внутри пакетовcom.elennaro.sshwa.config
иcom.elennaro.sshwa.controllers
. Тут специалисты советуют: лучше всегда указывать конкретные пакеты, а не всё сразу с помощью*
. То есть мы могли указать все пакеты:com.elennaro.sshwa.*
и все бы работало. Но появись у нас в другом месте такой же@ComponentScan
мы бы в лучшем случае пересканировали все пакеты дважды, а в худшем наткнулись на пару неприятных ошибок... - Указываем что вьюшки будут лежать в директории
/WEB-INF/views/
- Импортируем класс с настройками безопасности, собственно сам конфигуратор Spring Security (с помощью аннотации
@Import({ AppSecurityConfig.class }))
. Эта строчка специально закомментирована в коде. Хотелось показать, что класс с настройками безопасности(AppSecurityConfig.java
) отмеченный (как вы увидите ниже) аннотацией@Configuration
будет автоматически найден и подключен базовым контекстом апликации Spring фреймворка, потому что путь к пакету содерфащему классAppSecurityConfig
указан в аннотации@ComponentScan
. Благодаря этому Spring найдет и подключит конфигурационный класс автоматически. - Отметим что сам контроллер
AppController.java
тоже попадет под сканирование компонентов, так как путь к содержащему его пакету указан в@ComponentScan({ "com.elennaro.sshwa.config", "com.elennaro.sshwa.controllers" })
и аннотация@Controller
указана для этого класса.
Далее рассмотрим непосредственно настройки безопасности Spring Security.
Настройки Безопасности (Spring Security — AppSecurityConfig.java, SecurityInit.java)
AppSecurityConfig.java
package com.elennaro.sshwa.config;
//Import section ommited...
@Configuration
@EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
auth.inMemoryAuthentication().withUser("superadmin").password("superadmin").roles("SUPERADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/protected/**").access("hasRole('ROLE_ADMIN')")
.antMatchers("/confidential/**").access("hasRole('ROLE_SUPERADMIN')")
.and().formLogin().defaultSuccessUrl("/", false);
}
}
Наиболее важной роль в конфигурации, играют аннотации класса:
@Configuration
@EnableWebSecurity.
Важно также то, что он расширяет класс WebSecurityConfigurerAdapter.
Аннотация @EnableWebSecurity
в связке с WebSecurityConfigurerAdapter
классом работает над обеспечением аутентификации. По умолчанию в Spring Security встроены и активны HTTP аутентификация и аутентификация на базе веб форм.
Кроме всего, здесь прописываем пользователей с их ролями, а затем указываем адреса ресурсов с ограниченным доступом, ограничение задано по ролям. Имена и пароли пользователей, для простоты, указаны прямо в коде. Spring Security позволяет с легкостью указать другой источник для данных о пользователях, например базу данных. Обратите внимание что роли в месте где мы присваиваем их пользователю пишутся без префикса ROLE_
, в то время как в указании в методе access
, в котором мы, с помощью языка выражений SPEL (Spring Expression Language), задаем выражения проверки ресурса (в нашем случае выражение проверки роли пользователя hasRole(‘ROLE_имя роли’)
), мы пишем роль с префиксом ROLE_
. Еще одна маленькая хитрость для аутентификации: defaultSuccessUrl("/", false)
, установка второго параметра (alwaysUse
) в false
говорит Spring Security что в случае успешной авторизации можно перенаправить пользователя на ту страничку, с которой он пришел на страницу аутентификации.
Теперь в AppSecurityConfig.java у нас находятся настройки Безопасности а в WebConfig.java настройки MVC. Необходимо удостовериться, что настройки безопасности включены в основной контекст приложения (Иными словами их увидел и втянул в себя Root Application Context). Для этого можно создать класс расширяющий(наследующий) AbstractAnnotationConfigDispatcherServletInitializer. Нам нужно настроить все так чтобы определенный URL паттерн (путь к определенному ресурсу) проходил через уровень безопасности (проходил бы проверку фильтрами Spring Security). Традиционный подход подразумевал настройку сервлет фильтра, в котором мы проверяли бы учетные данные безопасности. С появлением Setvlet 3.x больше нету необходимости объявлять фильтры в web.xml, вся настройка может быть осуществлена с помощью Java классов. Как раз для этого нам нужен AbstractAnnotationConfigDispatcherServletInitializer
.
SecurityInit.java
package com.elennaro.sshwa.config;
//Import section ommited...
public class SecurityInit extends AbstractSecurityWebApplicationInitializer {
}
Для тех кто знаком с XML конфигурацией, выше приведенный код аналогичен следующему:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>
org.springframework.web.filter.DelegatingFilterProxy
</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>ERROR</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
Мы закончили настройку MVC и Spring Security. Осталось настроить Диспетчер Сервлета, который отвечает за инициализацию Spring MVC и меппинг URL паттернов. Опять же мы откажемся от традиционной настройки Диспетчера Сервлета через web.xml и будем использовать Java Классы.
Настраиваем Диспетчер Сервлета (WebAppInitializer.java)
WebAppInitializer.java
package com.elennaro.sshwa.config;
//Import section ommited...
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {WebConfig.class}; // We dont need any special servlet config yet.
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
}
Здесь мы настроили мэпинг сервлета на “/” и поэтому все запросы будут перехвачены Диспетчером Сервлета Spring.
Отметим что, наш класс WebAppInitializer.java наследует AbstractAnnotationConfigDispatcherservletinitializergetRootConfigClasses, замещая его метод getRootConfigClasses
так чтобы вернуть объявленный нами класс настроек приложения WebConfig.class
.
Все… Осталось, прописать вьюшки и попробовать запустить приложение.
Представления (Вьюшки)
helloworld.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello World</title>
</head>
<body>
<header>
<h1>Title : ${title}</h1>
</header>
<section>
<h1>Message : ${message}</h1>
</section>
<div>Get <a href="protected">protected</a> resource for admin.</div>
<div>Get <a href="confidential">confidential</a> resource for superadmin.</div>
<footer>
<div>Updated by Alex Art from <a href="https://in2circle.com" target="_blank">in2circle.com</a></div>
</footer>
</body>
</html>
protected.jsp
<%@ page isELIgnored="false" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Protected page</title>
</head>
<body>
<h1>Title : ${title}</h1>
<h1>Message : ${message}</h1>
<c:if test="${pageContext.request.userPrincipal.name != null}">
<h2>Welcome : ${pageContext.request.userPrincipal.name}
| <c:url value="login?logout" var="logoutUrl" />
<a href="${logoutUrl}">Log Out</a>
</c:if>
</h2>
<div>Get <a href="protected">protected</a> resource for admin.</div>
<div>Get <a href="confidential">confidential</a> resource for superadmin.</div>
<footer>
<div>Updated by Alex Art from <a href="https://in2circle.com" target="_blank">in2circle.com</a></div>
</footer>
</body>
</html>
Тут можно толко отметить, что ссылка на logout URL: login?logout
.
Spring Security, сам сгенерирует страницу login.
Финиш.
Запускаем Приложение: http://localhost:8080/sshwa/helloworld/
Как мы и хотели ресурс /helloworld незащищен
Когда же мы поменяем URL на http://localhost:8080/sshwa/protected/ Spring Security перенаправит нас на /login, с формой аутентификации по умолчанию. Если мы введем неверные логин или пароль, будут отображены сообщения об ошибках, и Spring сделает редирект на URL /login?error.
Для неавторизированных пользователей Spring сначала выкинет нас в root ("/"), а при следующей попытке зайти на ресурс с неподходящей ролью, отобразит страничку с кодом 403.
Полный код приложения доступен по адресу:
github.com/elennaro/sshwa/tree/0.2-SNAPSHOT
Надеюсь в будущем у меня будет время чтобы постепенно развивать этот туториал (в планах перевести все на Spring Boot, а когда выйдет Spring Security 4.0 Release попытаться прикрутить тесты).
На основе материала из источника: javahash.com/spring-security-hello-world-example/. Код переработан, текст дополнен кажущимися мне полезными комментариями.
Это мой первый материал, постараюсь учесть все пожелания и замечания.
Автор: elennaro