4 October 2011

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

Website development
Всем доброго времени суток. Данная статья является продолжением статьи про способы внедрения секьюрности в веб-приложение. За основу возьмем наше приложение, которое было описано и в предыдущем и в этом постах.
План работы:
  • добавим необходимые таблицы и определим для них маппинг-отображения;
  • изменим форму авторизации;
  • создадим класс, унаследованный от класса AbstractUserDetailsAuthenticationProvider, и реализуем в нем логику выполнения авторизации;


Начнем все по порядку. Мы немного изменим таблицы, в которых у нас хранились пользователи и их роли, а именно: добавим в таблицу users поле id. И создадим новую таблицу под названием USER_ROLE.
CREATE TABLE user_role
    (id                             NUMBER NOT NULL,
    name                           VARCHAR2(10 BYTE) NOT NULL)

Также создадим таблицу для организации связки М:1, назовем ее user_role_list
CREATE TABLE user_role_list
    (id_user                        NUMBER NOT NULL,
    id_role                        NUMBER NOT NULL)

Опишем маппинг-классы. Создадим класс User с таким содержанием:
package com.sample.data;

import java.io.Serializable;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "USERS")
public class User implements Serializable
{

	private static final long	serialVersionUID	= 1L;
	@Id
	private long				id;
	@Column(name = "USERNAME")
	private String				username;
	@Column(name = "PASSWORD")
	private String				password;
	@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
	Set<Role>					roleList;

	public long getId()
	{
		return id;
	}

	public void setId(long id)
	{
		this.id = id;
	}

	public String getUsername()
	{
		return username;
	}

	public void setUsername(String username)
	{
		this.username = username;
	}

	public String getPassword()
	{
		return password;
	}

	public void setPassword(String password)
	{
		this.password = password;
	}

	public Set<Role> getRoleList()
	{
		return roleList;
	}

	public void setRoleList(Set<Role> roleList)
	{
		this.roleList = roleList;
	}

}



Также нам нужен будет класс Role:
package com.sample.data;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "USER_ROLE")
public class Role implements Serializable
{
	private static final long	serialVersionUID	= 1L;
	@Id
	private long				id;
	@Column(name = "NAME")
	private String				name;
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinTable(name = "USER_ROLE_LIST", joinColumns = @JoinColumn(name = "ID_ROLE"), 
         inverseJoinColumns = @JoinColumn(name = "ID_USER"))
	private User				user;

	public long getId()
	{
		return id;
	}

	public void setId(long id)
	{
		this.id = id;
	}

	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	public User getUser()
	{
		return user;
	}

	public void setUser(User user)
	{
		this.user = user;
	}
}

Небольшое пояснение по коду: описав аннотацию @JoinTable(name = "USER_ROLE_LIST", joinColumns = @JoinColumn(name = "ID_ROLE"), inverseJoinColumns = @JoinColumn(name = "ID_USER")), мы тем самым определили в ней кросс-таблицу USER_ROLE_LIST с полями ID_USER и ID_ROLE.
Также в файл hibernate-config.xml добавим:
<mapping class="com.sample.data.User" />
<mapping class="com.sample.data.Role" />

Исходя из плана, изменим нашу форму авторизации, а точнее вместо jsp страницы создадим zk страницу, немного изменив функционал. Сделаем на форме авторизации не ввод пользователя, а выбор пользователя из списка. Напишем файл index.zul и положим его в папку WebContent

<?page id="testZul" title="Авторизация программы"?>
<window title="Авторизация" border="normal" width="500px" mode="modal"
	position="center" use="ui.component.Login" id="wndLogin">
	<html style="color:red" if="${not empty param.login_error}">
		<![CDATA[ Авторизация не удалась,проверьте правильность имени
		пользователя, или пароля <br/><br/> <!-- Reason: --> <!--
		${SPRING_SECURITY_LAST_EXCEPTION.message} --> ]]>		
    </html>
	<groupbox>
		<h:form id="f" name="f" action="j_spring_security_check"
			method="POST" xmlns:h="http://www.w3.org/1999/xhtml">
			<grid>
				<rows>
					<row>
						Пользователь:
						<combobox id="cbUser" name="j_username"
							 hflex="1" value="" />
					</row>					
					<row>
						Пароль:
						<textbox id="p" type="password"
							name="j_password" hflex="1" value="" />
					</row>
					<row visible="false">
						<checkbox id="r"
							name="_spring_security_remember_me" />
					</row>
					<row spans="2">
						<vbox align="center" hflex="1">
							<hbox>
								<h:input type="submit" value="Войти" />
								<h:input type="reset" value="Очистить" />
							</hbox>
						</vbox>
					</row>
				</rows>
			</grid>
		</h:form>
	</groupbox>
</window>

После этого опишем наш контроллер, который мы указали в строчке use=«ui.component.Login»
package ui.component;

import java.util.List;
import javax.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.zkoss.zul.Combobox;
import org.zkoss.zul.Window;
import com.sample.data.User;
import com.sample.service.ISecur;

public class Login extends Window
{
	private static final long	serialVersionUID	= 3974533449164635181L;
	private ISecur				userDao;
	private Combobox			cbUser;
	private List<User>			listUser;

