springboot中添加数据级别的权限拦截(包装SQL)

使用mybatis拦截器和aop实现SQL包装

在需要数据权限拦截的controller层添加更新权限列表注解,在mapper层添加开启拦截器的注解

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

1.注解

import java.lang.annotation.*;

/**
 * appName权限注解,必须和updateUserPermissions注解一同使用
 * 此注解在mapper层使用
 *
 * @author ***
 */
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionsForAppName {
    boolean flag() default true;
}
/**
 * 用于更新用户权限信息
 * 在需要加appName权限的接口(Controller)上使用,此注解和PermissionsForAppName注解搭配使用
 *
 * @author ***
 */
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UpdateUserPermissions {
}

2.mybatis SQL拦截器

因为使用的是pagehelper-spring-boot-starter分页器,找了许久一直没找到自定义拦截器先于pagehelper执行的办法,所以就连同分页的count查询一起包装了
注意,我在这遇到一个坑,在mybatis拦截器中不能进行sql查询的动作,否则会报空指针异常;原因是SQL查询时有一个本地线程变量,在mybatis拦截器查询会导致线程变量被清空,包装过的SQL再执行时就空指针
我的解决办法是:在Controller层加注解,注解用aop的方式将权限列表数据查询出来放在静态变量里,进行SQL包装时直接拿过来用就行

import com.*.admin_log_web.common.annotation.PermissionsForAppName;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Properties;

/**
 * @author ***
 */

@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
@Component
public class AppNamePermissionsInterceptor implements Interceptor {
    private final String COUNT_STR = "_COUNT";

    public static List<String> appNameList = null;
    public static boolean isSuperAdmin = false;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //判断用户是否为超级管理员,为true直接放行
        if (isSuperAdmin) {
            return invocation.proceed();
        }
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        //先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        //id为执行的mapper方法的全路径名,如com.*.dao.UserMapper.insertUser
        String id = mappedStatement.getId();
        //sql语句类型 select、delete、insert、update
        String sqlCommandType = mappedStatement.getSqlCommandType().toString();
        BoundSql boundSql = statementHandler.getBoundSql();

        //获取到原始sql语句
        String sql = boundSql.getSql();
        String mSql = sql;

        //TODO 修改位置
        //注解逻辑判断  添加注解了才拦截
        Class<?> classType = Class.forName(mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf(".")));
        //获取接口方法名
        String methodName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1, mappedStatement.getId().length());
        boolean isPageCount = false;
        //判断是否是分页查询,用于检查是否是加过权限注解
        int exist = methodName.indexOf(COUNT_STR);
        if (exist != -1) {
            //检查mName尾部是否是 “_COUNT”
            String temp = methodName.substring(methodName.length() - COUNT_STR.length());
            if (COUNT_STR.equals(temp)) {
                isPageCount = true;
                methodName = methodName.substring(0, methodName.length() - COUNT_STR.length());
            }
        }
        for (Method method : classType.getDeclaredMethods()) {
            if (method.isAnnotationPresent(PermissionsForAppName.class) && methodName.equals(method.getName())) {
                PermissionsForAppName interceptorAnnotation = method.getAnnotation(PermissionsForAppName.class);
                if (interceptorAnnotation.flag()) {
                    if (isPageCount) {
                        mSql = pageSqlRemould(sql);
                    } else {
                        mSql = sqlRemould(sql);
                    }
                }
            }
        }

        //通过反射修改sql语句
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, mSql);
        return invocation.proceed();

    }

    /**
     * 改造sql,添加权限
     *
     * @param sql
     * @return
     */
    private String sqlRemould(String sql) {
        int limitIndex = sql.indexOf("LIMIT ?");
        String limitStr = sql.substring(sql.indexOf("LIMIT"));
        if (limitIndex != -1) {
            sql = sql.substring(0, limitIndex);
        }
        sql = "SELECT * FROM (" + sql + ") as temp where app_name in (" + joinAppName(appNameList) + ") "+limitStr;
        return sql;
    }

    /**
     * 改造分页sql(count),添加权限
     *
     * @param sql
     * @return
     */
    private String pageSqlRemould(String sql) {
        final String COUNT_SQL = "SELECT count(0) FROM";
        sql = sql.substring(COUNT_SQL.length());
        sql = COUNT_SQL + "(select app_name from " + sql + ")as temp where app_name in (" + joinAppName(appNameList) + ")";
        return sql;
    }


    /**
     * 拼接sql函数in的参数(appName)
     *
     * @param appNameList
     * @return
     */
    private String joinAppName(List<String> appNameList) {
        String result = "";
        if (appNameList == null || appNameList.size() == 0) {
            return "''";
        }
        for (String appName : appNameList) {
            result += "'" + appName + "'" + ",";
        }
        return result.substring(0, result.lastIndexOf(","));
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }

    }

    @Override
    public void setProperties(Properties properties) {

    }
}

