Adicionando funções e privilégios ao aplicativo Reddit
1. Visão geral
Nesta parte, vamos introduzir funções e privilégios simples emour Reddit app, para então poder fazer algumas coisas interessantes como - limitar quantas postagens um usuário normal pode agendar no Reddit diariamente
E como teremos uma função de administrador - e implicitamente um usuário administrador - também vamos adicionar uma área de gerenciamento de administrador.
2. User,Role ePrivilege Entidades
Primeiro, vamos modificar a entidadeUser - que usamos por meio de nossoReddit App series - para adicionar funções:
@Entity
public class User {
...
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "users_roles",
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
private Collection roles;
...
}
Observe como o relacionamento Função do Usuário é flexível para muitos.
A seguir, vamos definir as entidadesRoleePrivilege. Para obter os detalhes completos dessa implementação, verifiquethis article on example.
3. Configuração
A seguir, vamos executar algumas configurações básicas no bootstrap do projeto, para criar estas funções e privilégios:
private void createRoles() {
Privilege adminReadPrivilege = createPrivilegeIfNotFound("ADMIN_READ_PRIVILEGE");
Privilege adminWritePrivilege = createPrivilegeIfNotFound("ADMIN_WRITE_PRIVILEGE");
Privilege postLimitedPrivilege = createPrivilegeIfNotFound("POST_LIMITED_PRIVILEGE");
Privilege postUnlimitedPrivilege = createPrivilegeIfNotFound("POST_UNLIMITED_PRIVILEGE");
createRoleIfNotFound("ROLE_ADMIN", Arrays.asList(adminReadPrivilege, adminWritePrivilege));
createRoleIfNotFound("ROLE_SUPER_USER", Arrays.asList(postUnlimitedPrivilege));
createRoleIfNotFound("ROLE_USER", Arrays.asList(postLimitedPrivilege));
}
E torne nosso usuário de teste um administrador:
private void createTestUser() {
Role adminRole = roleRepository.findByName("ROLE_ADMIN");
Role superUserRole = roleRepository.findByName("ROLE_SUPER_USER");
...
userJohn.setRoles(Arrays.asList(adminRole, superUserRole));
}
4. Registrar usuários padrão
Também precisamos ter certeza de que estamos registrando usuários padrão por meio da implementaçãoregisterNewUser():
@Override
public void registerNewUser(String username, String email, String password) {
...
Role role = roleRepository.findByName("ROLE_USER");
user.setRoles(Arrays.asList(role));
}
Observe que as funções no sistema são:
-
ROLE_USER: para usuários regulares (a função padrão) - eles têm um limite de quantas postagens eles podem programar por dia
-
ROLE_SUPER_USER: sem limite de agendamento
-
ROLE_ADMIN: opções adicionais de administrador
5. O diretor da escola
A seguir, vamos integrar esses novos privilégios em nossa implementação principal:
public class UserPrincipal implements UserDetails {
...
@Override
public Collection extends GrantedAuthority> getAuthorities() {
List authorities = new ArrayList();
for (Role role : user.getRoles()) {
for (Privilege privilege : role.getPrivileges()) {
authorities.add(new SimpleGrantedAuthority(privilege.getName()));
}
}
return authorities;
}
}
6. Restringir postagens programadas por usuários padrão
Agora vamos aproveitar as novas funções e privilégios erestrict standard users from scheduling more than – say – 3 new articles a day - para evitar spam no Reddit.
6.1. Post Repository
Primeiro, vamos adicionar uma nova operação à nossa implementaçãoPostRepository - para contar as postagens programadas por um usuário específico em um período de tempo específico:
public interface PostRepository extends JpaRepository {
...
Long countByUserAndSubmissionDateBetween(User user, Date start, Date end);
}
5.2. Post Controller Programado
Em seguida, adicionaremos uma verificação simples aos métodosschedule()eupdatePost():
public class ScheduledPostRestController {
private static final int LIMIT_SCHEDULED_POSTS_PER_DAY = 3;
public Post schedule(HttpServletRequest request,...) throws ParseException {
...
if (!checkIfCanSchedule(submissionDate, request)) {
throw new InvalidDateException("Scheduling Date exceeds daily limit");
}
...
}
private boolean checkIfCanSchedule(Date date, HttpServletRequest request) {
if (request.isUserInRole("POST_UNLIMITED_PRIVILEGE")) {
return true;
}
Date start = DateUtils.truncate(date, Calendar.DATE);
Date end = DateUtils.addDays(start, 1);
long count = postReopsitory.
countByUserAndSubmissionDateBetween(getCurrentUser(), start, end);
return count < LIMIT_SCHEDULED_POSTS_PER_DAY;
}
}
Há algumas coisas interessantes acontecendo aqui. Primeiro - observe como estamos interagindo manualmente com Spring Security e verificando se o usuário conectado no momento tem um privilégio ou não. Isso não é algo que você faz todos os dias - mas quando você tem que fazer, a API é muito útil.
Como a lógica está atualmente - se um usuário tiverPOST_UNLIMITED_PRIVILEGE - ele pode - surpreender - programar o quanto quiser.
Se, no entanto, eles não tiverem esse privilégio, eles poderão enfileirar no máximo 3 postagens por dia.
7. A página de usuários administrativos
Em seguida - agora que temos uma clara separação de usuários, com base na função que eles têm - vamos implementar um gerenciamento de usuário muito simples para o administrador de nosso pequeno aplicativo Reddit.
7.1. Exibir todos os usuários
Primeiro, vamos criar uma página básica listando todos os usuários no sistema:
Aqui a API para listar todos os usuários:
@PreAuthorize("hasRole('ADMIN_READ_PRIVILEGE')")
@RequestMapping(value="/admin/users", method = RequestMethod.GET)
@ResponseBody
public List getUsersList() {
return service.getUsersList();
}
E a implementação da camada de serviço:
@Transactional
public List getUsersList() {
return userRepository.findAll();
}
Em seguida, o front-end simples:
Username
Roles
Actions
7.2. Modificar a função do usuário
A seguir, algumas lógicas simples para gerenciar as funções desses usuários; vamos começar com o controlador:
@PreAuthorize("hasRole('USER_WRITE_PRIVILEGE')")
@RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
public void modifyUserRoles(
@PathVariable("id") Long id,
@RequestParam(value = "roleIds") String roleIds) {
service.modifyUserRoles(id, roleIds);
}
@PreAuthorize("hasRole('USER_READ_PRIVILEGE')")
@RequestMapping(value = "/admin/roles", method = RequestMethod.GET)
@ResponseBody
public List getRolesList() {
return service.getRolesList();
}
E a camada de serviço:
@Transactional
public List getRolesList() {
return roleRepository.findAll();
}
@Transactional
public void modifyUserRoles(Long userId, String ids) {
List roleIds = new ArrayList();
String[] arr = ids.split(",");
for (String str : arr) {
roleIds.add(Long.parseLong(str));
}
List roles = roleRepository.findAll(roleIds);
User user = userRepository.findOne(userId);
user.setRoles(roles);
userRepository.save(user);
}
Finalmente - o front-end simples:
Modify User Roles
8. Configuração de segurança
Por fim, precisamos modificar a configuração de segurança para redirecionar os usuários administrativos para esta nova página separada no sistema:
@Autowired
private AuthenticationSuccessHandler successHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
...
.authorizeRequests()
.antMatchers("/adminHome","/users").hasAuthority("ADMIN_READ_PRIVILEGE")
...
.formLogin().successHandler(successHandler)
}
Estamos usando um gerenciador de sucesso de autenticação personalizado paradecide where the user lands after login:
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication auth)
throws IOException, ServletException {
Set privieleges = AuthorityUtils.authorityListToSet(auth.getAuthorities());
if (privieleges.contains("ADMIN_READ_PRIVILEGE")) {
response.sendRedirect("adminHome");
} else {
response.sendRedirect("home");
}
}
}
E a página inicial do administrador extremamente simplesadminHome.html:
Welcome, Bob
Display Users List
9. Conclusão
Nesta nova parte do estudo de caso, adicionamos alguns artefatos de segurança simples em nosso aplicativo - funções e privilégios. Com esse suporte,we built two simple features - um limite de agendamento para usuários padrão e um administrador básico para usuários administradores.