Spring Boot : Custom Role – Permission Authorization using SpEL

This article continues the Registration with Spring Security series with a look at how to properly implement Roles and Permissions.

First, let’s start with our entities. We have three main entities:

-> User
-> Role
-> Permission

@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<Role> roles;
}
@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<Role> roles;
}
@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<Role> roles; }

Enter fullscreen mode Exit fullscreen mode

@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<Permission> permissions;
}
@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<Permission> permissions;
}
@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<Permission> permissions; }

Enter fullscreen mode Exit fullscreen mode

@Entity
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// Ex: READ,WRITE,UPDATE
private String name;
}
@Entity
public class Permission {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    // Ex: READ,WRITE,UPDATE
    private String name;
}
@Entity public class Permission { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; // Ex: READ,WRITE,UPDATE private String name; }

Enter fullscreen mode Exit fullscreen mode

@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) {
List<GrantedAuthority> roles= user.getRoles().stream().filter(Objects::nonNull)
.map(role -> new SimpleGrantedAuthority(role.getName().name()))
.collect(Collectors.toList());
List<GrantedAuthority> 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;
}
}
@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) {
            List<GrantedAuthority> roles= user.getRoles().stream().filter(Objects::nonNull)
                    .map(role -> new SimpleGrantedAuthority(role.getName().name()))
                    .collect(Collectors.toList());

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

}
@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) { List<GrantedAuthority> roles= user.getRoles().stream().filter(Objects::nonNull) .map(role -> new SimpleGrantedAuthority(role.getName().name())) .collect(Collectors.toList()); List<GrantedAuthority> 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; } }

Enter fullscreen mode Exit fullscreen mode

Note – We’re using the Permission – Role terms here, but in Spring, these are slightly different. In Spring, our Permission is referred to as Role, and also as a (granted) authority – which is slightly confusing. Not a problem for the implementation of course, but definitely worth noting.

Also – these Spring Roles (our Permissions) need a prefix; by default, that prefix is “ROLE”, but it can be changed. We’re not using that prefix here, just to keep things simple, but keep in mind that if you’re not explicitly changing it, it’s going to be required.

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

@Configuration
@EnableGlobalMethodSecurity( prePostEnabled = true )
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler();
return expressionHandler;
}
}
@Configuration
@EnableGlobalMethodSecurity( prePostEnabled = true )
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler();
        return expressionHandler;
    }
}
@Configuration @EnableGlobalMethodSecurity( prePostEnabled = true ) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityExpressionHandler createExpressionHandler() { CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler(); return expressionHandler; } }

Enter fullscreen mode Exit fullscreen mode

We now have two new security expression available and ready to be used: hasPermission & hasAnyPermission.

hasPermission@param– String[] , checks if current user has ALL permissions.

hasAnyPermission@param– String[], checks if Current User has ANY permission.

Now similarly how Spring Security has inbuilt expressions like hasRole & hasAnyRole , we can check permissions as well.

Example :

@GetMapping
@PreAuthorize("hasRole('ADMIN') or hasPermission('READ')")
public ResponseEntity<List<User>> findAll() {
return new ResponseEntity<>(User.build(userService.getAllUsers()), HttpStatus.OK);
}
@GetMapping
@PreAuthorize("hasAnyRole('ADMIN') or hasAnyPermission('READ','UPDATE','WRITE')")
public ResponseEntity<List<User>> findAll() {
return new ResponseEntity<>(User.build(userService.getAllUsers()), HttpStatus.OK);
}
@GetMapping
@PreAuthorize("hasRole('ADMIN') or hasPermission('READ')")
    public ResponseEntity<List<User>> findAll() {
        return new ResponseEntity<>(User.build(userService.getAllUsers()), HttpStatus.OK);
    }

@GetMapping
@PreAuthorize("hasAnyRole('ADMIN') or hasAnyPermission('READ','UPDATE','WRITE')")
    public ResponseEntity<List<User>> findAll() {
        return new ResponseEntity<>(User.build(userService.getAllUsers()), HttpStatus.OK);
    }
@GetMapping @PreAuthorize("hasRole('ADMIN') or hasPermission('READ')") public ResponseEntity<List<User>> findAll() { return new ResponseEntity<>(User.build(userService.getAllUsers()), HttpStatus.OK); } @GetMapping @PreAuthorize("hasAnyRole('ADMIN') or hasAnyPermission('READ','UPDATE','WRITE')") public ResponseEntity<List<User>> findAll() { return new ResponseEntity<>(User.build(userService.getAllUsers()), HttpStatus.OK); }

Enter fullscreen mode Exit fullscreen mode

原文链接:Spring Boot : Custom Role – Permission Authorization using SpEL

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
Worrying does not empty tomorrow of its troubles, it empties today of its strength.
担忧不会清空明日的烦恼,它只会丧失今日的勇气
评论 抢沙发

请登录后发表评论

    暂无评论内容