29 September 2011

Внедрение Spring Security в связку ZK+Spring Framework+Hibernate: часть первая

Website development
Всем доброго времени суток. Как и обещал, попытаюсь осветить тему секьюрности в веб-приложении, написанном на ZK Framework. Почему часть первая? Потому что в данной статье я покажу вам наиболее быстрый и простой метод внедрения Spring Security с использованием в качестве страницы авторизации- jsp страницу; в последующей(их) статье(ях) будут описаны более сложные и интересные методы с использованием zul в качестве построения страницы авторизации.
Веб-приложение писать с нуля не будем, а за основу возьмем мое прошлое приложение, которое я описывал в этом топике.
Что нам понадобится:
Данный метод можно реализовывать тоже по-разному, либо хранить юзеров, их пароли и права в xml конфигурации Spring Security, либо хранить в базе данных. Так как наше приложение и так работает с базой Oracle, так чего бы и юзеров не хранить в базе. Как говорит нам документация спринга, при дефолтном развертывании Spring Security смотрит в базу на 2 таблицы (users и authorities). При групповой политики, требуется наличие еще и таких таблиц, как :groups, group_authorities, group_members (скрипты таблиц можно взять отсюда).

Значит создаем в базе 2 таблицы вида:

CREATE TABLE users
(
    username   varchar2 (50) NOT NULL PRIMARY KEY,
    password   varchar2 (50) NOT NULL,
    enabled    number NOT NULL
);



CREATE TABLE authorities
(
    username    varchar2 (50) NOT NULL,
    authority   varchar2 (50) NOT NULL,
    CONSTRAINT fk_authorities_users FOREIGN KEY
        (username)
         REFERENCES users (username)
);

CREATE UNIQUE INDEX ix_auth_username
    ON authorities (username, authority);

Следующим шагом сконфигурируем наш Spring Security. В файл spring-config.xml внесем следующие изменения
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:security="http://www.springframework.org/schema/security"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.1.xsd
		http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
		http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-3.1.xsd">
	<context:annotation-config />
	<context:component-scan base-package="com.sample" />
	<tx:annotation-driven transaction-manager="txManager" />
	
	<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
		<property name="jndiName">
			<value>java:comp/env/jdbc/taskdb</value>
		</property>
	</bean>
	
	<bean id="txManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="dataSource" ref="dataSource" />
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource">
			<ref bean="dataSource" />
		</property>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
				<prop key="hibernate.show_sql">false</prop>
				<!--<prop key="hibernate.hbm2ddl.auto">update</prop> -->
			</props>
		</property>
		<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
		<property name="configLocation" value="/WEB-INF/hibernate.cfg.xml" />
	</bean>
	<!-- Configure the Spring Security -->
	<security:global-method-security
		secured-annotations="enabled" jsr250-annotations="enabled" />


	<security:http auto-config="true">
		<!-- Don't set any role restrictions on login.jsp -->
		<security:intercept-url pattern="/login.jsp"
			access="IS_AUTHENTICATED_ANONYMOUSLY" />
		<!-- Restrict access to ALL other pages -->
		<security:intercept-url pattern="/**"
			access="ROLE_ADMIN,ROLE_USER" />
		<security:form-login login-page="/login.jsp"
			default-target-url="/index.zul" always-use-default-target="true"
			authentication-failure-url="/login.jsp?login_error=1" />
		<security:logout logout-url="/j_spring_security_logout"
			invalidate-session="true" logout-success-url="/logoutSuccess.jsp" />
	</security:http>
	<!-- Configure the authentication provider -->
	<security:authentication-manager>
		<security:authentication-provider>
			<security:jdbc-user-service
				data-source-ref="dataSource" />
		</security:authentication-provider>
	</security:authentication-manager>
</beans>

