1. Обзор
В этом выпуске мы введем простые роли и привилегии в наше приложение Reddit, чтобы затем иметь возможность делать некоторые интересные вещи , такие как ограничение количества сообщений, которые обычный пользователь может запланировать на Reddit ежедневно.
И поскольку у нас будет роль администратора – и неявно пользователь – администратор, – мы также добавим область управления администратором.
2. Сущности пользователей, ролей и привилегий
Во – первых, мы изменим сущность User – которую мы используем в нашей серии приложений Reddit – для добавления ролей:
@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;
...
} Обратите внимание, что отношения между пользователями и ролями являются гибкими “многие ко многим”.
Далее мы определим Роль и Привилегии сущности. Для получения полной информации об этой реализации ознакомьтесь с этой статьей о строительстве .
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. Регистрация Стандартных Пользователей
Нам также нужно будет убедиться, что мы регистрируем стандартных пользователей с помощью реализации register New User() :
@Override
public void registerNewUser(String username, String email, String password) {
...
Role role = roleRepository.findByName("ROLE_USER");
user.setRoles(Arrays.asList(role));
}Обратите внимание, что Роли в системе:
- ROLE_USER : для обычных пользователей (роль по умолчанию) – у них есть ограничение на количество записей, которые они могут запланировать в день
- ROLE_SUPER_USER : нет ограничений по расписанию
- ROLE_ADMIN : дополнительные параметры администратора
5. Принципал
Далее, давайте интегрируем эти новые привилегии в нашу основную реализацию:
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. Ограничение запланированных сообщений стандартными пользователями
Давайте теперь воспользуемся новыми ролями и привилегиями и ограничим обычных пользователей от планирования более, скажем, 3 новых статей в день – чтобы избежать спама Reddit.
6.1. Хранилище сообщений
Во – первых, мы добавим новую операцию в нашу реализацию PostRepository – для подсчета запланированных сообщений конкретным пользователем в определенный период времени:
public interface PostRepository extends JpaRepository{ ... Long countByUserAndSubmissionDateBetween(User user, Date start, Date end); }
5.2. Контроллер запланированной почты
Затем мы добавим простую проверку в оба метода schedule() и update Post() :
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 ListgetUsersList() { 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 ListgetRolesList() { 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); }
Наконец – простой интерфейс:
Modify User Roles
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)
}Мы используем пользовательский обработчик успеха аутентификации, чтобы решить, где пользователь приземляется после входа в систему :
@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. Заключение
В этой новой части тематического исследования мы добавили в наше приложение несколько простых артефактов безопасности – роли и привилегии. С этой поддержкой мы создали две простые функции – ограничение по расписанию для стандартных пользователей и простой администратор для пользователей с правами администратора.