Предположим, что classi c Управление доступом на основе ролей (RBA C) , с ролями, такими как ROLE_USER
, ROLE_ADMIN
, et c., Как изначально поддерживается Spring Security. Теперь представьте, что у нас есть домен с указанными c ролями объекта домена.
@Entity
@Table(name = "users")
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String firstName;
@Column(nullable = false, length = 50)
private String lastName;
@Column(nullable = false, length = 20, unique = true)
private String username;
@Column(nullable = false, length = 100, unique = true)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private boolean enabled;
// Our classic authorities for RBAC
@EqualsAndHashCode.Exclude
@ToString.Exclude
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
// Project membership with project specific role
@OneToMany(mappedBy = "user")
@EqualsAndHashCode.Exclude
@ToString.Exclude
private Set<ProjectMember> memberships = new HashSet<>();
}
Каждый пользователь может быть участником проекта с ролью безопасности в рамках проекта. ProjectMember
представляет таблицу соединения между User
и Project
с дополнительным атрибутом authority
/ role.
@Entity
@Table(name = "project_members")
@AllArgsConstructor
@NoArgsConstructor
@Data
@IdClass(ProjectMember.ProjectMemberId.class)
public class ProjectMember {
@Id
@ManyToOne
@JoinColumn(name = "project_id")
private Project project;
@Id
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
// Member/User's authority WITHIN the associated board
@ManyToOne(optional = false)
@JoinColumn(name = "authority_id", referencedColumnName = "id")
private Authority authority;
@Embeddable
@AllArgsConstructor
@NoArgsConstructor
@Data
public static class ProjectMemberId implements Serializable {
protected Long project;
protected Long user;
}
}
Класс Authority
похож на Role
, просто на сущность JPA, содержащую id
и name
с соответствующими геттерами / сеттерами, где name
хранится в виде строкового перечисления с именем RoleName
или AuthorityName
.
При создании реализации UserDetails
вы установить GrantedAuthority
соответственно роли безопасности. Для ролей с префиксом classi c ROLE_*
проблем нет.
String[] userRoles = user.getRoles().stream()
.map(Role::getName)
.map(RoleName::toString)
.toArray(String[]::new);
AuthorityUtils.createAuthorityList(userRoles);
Но как справиться с объектами домена, указанными c ролями, такими как они, описывающими членство пользователя / проекта? Должен ли я добавить их в список GrantedAuthority
UserDetails? Мой первый подход состоял в том, чтобы работать с дополнительным префиксом, таким как ROLE_PROJECT_{PROJECT_ID}_*
, и сохранять его как обычную строку. Использование будет: @PreAuthorize("hasRole('ROLE_PROJECT_' + #id + '_MAINTAINER')")
Это рекомендуемый подход? Или я должен написать собственное выражение безопасности для моего подкласса UserDetails
'с его дополнительными полями? Любые дополнительные подходы?
PS: я знаю, что мог бы использовать простой Список контроля доступа (ACL) , но даже я бы дал разрешение в этом списке через динамические c роли вместо имен пользователей для предотвращения сотен записей.