Spring имеет много разных модулей. Все они полезны для конкретных целей. Сегодня я собираюсь поговорить о Spring Security. Этот модуль обеспечивает гибкий подход к управлению разрешениями для доступа к различным частям веб-приложения. В этой статье я рассмотрю интеграцию Spring MVC , Hibernate , MySQL с Spring Security .
Обычным случаем для любого веб-приложения является разделение функциональности между некоторыми группами пользователей. Например, пользователь с ролью «модератор» может редактировать существующие записи в базе данных. Пользователь
с ролью «администратор» может делать то же самое, что и пользователь с ролью «модератор», плюс создавать новые записи. В Spring MVC управление разрешениями приложений может быть реализовано с помощью Spring Security.
Цель
В качестве примера я буду использовать пример Spring MVC-приложения с Hibernate. Пользователи и их роли будут храниться в базе данных. MySQL будет использоваться в качестве базы данных. Я собираюсь создать три таблицы: пользователи, роли, user_roles. Как вы можете догадаться, таблица user_roles является промежуточной таблицей . В приложении будут две роли: модератор и админ. Там будет несколько страниц с доступом для модератора и для администратора.
подготовка
Чтобы сделать Spring Security доступным в проекте, просто добавьте следующие зависимости в файл pom.xml:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
<!-- Spring Security --> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-core</artifactid> <version>3.1.3.RELEASE</version> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-web</artifactid> <version>3.1.3.RELEASE</version> </dependency> <dependency> <groupid>org.springframework.security</groupid> <artifactid>spring-security-config</artifactid> <version>3.1.3.RELEASE</version> </dependency> |
Мне нужно создать три таблицы в базе данных и вставить туда несколько записей.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
CREATE TABLE `roles` ( `id` int(6) NOT NULL AUTO_INCREMENT, `role` varchar(20) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;CREATE TABLE `users` ( `id` int(6) NOT NULL AUTO_INCREMENT, `login` varchar(20) NOT NULL, `password` varchar(20) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;CREATE TABLE `user_roles` ( `user_id` int(6) NOT NULL, `role_id` int(6) NOT NULL, KEY `user` (`user_id`), KEY `role` (`role_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
А вот код для ролей и пользователей:
|
1
2
3
4
5
|
INSERT INTO hibnatedb.roles (role) VALUES ('admin'), ('moderator');INSERT INTO hibnatedb.users (login, password) VALUES ('moder', '111111'), ('adm', '222222');INSERT INTO hibnatedb.user_roles (user_id, role_id) VALUES (1, 2), (2, 1); |
Основная часть
Полная структура проекта имеет следующую структуру:
Поскольку вы можете найти этот проект на GitHub , я опущу некоторые вещи, которые не относятся к текущей теме. Я хочу начать с самого сердца каждого веб-проекта, я имею в виду файл web.xml. Spring Security основан на простых фильтрах, поэтому мне нужно добавить объявление фильтра в дескриптор развертывания:
|
01
02
03
04
05
06
07
08
09
10
|
... <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>... |
Теперь пришло время создать сущности для таблиц пользователей и ролей:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
@Entity@Table(name="users")public class User { @Id @GeneratedValue private Integer id; private String login; private String password; @OneToOne(cascade=CascadeType.ALL) @JoinTable(name="user_roles", joinColumns = {@JoinColumn(name="user_id", referencedColumnName="id")}, inverseJoinColumns = {@JoinColumn(name="role_id", referencedColumnName="id")} ) private Role role; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } } |
И
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
@Entity@Table(name="roles")public class Role { @Id @GeneratedValue private Integer id; private String role; @OneToMany(cascade=CascadeType.ALL) @JoinTable(name="user_roles", joinColumns = {@JoinColumn(name="role_id", referencedColumnName="id")}, inverseJoinColumns = {@JoinColumn(name="user_id", referencedColumnName="id")} ) private Set userRoles; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getRole() { return role; } public void setRole(String role) { this.role = role; } public Set getUserRoles() { return userRoles; } public void setUserRoles(Set userRoles) { this.userRoles = userRoles; }} |
Каждый класс сущности требует DAO и уровня сервиса.
|
1
2
3
4
5
|
public interface UserDAO { public User getUser(String login);} |
И
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Repositorypublic class UserDAOImpl implements UserDAO { @Autowired private SessionFactory sessionFactory; private Session openSession() { return sessionFactory.getCurrentSession(); } public User getUser(String login) { List userList = new ArrayList(); Query query = openSession().createQuery("from User u where u.login = :login"); query.setParameter("login", login); userList = query.list(); if (userList.size() > 0) return userList.get(0); else return null; }} |
Соответственно для ролевого класса:
|
1
2
3
4
5
|
public interface RoleDAO { public Role getRole(int id);} |
И
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
@Repositorypublic class RoleDAOImpl implements RoleDAO { @Autowired private SessionFactory sessionFactory; private Session getCurrentSession() { return sessionFactory.getCurrentSession(); } public Role getRole(int id) { Role role = (Role) getCurrentSession().load(Role.class, id); return role; }} |
Те же пары для сервисного уровня:
|
1
2
3
4
5
|
public interface UserService { public User getUser(String login);} |
И
|
01
02
03
04
05
06
07
08
09
10
11
12
|
@Service@Transactionalpublic class UserServiceImpl implements UserService { @Autowired private UserDAO userDAO; public User getUser(String login) { return userDAO.getUser(login); }} |
Соответственно для ролевого класса:
|
1
2
3
4
5
|
public interface RoleService { public Role getRole(int id);} |
И
|
01
02
03
04
05
06
07
08
09
10
11
12
|
@Service@Transactionalpublic class RoleServiceImpl implements RoleService { @Autowired private RoleDAO roleDAO; public Role getRole(int id) { return roleDAO.getRole(id); }} |
Все выше было просто механическим, обычным кодом. Теперь давайте поработаем над кодом Spring Security. Чтобы подключить Spring Security к проекту, мне нужно создать класс CustomUserDetailsService и реализовать интерфейс UserDetailsService.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
import java.util.ArrayList;import java.util.Collection;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import com.sprsec.dao.UserDAO;@Service@Transactional(readOnly=true)public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserDAO userDAO; public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException { com.sprsec.model.User domainUser = userDAO.getUser(login); boolean enabled = true; boolean accountNonExpired = true; boolean credentialsNonExpired = true; boolean accountNonLocked = true; return new User( domainUser.getLogin(), domainUser.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, getAuthorities(domainUser.getRole().getId()) ); } public Collection getAuthorities(Integer role) { List authList = getGrantedAuthorities(getRoles(role)); return authList; } public List getRoles(Integer role) { List roles = new ArrayList(); if (role.intValue() == 1) { roles.add("ROLE_MODERATOR"); roles.add("ROLE_ADMIN"); } else if (role.intValue() == 2) { roles.add("ROLE_MODERATOR"); } return roles; } public static List getGrantedAuthorities(List roles) { List authorities = new ArrayList(); for (String role : roles) { authorities.add(new SimpleGrantedAuthority(role)); } return authorities; }} |
Основное назначение этого класса — сопоставить класс User приложения с классом User Spring Security. Это одна из особенностей Spring Security. Таким образом, вы можете адаптировать любое приложение Spring MVC к использованию модуля Security.
Контроллеры и Представления
Один из наиболее частых вопросов, касающихся Spring Security, заключается в том, как создать собственную форму входа . Ответ достаточно прост. Вам нужно создать JSP-файл с формой и указать там атрибут действия ().
Большая часть сопоставления URL-адресов зависит от файла spring-security.xml:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
... <http auto-config="true"> <intercept-url pattern="/sec/moderation.html" access="ROLE_MODERATOR"> <intercept-url pattern="/admin/*" access="ROLE_ADMIN"> <form-login login-page="/user-login.html" default-target-url="/success-login.html" authentication-failure-url="/error-login.html"> <logout logout-success-url="/index.html"> </logout></form-login></intercept-url></intercept-url></http> <authentication-manager> <authentication-provider user-service-ref="customUserDetailsService"> <password-encoder hash="plaintext"> </password-encoder></authentication-provider> </authentication-manager>... |
Как видите, я указал URL для страницы входа в систему, страницы по умолчанию после успешного входа в систему, страницы ошибок для ситуаций, когда учетные данные недействительны. Также я объявил URL, которые требуют некоторых разрешений на доступ. И самое главное — это объявление менеджера аутентификации. Благодаря этому Spring Security будет использовать базу данных для идентификации пользователей и их ролей.
Контроллеры:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
@Controllerpublic class LinkNavigation { @RequestMapping(value="/", method=RequestMethod.GET) public ModelAndView homePage() { return new ModelAndView("home"); } @RequestMapping(value="/index", method=RequestMethod.GET) public ModelAndView indexPage() { return new ModelAndView("home"); } @RequestMapping(value="/sec/moderation", method=RequestMethod.GET) public ModelAndView moderatorPage() { return new ModelAndView("moderation"); } @RequestMapping(value="/admin/first", method=RequestMethod.GET) public ModelAndView firstAdminPage() { return new ModelAndView("admin-first"); } @RequestMapping(value="/admin/second", method=RequestMethod.GET) public ModelAndView secondAdminPage() { return new ModelAndView("admin-second"); }} |
И
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
@Controllerpublic class SecurityNavigation { @RequestMapping(value="/user-login", method=RequestMethod.GET) public ModelAndView loginForm() { return new ModelAndView("login-form"); } @RequestMapping(value="/error-login", method=RequestMethod.GET) public ModelAndView invalidLogin() { ModelAndView modelAndView = new ModelAndView("login-form"); modelAndView.addObject("error", true); return modelAndView; } @RequestMapping(value="/success-login", method=RequestMethod.GET) public ModelAndView successLogin() { return new ModelAndView("success-login"); }} |
Просмотры вы можете увидеть на GitHub.
Обратите внимание на добавление @ImportResource («classpath: spring-security.xml») в Java-класс WebAppConfig .
Резюме
Я думаю, что эта статья поможет вам погрузиться в Spring Security. Я использовал здесь Hibernate и MySQL, поскольку такое сочетание технологий не часто используется в других учебных пособиях в Интернете. Возможно, вы заметили, что я использовал некоторые XML в проекте, потому что в настоящее время нет способов реализовать все эти вещи, используя подход, основанный на аннотациях.


