В этой статье продолжается серия “Регистрация в Spring Security”, в которой рассматривается, как правильно реализовать роли и разрешения.
Во-первых, давайте начнем с наших сущностей. У нас есть три основных подразделения:
-> Пользователь -> Роль -> Разрешение
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String email; private String password; @ManyToMany @JoinTable( name = "users_roles", joinColumns = @JoinColumn( name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn( name = "role_id", referencedColumnName = "id")) private Collectionroles; }
@Entity public class Role{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; // Ex : ADMIN, USER private String name; @ManyToMany @JoinTable( name = "role_permissions", joinColumns = @JoinColumn( name = "role_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn( name = "permission_id", referencedColumnName = "id")) private Collectionpermissions; }
@Entity public class Permission { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; // Ex: READ,WRITE,UPDATE private String name; }
@Getter @Setter @Builder public class UserPrincipal implements UserDetails { private Long id; private String name; private String email; private Collection extends GrantedAuthority> roles; private Collection extends GrantedAuthority> permissions; public static UserPrincipal createUserPrincipal(User user) { if (user != null) { Listroles= user.getRoles().stream().filter(Objects::nonNull) .map(role -> new SimpleGrantedAuthority(role.getName().name())) .collect(Collectors.toList()); List permissions = user.getRoles().stream().filter(Objects::nonNull) .map(Role::getPermissions).flatMap(Collection::stream) .map(permission-> new SimpleGrantedAuthority(permission.getName().name())) .collect(Collectors.toList()); return UserPrincipal.builder() .id(user.getId()) .name(user.getName()) .email(user.getEmail()) .roles(roles) .permissions(permissions) .build(); } return null; } }
Примечание – Здесь мы используем термины “Роль –разрешение”, но весной они немного отличаются. Весной наше Разрешение упоминается как Роль, а также как (предоставленные) полномочия, что немного сбивает с толку. Не проблема для реализации, конечно, но определенно стоит отметить.
Кроме того, этим ролям Spring (нашим разрешениям) требуется префикс; по умолчанию этот префикс – “РОЛЬ”, но его можно изменить. Мы не используем этот префикс здесь, просто для простоты, но имейте в виду, что если вы явно не измените его, это потребуется.
public class CustomSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { private Object filterObject; private Object returnObject; private Object target; /** * Creates a new instance * * @param authentication the {@link Authentication} to use. Cannot be null. */ public CustomSecurityExpressionRoot(Authentication authentication) { super(authentication); } public boolean hasAnyPermission(String... permissions) { UserPrincipal authentication = (UserPrincipal) getPrincipal(); for (String permission : permissions) { if (authentication.getPermissions() .stream() .map(GrantedAuthority::getAuthority) .anyMatch(a -> a.equals(permission))) { return true; } } return false; } /** * Validates if Current User is authorized for ALL given permissions * * @param permissions cannot be empty */ public boolean hasPermission(String... permissions) { UserPrincipal authentication = (UserPrincipal) getPrincipal(); if (CollectionUtils.isNotEmpty(authentication.getPermissions())) { ListauthenticationPermissions = authentication.getPermissions() .stream() .filter(Objects::nonNull) .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()); return Arrays.stream(permissions) .filter(StringUtils::isNotBlank) .allMatch(permission -> authenticationPermissions.contains(permission)); } return false; } @Override public void setFilterObject(Object filterObject) { this.filterObject = filterObject; } @Override public Object getFilterObject() { return filterObject; } @Override public void setReturnObject(Object returnObject) { this.returnObject = returnObject; } @Override public Object getReturnObject() { return returnObject; } @Override public Object getThis() { return target; } public void setThis(Object target) { this.target = target; } }
@Component public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { @Override protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) { CustomSecurityExpressionRoot root = new CustomSecurityExpressionRoot(authentication); root.setThis(invocation.getThis()); root.setPermissionEvaluator(getPermissionEvaluator()); root.setTrustResolver(getTrustResolver()); root.setRoleHierarchy(getRoleHierarchy()); root.setDefaultRolePrefix(getDefaultRolePrefix()); return root; } }
@Configuration @EnableGlobalMethodSecurity( prePostEnabled = true ) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityExpressionHandler createExpressionHandler() { CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler(); return expressionHandler; } }
Теперь у нас есть два новых выражения безопасности, доступных и готовых к использованию: Имеет разрешение & имеет Какое-Либо Разрешение .
имеет разрешение
– @param
– Строка[], проверяет, есть ли у текущего пользователя ВСЕ разрешения.
имеет Какое-Либо Разрешение
– @param
– Строка[], проверяет, есть ли у текущего пользователя ЛЮБОЕ разрешение.
Теперь аналогично тому, как Spring Security имеет встроенные выражения, такие как hasRole
& hasAnyRole
, мы также можем проверить разрешения.
Пример:
@GetMapping @PreAuthorize("hasRole('ADMIN') or hasPermission('READ')") public ResponseEntity> findAll() { return new ResponseEntity<>(User.build(userService.getAllUsers()), HttpStatus.OK); } @GetMapping @PreAuthorize("hasAnyRole('ADMIN') or hasAnyPermission('READ','UPDATE','WRITE')") public ResponseEntity
> findAll() { return new ResponseEntity<>(User.build(userService.getAllUsers()), HttpStatus.OK); }
Оригинал: “https://dev.to/ashishrameshan/custom-role-based-permission-authorization-in-spring-boot-m7f”