	public void onCreate()
	{
		ApplicationContext ctx = WebApplicationContextUtils.
                                      getRequiredWebApplicationContext((ServletContext) getDesktop().getWebApp()
				      .getNativeContext());
		userDao = (ISecur) ctx.getBean("securImpl");
		cbUser = (Combobox) this.getFellow("cbUser");
		onOpenCB();
	}
	public void onOpenCB()
	{
		if (listUser == null || listUser.size() == 0)
		{
			listUser = userDao.findAllUsers();
			for (User pers : listUser)
			{
				cbUser.appendItem(pers.getUsername());
			}
		}
	}
}

Опишем интерфейс для работы с таблицами USERS, USER_ROLE_LIST и USER_ROLE, и напишем его реализацию.
Интерфейс ISecur:
package com.sample.service;
import java.util.List;
import com.sample.data.User;
public interface ISecur
{
	List<User> findAllUsers();
}

Реализация интерфейса SecurImpl
package com.sample.service;

import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.sample.data.User;

@Repository
@Transactional(readOnly = true)
public class SecurImpl implements ISecur
{
	private static SessionFactory	sessionFactory;
	// получаем наш datasource
	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory)
	{
		SecurImpl.sessionFactory = sessionFactory;
	}
	@SuppressWarnings("unchecked")
	@Override
	public List<User> findAllUsers()
	{
		return (List<User>) sessionFactory.getCurrentSession().createQuery("from User").list();
	}
}

Следующим и очень важным шагом является переопределение методов, унаследованных от класса AbstractUserDetailsAuthenticationProvider. Этот новый унаследованный класс, назовем его MyDaoAuthenticationProvider, будет содержать логику работы нашей авторизации:
package com.sample.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.sample.data.Role;

@SuppressWarnings("deprecation")
@Service("myDaoAuthenticationProvider")
public class MyDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider
{
	@Bean
	public ISecur userDao()
	{
		return new SecurImpl();
	}
	
	public Authentication authenticate(Authentication authentication) throws AuthenticationException
	{
		System.out.println(authentication.getName() + " " + new Date());
		return super.authenticate(authentication);
	}

	@Override
	protected void additionalAuthenticationChecks(UserDetails arg0, UsernamePasswordAuthenticationToken arg1) throws AuthenticationException
	{
	}

	@Override
	protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken userInfo) throws AuthenticationException
	{
		List<com.sample.data.User>	listUser	= userDao().findAllUsers();
		User user;
		if (listUser.size() == 0)
		{
			throw new UsernameNotFoundException("В системе пока нет пользователей");
		}
		com.sample.data.User person = getUser(listUser, username);
		if(person.getPassword().equals(userInfo.getCredentials().toString()))
		{
		user = new User(person.getUsername(), userInfo.getCredentials().toString(), true, true, true, true,
				getAuthorities((Set<Role>) person.getRoleList()));
		}
		else
		{
			user = new User(person.getPassword(), userInfo.getCredentials().toString(), true, true, true, false,
			getAuthorities("IS_AUTHENTICATED_ANONYMOUSLY"));
		}
		return user;
	}

	private com.sample.data.User getUser(List<com.sample.data.User> lp, String userName)
	{
		com.sample.data.User pers = null;
		for (com.sample.data.User p : lp)
		{
			if (userName.equals(p.getUsername()))
			{
				pers = p;
			}
		}
		return pers;
	}

	private Collection<GrantedAuthority> getAuthorities(Set<Role> set)
	{
		Collection<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
		for (Role role : set)
		{
			authList.add(new GrantedAuthorityImpl(role.getName()));
		}
		return authList;
	}

	private Collection<GrantedAuthority> getAuthorities(String grant_name)
	{
		Collection<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
		authList.add(new GrantedAuthorityImpl(grant_name));
		return authList;
	}
}

Метод protected UserDetails retrieveUser вызывается при нажатии кнопки «Войти».
В этом методе мы сравниваем на соответствие выбранного пользователя с введенным паролем, и если все нормально, то создаем класс User, который является реализацией интерфейса UserDetails, и будет содержать всю нужную информацию о пользователе (его логин, пароль и список прав).
Теперь укажем AuthenticationManager, что в качестве провайдера надо использовать наш MyDaoAuthenticationProvider класс. Для это в файле spring-config.xml вместо строчек:

        <security:authentication-manager>
		<security:authentication-provider>
			<security:jdbc-user-service
				data-source-ref="dataSource" />
		</security:authentication-provider>
	</security:authentication-manager>

напишем следующее:

        <security:authentication-manager>
		<security:authentication-provider
			ref="userDetailsService">
		</security:authentication-provider>
	</security:authentication-manager>

Также очень, очень важно изменить конфигурацию http, которая описана здесь, на следующее:

        <security:http auto-config="true" use-expressions="true">
		<security:intercept-url pattern="/index.zul"
			access="hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')" />
		<security:intercept-url pattern="/login.zul"
			access="permitAll" />
		<security:form-login login-page="/login.zul"
			always-use-default-target="true" default-target-url="/index.zul"
			authentication-failure-url="/login.zul?login_error=1" />
	</security:http>

Вот и все! Если запустить наше приложение, то мы увидим красивую форму авторизации, с возможностью выбора пользователя из выпадающего списка.
Всем спасибо за внимание.
Tags:javazkweb-разработкаspring frameworkspring securityhibernate
Hubs: Website development
+1
3.8k 19
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
Middle/Senior Java Developer
from 120,000 ₽OptiSystemsКраснодар
Ведущий разработчик Java
from 4,000 to 5,000 $SymbioWayRemote job