Боевая система управления разрешениями Spring Security — 9. Настройка разрешений данных

Spring Boot

Каталог серий

Борьба с системой управления правами Spring Security - 1. Внедрение проекта и подготовка среды разработки

Боевая система управления разрешениями Spring Security — 2. Внедрение логов, интерфейсных документов и т.д.

Боевая система управления правами SpringSecurity — три, главная страница и реализация интерфейса

Боевая система управления правами SpringSecurity — четыре, интеграция SpringSecurity (включена)

Борьба с системой управления правами SpringSecurity — 5. Интеграция SpringSecurity (ниже)

Боевая система управления правами SpringSecurity — 6. SpringSecurity интегрирует JWT

Борьба с системой управления правами Spring Security — семь, разбираемся с некоторыми проблемами

Боевая система управления правами SpringSecurity - 8, AOP записывает пользовательские журналы, журналы исключений

Боевая система управления разрешениями Spring Security — 9. Настройка разрешений данных

предисловие

Часть этой главы — самая утомительная часть, которую я написал до сих пор.Усталость заключается в обработке логики.Поскольку есть много операций с несколькими таблицами, очень сложно разобраться с логикой этой части.

Это также привело меня к обнаружению некоторых проблем с именами в начале проектирования базы данных (которые будут рассмотрены позже).

Это как id в пользовательской таблице лучше использовать user_id, но я раньше использовал id. Какую проблему это вызовет?Например, вы должны хранить user_id в таблице ассоциации role_user, поэтому, когда вы пишете sql-операторы в ассоциации между этими двумя таблицами, вы должны постоянно преобразовывать id и user_id, а мозги дают У вас кружится голова . Кровавые уроки, все должны обратить внимание.

Эта статья просто для того, чтобы дать представление, содержание и логика слишком сложны, и исходные части должны быть изменены, и код уже не может быть размещен пошагово, как раньше.

Я надеюсь, что вы можете дать больше звездной поддержки, ваши лайки - мотивация для моего обслуживания.

giteeиgithubСинхронное обновление исходного кода

Прикрепил:Спецификация дизайна базы данных Alibaba

1. Что такое разрешение данных

В частности, дизайн разрешений можно разделить на функциональные разрешения и разрешения на данные. Функциональные разрешения относятся к тому, с какими интерфейсами может работать роль, а разрешения к данным относятся к тем данным, которые может получить роль.

С визуальной точки зрения, если сейчас есть компания, в компании есть много отделов вверх и вниз, и в отделе много сотрудников, а разрешение данных позволяет людям в определенном отделе получать только сотрудника. информацию своего отдела или назначенного отдела.

2. Создайте следующую таблицу

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述Это таблица должностей, таблица отделов, таблица ассоциаций должностей пользователей и таблица ассоциаций ролевых отделов.

Добавьте поле dept_id в таблицу my_user. Добавьте поле data_scpoe в таблицу my_role. Первое хорошо понятно, это идентификатор отдела, а второе представляет собой область разрешений данных роли. (Эту статью сложно написать, и содержание кода, представленное в статье, также сложно написать. Если вы хотите досконально разобраться, вам все равно придется посмотреть исходный код и сделать это самостоятельно, чтобы по-настоящему понять его.)

3. Эффект

Эффект
在这里插入图片描述 在这里插入图片描述
在这里插入图片描述 在这里插入图片描述
在这里插入图片描述 在这里插入图片描述

Эффект такой, а код больше не выкладывается.Эта часть логики немного сложная и объем кода большой.Можете сами проверить в исходниках.

4. Реализация

Вот лишь введение в то, как реализовать права доступа к данным, со ссылкой на метод реализации в проекте Ruoyi. Сначала мы определяем аннотацию

/**
 * 数据权限过滤注解
 * @author codermy
 * @createTime 2020/8/22
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataPermission {
    /**
     * 部门表的别名
     */
    public String deptAlias() default "";

    /**
     * 用户表的别名
     */
    public String userAlias() default "";
}

Определите другой класс аспекта

	/**
 * 数据过滤处理
 * @author codermy
 * @createTime 2020/8/22
 */
@Aspect
@Component
public class DataScopeAspect {

    @Autowired
    public RoleUserService roleUserService;
    /**
     * 全部数据权限
     */
    public static final String DATA_SCOPE_ALL = "1";

    /**
     * 自定数据权限
     */
    public static final String DATA_SCOPE_CUSTOM = "2";

    /**
     * 部门数据权限
     */
    public static final String DATA_SCOPE_DEPT = "3";

    /**
     * 部门及以下数据权限
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";

    /**
     * 仅本人数据权限
     */
    public static final String DATA_SCOPE_SELF = "5";

    /**
     * 数据权限过滤关键字
     */
    public static final String DATA_SCOPE = "dataScope";

    /**
     * 配置织入点
     */
    @Pointcut("@annotation(com.codermy.myspringsecurityplus.admin.annotation.DataPermission)")
    public void dataScopePointCut()
    {
    }

    @Before("dataScopePointCut()")
    public void doBefore(JoinPoint point) throws Throwable
    {
        handleDataScope(point);
    }