Остановлюсь на некоторых моментах:
  • <security:global-method-security
    secured-annotations="enabled" jsr250-annotations="enabled" />
    — дает нам возможность использовать аннотации вида @RolesAllowed(«ROLE_ADMIN»), для группы прав данная строка будет иметь вид @RolesAllowed({«ROLE_ADMIN»,«ROLE_USER»});
  • <security:intercept-url pattern="/login.jsp"
    access="IS_AUTHENTICATED_ANONYMOUSLY" />
    — говорим, что все могут заходить на страницу login.jsp;
  • <security:intercept-url pattern="/**"
    access="ROLE_ADMIN,ROLE_USER" />
    — только пользователи, которые имеют права ROLE_ADMIN и/или ROLE_USER могут заходить на все страницы
  • <security:form-login login-page="/login.jsp"
    default-target-url="/index.zul" always-use-default-target="true"
    authentication-failure-url="/login.jsp?login_error=1" />
    — при правильном логине/пароле переходим на страницу index.zul (конечно если права у данного пользователя позволяют это сделать), в противном случае выводим код ошибки.

Также не забудем добавить в web.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>
	</filter-mapping>

Все, с конфигурацией покончено. Теперь напишем страничку авторизации login.jsp.
<%@ page language="java" contentType="text/html; charset=utf-8"
	pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core_rt'%>
<html>
<head>
<title>Форма Авторизации</title>
<style type="text/css">
body {
	background: #63bad8 50% 0px repeat-x;
	text-align: center;
}

div.main {
	margin: 50px auto;
	padding: 0 0 0 0;
	width: 340px;
	border-color: black;
}
</style>
</head>
<body>
	<div class="main">
		<h1
			style="background-color: #3F3F3F; color: white; padding: 0px; margin: 0px;">Авторизация</h1>
		<div
			style="background: white; border: black; padding: 0px; margin: 0px;"
			align="center" dir="ltr">
			<c:if test="${not empty param.login_error}">
				<font color="red"> Не правильный логин или пароль. Попробуйте
					заново.</font>
			</c:if>
			<form name="f" action="<c:url value='j_spring_security_check'/>"
				method="POST" style="background: white;">
				<table>
					<tr>
						<td style="font-style: oblique">Пользователь:</td>
						<td><input type='text' name='j_username'
							value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>' />
						</td>
					</tr>
					<tr>
						<td style="font-style: oblique">Пароль:</td>
						<td><input type='password' name='j_password'>
						</td>
					</tr>
					<tr align="center">
						<td colspan='2' align="center"><input name="submit"
							value="Войти" type="submit"> <input name="reset"
							value="Очистить" type="reset">
						</td>
					</tr>
				</table>
			</form>
		</div>
	</div>
</body>
</html>

Можно запускать и смотреть на наши плоды.
Давайте теперь поиграемся с разграничением прав. К примеру, разрешим только пользователю с правами ROLE_ADMIN, удалять пользователей из системы. Для этого в процедуре (PersonImpl) перед процедурой удаления пользователя, напишем следующее:
@RolesAllowed("ROLE_ADMIN")
public boolean delete(Person pers)

Также отобразим имя вошедшего пользователя.
Для начала создадим компоненты Label с id = «labelLogin», который будет служить для отображения имени пользователя и Toolbarbutton, который будет служить нам кнопкой выхода пользователя. В файле index.zul перед строкой <listbox id="lbPerson" hflex="1" vflex="1" checkmark="true", добавим следующее:
       <toolbar>
	     <label id="labelLogin"/>
	     /
             <toolbarbutton label="Выход" href="/j_spring_security_logout"/>
       </toolbar>

Ну и в классе PersonInfo внутри метода public void onCreate() реализуем возможность вывода имени пользователя:
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
((Label) this.getFellow("labelLogin")).setValue(userDetails.getUsername());

В данном коде мы получаем все данные пользователя, которые содержатся в UserDetails и компонент Label с id = «labelLogin» из формы index.zul, в который мы передадим имя пользователя.
Теперь, запустив наше приложении, по адресу
http://localhost:port/NameOfProject
мы увидим, что автоматом нас перенаправили на страницу login.jsp.
Tags:javazkspring frameworkhibernateweb-разработкаspring security
Hubs: Website development
+4
5.5k 17
Leave a comment
Popular right now
Senior/Middle Java разработчик (Daily Banking Team)
from 250,000 to 300,000 ₽ОТП БанкМосква
Java developer
from 230,000 ₽СберМосква
Java разработчик
from 140,000 ₽Virtu SystemsRemote job
Java разработчик
from 180,000 to 220,000 ₽Техносерв КонсалтингМоскваRemote job
Ведущий разработчик Java
from 4,000 to 5,000 $SymbioWayRemote job
Top of the last 24 hours