Ajouter des rôles et des privilèges à l’application Reddit

Ajout de rôles et de privilèges à l'application Reddit

1. Vue d'ensemble

Dans cet épisode, nous allons introduire des rôles et des privilèges simples dansour Reddit app, pour ensuite pouvoir faire des choses intéressantes telles que - limiter le nombre de publications qu'un utilisateur normal peut programmer quotidiennement sur Reddit.

Et comme nous allons avoir un rôle d'administrateur - et implicitement un utilisateur administrateur - nous allons également ajouter une zone de gestion d'administrateur.

2. EntitésUser,Role etPrivilege

Tout d'abord, nous allons modifier l'entitéUser - que nous l'utilisons via nosReddit App series - pour ajouter des rôles:

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

    ...
}

Notez à quel point la relation utilisateur-rôle est flexible.

Ensuite, nous allons définir les entitésRole etPrivilege. Pour plus de détails sur cette implémentation, consultezthis article on example.

3. Installer

Ensuite, nous allons exécuter une configuration de base sur le démarrage du projet, pour créer ces rôles et privilèges:

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

Et faire de notre utilisateur test un admin:

private void createTestUser() {
    Role adminRole = roleRepository.findByName("ROLE_ADMIN");
    Role superUserRole = roleRepository.findByName("ROLE_SUPER_USER");
    ...
    userJohn.setRoles(Arrays.asList(adminRole, superUserRole));
}

4. Enregistrer les utilisateurs standard

Nous devrons également nous assurer que nous enregistrons les utilisateurs standard via la mise en œuvreregisterNewUser():

@Override
public void registerNewUser(String username, String email, String password) {
    ...
    Role role = roleRepository.findByName("ROLE_USER");
    user.setRoles(Arrays.asList(role));
}

Notez que les rôles dans le système sont:

  1. ROLE_USER: pour les utilisateurs réguliers (le rôle par défaut) - ceux-ci ont une limite sur le nombre de messages qu'ils peuvent programmer par jour

  2. ROLE_SUPER_USER: pas de limite de planification

  3. ROLE_ADMIN: options d'administration supplémentaires

5. Le principal

Ensuite, intégrons ces nouveaux privilèges dans notre implémentation principale:

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. Restreindre les publications programmées par les utilisateurs standard

Profitons maintenant des nouveaux rôles et privilèges et derestrict standard users from scheduling more than – say – 3 new articles a day - pour éviter de spammer Reddit.

6.1. Dépôt de publication

Tout d'abord, nous allons ajouter une nouvelle opération à notre implémentationPostRepository - pour compter les publications programmées par un utilisateur spécifique sur une période donnée:

public interface PostRepository extends JpaRepository {
    ...

    Long countByUserAndSubmissionDateBetween(User user, Date start, Date end);

}

5.2. Contrôleur de poste programmé

Ensuite, nous ajouterons une simple vérification aux méthodesschedule() etupdatePost():

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

Il y a quelques choses intéressantes qui se passent ici. Tout d'abord, notez comment nous interagissons manuellement avec Spring Security et vérifions si l'utilisateur actuellement connecté dispose d'un privilège ou non. Ce n’est pas quelque chose que vous faites tous les jours, mais lorsque vous devez le faire, l’API est très utile.

Dans l'état actuel de la logique - si un utilisateur a lesPOST_UNLIMITED_PRIVILEGE - il peut - surprendre - planifier autant qu'il le souhaite.

Si toutefois, ils ne disposent pas de ce privilège, ils pourront mettre en file d'attente jusqu'à 3 messages par jour.

7. La page des utilisateurs administrateurs

Ensuite - maintenant que nous avons une séparation claire des utilisateurs, en fonction du rôle qu'ils ont - implémentons une gestion des utilisateurs très simple pour l'administrateur de notre petite application Reddit.

7.1. Afficher tous les utilisateurs

Commençons par créer une page de base répertoriant tous les utilisateurs du système:

Voici l'API pour lister tous les utilisateurs:

@PreAuthorize("hasRole('ADMIN_READ_PRIVILEGE')")
@RequestMapping(value="/admin/users", method = RequestMethod.GET)
@ResponseBody
public List getUsersList() {
    return service.getUsersList();
}

Et l'implémentation de la couche de service:

@Transactional
public List getUsersList() {
    return userRepository.findAll();
}

Ensuite, le simple front-end:

Username Roles Actions

7.2. Modifier le rôle de l'utilisateur

Ensuite, une logique simple pour gérer les rôles de ces utilisateurs; Commençons par le contrôleur:

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

Et la couche de service:

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

Enfin - le simple front-end:

8. Configuration de sécurité

Enfin, nous devons modifier la configuration de la sécurité pour rediriger les utilisateurs administrateurs vers cette nouvelle page distincte du système:

@Autowired
private AuthenticationSuccessHandler successHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.
    ...
    .authorizeRequests()
    .antMatchers("/adminHome","/users").hasAuthority("ADMIN_READ_PRIVILEGE")
    ...
    .formLogin().successHandler(successHandler)
}

Nous utilisons un gestionnaire de réussite d'authentification personnalisé pourdecide 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");
        }
    }
}

Et la page d'accueil d'administration extrêmement simpleadminHome.html:



    

Welcome, Bob


Display Users List

9. Conclusion

Dans cette nouvelle partie de l’étude de cas, nous avons ajouté quelques artefacts de sécurité simples à notre application - rôles et privilèges. Avec ce support,we built two simple features - une limite de planification pour les utilisateurs standard et un administrateur simple pour les utilisateurs administrateurs.