3.以AOP的方式更新用户权限列表

import com.vivo.admin_log_web.common.VivoFilter.GetSsoUserUtil;
import com.vivo.admin_log_web.service.impl.AdminAppNameServiceImpl;
import com.vivo.admin_log_web.service.impl.AdminUserServiceImpl;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * 更新访问用户权限和PermissionsForAppName注解搭配使用
 *
 * @author 高金山
 */
@Aspect
@Component
public class UpdateUserPermissions {
    @Autowired
    private AdminAppNameServiceImpl appNameService;
    @Autowired
    private AdminUserServiceImpl userService;
	//括号中填入注解所在的全路径
    @Before("@annotation(com.*.admin_log_web.common.annotation.UpdateUserPermissions)")
    public void updateUserPermission(){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //判断用户是否为超级管理员,并更新到appName权限拦截器
        AppNamePermissionsInterceptor.isSuperAdmin = userService.getUserComment(GetSsoUserUtil.getCurrentUserId(request));
        //更新用户appName权限列表到appName权限拦截器
        AppNamePermissionsInterceptor.appNameList = appNameService.getAppNameByUserId(GetSsoUserUtil.getCurrentUserId(request));;
    }

}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Mybatis-plus提供了加密拦截器`MybatisPlusInterceptor`,可以在查询和修改时对敏感数据进行加密和解密操作。以下是在Spring Boot配置Mybatis-plus加密拦截器的步骤: 1. 首先,需要在pom.xml文件添加mybatis-plus-boot-starter依赖: ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> ``` 2. 创建一个`MetaObjectHandler`实现类,该类可以在插入和更新时自动填充公共字段,例如创建时间、修改时间等。在该类,需要实现`insertFill`和`updateFill`方法,并在需要自动填充的字段上添加`@TableField(fill = FieldFill.INSERT)`和`@TableField(fill = FieldFill.UPDATE)`注解。 ```java @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class); } } ``` 3. 创建一个加密拦截器`EncryptInterceptor`,该拦截器可以在查询和修改时对敏感数据进行加密和解密操作。在该拦截,需要实现`intercept`方法,并在需要加密的字段上添加`@TableField(fill = FieldFill.INSERT_UPDATE)`注解。 ```java @Component public class EncryptInterceptor implements Interceptor { private static final String AES_KEY = "1234567890123456"; private static final String CHARSET = "UTF-8"; @Override public Object intercept(Invocation invocation) throws Throwable { Object parameter = invocation.getArgs()[1]; if (parameter instanceof MappedStatement && ((MappedStatement) parameter).getId().contains("update")) { MetaObject metaObject = SystemMetaObject.forObject(parameter); String[] propertyNames = metaObject.getSetterNames(); for (String propertyName : propertyNames) { if (metaObject.hasSetter(propertyName) && metaObject.hasGetter(propertyName)) { TableField tableField = metaObject.getOriginalObject().getClass().getDeclaredField(propertyName) .getAnnotation(TableField.class); if (tableField != null && tableField.fill() == FieldFill.UPDATE) { Object value = metaObject.getValue(propertyName); if (value != null && value instanceof String) { metaObject.setValue(propertyName, encrypt((String) value)); } } } } } return invocation.proceed(); } private String encrypt(String content) throws Exception { KeyGenerator keygen = KeyGenerator.getInstance("AES"); SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); random.setSeed(AES_KEY.getBytes()); keygen.init(128, random); SecretKey secretKey = keygen.generateKey(); byte[] enCodeFormat = secretKey.getEncoded(); SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES"); Cipher cipher = Cipher.getInstance("AES"); byte[] byteContent = content.getBytes(CHARSET); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] byteRresult = cipher.doFinal(byteContent); return Base64.encodeBase64String(byteRresult); } } ``` 4. 在配置文件配置Mybatis-plus的加密拦截器和自动填充功能: ```yaml mybatis-plus: global-config: meta-object-handler: com.example.demo.handler.MyMetaObjectHandler configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl interceptors: - com.example.demo.interceptor.EncryptInterceptor ``` 5. 在需要加密的字段上添加`@TableField(fill = FieldFill.INSERT_UPDATE)`注解,例如: ```java @TableField(fill = FieldFill.INSERT_UPDATE) private String password; ``` 通过以上步骤,就可以在Spring Boot配置Mybatis-plus加密拦截器了。注意,在实际应用,需要对加密算法、加密密钥等进行更加严格的保护。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值