天天看点

mybatis实现权限拦截器(一)

1. mybais拦截器

最近看公司的代码,发现mapper里面方法上有的方法添加了

@DAtaPermission

注解,查遍了也不知道是哪个框架里面的东西。问了带我的师傅,人给我回复 Intercept。下班后默默的查了起来,才知道是mybatis里面的插件功能。故查了查资料,看了看别的老哥写的东西 ,感谢感谢 下面的几篇文章

  1. 基于mybatis拦截器实现数据权限
  2. mybatis笔记-MappedStatement
  3. MyBatis 插件之拦截器(Interceptor)

    感谢感谢。如果想看详细的关于mybatis拦截器的工作原理和各个组件的功能,请看上面的这几篇文章。

2. 实现思路

我这里是做一个简单的权限管理 所有我在注解里面直接写好了角色,正常的应该是 在数据库里面查到。也可以根据返回的权限的sql和之前的sql做关联查询(加强sql)

  • 定义权限注解
  • 自定义拦截器
    • 得到标注注解的方法,
    • 根据注解里面的角色
    • 在mapper里面传递当前登录的用户
    • 判断是不是 相等,相等就放行,不相等就抛出异常(如果在springMvc中就统一处理这个异常,然后统一返回)

3. 代码

可能 bia ji 给一大堆代码,有点迷糊。慢慢看,自己做个例子就好了,我也是参考上面的几个例子的,要是不行debug运行,一步一步的看。遇到不知道的对象 就去查,不要猜

实体类(这个实体类就是模拟的,不要当真)

  1. 对应数据里里面的对象
package mybatisInterceptTest.eneity;

import java.io.Serializable;
import java.math.BigDecimal;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;


@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class TTest implements Serializable {
    private Integer id;

    private String name;

    private Double age;

    private static final long serialVersionUID = 1L;
}
           

2 数据权限对象。模拟当前登录的用户的信息

package mybatisInterceptTest.eneity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * @program: jdk8Test
 * @description:
 * @author: liuchen
 **/
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class DataAuth {
    private Integer userId;
    private String userRole;
}
           

自定义权限注解

package mybatisInterceptTest.intercept;

import java.lang.annotation.*;

/**
 * @program: jdk8Test
 * @description:
 * @author: liuchen
 **/
@Target({ElementType.METHOD})  //表示注解的使用范围
@Retention(RetentionPolicy.RUNTIME) //注解的保存时间
@Documented	//文档显示
@Inherited  //可以继承
public @interface MyIntercept {
    /*
        角色
     */
    public String role() default "";
}

           

异常类

package mybatisInterceptTest.exception;

import lombok.AllArgsConstructor;

/**
 * @program: jdk8Test
 * @description:
 * @author: liuchen
 **/
public class DataAuthException extends RuntimeException {
    public DataAuthException() {
    }

    public DataAuthException(String message) {
        super(message);
    }

    public DataAuthException(String message, Throwable cause) {
        super(message, cause);
    }

    public DataAuthException(Throwable cause) {
        super(cause);
    }

    public DataAuthException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
           

Mapper接口

package mybatisInterceptTest.mapper;

import mybatisInterceptTest.eneity.DataAuth;
import mybatisInterceptTest.eneity.TTest;
import mybatisInterceptTest.intercept.MyIntercept;
import org.apache.ibatis.annotations.Param;

public interface TTestDao  {

    int deleteByPrimaryKey(Integer id);

    int insert(TTest record);

    @MyIntercept(role = "admin") //表示这个访问这个方法需要 admin 角色
    TTest selectByPrimaryKey(@Param("id") Integer id, @Param("dataAuth") DataAuth dataAuth);

}
           

xml文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatisInterceptTest.mapper.TTestDao">
    <resultMap id="BaseResultMap" type="mybatisInterceptTest.eneity.TTest">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="age" jdbcType="DECIMAL" property="age" />
    </resultMap>

    <sql id="Base_Column_List">
    id, `name`, age
  </sql>
    <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultType="TTest">
        select
        <include refid="Base_Column_List" />
        from t_test
        where id = #{id,jdbcType=INTEGER}
    </select>
    <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
    delete from t_test
    where id = #{id,jdbcType=INTEGER}
  </delete>

    <insert id="insert" keyColumn="id" keyProperty="id" parameterType="mybatisInterceptTest.eneity.TTest" useGeneratedKeys="true">
    insert into t_test (`name`, age)
    values (#{name,jdbcType=VARCHAR}, #{age,jdbcType=DECIMAL})
  </insert>

</mapper>
           

mybatis 配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 根标签 -->
<configuration>

    <properties>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/t_test?useUnicode=true&amp;characterEncoding=utf-8&amp;allowMultiQueries=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </properties>

    <settings>
        <setting name="logPrefix" value="dao." />
    </settings>

    <typeAliases>
        <!--type:实体类的全路径。alias:别名,通常首字母大写-->
        <!--<typeAlias type="com.zpc.mybatis.pojo.User" alias="User"/>-->
        <package name="mybatisInterceptTest.eneity"/>
    </typeAliases>

    <plugins>
        <plugin interceptor="mybatisInterceptTest.intercept.AuthLcpInterceptor">
        </plugin>
    </plugins>


    <!-- 环境,可以配置多个,default:指定采用哪个环境 -->
    <environments default="test">
        <!-- id:唯一标识 -->
        <environment id="test">
            <!-- 事务管理器,JDBC类型的事务管理器 -->
            <transactionManager type="JDBC" />
            <!-- 数据源,池类型的数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/t_test" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
        <environment id="development">
            <!-- 事务管理器,JDBC类型的事务管理器 -->
            <transactionManager type="JDBC" />
            <!-- 数据源,池类型的数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}" /> <!-- 配置了properties,所以可以直接引用 -->
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/TTestDao.xml" />
    </mappers>
</configuration>
           

pom.xml文件

<dependencies> 
 
