Pull to refresh

Spring Security Hello World Java Config

Reading time 11 min
Views 101K
Привет всем!

В этом материале мы постараемся написать приложение защищенное 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}
				<%--@elvariable id="_csrf" type="org.springframework.web.bind.MissingServletRequestParameterException"--%>
				<c:url var="logoutUrl" value="/logout" />
			     <form action="${logoutUrl}" id="logout" method="post">
			      <input type="hidden" name="${_csrf.parameterName}"
                       value="${_csrf.token}" />
			      <input type="submit" name="submit" value="Log Out">
			     </form>
			</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: logout и что по умолчанию тип logout запроса должен быть POST и запрос logout должен содержать csrf token.
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. Код переработан, текст дополнен кажущимися мне полезными комментариями.

Это мой первый материал, постараюсь учесть все пожелания и замечания.

P.S. Спасибо drno-reg, за то, что нашли ошибку при logout (текст и исходники были обновлены).
Tags:
Hubs:
+6
Comments 4
Comments Comments 4

Articles