Spring Security - Funções e privilégios

Spring Security - Funções e privilégios

1. Visão geral

Este artigo continua a série Registro com Spring Security com uma olhada em como implementar corretamenteRoles and Privileges.

Leitura adicional:

Introdução às expressões de segurança da Spring

Guia simples e prático para Spring Security Expressions.

Read more

Introdução à Spring Method Security

Um guia para segurança em nível de método usando a estrutura Spring Security.

Read more

Spring Security - Redirecionar para o URL anterior após o login

Um pequeno exemplo de redirecionamento após o login no Spring Security

Read more

2. UserRole ePrivilege

Primeiro, vamos começar com nossas entidades. Temos três entidades principais:

  • oUser

  • oRole - representa as funções de alto nível do usuário no sistema; cada função terá um conjunto de privilégios de baixo nível

  • oPrivilege - representa um privilégio / autoridade granular de baixo nível no sistema

Aqui está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;
}

Como você pode ver, o usuário contém as funções, mas também alguns detalhes adicionais necessários para um mecanismo de registro adequado.

Próximo - aqui está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;
}

E finalmentethe privilege:

@Entity
public class Privilege {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "privileges")
    private Collection roles;
}

Como você pode ver, estamos considerando o Usuário <→ Função e também a Função <→ Relações de privilégiomany-to-many bidirectional.

3. Configurar privilégios e funções

A seguir - vamos nos concentrar em fazer algumas configurações iniciais dos privilégios e funções do sistema.

Vincularemos isso à inicialização do aplicativo e usaremos umApplicationListener emContextRefreshedEvent para carregar nossos dados iniciais na inicialização do servidor:

@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;
    }
}

Então, o que está acontecendo durante este código de configuração simples? Nada complicado:

  • estamos criando os privilégios

  • estamos criando as funções e atribuindo os privilégios a elas

  • estamos criando um usuário e atribuindo uma função a ele

Observe como estamos usando um sinalizadoralreadySetup paradetermine if the setup needs to run or not. Isso ocorre simplesmente porque, dependendo de quantos contextos você configurou em seu aplicativo - oContextRefreshedEvent pode ser disparado várias vezes. E queremos apenas que a instalação seja executada uma vez.

Duas notas rápidas aqui -first, about terminology. Estamos usando os termosPrivilege – Role aqui, mas na primavera, eles são um pouco diferentes. Na primavera, nosso privilégio é chamado de função e também como uma autoridade (concedida) - o que é um pouco confuso. Não é um problema para a implementação, é claro, mas definitivamente vale a pena notar.

Second – these Spring Roles (our Privileges) need a prefix; por padrão, esse prefixo é “ROLE”, mas pode ser alterado. Não estamos usando esse prefixo aqui, apenas para manter as coisas simples, mas lembre-se de que, se você não estiver alterando explicitamente, será necessário.

4. UserDetailsService personalizado

Agora - vamos verificar o processo de autenticação.

Veremos como recuperar o usuário em nossoUserDetailsService personalizado e como mapear o conjunto certo de autoridades a partir das funções e privilégios que o usuário atribuiu:

@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;
    }
}

O interessante a seguir é como os privilégios (e funções) são mapeados para as entidades GrantedAuthority.

Esse mapeamento torna toda a configuração de segurançahighly flexible and powerful - você pode misturar e combinar funções e privilégios tão granulares quanto necessário e, no final, eles serão mapeados corretamente para as autoridades e retornados à estrutura.

5. User Registro

Finalmente - vamos dar uma olhada no registro de um novo usuário.

Vimos como a configuração funciona para criar o usuário e atribuir funções (e privilégios) a ele - vamos agora dar uma olhada em como isso precisa ser feito durante o registro de um novo usuário:

@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);
}

Nesta implementação simples, estamos assumindo que um usuário padrão está sendo registrado, então a funçãoROLE_USER é atribuída a ele.

Claro, uma lógica mais complexa pode ser facilmente implementada da mesma maneira - seja por meio de vários métodos de registro codificados ou permitindo que o cliente envie o tipo de usuário que está sendo registrado.

6. Conclusão

Neste tutorial, ilustramos como implementar funções e privilégios com o JPA, para um sistema suportado pelo Spring Security.

Ofull implementation deste tutorial de Registro com Spring Security pode ser encontrado emthe GitHub project - este é um projeto baseado em Maven, portanto, deve ser fácil de importar e executar como está.