background

The requirements are as follows: the project adopts the architecture of the separation of the front and back ends, and uses RESTFUL apis. The related requests of the same resource have the same URL, but the HTTP method is different.

If you want to allow a person to access a resource, but not create it, a URL-based permission design is clearly not going to work.

When I looked at the permission controls that can be method-based, I thought this was the best solution. The problem, however, is that a method may trigger different permissions for different inputs. For example, A user may have access to directory A, but not to directory B. Both actions call the same Controller method, but look at different directories based on the input parameter.

The default hasAuthority and hasRole expressions are inadequate because they can only judge a hard-coded permission or role string. So we need to use custom expressions to highly customize permission judgments to meet our requirements. Here’s how to use it.

The sample

We will create an expression for canRead. If the entry parameter is “A”, the system determines whether the current user has the permission to view A. When the entry parameter is “B”, the system determines whether the current user has the permission to view B.

configuration

To create custom expressions, we first need to implement root expressions:

public class CustomMethodSecurityExpressionRoot
        extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    private Object filterObject;
    private Object returnObject;

    public CustomMethodSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    // Our custom expression
    public boolean canRead(String foo) {
        if (foo.equals("A") &&!this.hasAuthority("CAN_READ_A")) {
            return false;
        }

        if (foo.equals("B") &&!this.hasAuthority("CAN_READ_B")) {
            return false;
        }
        
        return true;
    }

    @Override
    public Object getFilterObject(a) {
        return this.filterObject;
    }

    @Override
    public Object getReturnObject(a) {
        return this.returnObject;
    }

    @Override
    public Object getThis(a) {
        return this;
    }

    @Override
    public void setFilterObject(Object obj) {
        this.filterObject = obj;
    }

    @Override
    public void setReturnObject(Object obj) {
        this.returnObject = obj; }}Copy the code

Next, we need to put CustomMethodSecurityExpressionRoot into expression within the processor:

public class CustomMethodSecurityExpressionHandler 
  extends DefaultMethodSecurityExpressionHandler {
    private AuthenticationTrustResolver trustResolver = 
      new AuthenticationTrustResolverImpl();
 
    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot( Authentication authentication, MethodInvocation invocation) {
        CustomMethodSecurityExpressionRoot root = 
          new CustomMethodSecurityExpressionRoot(authentication);
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setTrustResolver(this.trustResolver);
        root.setRoleHierarchy(getRoleHierarchy());
        returnroot; }}Copy the code

You then need to write CustomMethodSecurityExpressionHandler method inside the security configuration:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler(a) {
        CustomMethodSecurityExpressionHandler expressionHandler = 
          new CustomMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator());
        returnexpressionHandler; }}Copy the code

use

@PreAuthorize("canRead(#foo)")
@GetMapping("/")
public Foo getFoo(@RequestParam("foo") String foo) {
    return fooService.findAll(foo);
}
Copy the code

If the user accesses A but does not have CAN_READ_A permission, the interface returns 403.