    protected void handleDataScope(final JoinPoint joinPoint)
    {
        // 获得注解
        DataPermission controllerDataScope = getAnnotationLog(joinPoint);
        if (controllerDataScope == null)
        {
            return;
        }
        // 获取当前的用户
        JwtUserDto currentUser = SecurityUtils.getCurrentUser();
        if (currentUser != null)
        {
            // 如果是超级管理员,则不过滤数据
            if (!currentUser.isAdmin())
            {
                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                        controllerDataScope.userAlias());
            }
        }
    }

    /**
     * 数据范围过滤
     *
     * @param joinPoint 切点
     * @param user 用户
     * @param deptAlias 部门别名
     * @param userAlias 用户别名
     */
    public static void dataScopeFilter(JoinPoint joinPoint, JwtUserDto user, String deptAlias, String userAlias)
    {
        StringBuilder sqlString = new StringBuilder();

        for (MyRole role : user.getRoleInfo())
        {
            String dataScope = role.getDataScope();
            if (DATA_SCOPE_ALL.equals(dataScope))
            {
                sqlString = new StringBuilder();
                break;
            }
            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
            {
                sqlString.append(StrUtil.format(
                        " OR {}.id IN ( SELECT dept_id FROM my_role_dept WHERE role_id = {} ) ", deptAlias,
                        role.getId()));
            }
            else if (DATA_SCOPE_DEPT.equals(dataScope))
            {
                sqlString.append(StrUtil.format(" OR {}.id = {} ", deptAlias, user.getMyUser().getDeptId()));
            }
            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
            {
                sqlString.append(StrUtil.format(
                        " OR {}.id IN ( SELECT id FROM my_dept WHERE id = {} or find_in_set( {} , ancestors ) )",
                        deptAlias, user.getMyUser().getDeptId(), user.getMyUser().getDeptId()));
            }
            else if (DATA_SCOPE_SELF.equals(dataScope))
            {
                if (StrUtil.isNotBlank(userAlias))
                {
                    sqlString.append(StrUtil.format(" OR {}.id = {} ", userAlias, user.getMyUser().getId()));
                }
                else
                {
                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
                    sqlString.append(" OR 1=0 ");
                }

            }
        }
        if (StrUtil.isNotBlank(sqlString.toString()))
        {
            BaseEntity baseEntity;
            for (int i = 0;i < joinPoint.getArgs().length ;i++ ){
                if (joinPoint.getArgs()[i] instanceof BaseEntity){
                    baseEntity= (BaseEntity) joinPoint.getArgs()[i];
                    baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
                }
            }

        }
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private DataPermission getAnnotationLog(JoinPoint joinPoint)
    {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null)
        {
            return method.getAnnotation(DataPermission.class);
        }
        return null;
    }
}

Объясните роль этого аспектного класса: Когда вы определяете эту аннотацию в методе ServiceImpl, она получает dataScope (то есть область данных) роли текущего пользователя, вошедшего в систему, затем сравнивает ее значения и сохраняет соответствующий оператор sql в параметрах baseEntity

Затем нам просто нужно добавить ${params.dataScope} в mapper.xml для разрешения на настройку данных.在这里插入图片描述Добавьте аннотацию @DataPermission(deptAlias="d", userAlias="u") к сервисному методу, который вызывает этот метод Просто在这里插入图片描述Здесь я добавил атрибут params в BaseEntity для хранения sql разрешений данных.在这里插入图片描述Итак, когда нам нужно вызвать этот sql, если разрешения не фильтруются, sql выглядит так

SELECT u.id, u.dept_id, u.user_name, u.password, u.nick_name
	, u.phone, u.email, u.status, u.create_time, u.update_time
FROM my_user u
	LEFT JOIN my_dept d ON u.dept_id = d.id
WHERE u.dept_id = ?
	OR u.dept_id IN (
		SELECT e.id
		FROM my_dept e
		WHERE FIND_IN_SET(?, ancestors)
	)
ORDER BY u.id

Если тип разрешения данных, который мы даем этой роли, является настраиваемым разрешением данных, sql будет следующим

SELECT u.id, u.dept_id, u.user_name, u.password, u.nick_name
	, u.phone, u.email, u.status, u.create_time, u.update_time
FROM my_user u
	LEFT JOIN my_dept d ON u.dept_id = d.id
WHERE (u.dept_id = ?
		OR u.dept_id IN (
			SELECT e.id
			FROM my_dept e
			WHERE FIND_IN_SET(?, ancestors)
		))
	AND d.id IN (
		SELECT dept_id
		FROM my_role_dept
		WHERE role_id = 2
	)
ORDER BY u.id

Затем, если мы дадим роли «обычный пользователь» следующие разрешения данных在这里插入图片描述Затем, когда мы входим в систему под пользователем этой роли, мы можем получить доступ только к информации о пользователе в соответствующем отделе.在这里插入图片描述 在这里插入图片描述Когда мы фильтруем оператор, когда добавляем аннотации к соответствующему методу отдела и sql, то эффект такой在这里插入图片描述 在这里插入图片描述 giteeиgithubСинхронное обновление исходного кода