According to the source code analysis: DataScopeAspect realizes the control of the data range

Article directory

  • source code
  • scenes to be used
    • interface operation
    • SysDeptServiceImpl
    • SysUserServiceImpl
    • SysUserMapper
    • DataScope definition
  • code analysis
    • @Aspect and @Component
    • Different Data Permission Types
    • @BeforeNotification
    • Methods for working with data ranges

Source code

@Aspect
@Component
public class DataScopeAspect
{<!-- -->
    /**
     * Full data access
     */
    public static final String DATA_SCOPE_ALL = "1";

    /**
     * Custom data permissions
     */
    public static final String DATA_SCOPE_CUSTOM = "2";

    /**
     * Department data permissions
     */
    public static final String DATA_SCOPE_DEPT = "3";

    /**
     * Department and the following data permissions
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";

    /**
     * Only personal data permission
     */
    public static final String DATA_SCOPE_SELF = "5";

    /**
     * Data permission filter keywords
     */
    public static final String DATA_SCOPE = "dataScope";

    @Before("@annotation(controllerDataScope)")
    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
    {<!-- -->
        clearDataScope(point);
        handleDataScope(point, controllerDataScope);
    }

    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
    {<!-- -->
        // get the current user
        LoginUser loginUser = SecurityUtils. getLoginUser();
        if (StringUtils. isNotNull(loginUser))
        {<!-- -->
            SysUser currentUser = loginUser. getUser();
            // If it is a super administrator, the data will not be filtered
            if (StringUtils.isNotNull(currentUser) & amp; & amp; !currentUser.isAdmin())
            {<!-- -->
                dataScopeFilter(joinPoint, currentUser, controllerDataScope. deptAlias(),
                        controllerDataScope. userAlias());
            }
        }
    }

    /**
     * Data range filtering
     *
     * @param joinPoint cut point
     * @param user user
     * @param userAlias alias
     */
    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
    {<!-- -->
        StringBuilder sqlString = new StringBuilder();

        for (SysRole role : user. getRoles())
        {<!-- -->
            String dataScope = role. getDataScope();
            if (DATA_SCOPE_ALL. equals(dataScope))
            {<!-- -->
                sqlString = new StringBuilder();
                break;
            }
            else if (DATA_SCOPE_CUSTOM. equals(dataScope))
            {<!-- -->
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                        role. getRoleId()));
            }
            else if (DATA_SCOPE_DEPT. equals(dataScope))
            {<!-- -->
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
            }
            else if (DATA_SCOPE_DEPT_AND_CHILD. equals(dataScope))
            {<!-- -->
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                        deptAlias, user.getDeptId(), user.getDeptId()));
            }
            else if (DATA_SCOPE_SELF. equals(dataScope))
            {<!-- -->
                if (StringUtils.isNotBlank(userAlias))
                {<!-- -->
                    sqlString.append(StringUtils.format("OR {}.user_id = {} ", userAlias, user.getUserId()));
                }
                else
                {<!-- -->
                    // The data permission is only for me and there is no userAlias alias without querying any data
                    sqlString. append(" OR 1=0 ");
                }
            }
        }

        if (StringUtils. isNotBlank(sqlString. toString()))
        {<!-- -->
            Object params = joinPoint. getArgs()[0];
            if (StringUtils.isNotNull(params) & amp; & amp; params instanceof BaseEntity)
            {<!-- -->
                BaseEntity baseEntity = (BaseEntity) params;
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }
    }

    /**
     * Clear the params.dataScope parameter before splicing permission sql to prevent injection
     */
    private void clearDataScope(final JoinPoint joinPoint)
    {<!-- -->
        Object params = joinPoint. getArgs()[0];
        if (StringUtils.isNotNull(params) & amp; & amp; params instanceof BaseEntity)
        {<!-- -->
            BaseEntity baseEntity = (BaseEntity) params;
            baseEntity.getParams().put(DATA_SCOPE, "");
        }
    }
}

Usage scenario

Interface operation

Role Management>Click More>Modify Data Permissions

SysDeptServiceImpl