        <!--java connection-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>
        <!-- mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.8</version>
        </dependency>


        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>


        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
</dependencies>
           

lo4j配置

log4j.rootLogger=DEBUG,INFO,A1
log4j.logger.org.apache=DEBUG
mybatisInterceptTest.eneity=DEBUG
mybatisInterceptTest.intercept=INFO
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n


           

自定义的拦截器

这里面写的其实很简单,拦截执行的sql,得到方法上面的注解,看有没有权限,有就放行,没有就抛出异常。

package mybatisInterceptTest.intercept;

import lombok.extern.slf4j.Slf4j;
import mybatisInterceptTest.eneity.DataAuth;
import mybatisInterceptTest.exception.DataAuthException;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Properties;

/**
 * @program: jdk8Test
 * @description:
 * @author: liuchen
 **/
@Slf4j
@Intercepts({@Signature(method = "query", type = Executor.class,
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
        })})
public class AuthLcpInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = null;
        /**
         * 思路:
         *  1:找到标注注解的方法
         *  2: 看当前的用户有没有权限
         *  3: 有,放行。没有,就抛出异常
         */
        //[mappedStatement,参数,rowBounds]
        Object[] args = invocation.getArgs();
        //得到mapperStatement(xml文件中的select ,update,delete,insert 对应的标签)
        MappedStatement mappedStatement = (MappedStatement) args[0];
        MyIntercept myIntercept = getAnnotationMethod(mappedStatement);
        if (myIntercept == null) {
            return invocation.proceed();
        }
        //得到角色
        String role = myIntercept.role();
        //得到参数也就是mapper接口自己写的方法里面的参数,
        MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) args[1];
        //得到dataAuth
        DataAuth dataAuth = (DataAuth) paramMap.get("dataAuth");
        //用户id
        Integer userId = dataAuth.getUserId();
        //用户角色
        String userRole = dataAuth.getUserRole();
        if (!role.equals(userRole)) {
            throw new DataAuthException("当前用户没有权限");
        }


        //得到标注注解的方法
        result = invocation.proceed();
        return result;
    }

    /**
     * 得到标注注解的方法,
     *
     * @return 返回注解
     */
    private MyIntercept getAnnotationMethod(MappedStatement statement) throws ClassNotFoundException {
        // namespace + xml文件中对应的id
        String id = statement.getId();
        String className = id.substring(0, id.lastIndexOf("."));
        String methodName = id.substring(id.lastIndexOf(".") + 1, id.length());
        Class<?> aClass = Class.forName(className);
        Method[] declaredMethods = aClass.getDeclaredMethods();
        MyIntercept annotation = null;
        for (Method declaredMethod : declaredMethods) {
            declaredMethod.setAccessible(true);
            //方法名相同,并且注解是myintercept
            if (methodName.equals(declaredMethod.getName()) && declaredMethod.isAnnotationPresent(MyIntercept.class)) {
                log.info("------> {},{},{},{}", declaredMethod.getModifiers(), declaredMethod.getName(), declaredMethod.getParameters(), declaredMethod.getReturnType());
                annotation = declaredMethod.getAnnotation(MyIntercept.class);
            }
        }
        return annotation;
    }

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

    @Override
    public void setProperties(Properties properties) {

    }

    private String getUserRoleById(Integer id) {
        return "admin";
    }
}

           

测试

package mybatisInterceptTest.mapper;

import lombok.extern.slf4j.Slf4j;
import mybatisInterceptTest.eneity.DataAuth;
import mybatisInterceptTest.eneity.TTest;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

import java.io.InputStream;

@Slf4j
public class TTestDaoTest {

    public TTestDao testDao;
    public SqlSession sqlSession;

    @Before
    public void setUp() throws Exception {
        // mybatis-config.xml
        String resource = "mybatis-config.xml";
        // 读取配置文件
        InputStream is = Resources.getResourceAsStream(resource);
        // 构建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        // 获取sqlSession
        sqlSession = sqlSessionFactory.openSession();
        this.testDao = sqlSession.getMapper(TTestDao.class);
    }

    @Test
    public void deleteByPrimaryKey() {
        log.info("{}",testDao.deleteByPrimaryKey(1));
    }

    @Test
    public void insert() {
        TTest aaa = new TTest(null, "bbb", 12.345);
        log.info("{}",testDao.insert(aaa));
        sqlSession.commit();
    }

    /**
     * 这个是主测试方法,上面的俩@Test 没意思
     */
    @Test
    public void selectByPrimaryKey() {
        log.info("{}",testDao.selectByPrimaryKey(3,new DataAuth(1,"admin")));
    }
}
           

结果

如果是 admin 就没有问题

mybatis实现权限拦截器(一)

如果是 customer

mybatis实现权限拦截器(一)

结果

mybatis实现权限拦截器(一)

如果是SpringMVC的话,异常统一处理,返回前端就好