Рубрики
Без рубрики

Пружинный ботинок : Пользовательская роль – Разрешение на авторизацию с использованием заклинания

В этой статье продолжается регистрация в серии Spring Security, в которой рассказывается о том, как правильно обмениваться мгновенными сообщениями… С тегами java, безопасность, веб-разработчик, программирование.

В этой статье продолжается серия “Регистрация в 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 Collection roles;
}
@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 Collection permissions;
}
@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 roles;
    private Collection permissions;

    public static UserPrincipal createUserPrincipal(User user) {
        if (user != null) {
            List roles= 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())) {
            List authenticationPermissions = 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”