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
|
@Repository public 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
|
@Repository public 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 @Transactional public 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 @Transactional public 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
|
@Controller public 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
|
@Controller public 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 в проекте, потому что в настоящее время нет способов реализовать все эти вещи, используя подход, основанный на аннотациях.