使用shiro+aop实现权限控制

对于一个后台管理系统,权限访问控制非常重要,而实现权限控制的方法有很多

  • 过滤器
  • 拦截器
  • AOP
  • 微服务网关

这篇文章主要介绍如何通过aop整合shiro权限框架来实现权限访问控制。

第一步:准备数据库

实现权限管理一般需要5张表:用户表、角色表、权限表、用户-角色关系表、角色-权限关系表。

1、管理员用户表

-- ----------------------------
-- Table structure for admin
-- ----------------------------
DROP TABLE IF EXISTS `admin`;
CREATE TABLE `admin`  (
  `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '姓名',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '12345' COMMENT '密码',
  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号',
  `gender` tinyint UNSIGNED NOT NULL COMMENT '性别',
  `is_enable` tinyint NOT NULL,
  `last_login_time` datetime NULL DEFAULT NULL COMMENT '上一次登录时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '管理员表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of admin
-- ----------------------------
INSERT INTO `admin` VALUES ('2023', '系统管理员', 'system', '12345', '17777777777', 2, 1, '2022-11-18 10:45:57');
INSERT INTO `admin` VALUES ('mhxy1218', '沐雨橙风ιε', 'mumu', 'mhxy1218', '16782209635', 1, 1, '2022-12-31 19:38:43');

2、角色表

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '名称',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
  `sort` int UNSIGNED NULL DEFAULT 0 COMMENT '自定义排序序号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统角色表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, '超级管理员', '最高权限,拥有系统所有权限', 0);
INSERT INTO `role` VALUES (2, '系统管理员', '拥有系统设置相关权限', 100);

3、权限表

权限表的数据通过扫描controller接口信息得到,文章后面会介绍怎么获取。

-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '名称',
  `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '值',
  `type` tinyint UNSIGNED NOT NULL COMMENT '权限类型(目录/菜单)',
  `parent_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '父级权限id',
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '接口路径',
  `method` tinyint UNSIGNED NULL DEFAULT 0 COMMENT '请求方式(0-get;1-post)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统权限表' ROW_FORMAT = DYNAMIC;

4、用户-角色表

-- ----------------------------
-- Table structure for admin_role
-- ----------------------------
DROP TABLE IF EXISTS `admin_role`;
CREATE TABLE `admin_role`  (
  `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
  `admin_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '管理员id',
  `role_id` int UNSIGNED NOT NULL COMMENT '角色id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '管理员-角色关系表表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of admin_role
-- ----------------------------
INSERT INTO `admin_role` VALUES (1, 'mhxy1218', 1);
INSERT INTO `admin_role` VALUES (2, '2023', 2);

5、角色-权限表

-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission`  (
  `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
  `role_id` int UNSIGNED NOT NULL COMMENT '角色id',
  `permission_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '权限id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1281 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色-权限关联表' ROW_FORMAT = DYNAMIC;

二:shiro的相关配置

1、pom.xml中添加依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>

项目完整配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>cn.edu.sgu.www</groupId>
    <artifactId>mhxysy</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <mysql.version>8.0.28</mysql.version>
        <druid.version>1.1.21</druid.version>
        <lombok.version>1.18.22</lombok.version>
        <mybatis-boot.version>2.2.2</mybatis-boot.version>
        <mybatis-plus.version>3.5.1</mybatis-plus.version>
        <shiro.version>1.3.2</shiro.version>
        <fastjson.version>2.0.8</fastjson.version>
        <knife4j.version>2.0.9</knife4j.version>
        <flyway.version>5.2.1</flyway.version>
        <easyexcell.version>2.1.1</easyexcell.version>
        <nacos.version>2.2.0.RELEASE</nacos.version>
        <feign.version>2.2.9.RELEASE</feign.version>
        <sentinel.version>2.2.9.RELEASE</sentinel.version>
    </properties>

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

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

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

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

        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-boot.version}</version>
        </dependency>

        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>

        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

        <!--flyway-->
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
            <version>${flyway.version}</version>
        </dependency>

        <!--knife4j-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>

        <!--easy-excel-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>${easyexcell.version}</version>
        </dependency>

        <!--nacos注册中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>${nacos.version}</version>
        </dependency>

        <!--nacos配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>${nacos.version}</version>
        </dependency>

        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>${feign.version}</version>
        </dependency>

        <!--sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>${sentinel.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2、创建Redis工具类

RedisUtils.java

package cn.edu.sgu.www.mhxysy.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Set;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class RedisUtils {
    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 保存数据到Redis
     * @param key 键
     * @param value 值
     */
    public void save(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
        redisTemplate.expire(key, 7, TimeUnit.DAYS);
    }

    /**
     * 通过用户名获取用户的权限信息
     * @param key 键
     */
    public Object get(String key) {
        log.debug("从redis中获取key{}...", key);

        if (hasKey(key)) {
            return redisTemplate.opsForValue().get(key);
        } else {
            return null;
        }
    }

    /**
     * 通过用户名删除用户的权限信息
     * @param key 用户名
     */
    public void remove(String key) {
        if (hasKey(key)) {
            redisTemplate.delete(key);
            log.debug("从redis中删除key{}...", key);
        }
    }

    /**
     * 删除全部用户的权限信息
     */
    public void removeAll() {
        Set<String> keys = redisTemplate.keys("*");

        if (keys != null && !keys.isEmpty()) {
            redisTemplate.delete(keys);
        }

        log.debug("从redis中删除全部key...");
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return Boolean
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

}

RedisRepository.java:只用于保存用户权限信息

package cn.edu.sgu.www.mhxysy.redis;

import cn.edu.sgu.www.mhxysy.consts.RedisKeyPrefixConst;
import cn.edu.sgu.www.mhxysy.feign.FeignService;
import cn.edu.sgu.www.mhxysy.util.StringUtils;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class RedisRepository extends RedisUtils {
    private static final String SUFFIX = RedisKeyPrefixConst.PREFIX_ROLE_PERMISSION;

    private final FeignService feignService;
    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public RedisRepository(FeignService feignService, RedisTemplate<String, Object> redisTemplate) {
        super(redisTemplate);

        this.feignService = feignService;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 查询用户的权限信息,并保存到redis
     * @param username 用户名
     */
    public void save(String username) {
        // 查询用户的权限
        List<String> permissions = feignService.selectPermissionsByUsername(username);
        // 拼接得到key
        String key = SUFFIX + username;

        redisTemplate.opsForValue().set(key, JSON.toJSONString(permissions));
        redisTemplate.expire(key, 7, TimeUnit.DAYS);

        log.debug("用户{}的权限保存到了redis中", username);
    }

    /**
     * 通过用户名获取用户的权限信息
     * @param username 用户名
     */
    @Override
    public List<String> get(String username) {
        String key = SUFFIX + username;
        log.info("从redis中获取用户{}的权限", username);

        if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
            String permissions = (String) redisTemplate.opsForValue().get(key);

            if (StringUtils.isNotEmpty(permissions)) {
                Object object = JSON.parse(permissions);

                return object != null ? (List<String>) object : null;
            }

            return null;
        } else {
            save(username);

            return get(username);
        }
    }

    /**
     * 通过用户名删除用户的权限信息
     * @param username 用户名
     */
    @Override
    public void remove(String username) {
        String key = SUFFIX + username;

        super.remove(key);
        log.debug("从redis中删除用户{}的权限...", username);
    }

}

3、创建Redis配置类

RedisConfig.java

package cn.edu.sgu.www.mhxysy.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.util.List;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, List<String>> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, List<String>> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(RedisSerializer.json()); // 值(Value)使用JSON

        return redisTemplate;
    }

}

三、创建一个Realm

AdminRealm.java

package cn.edu.sgu.www.mhxysy.realm;

import cn.edu.sgu.www.mhxysy.entity.system.Admin;
import cn.edu.sgu.www.mhxysy.redis.RedisRepository;
import cn.edu.sgu.www.mhxysy.service.system.AdminService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class AdminRealm extends AuthorizingRealm {
	@Autowired
	private AdminService adminService;

	@Autowired
	private RedisRepository redisRepository;

	/**
	 *	登录认证
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
			throws AuthenticationException {
		// 得到令牌
		UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
		
		// 从token中获取用户的登录信息
		String username = token.getUsername();
		String password = new String(token.getPassword());
		
		// 根据用户名查询管理员信息
		Admin admin = adminService.selectByUsername(username);
		
		if (admin != null && admin.getPassword().equals(password)) {
			return new SimpleAuthenticationInfo(admin, password, username);
		}
		
		return null;
	}
	
	/**
	 *	授权
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		Admin admin = (Admin) principals.getPrimaryPrincipal();
		String username = admin.getUsername();

		// 从redis中获取用户的权限
		List<String> permissions = redisRepository.get(username);
		Set<String> set = new HashSet<>(permissions);

		authorizationInfo.setStringPermissions(set);

		return authorizationInfo;
	}

}

四、创建shiro配置类

ShiroConfig.java

package cn.edu.sgu.www.mhxysy.config;

import cn.edu.sgu.www.mhxysy.realm.AdminRealm;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * shiro配置类
 */
@Configuration
public class ShiroConfig {

    @Bean
    public AdminRealm adminRealm() {
        return new AdminRealm();
    }

    /**
     * 配置安全管理器
     * @param adminRealm 管理员realm
     * @return DefaultWebSecurityManager
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("adminRealm") AdminRealm adminRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(adminRealm);

        return securityManager;
    }

    /**
     * 配置Shiro过滤器工厂
     * @param securityManager 安全管理器
     * @return ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 注册安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 当用户访问认证资源的时候,如果用户没有登录,那么就会跳转到该属性指定的页面
        shiroFilterFactoryBean.setLoginUrl("/login.html");

        // 定义资源访问规则
        Map<String, String> map = new LinkedHashMap<>();

        map.put("/index.html", "authc");
        map.put("/html/*", "authc");
        map.put("/", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }

}

五、创建AOP类

创建一个鉴权aop类AuthenticationAop.java,在每个控制器接口前进行鉴权操作

package cn.edu.sgu.www.mhxysy.aop;

import cn.edu.sgu.www.mhxysy.annotation.AnonymityAccess;
import cn.edu.sgu.www.mhxysy.config.NetAccessConfig;
import cn.edu.sgu.www.mhxysy.exception.GlobalException;
import cn.edu.sgu.www.mhxysy.restful.ResponseCode;
import cn.edu.sgu.www.mhxysy.util.IpUtils;
import cn.edu.sgu.www.mhxysy.util.UserUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 鉴权AOP类
 */
@Slf4j
@Aspect
@Component
public class AuthenticationAop {
    /**
     * 3秒内最多处理10次请求
     */
    @Value("${access.count.three-seconds}")
    private int times;

    /**
     * 3秒内最多处理10次请求
     */
    @Value("${access.limit}")
    private boolean limit;

    private final NetAccessConfig netAccessConfig;
    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public AuthenticationAop(NetAccessConfig netAccessConfig, RedisTemplate<String, Object> redisTemplate) {
        this.netAccessConfig = netAccessConfig;
        this.redisTemplate = redisTemplate;
    }

    @Pointcut("execution(public * cn.edu.sgu.www.mhxysy.controller..*(..))")
    public void requestAspect(){}

    @Before(value = "requestAspect()")
    public void before(JoinPoint joinPoint) throws Throwable {
        // 是否开启内外网访问控制
        boolean innerIpAccess = netAccessConfig.isEnableInnerIpAccess();
        // 获取配置的内网IP地址
        List<String> innerIps = netAccessConfig.getInnerIps();
        // 获取客户端IP
        String ip = IpUtils.getIp();

        // 判断是不是内网访问
        if (innerIpAccess && !innerIps.contains(ip)) {
            throw new GlobalException(ResponseCode.FORBIDDEN, "只允许内网访问~");
        }

        synchronized (AuthenticationAop.class) {
            log.debug("~~~~~~~~~~~~~~~~~~~~~~~~~~进入方法AuthenticationAop.before()");

            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

            HttpServletRequest request = UserUtils.getRequest();
            String requestURI = request.getRequestURI();

            //打印请求信息
            log.debug("COOKIE:{}", request.getHeader("COOKIE"));
            log.info("请求ip:{}", request.getRemoteAddr());
            log.info("请求地址:{}", requestURI);
            log.info("请求方式:{}", request.getMethod());
            log.info("请求类方法:{}", joinPoint.getSignature());
            log.info("请求类方法参数:{}", Arrays.toString(joinPoint.getArgs()));

            if (limit) {
                // 3秒内最多处理10次请求
                Long count = redisTemplate.boundValueOps(requestURI).increment();

                if(count != null) {
                    if (count.intValue() == 1) {
                        redisTemplate.expire(requestURI, 3, TimeUnit.SECONDS);
                    } else if (count.intValue() > times) {
                        throw new GlobalException(ResponseCode.FORBIDDEN, "服务器忙...");
                    }
                }
            }

            // 开启了匿名访问,放行
            if (method.isAnnotationPresent(AnonymityAccess.class)) {
                AnonymityAccess annotation = method.getAnnotation(AnonymityAccess.class);

                if (annotation.value()) {
                    log.debug("接口{}开启了匿名访问,放行", requestURI);
                    return;
                }
            }

            handlePermissions(method);

            log.debug("~~~~~~~~~~~~~~~~~~~~~~~~~~AuthenticationAop.before()执行完成");
        }
    }

    @After(value = "requestAspect()")
    public void after() throws Throwable {
        HttpServletResponse response = UserUtils.getResponse();
        Collection<String> headerNames = response.getHeaderNames();

        log.debug("~~~~~~~~~~~~~~~~~~~~~~~~~~AuthenticationAop.after()开始执行");

        for (String headerName : headerNames) {
            String header = response.getHeader(headerName);

            log.debug("header => {}:{}", headerName, header);
        }

        log.debug("~~~~~~~~~~~~~~~~~~~~~~~~~~AuthenticationAop.after()执行完成");
    }

    /**
     * 判断用户是否有访问资源需要的权限
     * @param method Method
     */
    @Deprecated
    private void handlePermissions(Method method) {
        log.debug("开始鉴权:~~~");

        // 鉴权流程开始
        Class<?> cls = method.getDeclaringClass();

        if (cls.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMapping = cls.getAnnotation(RequestMapping.class);

            // 获取控制器类的路径,如:/chongwu
            String[] path = requestMapping.path();

            if (path.length == 0) {
                path = requestMapping.value();
            }

            String prefix = path[0];

            if (method.isAnnotationPresent(RequestMapping.class)) {
                requestMapping = method.getAnnotation(RequestMapping.class);
                // 获取控制器类方法上的路径,如:/selectByPage
                String[] value = requestMapping.value();

                if (value.length == 0) {
                    value = requestMapping.path();
                }

                // 拼接得到接口的完整路径,如:/chongwu/selectByPage
                String requestUrl = prefix + value[0];

                // 调用shiro鉴权,判断用户是否有权限访问改请求的路径
                boolean result = UserUtils.getSubject().isPermitted(requestUrl);

                // 无权访问,抛出异常返回
                if (!result) {
                    log.error("正在访问未授权的资源:{}", requestUrl);

                    throw new GlobalException(ResponseCode.UNAUTHORIZED, "您正在访问未授权的资源。");
                }
            }
        }
    }

}

六、创建对应的实体类

1、Admin.java

package cn.edu.sgu.www.mhxysy.entity.system;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 管理员
 * @author heyunlin
 * @version 1.0
 */
@Data
@TableName("admin")
public class Admin implements Serializable {
	private static final long serialVersionUID = 18L;

	@TableId(value = "id", type = IdType.INPUT)
	private String id;

	/**
	 * 姓名
	 */
	private String name;

	/**
	 * 性别
	 */
	private Integer gender;

	/**
	 * 用户名
	 */
	private String username;

	/**
	 * 密码
	 */
	private String password;

	/**
	 * 手机号
	 */
	private String phone;

	/**
	 * 是否启用
	 */
	private Integer isEnable;

	/**
	 * 最后一次登录时间
	 */
	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
	private LocalDateTime lastLoginTime;
}

2、AdminRole.java

package cn.edu.sgu.www.mhxysy.entity.system;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * 管理员登录历史
 * @author heyunlin
 * @version 1.0
 */
@Data
@TableName("admin_role")
public class AdminRole implements Serializable {
    private static final long serialVersionUID = 18L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 管理员id
     */
    private String adminId;

    /**
     * 角色ID
     */
    private Integer roleId;
}

3、Permission.java

package cn.edu.sgu.www.mhxysy.entity.system;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * 权限
 * @author heyunlin
 * @version 1.0
 */
@Data
@TableName("permission")
public class Permission implements Serializable {
    private static final long serialVersionUID = 18L;

    @TableId(value = "id", type = IdType.AUTO)
    private String id;

    /**
     * 权限名称
     */
    private String name;

    /**
     * 类型
     *  0-顶级权限(控制器类)
     *  1-子级权限(接口方法)
     */
    private Integer type;

    /**
     * 请求路径
     */
    private String url;

    /**
     * 请求方式:0-get;1-post
     */
    private Integer method;

    /**
     * 服务名
     */
    private String service;

    /**
     * 父级权限id
     */
    private String parentId;
}

4、Role.java

package cn.edu.sgu.www.mhxysy.entity.system;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * 角色
 * @author heyunlin
 * @version 1.0
 */
@Data
@TableName("role")
public class Role implements Serializable {
    private static final long serialVersionUID = 18L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 角色名称
     */
    private String name;

    /**
     * 描述
     */
    private String description;

    /**
     * 排序
     */
    private Integer sort;
}

5、RolePermission.java

package cn.edu.sgu.www.mhxysy.entity.system;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * 角色权限
 * @author heyunlin
 * @version 1.0
 */
@Data
@TableName("role_permission")
public class RolePermission implements Serializable {
    private static final long serialVersionUID = 18L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 角色id
     */
    private Integer roleId;

    /**
     * 权限id
     */
    private String permissionId;
}

七、持久层

持久层使用了mybatis-plus,直接给出代码

1、AdminMapper.java

package cn.edu.sgu.www.mhxysy.mapper.system;

import cn.edu.sgu.www.mhxysy.entity.system.Admin;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface AdminMapper extends BaseMapper<Admin> {
    
}

AdminMapper.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="cn.edu.sgu.www.mhxysy.mapper.system.AdminMapper">
    <resultMap id="resultMap" type="cn.edu.sgu.www.mhxysy.vo.TableInfoVO">
        <result property="name" column="table_name" />
        <result property="comment" column="table_comment" />
    </resultMap>

    <select id="getTableNames" resultType="java.lang.String">
        select table_name from table_names
    </select>

    <select id="selectTables" resultMap="resultMap">
        select table_name, table_comment from table_names
    </select>
</mapper>

2、AdminRoleMapper.java

package cn.edu.sgu.www.mhxysy.mapper.system;

import cn.edu.sgu.www.mhxysy.entity.system.AdminRole;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;

@Repository
public interface AdminRoleMapper extends BaseMapper<AdminRole> {

}

AdminRoleMapper.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="cn.edu.sgu.www.mhxysy.mapper.system.AdminRoleMapper">

</mapper>

3、PermissionMapper.java

package cn.edu.sgu.www.mhxysy.mapper.system;

import cn.edu.sgu.www.mhxysy.entity.system.Permission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface PermissionMapper extends BaseMapper<Permission> {
    /**
     * 通过服务名删除权限
     * @param service 服务名
     */
    void deleteByService(String service);

    /**
     * 通过类型查询权限列表
     * @return List<Permission>
     */
    List<Permission> selectByType(Integer type);

    /**
     * 通过父级权限id查询权限列表
     * @param parentId 父级权限id
     * @return List<Permission>
     */
    List<Permission> selectByParentId(String parentId);

    /**
     * 通过用户名查询权限
     * @param username 用户名
     * @return Set<String>
     */
    List<String> selectPermissionsByUsername(String username);
}

PermissionMapper.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="cn.edu.sgu.www.mhxysy.mapper.system.PermissionMapper">
    <resultMap id="baseMap" type="cn.edu.sgu.www.mhxysy.entity.system.Permission">
        <result column="id" property="id" />
        <result column="name" property="name" />
        <result column="url" property="url" />
        <result column="type" property="type" />
        <result column="method" property="method" />
        <result column="parent_id" property="parentId" />
    </resultMap>

    <delete id="deleteByService">
        delete from permission where service = #{service}
    </delete>

    <select id="selectByType" resultMap="baseMap">
        select * from permission where type = #{type}
    </select>

    <select id="selectByParentId" resultMap="baseMap">
        select * from permission where parent_id = #{parentId}
    </select>

    <select id="selectPermissionsByUsername" resultType="java.lang.String">
        select p.url from permission p
        left join role_permission rp on p.id = rp.permission_id
        left join admin_role ar on rp.role_id = ar.role_id
        left join admin adm on ar.admin_id = adm.id
        where adm.username= #{username}
    </select>
</mapper>

4、RoleMapper.java

package cn.edu.sgu.www.mhxysy.mapper.system;

import cn.edu.sgu.www.mhxysy.entity.system.Role;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;

@Repository
public interface RoleMapper extends BaseMapper<Role> {

}

RoleMapper.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="cn.edu.sgu.www.mhxysy.mapper.system.RoleMapper">

</mapper>

5、RolePermissionMapper.java

package cn.edu.sgu.www.mhxysy.mapper.system;

import cn.edu.sgu.www.mhxysy.entity.system.Permission;
import cn.edu.sgu.www.mhxysy.entity.system.RolePermission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface RolePermissionMapper extends BaseMapper<RolePermission> {
    /**
     * 清空权限列表
     */
    void clearPermissions();

    /**
     * 通过角色id查询
     * @param roleId 角色id
     * @return List<Permission>
     */
    List<Permission> selectByRoleId(Integer roleId);
}
<?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="cn.edu.sgu.www.mhxysy.mapper.system.RolePermissionMapper">
    <resultMap id="baseMap" type="cn.edu.sgu.www.mhxysy.entity.system.Permission">
        <result column="id" property="id" />
        <result column="name" property="name" />
        <result column="url" property="url" />
        <result column="type" property="type" />
        <result column="method" property="method" />
        <result column="service" property="service" />
        <result column="parent_id" property="parentId" />
    </resultMap>

    <update id="clearPermissions">
        truncate table role_permission
    </update>

    <select id="selectByRoleId" resultMap="baseMap">
        select p.id, p.name, p.type, p.method, p.url, p.service, p.parent_id from permission p
        left join role_permission rp on p.id = rp.permission_id
        where rp.role_id = #{roleId}
    </select>
</mapper>

八、service层

1、AdminService.java

package cn.edu.sgu.www.mhxysy.service.system;

import cn.edu.sgu.www.mhxysy.dto.system.AdminLoginDTO;
import cn.edu.sgu.www.mhxysy.entity.system.Admin;
import cn.edu.sgu.www.mhxysy.pager.system.AdminPager;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @author heyunlin
 * @version 1.0
 */
public interface AdminService {
	/**
	 * 添加管理员
	 * @param admin 管理员信息
	 */
	void insert(Admin admin);

	/**
	 * 通过id删除管理员
	 * @param id 管理员id
	 */
	@Transactional
	void deleteById(String id);

	/**
	 * 根据id修改管理员信息
	 * @param admin 管理员信息
	 */
	void updateById(Admin admin);

	/**
	 * 查询全部管理员
	 * @return List<Admin>
	 */
	List<Admin> selectAll();

	/**
	 * 通过id查询管理员信息
	 * @param id 管理员id
	 * @return Admin
	 */
	Admin selectById(String id);

	/**
	 * 根据用户名查询管理员信息
	 * @param username 用户名
	 * @return Admin 通过用户名查询到的查询结果
	 */
	Admin selectByUsername(String username);

	/**
	 * 查询数据库所有表的名称
	 * @return List<String>
	 */
	List<String> getTableNames();

	/**
	 * 分页查询管理员列表
	 * @param pager 分页参数
	 * @return Page<Admin>
	 */
	Page<Admin> selectByPage(AdminPager pager);

	/**
	 * 管理员登录
	 * @param adminLoginDTO 登录信息
	 */
	@Transactional(rollbackFor = Exception.class)
	void login(AdminLoginDTO adminLoginDTO);

	/**
	 * 退出登录
	 */
	void logout();
}

AdminServiceImpl.java

package cn.edu.sgu.www.mhxysy.service.system.impl;

import cn.edu.sgu.www.mhxysy.base.Pager;
import cn.edu.sgu.www.mhxysy.dto.system.AdminLoginDTO;
import cn.edu.sgu.www.mhxysy.entity.system.Admin;
import cn.edu.sgu.www.mhxysy.entity.system.AdminLoginLog;
import cn.edu.sgu.www.mhxysy.exception.GlobalException;
import cn.edu.sgu.www.mhxysy.mapper.system.AdminLoginLogMapper;
import cn.edu.sgu.www.mhxysy.mapper.system.AdminMapper;
import cn.edu.sgu.www.mhxysy.pager.system.AdminPager;
import cn.edu.sgu.www.mhxysy.redis.RedisRepository;
import cn.edu.sgu.www.mhxysy.restful.ResponseCode;
import cn.edu.sgu.www.mhxysy.service.system.AdminService;
import cn.edu.sgu.www.mhxysy.util.IpUtils;
import cn.edu.sgu.www.mhxysy.util.StringUtils;
import cn.edu.sgu.www.mhxysy.util.UserUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

/**
 * @author heyunlin
 * @version 1.0
 */
@Slf4j
@Service
public class AdminServiceImpl implements AdminService {
	private final AdminMapper mapper;
	private final AdminLoginLogMapper loginLogMapper;
	private final RedisRepository redisRepository;

	@Value("${syslog.enable}")
	private boolean enable;

	@Autowired
	public AdminServiceImpl(AdminMapper mapper, AdminLoginLogMapper loginLogMapper, RedisRepository redisRepository) {
		this.mapper = mapper;
		this.loginLogMapper = loginLogMapper;
		this.redisRepository = redisRepository;
	}

	@Override
	public void insert(Admin admin) {
		admin.setId(StringUtils.uuid());

		mapper.insert(admin);
	}

	@Override
	public void deleteById(String id) {
		// 删除管理员登录日志
		QueryWrapper<AdminLoginLog> wrapper = new QueryWrapper<>();

		wrapper.eq("admin_id", id);

		loginLogMapper.delete(wrapper);

		// 删除管理员
		mapper.deleteById(id);
	}

	@Override
	public void updateById(Admin admin) {
		mapper.updateById(admin);
	}

	@Override
	public List<Admin> selectAll() {
		return mapper.selectList(null);
	}

	@Override
	public Admin selectById(String id) {
		return mapper.selectById(id);
	}

	@Override
	public Admin selectByUsername(String username) {
		QueryWrapper<Admin> wrapper = new QueryWrapper<>();

		wrapper.eq("username", username);

		List<Admin> list = mapper.selectList(wrapper);

		if (list.isEmpty()) {
			throw new GlobalException(ResponseCode.NOT_FOUND, "查询失败,管理员不存在");
		} else if (list.size() > 1) {
			throw new GlobalException(ResponseCode.CONFLICT, "数据异常,查询到多条管理员信息");
		} else {
			return list.get(0);
		}
	}

	@Override
	public List<String> getTableNames() {
		return mapper.getTableNames();
	}

	@Override
	public Page<Admin> selectByPage(AdminPager pager) {
		QueryWrapper<Admin> wrapper = new QueryWrapper<>();
		Page<Admin> page = Pager.ofPage(pager);

		wrapper.eq(
				StringUtils.isNotEmpty(pager.getPhone()),
				"phone", pager.getPhone()
		);

		return mapper.selectPage(page, wrapper);
	}

	@Override
	public void login(AdminLoginDTO loginDTO) {
		// 得到用户名
		String username = loginDTO.getUsername();
		log.trace("用户{}正在登录...", username);

		// shiro登录认证
		UsernamePasswordToken token = new UsernamePasswordToken(username, loginDTO.getPassword());
		Subject subject = UserUtils.getSubject();

		subject.login(token);
		// 设置session失效时间:永不超时
		subject.getSession().setTimeout(-1001);

		// 修改管理员上一次登录时间
		Admin admin = selectByUsername(username);
		admin.setLastLoginTime(LocalDateTime.now());
		mapper.updateById(admin);

		// 如果开启了系统日志
		if (enable) {
			// 添加管理员登录历史
			AdminLoginLog history = new AdminLoginLog();

			history.setId(StringUtils.uuid());
			history.setAdminId(admin.getId());
			history.setLoginTime(LocalDateTime.now());
			history.setLoginIp(IpUtils.getLocalHostAddress());
			history.setLoginHostName(IpUtils.getLocalHostName());

			loginLogMapper.insert(history);
		}

		// 从redis中删除用户权限
		redisRepository.remove(username);

		// 查询用户的权限信息,并保存到redis
		redisRepository.save(username);
	}

	@Override
	public void logout() {
		// 删除角色的权限
		redisRepository.remove(UserUtils.getLoginUsername());

		// 注销
		UserUtils.getSubject().logout();
	}

}

2、AdminRoleService.java

package cn.edu.sgu.www.mhxysy.service.system;

import cn.edu.sgu.www.mhxysy.entity.system.AdminRole;
import cn.edu.sgu.www.mhxysy.pager.system.AdminRolePager;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

/**
 * @author heyunlin
 * @version 1.0
 */
public interface AdminRoleService {
	/**
	 * 添加管理员角色
	 * @param role 管理员角色信息
	 */
	void insert(AdminRole role);
	
	/**
	 * 通过id删除管理员角色
	 * @param id 管理员角色id
	 */
	void deleteById(Integer id);

	/**
	 * 通过id修改管理员角色信息
	 * @param role 管理员角色信息
	 */
	void updateById(AdminRole role);

	/**
	 * 分页查询管理员角色列表
	 * @param pager 分页查询的条件
	 * @return Page<AdminRole>
	 */
	Page<AdminRole> selectByPage(AdminRolePager pager);
}

 AdminRoleServiceImpl.java

package cn.edu.sgu.www.mhxysy.service.system.impl;

import cn.edu.sgu.www.mhxysy.base.Pager;
import cn.edu.sgu.www.mhxysy.entity.system.AdminRole;
import cn.edu.sgu.www.mhxysy.mapper.system.AdminRoleMapper;
import cn.edu.sgu.www.mhxysy.pager.system.AdminRolePager;
import cn.edu.sgu.www.mhxysy.service.system.AdminRoleService;
import cn.edu.sgu.www.mhxysy.util.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author heyunlin
 * @version 1.0
 */
@Service
public class AdminRoleServiceImpl implements AdminRoleService {
    private final AdminRoleMapper mapper;

    @Autowired
    public AdminRoleServiceImpl(AdminRoleMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public void insert(AdminRole role) {
        mapper.insert(role);
    }

    @Override
    public void deleteById(Integer id) {
        mapper.deleteById(id);
    }

    @Override
    public void updateById(AdminRole role) {
        mapper.updateById(role);
    }

    @Override
    public Page<AdminRole> selectByPage(AdminRolePager pager) {
        QueryWrapper<AdminRole> wrapper = new QueryWrapper<>();
        Page<AdminRole> page = Pager.ofPage(pager);

        wrapper.eq(
                StringUtils.isNotEmpty(pager.getAdminId()),
                "admin_id", pager.getAdminId()
        );
        wrapper.eq(
                pager.getRoleId() != null,
                "role_id", pager.getRoleId()
        );

        return mapper.selectPage(page, wrapper);
    }

}

3、PermissionService.java

package cn.edu.sgu.www.mhxysy.service.system.impl;

import cn.edu.sgu.www.mhxysy.service.BaseService;
import cn.edu.sgu.www.common.util.StringUtils;
import cn.edu.sgu.www.mhxysy.MhxysyApplication;
import cn.edu.sgu.www.mhxysy.dto.pager.PermissionPager;
import cn.edu.sgu.www.mhxysy.entity.system.Permission;
import cn.edu.sgu.www.mhxysy.enums.RequestMethod;
import cn.edu.sgu.www.mhxysy.mapper.system.PermissionMapper;
import cn.edu.sgu.www.mhxysy.service.system.PermissionService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

@Service
public class PermissionServiceImpl extends BaseService<Permission> implements PermissionService {
    private final PermissionMapper mapper;
    private final static String serviceId = "mhxysy";
    List<String> classPaths = new ArrayList<>();
    List<Permission> resources = new ArrayList<>();

    public PermissionServiceImpl(PermissionMapper mapper) {
        super(mapper);

        this.mapper = mapper;
    }

    @Override
    public boolean insert(Permission permission) {
        permission.setId(StringUtils.uuid());

        return super.insert(permission);
    }

    @Override
    public boolean deleteById(String id) {
        super.deleteById(id);

        return true;
    }

    @Override
    public boolean updateById(Permission permission) {
        super.updateById(permission);

        return true;
    }

    @Override
    public List<Permission> selectAll() {
        return super.selectAll();
    }

    @Override
    public Permission selectById(String id) {
        return mapper.selectById(id);
    }

    @Override
    public List<Permission> selectByType(Integer type) {
        QueryWrapper<Permission> wrapper = new QueryWrapper<>();

        wrapper.eq("type", type);

        return mapper.selectList(wrapper);
    }

    @Override
    public List<String> selectPermissionsByUsername(String username) {
        return mapper.selectPermissionsByUsername(username);
    }

    @Override
    public Page<Permission> selectByPage(PermissionPager pagerDTO) {
        Page<Permission> page = new Page<>(pagerDTO.getPage(), pagerDTO.getRows());
        QueryWrapper<Permission> wrapper = new QueryWrapper<>();

        wrapper.eq(pagerDTO.getType() != null, "type", pagerDTO.getType());
        wrapper.like(StringUtils.isNotEmpty(pagerDTO.getName()), "name", pagerDTO.getName());
        wrapper.like(StringUtils.isNotEmpty(pagerDTO.getValue()), "value", pagerDTO.getValue());
        wrapper.eq(StringUtils.isNotEmpty(pagerDTO.getParentId()), "parent_id", pagerDTO.getParentId());

        return mapper.selectPage(page, wrapper);
    }

    @Override
    public void init() throws ClassNotFoundException {
        mapper.truncate();

        String basePackage = "cn.edu.sgu.www.mhxysy.controller";
        String classpath = MhxysyApplication.class.getResource("/").getPath().replaceFirst("/", "");
        String searchPath = classpath + basePackage.replace(".", "/");
        searchPath = searchPath.replace("test-classes", "classes");
        List<String> classPaths = doPath(new File(searchPath));

        for(String classPath : classPaths) {
            classPath = classPath.replace(classpath.replace("/", "\\")
                    .replaceFirst("\\\\", ""), "")
                    .replace("\\", ".")
                    .replace(".class", "");
            classpath = classPath.substring(classPath.indexOf(basePackage));
            Class<?> cls = Class.forName(classpath);
            RequestMapping requestMapping = cls.getAnnotation(RequestMapping.class);

            // 判断多种注解的情况
            String prefix = "";
            Permission parent = new Permission();

            if(requestMapping != null) {
                // path或者value
                prefix = requestMapping.value().length > 0 ? requestMapping.value()[0] : requestMapping.path()[0];

                parent.setId(serviceId + "_" + cls.getSimpleName());
                parent.setValue(prefix.substring(1));
                parent.setType(0);
                parent.setUrl(prefix);

                // 设置value
                if (cls.isAnnotationPresent(Api.class)) {
                    Api api = cls.getAnnotation(Api.class);

                    if (api != null) {
                        String name = api.value();

                        // 类的接口文档@Api注解的值以“控制器”或“控制器类”结束
                        parent.setName(name.substring(0, name.indexOf("控制器")).concat("管理"));
                    }
                }

                resources.add(parent);
            }
            Method[] methods = cls.getDeclaredMethods();

            for (Method value : methods) {
                getClassAnnotation(value, prefix, cls, parent.getId());
            }
        }

        for (Permission permission : resources) {
            if (permission.getName() != null) {
                mapper.insert(permission);
            }
        }
    }

    /**
     * 得到类上面的注解信息
     * @param method
     * @param prefix
     * @param cls
     * @param parentId
     */
    public void getClassAnnotation(Method method, String prefix, Class<?> cls, String parentId) {
        String url = null;
        Permission permission = new Permission();

        if (method.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
            url = prefix + (requestMapping.value().length > 0 ? requestMapping.value()[0] : requestMapping.path()[0]);
            String requestMethod = requestMapping.method().length > 0 ? requestMapping.method()[0].name() : "get";

            permission.setMethod(RequestMethod.getValueByName(requestMethod));
        } else if (method.isAnnotationPresent(GetMapping.class)) {
            GetMapping getMapping = method.getAnnotation(GetMapping.class);
            url = prefix + getMapping.value()[0];

            permission.setMethod(RequestMethod.GET.getValue());
        } else if (method.isAnnotationPresent(PostMapping.class)) {
            PostMapping postMapping = method.getAnnotation(PostMapping.class);
            url = prefix + postMapping.value()[0];

            permission.setMethod(RequestMethod.POST.getValue());
        }

        // 处理URL
        if(url != null && url.endsWith("/")) {
            url = url.substring(0, url.length() - 1);
        }
        permission.setUrl(url);

        // 设置value
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation operation = method.getAnnotation(ApiOperation.class);

            if (operation != null) {
                String name = operation.value();

                permission.setName(name);
            }
        }

        permission.setType(1);
        permission.setParentId(parentId);
        permission.setId(serviceId + "_" + cls.getSimpleName() + "_" + method.getName());

        if (url != null) {
            permission.setValue(String.join(">>", url.substring(1).split("/")));
        }

        resources.add(permission);
    }

    private List<String> doPath(File file) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();

            if (files != null) {
                for (File f1 : files) {
                    doPath(f1);
                }
            }
        } else {
            if (file.getName().endsWith(".class")) {
                classPaths.add(file.getPath());
            }
        }

        return classPaths;
    }

}

4、RolePermissionServiceImpl

package cn.edu.sgu.www.mhxysy.service.system.impl;

import cn.edu.sgu.www.mhxysy.service.BaseService;
import cn.edu.sgu.www.mhxysy.exception.GlobalException;
import cn.edu.sgu.www.common.restful.ResponseCode;
import cn.edu.sgu.www.common.util.StringUtils;
import cn.edu.sgu.www.mhxysy.dto.pager.RolePermissionPager;
import cn.edu.sgu.www.mhxysy.entity.system.Permission;
import cn.edu.sgu.www.mhxysy.entity.system.RolePermission;
import cn.edu.sgu.www.mhxysy.enums.PermissionType;
import cn.edu.sgu.www.mhxysy.mapper.system.PermissionMapper;
import cn.edu.sgu.www.mhxysy.mapper.system.RolePermissionMapper;
import cn.edu.sgu.www.mhxysy.service.system.RolePermissionService;
import cn.edu.sgu.www.mhxysy.vo.PermissionTreeChildrenVO;
import cn.edu.sgu.www.mhxysy.vo.PermissionTreeVO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class RolePermissionServiceImpl extends BaseService<RolePermission> implements RolePermissionService {
    private final RolePermissionMapper mapper;
    private final PermissionMapper permissionMapper;

    public RolePermissionServiceImpl(RolePermissionMapper mapper, PermissionMapper permissionMapper) {
        super(mapper);

        this.mapper = mapper;
        this.permissionMapper = permissionMapper;
    }

    @Override
    public boolean insert(RolePermission permission) {
        String permissionId = permission.getPermissionId();
        // 查询权限信息
        Permission perm = permissionMapper.selectById(permissionId);

        // 如果是父级权限,为角色添加该权限下的所有子权限
        if (perm.getType().equals(PermissionType.FQX.getValue())) {
            // 查询权限的所有子权限
            List<Permission> permissions = permissionMapper.selectByParentId(permissionId);
            // 查询角色已有权限
            List<Permission> perms = mapper.selectByRoleId(permission.getRoleId());
            
            if (perms.isEmpty()) {
                permissions.forEach(p -> {
                    permission.setId(null);
                    permission.setPermissionId(p.getId());

                    mapper.insert(permission);
                });
            } else if (!permissions.containsAll(perms)) {
                permissions.forEach(p -> {
                    if (!perms.contains(p)) {
                        permission.setId(null);
                        permission.setPermissionId(p.getId());

                        mapper.insert(permission);
                    }
                });
            } else {
                throw new GlobalException(ResponseCode.CONFLICT, "角色已经添加了此权限。");
            }

            return true;
        } else {
            permission.setId(null);

            return super.insert(permission);
        }
    }

    @Override
    public boolean deleteById(Integer id) {
        return super.deleteById(id);
    }

    @Override
    public boolean updateById(RolePermission permission) {
        return super.updateById(permission);
    }

    @Override
    public RolePermission selectById(String id) {
        return mapper.selectById(id);
    }

    @Override
    public List<PermissionTreeVO> listTree(Integer roleId) {
        Map<String, List<PermissionTreeChildrenVO>> allMap = new HashMap<>();
        Map<String, List<PermissionTreeChildrenVO>> map = new HashMap<>();
        List<PermissionTreeVO> tree = new ArrayList<>();

        // 查询全部二级权限
        List<Permission> list = permissionMapper.selectByType(PermissionType.ZQX.getValue());
        // 遍历,把查询出来的权限按照parentId存到map中
        for (Permission permission : list) {
            String parentId = permission.getParentId();
            PermissionTreeChildrenVO childrenVO = new PermissionTreeChildrenVO();

            childrenVO.setId(permission.getId());
            childrenVO.setText(permission.getName());
            childrenVO.setChecked(true);

            if (allMap.containsKey(parentId)) {
                allMap.get(parentId).add(childrenVO);
            } else {
                allMap.put(parentId, new ArrayList<>());
            }
        }

        // 查询当前用户的角色权限
        List<Permission> permissions = mapper.selectByRoleId(roleId);
        // 遍历,把查询出来的权限按照parentId存到map中
        for (Permission permission : permissions) {
            String parentId = permission.getParentId();
            PermissionTreeChildrenVO childrenVO = new PermissionTreeChildrenVO();

            childrenVO.setId(permission.getId());
            childrenVO.setText(permission.getName());
            childrenVO.setChecked(true);

            if (map.containsKey(parentId)) {
                map.get(parentId).add(childrenVO);
            } else {
                map.put(parentId, new ArrayList<>());
            }
        }

        // 遍历map,生成菜单树
        allMap.forEach((key, value) -> {
            // 解决有些controller暂未使用,没有写接口方法的问题
            if (!value.isEmpty()) {
                // 如果是用户已有权限
                if (map.containsKey(key)) {
                    // 遍历菜单树,把用户的权限的checked设置为true
                    for (PermissionTreeChildrenVO childrenVO : value) {
                        childrenVO.setChecked(map.get(key).contains(childrenVO));
                    }
                } else {
                    // 遍历菜单树,把其他权限的checked设置为false
                    for (PermissionTreeChildrenVO childrenVO : value) {
                        childrenVO.setChecked(false);
                    }
                }
            }

            Permission parent = permissionMapper.selectById(key);
            PermissionTreeVO treeVO = new PermissionTreeVO();

            treeVO.setId(parent.getId());
            treeVO.setText(parent.getName());
            treeVO.setState("open");
            treeVO.setChildren(value);

            tree.add(treeVO);
        });

        return tree;
    }

    @Override
    public Page<RolePermission> selectByPage(RolePermissionPager pager) {
        Page<RolePermission> page = new Page<>(pager.getPage(), pager.getRows());
        QueryWrapper<RolePermission> wrapper = new QueryWrapper<>();

        wrapper.eq(pager.getRoleId() != null, "role_id", pager.getRoleId());
        wrapper.eq(StringUtils.isNotEmpty(pager.getPermissionId()), "permission_id", pager.getPermissionId());


        return mapper.selectPage(page, wrapper);
    }

    @Override
    public void init() {
        mapper.deleteByRoleId(1);

        // 查询全部一级权限
        List<Permission> list = permissionMapper.selectByType(PermissionType.ZQX.getValue());

        list.forEach(permission -> {
            RolePermission rolePermission = new RolePermission();

            rolePermission.setId(null);
            rolePermission.setRoleId(1);
            rolePermission.setPermissionId(permission.getId());

            mapper.insert(rolePermission);
        });
    }

}

5、RoleServiceImpl

package cn.edu.sgu.www.mhxysy.service.system.impl;

import cn.edu.sgu.www.mhxysy.service.BaseService;
import cn.edu.sgu.www.mhxysy.dto.pager.RolePager;
import cn.edu.sgu.www.mhxysy.entity.system.Role;
import cn.edu.sgu.www.mhxysy.mapper.system.RoleMapper;
import cn.edu.sgu.www.mhxysy.service.system.RoleService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Set;

@Service
public class RoleServiceImpl extends BaseService<Role> implements RoleService {
    private final RoleMapper mapper;

    @Autowired
    public RoleServiceImpl(RoleMapper mapper) {
        super(mapper);

        this.mapper = mapper;
    }

    @Override
    public boolean insert(Role role) {
        super.insert(role);

        return true;
    }

    @Override
    public boolean deleteById(Integer id) {
        super.deleteById(id);

        return true;
    }

    @Override
    public boolean updateById(Role role) {
        super.updateById(role);

        return true;
    }

    @Override
    public List<Role> selectAll() {
        return super.selectAll();
    }

    @Override
    public Role selectById(String id) {
        return mapper.selectById(id);
    }

    @Override
    public Set<String> selectRolesByUsername(String username) {
        return mapper.selectRolesByUsername(username);
    }

    @Override
    public Page<Role> selectByPage(RolePager pager) {
        Page<Role> page = new Page<>(pager.getPage(), pager.getRows());
        QueryWrapper<Role> wrapper = new QueryWrapper<>();

        return mapper.selectPage(page, wrapper);
    }

}

主要的流程为:登录系统时,会查询登录用户的权限并保存到redis中,当新增或者删除了接口时,只需要点击前端权限列表页面的【初始化】按钮就可以重新加载系统的权限信息,再点击角色权限列表的对应按钮就可以重新为超管分配所有权限。只需要重新登录系统就可以重新获取用户的权限。页面效果如下图所示:

除此之外,还可以通过过滤器处理鉴权(推荐方式),可以参考以下文章。

springboot整合shiro实现认证和授权(非常详细)https://blog.csdn.net/heyl163_/article/details/131504939

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,我们需要定义一个注解 @RequiresPermissions,用于标记需要权限控制的方法。代码如下: ``` @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresPermissions { String[] value(); } ``` 然后,我们需要定义一个切面类,用于拦截被 @RequiresPermissions 标记的方法,并进行权限控制的逻辑。代码如下: ``` @Component @Aspect public class PermissionAspect { @Autowired private PermissionService permissionService; @Pointcut("@annotation(com.example.demo.annotation.RequiresPermissions)") public void requiresPermissionsPointcut() {} @Around("requiresPermissionsPointcut()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { // 获取当前用户信息 User currentUser = UserContext.getCurrentUser(); // 获取方法上的权限标记 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); RequiresPermissions annotation = signature.getMethod().getAnnotation(RequiresPermissions.class); String[] permissions = annotation.value(); // 校验权限 boolean hasPermission = permissionService.checkPermissions(currentUser, permissions); if (!hasPermission) { throw new UnauthorizedException("没有访问权限"); } // 执行原方法 return joinPoint.proceed(); } } ``` 在上述代码中,我们通过 @Pointcut 注解定义了一个切点,用于匹配被 @RequiresPermissions 标记的方法。然后,在 @Around 注解的方法中,我们获取了当前用户信息,以及方法上的权限标记。接着,我们调用 PermissionService 的 checkPermissions 方法,校验当前用户是否拥有对应的权限。如果权限校验失败,我们抛出 UnauthorizedException 异常,表示没有访问权限。否则,我们执行原方法,并返回执行结果。 最后,我们定义一个 PermissionService 接口,用于查询用户权限信息。具体实现可以根据实际情况进行编写。代码如下: ``` public interface PermissionService { /** * 校验用户是否拥有指定的权限 * * @param user 当前用户 * @param permissions 权限列表 * @return 是否拥有指定权限 */ boolean checkPermissions(User user, String[] permissions); } ``` 综上,我们通过 Spring AOP 和自定义注解,模拟实现Shiro 框架的权限控制功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值