Spring Security - роли и привилегии
1. обзор
Эта статья продолжает серию «Регистрация с помощью Spring Security», в которой рассказывается, как правильно реализоватьRoles and Privileges.
Дальнейшее чтение:
Введение в Spring Security Expressions
Простое и практичное руководство по Spring Security Expressions.
Введение в безопасность методов Spring
Руководство по безопасности на уровне методов с использованием среды безопасности Spring.
Spring Security - Перенаправление на предыдущий URL после входа в систему
Краткий пример перенаправления после входа в Spring Security
2. UserRole иPrivilege
Во-первых, давайте начнем с наших сущностей. У нас есть три основных объекта:
-
User
-
Role - представляет высокоуровневые роли пользователя в системе; каждая роль будет иметь набор низкоуровневых привилегий
-
Privilege - представляет низкоуровневые, детализированные привилегии / полномочия в системе
Вотthe user:
@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 roles;
}
Как видите, пользователь содержит роли, а также несколько дополнительных деталей, которые необходимы для правильного механизма регистрации.
Далее - вотthe role:
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private Collection users;
@ManyToMany
@JoinTable(
name = "roles_privileges",
joinColumns = @JoinColumn(
name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(
name = "privilege_id", referencedColumnName = "id"))
private Collection privileges;
}
И наконецthe privilege:
@Entity
public class Privilege {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToMany(mappedBy = "privileges")
private Collection roles;
}
Как видите, мы рассматриваем отношения как Пользователь <→ Роль, так и Роль <→ Привилегииmany-to-many bidirectional.
3. Настройка привилегий и ролей
Далее - давайте сосредоточимся на ранней настройке привилегий и ролей в системе.
Мы свяжем это с запуском приложения и будем использоватьApplicationListener наContextRefreshedEvent для загрузки наших исходных данных при запуске сервера:
@Component
public class InitialDataLoader implements
ApplicationListener {
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 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("[email protected]");
user.setRoles(Arrays.asList(adminRole));
user.setEnabled(true);
userRepository.save(user);
alreadySetup = true;
}
@Transactional
private Privilege createPrivilegeIfNotFound(String name) {
Privilege privilege = privilegeRepository.findByName(name);
if (privilege == null) {
privilege = new Privilege(name);
privilegeRepository.save(privilege);
}
return privilege;
}
@Transactional
private Role createRoleIfNotFound(
String name, Collection privileges) {
Role role = roleRepository.findByName(name);
if (role == null) {
role = new Role(name);
role.setPrivileges(privileges);
roleRepository.save(role);
}
return role;
}
}
Итак, что происходит во время этого простого кода настройки? Ничего сложного
-
мы создаем привилегии
-
мы создаем роли и даем им привилегии
-
мы создаем пользователя и назначаем ему роль
Обратите внимание, как мы используем флагalreadySetup дляdetermine if the setup needs to run or not. Это просто потому, что в зависимости от того, сколько контекстов вы настроили в своем приложении,ContextRefreshedEvent может запускаться несколько раз. И мы хотим, чтобы установка была выполнена один раз.
Здесь две небольшие заметки -first, about terminology. Здесь мы используем терминыPrivilege – Role, но в Spring они немного отличаются. Весной наша привилегия упоминается как роль, а также как (предоставленный) авторитет, что немного сбивает с толку. Не проблема для реализации, конечно, но определенно стоит отметить.
Second – these Spring Roles (our Privileges) need a prefix; по умолчанию этот префикс - «РОЛЬ», но его можно изменить. Мы не используем этот префикс здесь, чтобы упростить задачу, но имейте в виду, что если вы не изменяете его явно, он потребуется.
4. ПользовательскийUserDetailsService
А теперь давайте проверим процесс аутентификации.
Мы собираемся увидеть, как получить пользователя в наших пользовательских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 roles) {
return getGrantedAuthorities(getPrivileges(roles));
}
private List getPrivileges(Collection roles) {
List privileges = new ArrayList<>();
List collection = new ArrayList<>();
for (Role role : roles) {
collection.addAll(role.getPrivileges());
}
for (Privilege item : collection) {
privileges.add(item.getName());
}
return privileges;
}
private List getGrantedAuthorities(List privileges) {
List authorities = new ArrayList<>();
for (String privilege : privileges) {
authorities.add(new SimpleGrantedAuthority(privilege));
}
return authorities;
}
}
Здесь интересно следить за тем, как привилегии (и роли) сопоставляются с сущностями GrantedAuthority.
Это сопоставление делает всю конфигурацию безопасностиhighly flexible and powerful - вы можете смешивать и сопоставлять роли и привилегии настолько детально, насколько это необходимо, и в конце они будут правильно сопоставлены с полномочиями и возвращены обратно в структуру.
5. User Регистрация
Наконец, давайте посмотрим на регистрацию нового пользователя.
Мы видели, как настройка создает пользователя и назначает ему роли (и привилегии) - давайте теперь посмотрим, как это нужно сделать во время регистрации нового пользователя:
@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.
Конечно, более сложная логика может быть легко реализована таким же образом - либо с помощью нескольких жестко запрограммированных методов регистрации, либо путем разрешения клиенту отправлять тип регистрируемого пользователя.
6. Заключение
В этом руководстве мы продемонстрировали, как реализовать роли и привилегии с помощью JPA для системы, поддерживаемой Spring Security.
full implementation этого руководства по регистрации с помощью Spring Security можно найти вthe GitHub project - это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.