Rollen und Berechtigungen zur Reddit-App hinzufügen

Hinzufügen von Rollen und Berechtigungen zur Reddit-App

1. Überblick

In diesem Teil werden einfache Rollen und Berechtigungen inour Reddit app eingeführt, um dann einige interessante Dinge tun zu können, z. B. - Begrenzen Sie die Anzahl der Posts, die ein normaler Benutzer täglich für Reddit planen kann.

Und da wir eine Administratorrolle haben werden - und implizit einen Administratorbenutzer -, werden wir auch einen Administratorverwaltungsbereich hinzufügen.

2. User,Role undPrivilege Entitäten

Zuerst ändern wir die EntitätUser, die wir überReddit App seriesverwenden, um Rollen hinzuzufügen:

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

    ...
}

Beachten Sie, wie flexibel die Benutzer-Rollen-Beziehung ist.

Als Nächstes definieren wir die EntitätenRole undPrivilege. Ausführliche Informationen zu dieser Implementierung finden Sie unterthis article on example.

3. Konfiguration

Als Nächstes führen wir einige grundlegende Einstellungen für den Projekt-Bootstrap durch, um die folgenden Rollen und Berechtigungen zu erstellen:

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

Und machen Sie unseren Testbenutzer zum Administrator:

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

4. Registrieren Sie Standardbenutzer

Wir müssen auch sicherstellen, dass wir Standardbenutzer über die Implementierung vonregisterNewUser()registrieren:

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

Beachten Sie, dass die Rollen im System:

  1. ROLE_USER: Für reguläre Benutzer (die Standardrolle) - Diese haben eine Begrenzung für die Anzahl der Posts, die sie pro Tag planen können

  2. ROLE_SUPER_USER: kein Planungslimit

  3. ROLE_ADMIN: zusätzliche Administratoroptionen

5. Der Rektor

Als nächstes integrieren wir diese neuen Berechtigungen in unsere Hauptimplementierung:

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. Beschränken Sie geplante Posts auf Standardbenutzer

Nutzen wir jetzt die neuen Rollen und Berechtigungen sowierestrict standard users from scheduling more than – say – 3 new articles a day, um Reddit-Spam zu vermeiden.

6.1. Post-Repository

Zunächst fügen wir der Implementierung vonPostRepositoryeine neue Operation hinzu, um die geplanten Beiträge eines bestimmten Benutzers in einem bestimmten Zeitraum zu zählen:

public interface PostRepository extends JpaRepository {
    ...

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

}

5.2. Geplanter Post-Controller

Dann fügen wir den Methodenschedule() undupdatePost() eine einfache Prüfung hinzu:

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

Es gibt ein paar interessante Dinge, die hier vor sich gehen. Beachten Sie zunächst, wie wir manuell mit Spring Security interagieren und prüfen, ob der aktuell angemeldete Benutzer über Berechtigungen verfügt oder nicht. Das tun Sie nicht jeden Tag - aber wenn Sie es tun müssen, ist die API sehr nützlich.

Nach dem derzeitigen Stand der Logik kann ein Benutzer - wenn er diePOST_UNLIMITED_PRIVILEGE hat - überraschen - planen, wie viel er möchte.

Wenn sie dieses Privileg jedoch nicht haben, können sie maximal 3 Posts pro Tag in die Warteschlange stellen.

7. Die Admin-Benutzerseite

Als nächstes - jetzt, da wir eine klare Trennung der Benutzer haben, basierend auf ihrer Rolle - implementieren wir eine sehr einfache Benutzerverwaltung für den Administrator unserer kleinen Reddit-App.

7.1. Alle Benutzer anzeigen

Erstellen wir zunächst eine Basisseite, auf der alle Benutzer im System aufgelistet sind:

Hier die API zum Auflisten aller Benutzer:

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

Und die Service-Layer-Implementierung:

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

Dann das einfache Frontend:

Username Roles Actions

7.2. Ändern Sie die Rolle des Benutzers

Als nächstes eine einfache Logik zum Verwalten der Rollen dieser Benutzer; Beginnen wir mit dem Controller:

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

Und die Serviceschicht:

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

Endlich - das einfache Frontend:

8. Sicherheitskonfiguration

Schließlich müssen wir die Sicherheitskonfiguration ändern, um die Administratorbenutzer auf diese neue, separate Seite im System umzuleiten:

@Autowired
private AuthenticationSuccessHandler successHandler;

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

Wir verwenden einen benutzerdefinierten Authentifizierungserfolgshandler fürdecide 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");
        }
    }
}

Und die extrem einfache Admin-HomepageadminHome.html:



    

Welcome, Bob


Display Users List

9. Fazit

In diesem neuen Teil der Fallstudie haben wir einige einfache Sicherheitsartefakte in unsere App eingefügt - Rollen und Berechtigungen. Mit dieser Unterstützung istwe built two simple features - ein Planungslimit für Standardbenutzer und ein Bare-Bones-Administrator für Administratorbenutzer.