Добавление ролей и привилегий в приложение Reddit

Добавление ролей и привилегий в приложение Reddit

1. обзор

В этой статье мы введем простые роли и привилегии вour Reddit app, чтобы затем можно было делать некоторые интересные вещи, такие как - ограничение количества публикаций, которые обычный пользователь может запланировать на Reddit ежедневно.

И поскольку у нас будет роль администратора - и неявно администратора - мы также добавим область управления администратором.

2. СущностиUser,Role иPrivilege

Сначала мы изменим сущностьUser, которую мы используем через нашReddit App series, чтобы добавить роли:

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

    ...
}

Обратите внимание, что отношение «пользователь-роль» является гибким для многих.

Далее мы собираемся определить сущностиRole иPrivilege. Для получения полной информации об этой реализации ознакомьтесь сthis article on example.

3. Настроить

Затем мы собираемся запустить базовую настройку при начальной загрузке проекта, чтобы создать эти роли и привилегии:

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

И сделайте нашего тестового пользователя администратором:

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

4. Зарегистрируйте стандартных пользователей

Нам также необходимо убедиться, что мы регистрируем обычных пользователей с помощью реализацииregisterNewUser():

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

Обратите внимание, что роли в системе:

  1. ROLE_USER: для обычных пользователей (роль по умолчанию) - у них есть ограничение на количество сообщений, которые они могут запланировать в день.

  2. ROLE_SUPER_USER: без ограничения расписания

  3. ROLE_ADMIN: дополнительные параметры администратора

5. Директор

Затем давайте интегрировать эти новые привилегии в нашу основную реализацию:

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. Ограничение запланированных сообщений стандартными пользователями

Давайте теперь воспользуемся новыми ролями и привилегиями иrestrict standard users from scheduling more than – say – 3 new articles a day - чтобы избежать спама Reddit.

6.1. Пост репозиторий

Во-первых, мы добавим новую операцию в нашу реализациюPostRepository - для подсчета запланированных публикаций определенного пользователя в определенный период времени:

public interface PostRepository extends JpaRepository {
    ...

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

}

5.2. Запланированный почтовый контроллер

Затем мы добавим простую проверку к методамschedule() иupdatePost():

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

Здесь происходит пара интересных вещей. Во-первых, обратите внимание на то, как мы вручную взаимодействуем со Spring Security и проверяем, есть ли у текущего авторизованного пользователя права или нет. Это не то, что вы делаете каждый день, но когда вам действительно нужно это делать, API очень полезен.

По нынешней логике - если у пользователя естьPOST_UNLIMITED_PRIVILEGE - они могут - сюрприз - запланировать, сколько захотят.

Однако если у них нет такой привилегии, они смогут ставить в очередь максимум 3 сообщения в день.

7. Страница пользователей с правами администратора

Затем - теперь, когда у нас есть четкое разделение пользователей в зависимости от их роли, - давайте реализуем очень простое управление пользователями для администратора нашего небольшого приложения Reddit.

7.1. Показать всех пользователей

Во-первых, давайте создадим базовую страницу со списком всех пользователей системы:

Здесь API для перечисления всех пользователей:

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

И реализация уровня обслуживания:

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

Затем простой интерфейс:

Username Roles Actions

7.2. Изменить роль пользователя

Затем немного простой логики для управления ролями этих пользователей; начнем с контроллера:

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

И уровень обслуживания:

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

Напоследок - простой интерфейс:

8. Конфигурация безопасности

Наконец, нам нужно изменить конфигурацию безопасности, чтобы перенаправить пользователей с правами администратора на эту новую отдельную страницу в системе:

@Autowired
private AuthenticationSuccessHandler successHandler;

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

Мы используем специальный обработчик успешной аутентификации дляdecide 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");
        }
    }
}

И чрезвычайно простая домашняя страница администратораadminHome.html:



    

Welcome, Bob


Display Users List

9. Заключение

В этой новой части кейса мы добавили в наше приложение несколько простых артефактов безопасности - роли и привилегии. С этой поддержкойwe built two simple features - ограничение по расписанию для стандартных пользователей и простой администратор для пользователей с правами администратора.