@Override
    @DataScope(deptAlias = "d")
    public List<SysDept> selectDeptList(SysDept dept)
    {<!-- -->
        return deptMapper. selectDeptList(dept);
    }

SysUserServiceImpl

@Override
    @DataScope(deptAlias = "d", userAlias = "u")
    public List<SysUser> selectUserList(SysUser user)
    {<!-- -->
        return userMapper. selectUserList(user);
    }

SysUserMapper

DataScope definition

@Target(ElementType. METHOD)
@Retention(RetentionPolicy. RUNTIME)
@Documented
public @interface DataScope
{<!-- -->
    /**
     * Alias of department table
     */
    public String deptAlias() default "";

    /**
     * Alias for user table
     */
    public String userAlias() default "";
}

Code Analysis

This code is an aspect class named DataScopeAspect, which is used to control the data scope. It uses Spring’s AOP (Aspect-Oriented Programming) to intercept methods annotated with @DataScope and perform data scope processing before method execution.

The following is an analysis of the code:

@Aspect and @Component

This class is annotated as @Aspect and @Component, indicating that it is an aspect class and will be managed by Spring.
@Aspect and @Component are annotations in the Spring framework, which are used to declare aspects and components in the application, and are managed and used by Spring.

  1. @Aspect annotation:

    • The @Aspect annotation is used to identify a class as an aspect class, that is, a class that contains aspect logic. It tells Spring that this class is an aspect and needs to be processed by the aspect.
    • When Spring scans a class annotated by @Aspect, it will recognize it as an aspect, and implement the aspect behavior according to the definition of the aspect at runtime.
  2. @Component annotation:

    • The @Component annotation is a common annotation in the Spring framework and is used to identify a class as a component, indicating that it will be managed by Spring.
    • The @Component annotation is usually used to bring ordinary Java classes into the Spring context so that these classes can be autowired and used.
    • When Spring scans a class annotated by @Component, it will instantiate it and manage it as a Spring Bean.

By applying @Aspect and @Component annotations to corresponding classes, Spring can identify and process these classes, thereby realizing the functions of AOP and component management. At runtime, Spring creates and maintains these aspects and components, and applies features such as aspect logic and dependency injection when needed.

Different data permission types

It defines some constants, such as DATA_SCOPE_ALL, DATA_SCOPE_CUSTOM, etc., used to represent different types of data permissions.

/**
     * Full data access
     */
    public static final String DATA_SCOPE_ALL = "1";

    /**
     * Custom data permissions
     */
    public static final String DATA_SCOPE_CUSTOM = "2";

    /**
     * Department data permissions
     */
    public static final String DATA_SCOPE_DEPT = "3";

    /**
     * Department and the following data permissions
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";

    /**
     * Only personal data permission
     */
    public static final String DATA_SCOPE_SELF = "5";

@Before notification

A @Before notification is defined in the aspect class to execute related logic before the method annotated with @DataScope is executed.
The doBefore method is the implementation method of @Before notification, which receives JoinPoint and DataScope objects as parameters.

/**
     * Intercept methods annotated with @DataScope
     * It receives JoinPoint and DataScope objects as parameters. Intercept methods annotated with @DataScope
     * DataScope is an annotation class
     * @param point
     * @param controllerDataScope
     * @throws Throwable
     */
    @Before("@annotation(controllerDataScope)")
    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
    {<!-- -->
        clearDataScope(point);
        handleDataScope(point, controllerDataScope);
    }

Methods for handling data ranges

  • The handleDataScope method is used to handle the data scope and filter according to the role and data permissions of the currently logged in user.
  • The dataScopeFilter method is the actual data scope filtering logic. According to the user’s role, different SQL statements are constructed for data filtering.
  • The clearDataScope method is used to clear the params.dataScope parameter to prevent injection attacks.
  • In the dataScopeFilter method, according to the dataScope value of the user’s role, different SQL statements are constructed, and finally the conditions of the data scope are stored in the params attribute of the BaseEntity object.

The function of this aspect class is to filter the data before the specific method is executed according to the user’s role and data permissions, so as to ensure that the user can only access the data with permissions.