Adicionando funções e privilégios ao aplicativo Reddit

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:

  1. ROLE_USER: para usuários regulares (a função padrão) - eles têm um limite de quantas postagens eles podem programar por dia

  2. ROLE_SUPER_USER: sem limite de agendamento

  3. 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 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:

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.