We have tackled with the task of securing a service in a previous post. We had used classic Proxy pattern. In this case, we will tackle the same task with JDK proxies.

1. Task

Secure a given service according to current user’s privileges using JDK proxies. Unauthorized calls should be discarded.

2. Given

DataService is the interface that we want to secure. It includes annotations to specify authorization metadata.

public interface DataService {

    @Authorized(allowed = "read")
    void read();

    @Authorized(allowed = "update")
    void update();

    @Authorized(allowed = "delete")
    void delete();
}

 

Authorized annotation enables defining allowed privileges for an operation.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Authorized {

    String[] allowed();
}

 

DataServiceImpl is the default implementation.

public class DataServiceImpl implements DataService {

    @Override
    public void read() {
        System.out.println("Read the value...");
    }

    @Override
    public void update() {
        System.out.println("Edited the value...");
    }

    @Override
    public void delete() {
        System.out.println("Deleted the value...");
    }
}

 

And User class holds user’s privileges.

public class User {

    private List<String> privileges;

    public List<String> getPrivileges() {
        return privileges;
    }

    public void setPrivileges(List<String> privileges) {
        this.privileges = privileges;
    }
}

 

3. Solution

To create a Java dynamic proxy, an InvocationHandler should be defined. In this invocation handler, we are comparing user’s privileges with the ones defined on the method. If user has one of the listed privileges, operation commences.

public class DynamicDataServiceProxy implements InvocationHandler {

    private final DataService dataService;
    private final User user;

    public DynamicDataServiceProxy(DataService dataService, User user) {
        this.dataService = dataService;
        this.user = user;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Authorized authorized = method.getDeclaredAnnotation(Authorized.class);
        if (authorized != null) {
            String[] allowedPrivileges = authorized.allowed();
            if (isAllowed(allowedPrivileges)) {
                return method.invoke(dataService, args);
            } else {
                return null;
            }
        }

        return method.invoke(dataService, args);
    }

    private boolean isAllowed(String[] allowedPrivileges) {
        for (String allowedPrivilege : allowedPrivileges) {
            if (user.getPrivileges().contains(allowedPrivilege)) {
                return true;
            }
        }

        return false;
    }
}

 

A new Java Proxy is created using this InvocationHandler. Sample invocation is as follows:

public class ClientMain {

    public static void main(String[] args) {
        DataService dataService = new DataServiceImpl();
        System.out.println("Read-only user...");
        User readOnlyUser = new User();
        readOnlyUser.setPrivileges(Lists.newArrayList("read"));
        DataService dataServiceProxy = getDynamicProxy(dataService, readOnlyUser);
        dataServiceProxy.read();
        dataServiceProxy.update();
        dataServiceProxy.delete();

        System.out.println("Admin user...");
        User adminUser = new User();
        adminUser.setPrivileges(Lists.newArrayList("read", "update", "delete"));
        dataServiceProxy = getDynamicProxy(dataService, adminUser);
        dataServiceProxy.read();
        dataServiceProxy.update();
        dataServiceProxy.delete();
    }

    private static DataService getDynamicProxy(DataService dataService, User user) {
        return (DataService)
                Proxy.newProxyInstance(
                        dataService.getClass().getClassLoader(),
                        dataService.getClass().getInterfaces(),
                        new DynamicDataServiceProxy(dataService, user));
    }
}

Output:

Read-only user...
Read the value...
Admin user...
Read the value...
Edited the value...
Deleted the value...

 

4. Source Code

Source code can be found on Github.

Leave a Reply

Close Menu