Всем доброго времени суток. Данная статья является продолжением статьи про способы внедрения секьюрности в веб-приложение. За основу возьмем наше приложение, которое было описано и в предыдущем и в этом постах.
План работы:
Начнем все по порядку. Мы немного изменим таблицы, в которых у нас хранились пользователи и их роли, а именно: добавим в таблицу users поле id. И создадим новую таблицу под названием USER_ROLE.
Также создадим таблицу для организации связки М:1, назовем ее user_role_list
Опишем маппинг-классы. Создадим класс User с таким содержанием:
Также нам нужен будет класс Role:
Небольшое пояснение по коду: описав аннотацию
Также в файл hibernate-config.xml добавим:
Исходя из плана, изменим нашу форму авторизации, а точнее вместо jsp страницы создадим zk страницу, немного изменив функционал. Сделаем на форме авторизации не ввод пользователя, а выбор пользователя из списка. Напишем файл index.zul и положим его в папку WebContent
После этого опишем наш контроллер, который мы указали в строчке use=«ui.component.Login»
Опишем интерфейс для работы с таблицами USERS, USER_ROLE_LIST и USER_ROLE, и напишем его реализацию.
Интерфейс ISecur:
Реализация интерфейса SecurImpl
Следующим и очень важным шагом является переопределение методов, унаследованных от класса AbstractUserDetailsAuthenticationProvider. Этот новый унаследованный класс, назовем его MyDaoAuthenticationProvider, будет содержать логику работы нашей авторизации:
Метод protected UserDetails retrieveUser вызывается при нажатии кнопки «Войти».
В этом методе мы сравниваем на соответствие выбранного пользователя с введенным паролем, и если все нормально, то создаем класс User, который является реализацией интерфейса UserDetails, и будет содержать всю нужную информацию о пользователе (его логин, пароль и список прав).
Теперь укажем AuthenticationManager, что в качестве провайдера надо использовать наш MyDaoAuthenticationProvider класс. Для это в файле spring-config.xml вместо строчек:
напишем следующее:
Также очень, очень важно изменить конфигурацию http, которая описана здесь, на следующее:
Вот и все! Если запустить наше приложение, то мы увидим красивую форму авторизации, с возможностью выбора пользователя из выпадающего списка.
Всем спасибо за внимание.
План работы:
- добавим необходимые таблицы и определим для них маппинг-отображения;
- изменим форму авторизации;
- создадим класс, унаследованный от класса 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>
Вот и все! Если запустить наше приложение, то мы увидим красивую форму авторизации, с возможностью выбора пользователя из выпадающего списка.
Всем спасибо за внимание.