Spring Security - роли и привилегии

Spring Security - роли и привилегии

1. обзор

Эта статья продолжает серию «Регистрация с помощью Spring Security», в которой рассказывается, как правильно реализоватьRoles and Privileges.

Дальнейшее чтение:

Введение в Spring Security Expressions

Простое и практичное руководство по Spring Security Expressions.

Read more

Введение в безопасность методов Spring

Руководство по безопасности на уровне методов с использованием среды безопасности Spring.

Read more

Spring Security - Перенаправление на предыдущий URL после входа в систему

Краткий пример перенаправления после входа в Spring Security

Read more

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 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, поэтому его должно быть легко импортировать и запускать как есть.