1. Обзор
Этот учебник продолжает серию «Регистрация в Spring Security», в которой рассматривается, как правильно реализовать роли и привилегии.
2. Пользователь
, роль
и привилегия
Начнем с наших сущностей. У нас есть три основных объекта:
Пользователь
_ ``- Роль представляет высокоуровневые роли пользователя в системе
.
Каждая роль будет иметь набор низкоуровневых привилегий. Привилегия
представляет собой низкоуровневую детальную привилегию/полномочия в системе .
Вот пользователь :
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String email;
private String password;
private boolean enabled;
private boolean tokenExpired;
@ManyToMany
@JoinTable(
name = "users_roles",
joinColumns = @JoinColumn(
name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(
name = "role_id", referencedColumnName = "id"))
private Collection<Role> roles;
}
Как мы видим, пользователь содержит роли, а также несколько дополнительных сведений, необходимых для правильного механизма регистрации.
Далее, вот роль :
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private Collection<User> users;
@ManyToMany
@JoinTable(
name = "roles_privileges",
joinColumns = @JoinColumn(
name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(
name = "privilege_id", referencedColumnName = "id"))
private Collection<Privilege> privileges;
}
Наконец, давайте посмотрим на привилегию :
@Entity
public class Privilege {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToMany(mappedBy = "privileges")
private Collection<Role> roles;
}
Как мы видим, мы рассматриваем отношения «Пользователь <-> Роль» и «Роль <-> Привилегия» как двунаправленные «многие ко многим».
3. Настройте привилегии и роли
Далее давайте сосредоточимся на начальной настройке привилегий и ролей в системе.
Мы свяжем это с запуском приложения и будем использовать ApplicationListener
в ContextRefreshedEvent
для загрузки наших исходных данных при запуске сервера:
@Component
public class SetupDataLoader implements
ApplicationListener<ContextRefreshedEvent> {
boolean alreadySetup = false;
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PrivilegeRepository privilegeRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
@Transactional
public void onApplicationEvent(ContextRefreshedEvent event) {
if (alreadySetup)
return;
Privilege readPrivilege
= createPrivilegeIfNotFound("READ_PRIVILEGE");
Privilege writePrivilege
= createPrivilegeIfNotFound("WRITE_PRIVILEGE");
List<Privilege> adminPrivileges = Arrays.asList(
readPrivilege, writePrivilege);
createRoleIfNotFound("ROLE_ADMIN", adminPrivileges);
createRoleIfNotFound("ROLE_USER", Arrays.asList(readPrivilege));
Role adminRole = roleRepository.findByName("ROLE_ADMIN");
User user = new User();
user.setFirstName("Test");
user.setLastName("Test");
user.setPassword(passwordEncoder.encode("test"));
user.setEmail("test@test.com");
user.setRoles(Arrays.asList(adminRole));
user.setEnabled(true);
userRepository.save(user);
alreadySetup = true;
}
@Transactional
Privilege createPrivilegeIfNotFound(String name) {
Privilege privilege = privilegeRepository.findByName(name);
if (privilege == null) {
privilege = new Privilege(name);
privilegeRepository.save(privilege);
}
return privilege;
}
@Transactional
Role createRoleIfNotFound(
String name, Collection<Privilege> privileges) {
Role role = roleRepository.findByName(name);
if (role == null) {
role = new Role(name);
role.setPrivileges(privileges);
roleRepository.save(role);
}
return role;
}
}
Итак, что же происходит во время выполнения этого простого кода установки? Ничего сложного:
- Мы создаем привилегии.
- Затем мы создаем роли и назначаем им привилегии.
- Наконец, мы создаем пользователя и назначаем ему роль.
Обратите внимание, как мы используем флаг ужеSetup
, чтобы определить, нужно ли запускать установку или нет. Это просто потому, что ContextRefreshedEvent
может запускаться несколько раз в зависимости от того, сколько контекстов мы настроили в нашем приложении. И мы хотим запустить установку только один раз.
Две короткие заметки здесь. Сначала рассмотрим терминологию. Здесь мы используем термины « привилегия — роль ».
Но весной они немного отличаются. В Spring наша привилегия называется ролью, а также (предоставленной) властью, что немного сбивает с толку.
Это не проблема для реализации конечно, но это определенно стоит отметить.
Во-вторых, этим Spring-ролям (нашим привилегиям) нужен префикс. По умолчанию этот префикс — «ROLE», но его можно изменить. Мы не используем этот префикс здесь, просто для простоты, но имейте в виду, что он потребуется, если мы не изменим его явно.
4. Пользовательская служба сведений о пользователе
Теперь давайте проверим процесс аутентификации.
Мы увидим, как получить пользователя в нашей пользовательской службе UserDetailsService
и как сопоставить правильный набор полномочий с ролями и привилегиями, назначенными пользователем:
@Service("userDetailsService")
@Transactional
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private IUserService service;
@Autowired
private MessageSource messages;
@Autowired
private RoleRepository roleRepository;
@Override
public UserDetails loadUserByUsername(String email)
throws UsernameNotFoundException {
User user = userRepository.findByEmail(email);
if (user == null) {
return new org.springframework.security.core.userdetails.User(
" ", " ", true, true, true, true,
getAuthorities(Arrays.asList(
roleRepository.findByName("ROLE_USER"))));
}
return new org.springframework.security.core.userdetails.User(
user.getEmail(), user.getPassword(), user.isEnabled(), true, true,
true, getAuthorities(user.getRoles()));
}
private Collection<? extends GrantedAuthority> getAuthorities(
Collection<Role> roles) {
return getGrantedAuthorities(getPrivileges(roles));
}
private List<String> getPrivileges(Collection<Role> roles) {
List<String> privileges = new ArrayList<>();
List<Privilege> collection = new ArrayList<>();
for (Role role : roles) {
privileges.add(role.getName());
collection.addAll(role.getPrivileges());
}
for (Privilege item : collection) {
privileges.add(item.getName());
}
return privileges;
}
private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) {
List<GrantedAuthority> authorities = new ArrayList<>();
for (String privilege : privileges) {
authorities.add(new SimpleGrantedAuthority(privilege));
}
return authorities;
}
}
Здесь интересно проследить, как привилегии (и роли) сопоставляются с сущностями GrantedAuthority.
Такое сопоставление делает всю конфигурацию безопасности очень гибкой и мощной. Мы можем смешивать и сопоставлять роли и привилегии настолько детально, насколько это необходимо, и в конце концов они будут правильно сопоставлены с полномочиями и возвращены обратно во фреймворк.
5. Ролевая иерархия
Кроме того, давайте организуем наши роли в иерархии.
Мы увидели, как реализовать управление доступом на основе ролей, сопоставив привилегии с ролями. Это позволяет нам назначать пользователю одну роль вместо того, чтобы назначать все отдельные привилегии.
Однако по мере увеличения количества ролей пользователям может потребоваться несколько ролей , что приведет к взрывному росту числа ролей:
Чтобы преодолеть это, мы можем использовать иерархию ролей Spring Security:
Назначение роли ADMIN
автоматически дает пользователю привилегии ролей STAFF
и USER .
Однако пользователь с ролью ПЕРСОНАЛ
может выполнять только действия ролей ПЕРСОНАЛ
и ПОЛЬЗОВАТЕЛЬ
.
Давайте создадим эту иерархию в Spring Security, просто предоставив bean-компонент типа RoleHierarchy
:
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_ADMIN > ROLE_STAFF \n ROLE_STAFF > ROLE_USER";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
Мы используем символ >
в выражении, чтобы определить иерархию ролей. Здесь мы настроили роль ADMIN
для включения роли STAFF
, которая, в свою очередь, включает роль USER.
Наконец, чтобы включить эту иерархию ролей в Spring Web Expressions
, мы добавляем экземпляр roleHierarchy
в WebSecurityExpressionHandler
:
@Bean
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy());
return expressionHandler;
}
Как мы видим, иерархия ролей — отличный способ уменьшить количество ролей и полномочий, которые нам нужно добавить пользователю.
6. Регистрация пользователя
Наконец, давайте взглянем на регистрацию нового пользователя.
Мы видели, как установка создает пользователя и назначает ему роли (и привилегии).
Давайте теперь посмотрим, как это нужно сделать при регистрации нового пользователя:
@Override
public User registerNewUserAccount(UserDto accountDto) throws EmailExistsException {
if (emailExist(accountDto.getEmail())) {
throw new EmailExistsException
("There is an account with that email adress: " + accountDto.getEmail());
}
User user = new User();
user.setFirstName(accountDto.getFirstName());
user.setLastName(accountDto.getLastName());
user.setPassword(passwordEncoder.encode(accountDto.getPassword()));
user.setEmail(accountDto.getEmail());
user.setRoles(Arrays.asList(roleRepository.findByName("ROLE_USER")));
return repository.save(user);
}
В этой простой реализации, поскольку мы предполагаем, что регистрируется обычный пользователь, мы назначаем ему роль ROLE_USER
.
Конечно, более сложную логику можно легко реализовать таким же образом, либо используя несколько жестко закодированных методов регистрации, либо позволяя клиенту отправлять тип регистрируемого пользователя.
7. Заключение
В этой статье мы показали, как реализовать роли и привилегии с помощью JPA для системы с поддержкой Spring Security.
Мы также настроили иерархию ролей, чтобы упростить настройку контроля доступа.
Полную реализацию этого руководства по регистрации с помощью Spring Security можно найти на GitHub .