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.
Introdução à Spring Method Security
Um guia para segurança em nível de método usando a estrutura Spring Security.
Spring Security - Redirecionar para o URL anterior após o login
Um pequeno exemplo de redirecionamento após o login no Spring Security
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 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;
}
}
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á.