1.前言

本文主要介绍使用SpringBoot与shiro实现基于数据库的细粒度动态权限管理系统实例。
使用技术:SpringBoot、mybatis、shiro、freemarker、pagehelper、Mapper插件、druid、
开发工具:intellij idea
数据库:mysql、redis

开发环境

工具 版本或描述
OS Windows 7
JDK 1.8+
IDE IntelliJ IDEA 2017.3
Maven 3.3.1
MySQL 5.6.4

模块划分

模块 释义
shiro-core 核心业务类模块,提供基本的数据操作、工具处理等
shiro-admin 后台管理模块

SQL Model

使用说明

  1. 使用IDE导入本项目
  2. 新建数据库CREATE DATABASE shiro;
  3. 导入数据库docs/db/shiro.sql
  4. 修改(resources/application.yml)配置文件
    1. 数据库链接属性(可搜索datasource或定位到L.19)
    2. redis配置(可搜索redis或定位到L.69)
  5. 运行项目(三种方式)
    1. 项目根目录下执行mvn -X clean package -Dmaven.test.skip=true编译打包,然后执行java -jar shiro-admin/target/shiro-admin.jar
    2. 项目根目录下执行mvn springboot:run
    3. 直接运行ShiroAdminApplication.java
  6. 浏览器访问http://127.0.0.1:8080

用户密码

超级管理员: 账号:root 密码:123456

普通管理员: 账号:admin 密码:123456

Druid监控

链接http://127.0.0.1:8080/druid/index.html

用户名:zyd-druid 密码:zyd-druid

2.表结构

用标准的5张表来实现权限,分别为用户表,角色表,资源表,用户角色表,角色资源表。

SET FOREIGN_KEY_CHECKS=0;
-- ------------------------------ ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` varchar(100) DEFAULT NULL COMMENT '登录账号',
`password` varchar(100) DEFAULT NULL COMMENT '登录密码',
`nickname` varchar(30) DEFAULT '' COMMENT '角色名称',
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱地址',
`qq` varchar(20) DEFAULT NULL COMMENT 'QQ',
`birthday` date DEFAULT NULL COMMENT '生日',
`gender` tinyint(2) unsigned DEFAULT NULL COMMENT '性别',
`avatar` varchar(255) DEFAULT NULL COMMENT '头像地址',
`user_type` enum('ROOT','ADMIN','USER') DEFAULT 'ADMIN' COMMENT '用户类型 ROOT:超级管理员、管理员:ADMIN、普通用户:USER',
`reg_ip` varchar(30) DEFAULT NULL COMMENT '注册IP',
`last_login_ip` varchar(30) DEFAULT NULL COMMENT '最近登录IP',
`last_login_time` datetime DEFAULT NULL COMMENT '最近登录时间',
`login_count` int(10) unsigned DEFAULT '0' COMMENT '登录次数',
`remark` varchar(100) DEFAULT NULL COMMENT '用户备注',
`status` int(1) unsigned DEFAULT NULL COMMENT '用户状态',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户表';-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', 'root', 'CGUx1FN++xS+4wNDFeN6DA==', '超级管理员', '15151551516', '843977358@qq.com', '843977358', null, null, 'https://static.zhyd.me/static/img/favicon.ico', 'ROOT', null, '127.0.0.1', '2018-05-17 13:09:35', '228', null, '1', '2018-01-02 09:32:15', '2018-05-17 13:09:35');
INSERT INTO `sys_user` VALUES ('2', 'admin', 'gXp2EbyZ+sB/A6QUMhiUJQ==', '管理员', '15151551516', '843977358@qq.com', '843977358', null, null, null, 'ADMIN', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1', '2018-05-17 13:08:30', '13', null, '1', '2018-01-02 15:56:34', '2018-05-17 13:08:30');-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT  COMMENT '角色ID',
`name` varchar(100) DEFAULT NULL COMMENT '角色名',
`description` varchar(100) DEFAULT NULL COMMENT '角色描述',
`available` tinyint(1) DEFAULT '0' COMMENT '是否可用:1可用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='角色表';-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', 'role:root', '超级管理员', '1', '2017-12-20 16:40:24', '2017-12-20 16:40:26');
INSERT INTO `sys_role` VALUES ('2', 'role:admin', '管理员', '1', '2017-12-22 13:56:39', '2017-12-22 13:56:39');INSERT INTO `sys_role` VALUES ('3', 'role:user', '管理员', '1', '2017-12-22 13:56:39', '2017-12-22 13:56:39');-- ----------------------------
-- Table structure for sys_resources
-- ----------------------------
DROP TABLE IF EXISTS `sys_resources`;
CREATE TABLE `sys_resources` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '资源ID',`name` varchar(100) DEFAULT NULL COMMENT '资源名称',`type` varchar(50) DEFAULT NULL COMMENT '资源类型:menu:菜单,button:按钮',`url` varchar(200) DEFAULT NULL COMMENT '资源url',`permission` varchar(100) DEFAULT NULL COMMENT '权限',`parent_id` bigint(20) unsigned DEFAULT '0'  COMMENT '父级id',`sort` int(10) unsigned DEFAULT NULL  COMMENT '排序',`external` tinyint(1) unsigned DEFAULT NULL COMMENT '是否外部链接',`available` tinyint(1) unsigned DEFAULT '0'  COMMENT '是否可用',`icon` varchar(100) DEFAULT NULL COMMENT '菜单图标',`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,KEY `idx_sys_resource_parent_id` (`parent_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT '资源表';-- ----------------------------
-- Records of sys_resources
-- ----------------------------
INSERT INTO `sys_resources` VALUES ('1', '用户管理', 'menu', null, null, '0', '1', '0', '1', 'fa fa-users', '2018-05-16 17:02:54', '2018-05-16 17:02:54');
INSERT INTO `sys_resources` VALUES ('2', '用户列表', 'menu', '/users', 'users', '1', '1', '0', '1', null, '2017-12-22 13:56:15', '2018-05-16 14:44:20');
INSERT INTO `sys_resources` VALUES ('3', '新增用户', 'button', null, 'user:add', '2', '2', '0', '1', null, '2018-05-16 14:07:43', '2018-05-16 14:16:23');
INSERT INTO `sys_resources` VALUES ('4', '批量删除用户', 'button', null, 'user:batchDelete', '2', '3', '0', '1', null, '2018-05-16 14:12:23', '2018-05-16 14:16:35');
INSERT INTO `sys_resources` VALUES ('5', '编辑用户', 'button', null, 'user:edit', '2', '4', '0', '1', null, '2018-05-16 14:12:50', '2018-05-16 14:16:43');
INSERT INTO `sys_resources` VALUES ('6', '删除用户', 'button', null, 'user:delete', '2', '5', '0', '1', null, '2018-05-16 14:13:09', '2018-05-16 14:51:50');
INSERT INTO `sys_resources` VALUES ('7', '分配用户角色', 'button', null, 'user:allotRole', '2', '6', '0', '1', null, '2018-05-16 14:15:28', '2018-05-16 14:16:54');
INSERT INTO `sys_resources` VALUES ('8', '系统配置', 'menu', null, null, '0', '2', '0', '1', 'fa fa-cogs', '2017-12-20 16:40:06', '2017-12-20 16:40:08');
INSERT INTO `sys_resources` VALUES ('9', '资源管理', 'menu', '/resources', 'resources', '8', '1', '0', '1', null, '2017-12-22 15:31:05', '2017-12-22 15:31:05');
INSERT INTO `sys_resources` VALUES ('10', '新增资源', 'button', null, 'resource:add', '9', '2', '0', '1', null, '2018-05-16 14:07:43', '2018-05-16 14:16:23');
INSERT INTO `sys_resources` VALUES ('11', '批量删除资源', 'button', null, 'resource:batchDelete', '9', '3', '0', '1', null, '2018-05-16 14:12:23', '2018-05-16 14:16:35');
INSERT INTO `sys_resources` VALUES ('12', '编辑资源', 'button', null, 'resource:edit', '9', '4', '0', '1', null, '2018-05-16 14:12:50', '2018-05-16 14:16:43');
INSERT INTO `sys_resources` VALUES ('13', '删除资源', 'button', null, 'resource:delete', '9', '5', '0', '1', null, '2018-05-16 14:13:09', '2018-05-16 14:51:50');
INSERT INTO `sys_resources` VALUES ('14', '角色管理', 'menu', '/roles', 'roles', '8', '2', '0', '1', '', '2017-12-22 15:31:27', '2018-05-17 12:51:06');
INSERT INTO `sys_resources` VALUES ('15', '新增角色', 'button', null, 'role:add', '14', '2', '0', '1', null, '2018-05-16 14:07:43', '2018-05-16 14:16:23');
INSERT INTO `sys_resources` VALUES ('16', '批量删除角色', 'button', null, 'role:batchDelete', '14', '3', '0', '1', null, '2018-05-16 14:12:23', '2018-05-16 14:16:35');
INSERT INTO `sys_resources` VALUES ('17', '编辑角色', 'button', null, 'role:edit', '14', '4', '0', '1', null, '2018-05-16 14:12:50', '2018-05-16 14:16:43');
INSERT INTO `sys_resources` VALUES ('18', '删除角色', 'button', null, 'role:delete', '14', '5', '0', '1', null, '2018-05-16 14:13:09', '2018-05-16 14:51:50');
INSERT INTO `sys_resources` VALUES ('19', '分配角色资源', 'button', null, 'role:allotResource', '14', '6', '0', '1', null, '2018-05-17 10:04:21', '2018-05-17 10:04:21');
INSERT INTO `sys_resources` VALUES ('20', '数据监控', 'menu', '', '', null, '3', '0', '1', 'fa fa-heartbeat', '2018-05-17 12:38:20', '2018-05-17 12:53:06');
INSERT INTO `sys_resources` VALUES ('21', 'Druid监控', 'menu', '/druid/index.html', 'druid', '20', '1', '1', '1', '', '2018-05-17 12:46:37', '2018-05-17 12:52:33');INSERT INTO `sys_resources` VALUES ('22', '数据报表', 'menu',    null,   null,   '0', '1', '0', '1', 'fa fa-users', '2018-05-16 17:02:54', '2018-05-16 17:02:54');
INSERT INTO `sys_resources` VALUES ('23', '查看报表', 'menu', '/users', 'users', '22', '1', '0', '1', null, '2017-12-22 13:56:15', '2018-05-16 14:44:20');-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`user_id` bigint(20) unsigned NOT NULL,`role_id` bigint(20) unsigned NOT NULL,`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT ;-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1', '1', '2018-01-02 10:47:27', '2018-01-02 10:47:27');
INSERT INTO `sys_user_role` VALUES ('2', '2', '2', '2018-01-05 18:21:02', '2018-01-05 18:21:02');-- ----------------------------
-- Table structure for sys_role_resources
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_resources`;
CREATE TABLE `sys_role_resources` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`role_id` bigint(20) unsigned NOT NULL,`resources_id` bigint(20) unsigned NOT NULL,`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',`update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=57 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;-- ----------------------------
-- Records of sys_role_resources
-- ----------------------------
INSERT INTO `sys_role_resources` VALUES ('27', '1', '20', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('28', '1', '21', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('29', '1', '1', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('30', '1', '2', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('31', '1', '3', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('32', '1', '4', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('33', '1', '5', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('34', '1', '6', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('35', '1', '7', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('36', '1', '8', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('37', '1', '9', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('38', '1', '10', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('39', '1', '11', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('40', '1', '12', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('41', '1', '13', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('42', '1', '14', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('43', '1', '15', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('44', '1', '16', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('45', '1', '17', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('46', '1', '18', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('47', '1', '19', '2018-05-17 12:52:41', '2018-05-17 12:52:41');
INSERT INTO `sys_role_resources` VALUES ('48', '2', '20', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('49', '2', '21', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('50', '2', '2', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('51', '2', '3', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('52', '2', '8', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('53', '2', '9', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('54', '2', '10', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('55', '2', '14', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('56', '2', '15', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('57', '2', '1', '2018-05-17 12:52:51', '2018-05-17 12:52:51');INSERT INTO `sys_role_resources` VALUES ('58', '1', '22', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('59', '1', '22', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('60', '2', '22', '2018-05-17 12:52:51', '2018-05-17 12:52:51');
INSERT INTO `sys_role_resources` VALUES ('61', '2', '22', '2018-05-17 12:52:51', '2018-05-17 12:52:51');ALTER TABLE sys_user COMMENT='用户表';
ALTER TABLE sys_role COMMENT='角色表';
ALTER TABLE sys_resources COMMENT='资源表';
ALTER TABLE sys_resources COMMENT='用户角色表';
ALTER TABLE sys_user_role COMMENT='角色资源表';

3. maven配置

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.study</groupId><artifactId>springboot-shiro</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>springboot-shiro</name><description>Demo project for Spring Boot</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.2.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.1.0</version></dependency><dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId><version>1.1.1</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.29</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>net.sourceforge.nekohtml</groupId><artifactId>nekohtml</artifactId><version>1.9.22</version></dependency><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>1.2.1</version></dependency><dependency><groupId>org.crazycake</groupId><artifactId>shiro-redis</artifactId><version>2.4.2.1-RELEASE</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.5</version><configuration><configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile><overwrite>true</overwrite><verbose>true</verbose></configuration><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId><version>3.4.0</version></dependency></dependencies></plugin></plugins></build></project>

4.配置Druid


package com.zyd.shiro.framework.property;import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;import java.util.List;/*** druid属性*/
@Configuration
@ConfigurationProperties(prefix = "zyd.druid")
@Data
@EqualsAndHashCode(callSuper = false)
@Order(-1)
public class DruidProperties {private String username;private String password;private String servletPath = "/druid/*";private Boolean resetEnable = false;private List<String> allowIps;private List<String> denyIps;
}

DruidConfig配置


package com.zyd.shiro.framework.config;import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.zyd.shiro.framework.property.DruidProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import java.util.List;/*** Druid Monitor 配置*/
@Configuration
public class DruidConfig {@Autowiredprivate DruidProperties druidProperties;@Beanpublic ServletRegistrationBean druidStatViewServlet() {ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), druidProperties.getServletPath());// IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.List<String> denyIps = druidProperties.getDenyIps();if (!CollectionUtils.isEmpty(denyIps)) {bean.addInitParameter("deny", StringUtils.collectionToDelimitedString(denyIps, ","));}// IP白名单List<String> allowIps = druidProperties.getAllowIps();if (!CollectionUtils.isEmpty(allowIps)) {bean.addInitParameter("allow", StringUtils.collectionToDelimitedString(allowIps, ","));}// 登录查看信息的账号密码.bean.addInitParameter("loginUsername", druidProperties.getUsername());bean.addInitParameter("loginPassword", druidProperties.getPassword());// 禁用HTML页面上的"Reset All"功能(默认false)bean.addInitParameter("resetEnable", String.valueOf(druidProperties.getResetEnable()));return bean;}/*** Druid的StatFilter* @return*/@Beanpublic FilterRegistrationBean druidStatFilter() {FilterRegistrationBean bean = new FilterRegistrationBean(new WebStatFilter());// 添加过滤规则bean.addUrlPatterns("/*");// 排除的urlbean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");return bean;}
}

application.properties配置Druid

# 数据源基础配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro
spring.datasource.username=root
spring.datasource.password=root
# 连接池配置
# 初始化大小,最小,最大
spring.datasource.initialSize=1
spring.datasource.minIdle=1
spring.datasource.maxActive=20

配置好后,运行项目访问http://localhost:8080/druid/ 输入配置的账号密码进入:

5.Mybatis配置

package com.zyd.shiro.framework.config;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.stereotype.Component;/****/
@Component
@MapperScan("com.zyd.shiro.persistence.mapper")
public class MybatisConfig {
}

mybatis在application.properties配置

mybatis.type-aliases-package=com.study.model
mybatis.mapper-locations=classpath:mapper/*.xml
mapper.mappers=com.study.util.MyMapper
mapper.not-empty=false
mapper.identity=MYSQL
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count\=countSql

将相应的路径改成项目包所在的路径即可。配置文件中可以看出来还加入了pagehelper 和Mapper插件。如果不需要,把上面配置文件中的 pagehelper删除。

6.freemark配置


package com.zyd.shiro.framework.config;import com.jagregory.shiro.freemarker.ShiroTags;
import com.zyd.shiro.framework.tag.CustomTagDirective;
import freemarker.template.TemplateModelException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;import javax.annotation.PostConstruct;/*** freemarker配置类*/
@Configuration
public class FreeMarkerConfig {@Autowiredprotected freemarker.template.Configuration configuration;@Autowiredprotected CustomTagDirective customTagDirective;/*** 添加自定义标签*/@PostConstructpublic void setSharedVariable() {configuration.setSharedVariable("zhydTag", customTagDirective);//shiro标签configuration.setSharedVariable("shiro", new ShiroTags());}
}

自定义的freemarker标签


package com.zyd.shiro.framework.tag;import com.zyd.shiro.business.service.SysResourcesService;
import freemarker.core.Environment;
import freemarker.template.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/*** 自定义的freemarker标签** @author*/
@Component
public class CustomTagDirective implements TemplateDirectiveModel {private static final String METHOD_KEY = "method";@Autowiredprivate SysResourcesService resourcesService;@Overridepublic void execute(Environment environment, Map map, TemplateModel[] templateModels, TemplateDirectiveBody templateDirectiveBody) throws TemplateException, IOException {DefaultObjectWrapperBuilder builder = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25);if (map.containsKey(METHOD_KEY)) {String method = map.get(METHOD_KEY).toString();switch (method) {case "availableMenus":// 获取所有可用的菜单资源environment.setVariable("availableMenus", builder.build().wrap(resourcesService.listAllAvailableMenu()));break;case "menus":Integer userId = null;if (map.containsKey("userId")) {String userIdStr = map.get("userId").toString();if (StringUtils.isEmpty(userIdStr)) {return;}userId = Integer.parseInt(userIdStr);}Map<String, Object> params = new HashMap<>(2);params.put("type", "menu");params.put("userId", userId);environment.setVariable("menus", builder.build().wrap(resourcesService.listUserResources(params)));break;default:break;}}templateDirectiveBody.render(environment.getOut());}
}

7.RedisConfig配置

package com.zyd.shiro.framework.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;import java.lang.reflect.Method;/*** Redis配置类*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {/*** 缓存数据时Key的生成器,可以依据业务和技术场景自行定制* 调用 @Cacheable(cacheNames = "users",keyGenerator = "keyGenerator")*/@Bean@Override@Deprecatedpublic KeyGenerator keyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuilder sb = new StringBuilder();//类名+方法名sb.append(target.getClass().getName());sb.append("." + method.getName());for (Object obj : params) {sb.append(String.valueOf(obj));}System.out.println("============:" + sb.toString());return sb.toString();}};}@SuppressWarnings("rawtypes")@Beanpublic CacheManager cacheManager(RedisTemplate redisTemplate) {RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);//设置缓存过期时间:秒cacheManager.setDefaultExpiration(30 * 24 * 60 * 60);return cacheManager;}@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om);template.setValueSerializer(jackson2JsonRedisSerializer);template.setKeySerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();return template;}
}

8.静态资源配置


package com.zyd.shiro.framework.config;import com.zyd.shiro.framework.interceptor.RememberAuthenticationInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;/*** @author * @desc 静态资源配置*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {@Autowiredprivate RememberAuthenticationInterceptor rememberAuthenticationInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(rememberAuthenticationInterceptor).excludePathPatterns("/passport/**", "/error/**", "/assets/**", "favicon.ico").addPathPatterns("/**");}
}

9.自定义的异常页面配置

package com.zyd.shiro.framework.config;import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;/*** 自定义的异常页面配置*/
@Component
public class ErrorPagesConfig {/*** 自定义异常处理路径*/@Beanpublic EmbeddedServletContainerCustomizer containerCustomizer() {return new EmbeddedServletContainerCustomizer() {@Overridepublic void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) {configurableEmbeddedServletContainer.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/error/400"));configurableEmbeddedServletContainer.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/error/401"));configurableEmbeddedServletContainer.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN, "/error/403"));configurableEmbeddedServletContainer.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"));configurableEmbeddedServletContainer.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"));configurableEmbeddedServletContainer.addErrorPages(new ErrorPage(Throwable.class, "/error/500"));}};}
}

10.ShiroConfig配置

package com.zyd.shiro.framework.config;import com.zyd.shiro.business.service.ShiroService;
import com.zyd.shiro.business.shiro.credentials.RetryLimitCredentialsMatcher;
import com.zyd.shiro.business.shiro.realm.ShiroRealm;
import com.zyd.shiro.framework.property.RedisProperties;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.annotation.Order;import java.util.Map;/*** Shiro配置类*/
@Configuration
@Order(-1)
public class ShiroConfig {@Autowiredprivate ShiroService shiroService;@Autowiredprivate RedisProperties redisProperties;@Bean(name = "lifecycleBeanPostProcessor")public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** ShiroFilterFactoryBean 处理拦截资源文件问题。* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager* Filter Chain定义说明* 1、一个URL可以配置多个Filter,使用逗号分隔* 2、当设置多个过滤器时,全部验证通过,才视为通过* 3、部分过滤器可指定参数,如perms,roles*/@Bean(name = "shiroFilter")public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 必须设置 SecurityManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面shiroFilterFactoryBean.setLoginUrl("/passport/login/");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/index");// 未授权界面;shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");// 配置数据库中的resourceMap<String, String> filterChainDefinitionMap = shiroService.loadFilterChainDefinitions();shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}@Bean(name = "securityManager")public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm authRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 设置realm.securityManager.setRealm(authRealm);securityManager.setCacheManager(redisCacheManager());// 自定义session管理 使用redissecurityManager.setSessionManager(sessionManager());// 注入记住我管理器securityManager.setRememberMeManager(rememberMeManager());return securityManager;}@Bean(name = "shiroRealm")public ShiroRealm shiroRealm(@Qualifier("credentialsMatcher") RetryLimitCredentialsMatcher matcher) {ShiroRealm shiroRealm = new ShiroRealm();shiroRealm.setCredentialsMatcher(credentialsMatcher());return shiroRealm;}/*** 凭证匹配器* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了* 所以我们需要修改下doGetAuthenticationInfo中的代码;*/@Bean(name = "credentialsMatcher")public RetryLimitCredentialsMatcher credentialsMatcher() {return new RetryLimitCredentialsMatcher();}/*** 开启shiro aop注解支持.* 使用代理方式;所以需要开启代码支持;*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** 支持注解实现权限控制*/@Bean@DependsOn("lifecycleBeanPostProcessor")public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();creator.setProxyTargetClass(true);return creator;}/*** 配置shiro redisManager* 使用的是shiro-redis开源插件*/public RedisManager redisManager() {RedisManager redisManager = new RedisManager();redisManager.setHost(redisProperties.getHost());redisManager.setPort(redisProperties.getPort());redisManager.setDatabase(redisProperties.getDatabase());redisManager.setTimeout(redisProperties.getTimeout());redisManager.setPassword(redisProperties.getPassword());return redisManager;}/*** RedisSessionDAO shiro sessionDao层的实现 通过redis* 使用的是shiro-redis开源插件*///@Beanpublic RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();redisSessionDAO.setRedisManager(redisManager());return redisSessionDAO;}/*** shiro session的管理*/@Beanpublic DefaultWebSessionManager sessionManager() {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setGlobalSessionTimeout(redisProperties.getExpire() * 1000L);sessionManager.setSessionDAO(redisSessionDAO());return sessionManager;}/*** cacheManager 缓存 redis实现* 使用的是shiro-redis开源插件** @return*/@Beanpublic RedisCacheManager redisCacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();redisCacheManager.setRedisManager(redisManager());return redisCacheManager;}@Beanpublic MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager) {MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean();bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");bean.setArguments(securityManager);return bean;}/*** cookie对象;*/public SimpleCookie rememberMeCookie() {// 这个参数是cookie的名称,对应前端的checkbox的name = rememberMeSimpleCookie simpleCookie = new SimpleCookie("rememberMe");// 记住我cookie生效时间30天 ,单位秒。 注释掉,默认永久不过期 2018-07-15simpleCookie.setMaxAge(redisProperties.getExpire());return simpleCookie;}/*** cookie管理对象;记住我功能*/public CookieRememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)cookieRememberMeManager.setCipherKey(Base64.decode("1QWLxg+NYmxraMoxAXu/Iw=="));return cookieRememberMeManager;}
}

RedisProperties

package com.zyd.shiro.framework.property;import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;/*** redis属性配置文件*/
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
@EqualsAndHashCode(callSuper = false)
@Order(-1)
public class RedisProperties {private Integer database;private String host;private Integer port;private String password;private Integer timeout;/*** 默认30天 = 2592000s*/private Integer expire = 2592000;}

11.用到的工具类

(1)SpringContextHolder


package com.zyd.shiro.framework.holder;import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;/*** @author zjjlive)* @version 1.0* @website https://www.foreknow.me* @date 2018/4/16 16:26* @since Spring上下文*/
@Component
public class SpringContextHolder implements ApplicationContextAware {private static ApplicationContext appContext = null;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {if (appContext == null) {appContext = applicationContext;}}/*** 通过name获取 Bean.** @param name* @return*/public static Object getBean(String name) {return appContext.getBean(name);}/*** 通过class获取Bean.** @param clazz* @param <T>* @return*/public static <T> T getBean(Class<T> clazz) {return appContext.getBean(clazz);}/*** 通过name,以及Clazz返回指定的Bean** @param name* @param clazz* @param <T>* @return*/public static <T> T getBean(String name, Class<T> clazz) {return appContext.getBean(name, clazz);}}

(2)RequestHolder

package com.zyd.shiro.framework.holder;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;/**** @author zjjlive)* @version 1.0* @website https://www.foreknow.me* @date 2018/4/16 16:26* @since 1.0*/
public class RequestHolder {private static final Logger LOGGER = LoggerFactory.getLogger(RequestHolder.class);/*** 获取request** @return HttpServletRequest*/public static HttpServletRequest getRequest() {LOGGER.debug("getRequest -- Thread id :{}, name : {}", Thread.currentThread().getId(), Thread.currentThread().getName());return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();}/*** 获取Response** @return HttpServletRequest*/public static HttpServletResponse getResponse() {LOGGER.debug("getResponse -- Thread id :{}, name : {}", Thread.currentThread().getId(), Thread.currentThread().getName());return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();}/*** 获取session** @return HttpSession*/public static HttpSession getSession() {LOGGER.debug("getSession -- Thread id :{}, name : {}", Thread.currentThread().getId(), Thread.currentThread().getName());return getRequest().getSession();}/*** 获取session的Attribute** @param name*         session的key* @return Object*/public static Object getSession(String name) {LOGGER.debug("getSession -- Thread id :{}, name : {}", Thread.currentThread().getId(), Thread.currentThread().getName());return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getAttribute(name, RequestAttributes.SCOPE_SESSION);}/*** 添加session** @param name* @param value*/public static void setSession(String name, Object value) {LOGGER.debug("setSession -- Thread id :{}, name : {}", Thread.currentThread().getId(), Thread.currentThread().getName());((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).setAttribute(name, value, RequestAttributes.SCOPE_SESSION);}/*** 清除指定session** @param name* @return void*/public static void removeSession(String name) {LOGGER.debug("removeSession -- Thread id :{}, name : {}", Thread.currentThread().getId(), Thread.currentThread().getName());((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).removeAttribute(name, RequestAttributes.SCOPE_SESSION);}/*** 获取所有session key** @return String[]*/public static String[] getSessionKeys() {LOGGER.debug("getSessionKeys -- Thread id :{}, name : {}", Thread.currentThread().getId(), Thread.currentThread().getName());return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getAttributeNames(RequestAttributes.SCOPE_SESSION);}
}

(3)AesUtil


package com.zyd.shiro.util;import org.apache.commons.codec.binary.Base64;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;/*** AesUtil*/
public class AesUtil {private static final String KEY_ALGORITHM = "AES";private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";/*** AES加密** @param passwd  加密的密钥* @param content 需要加密的字符串* @return 返回Base64转码后的加密数据* @throws Exception*/public static String encrypt(String passwd, String content) throws Exception {// 创建密码器Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);byte[] byteContent = content.getBytes("utf-8");// 初始化为加密模式的密码器cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(passwd));// 加密byte[] result = cipher.doFinal(byteContent);//通过Base64转码返回return Base64.encodeBase64String(result);}/*** AES解密** @param passwd    加密的密钥* @param encrypted 已加密的密文* @return 返回解密后的数据* @throws Exception*/public static String decrypt(String passwd, String encrypted) throws Exception {//实例化Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);//使用密钥初始化,设置为解密模式cipher.init(Cipher.DECRYPT_MODE, getSecretKey(passwd));//执行操作byte[] result = cipher.doFinal(Base64.decodeBase64(encrypted));return new String(result, "utf-8");}/*** 生成加密秘钥** @return*/private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException {//返回生成指定算法密钥生成器的 KeyGenerator 对象KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);// javax.crypto.BadPaddingException: Given final block not properly padded解决方案// https://www.cnblogs.com/zempty/p/4318902.html - 用此法解决的// https://www.cnblogs.com/digdeep/p/5580244.html - 留作参考吧SecureRandom random = SecureRandom.getInstance("SHA1PRNG");random.setSeed(password.getBytes());//AES 要求密钥长度为 128kg.init(128, random);//生成一个密钥SecretKey secretKey = kg.generateKey();// 转换为AES专用密钥return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);}}

(4)AesUtil


package com.zyd.shiro.util;import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;/*** 获取IP的工具类** @author zjjlive)* @version 1.0* @website https://www.foreknow.me* @date 2018/4/16 16:26* @since 1.0*/
public class IpUtil {/*** 获取真实IP** @param request* @return*/public static String getRealIp(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");return checkIp(ip) ? ip : (checkIp(ip = request.getHeader("Proxy-Client-IP")) ? ip : (checkIp(ip = request.getHeader("WL-Proxy-Client-IP")) ? ip :request.getRemoteAddr()));}/*** 校验IP** @param ip* @return*/private static boolean checkIp(String ip) {return !StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip);}
}

(5)Md5Util


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.security.MessageDigest;/*** MD5加密工具类** @author zjjlive)* @version 1.0* @website https://www.foreknow.me* @date 2018/4/16 16:26* @since 1.0*/
public class Md5Util {private static final Logger log = LoggerFactory.getLogger(Md5Util.class);/*** 通过盐值对字符串进行MD5加密** @param param 需要加密的字符串* @param salt  盐值* @return*/public static String md5(String param, String salt) {return md5(param + salt);}/*** 加密字符串** @param s 字符串* @return*/public static String md5(String s) {char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};try {byte[] btInput = s.getBytes();MessageDigest mdInst = MessageDigest.getInstance("MD5");mdInst.update(btInput);byte[] md = mdInst.digest();int j = md.length;char[] str = new char[j * 2];int k = 0;for (byte byte0 : md) {str[k++] = hexDigits[byte0 >>> 4 & 0xf];str[k++] = hexDigits[byte0 & 0xf];}return new String(str);} catch (Exception e) {log.error("MD5生成失败", e);return null;}}
}

(6)PasswordUtil

package com.zyd.shiro.util;import com.zyd.shiro.business.consts.CommonConst;/*** @author zjjlive)* @version 1.0* @website https://www.foreknow.me* @date 2018/4/16 16:26*/
public class PasswordUtil {/*** AES 加密* @param password*         未加密的密码* @param salt*         盐值,默认使用用户名就可* @return* @throws Exception*/public static String encrypt(String password, String salt) throws Exception {return AesUtil.encrypt(Md5Util.md5(salt + CommonConst.ZYD_SECURITY_KEY), password);}/*** AES 解密* @param encryptPassword*         加密后的密码* @param salt*         盐值,默认使用用户名就可* @return* @throws Exception*/public static String decrypt(String encryptPassword, String salt) throws Exception {return AesUtil.decrypt(Md5Util.md5(salt + CommonConst.ZYD_SECURITY_KEY), encryptPassword);}
}

(7)SessionUtil


package com.zyd.shiro.util;import com.zyd.shiro.business.consts.SessionConst;
import com.zyd.shiro.business.entity.User;
import com.zyd.shiro.framework.holder.RequestHolder;import java.util.UUID;/*** Session工具类** @author zjjlive)* @version 1.0* @website https://www.foreknow.me* @date 2018/4/16 16:26* @since 1.0*/
public class SessionUtil {/*** 获取session中的用户信息** @return User*/public static User getUser() {return (User) RequestHolder.getSession(SessionConst.USER_SESSION_KEY);}/*** 添加session** @param user*/public static void setUser(User user) {RequestHolder.setSession(SessionConst.USER_SESSION_KEY, user);}/*** 删除session信息*/public static void removeUser() {RequestHolder.removeSession(SessionConst.USER_SESSION_KEY);}/*** 获取session中的Token信息** @return String*/public static String getToken(String key) {return (String) RequestHolder.getSession(key);}/*** 添加Token*/public static void setToken(String key) {RequestHolder.setSession(key, UUID.randomUUID().toString());}/*** 删除Token信息*/public static void removeToken(String key) {RequestHolder.removeSession(key);}/*** 删除所有的session信息*/public static void removeAllSession() {String[] keys = RequestHolder.getSessionKeys();if (keys != null && keys.length > 0) {for (String key : keys) {RequestHolder.removeSession(key);}}}
}

(7)ResultUtil

package com.zyd.shiro.util;import com.github.pagehelper.PageInfo;
import com.zyd.shiro.business.consts.CommonConst;
import com.zyd.shiro.business.enums.ResponseStatus;
import com.zyd.shiro.framework.object.PageResult;
import com.zyd.shiro.framework.object.ResponseVO;
import org.springframework.web.servlet.ModelAndView;import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** 接口返回工具类,支持ModelAndView、ResponseVO、PageResult** @author zjjlive)*/
public class ResultUtil {public static ModelAndView view(String view) {return new ModelAndView(view);}public static ModelAndView view(String view, Map<String, Object> model) {return new ModelAndView(view, model);}public static ModelAndView redirect(String view) {return new ModelAndView("redirect:" + view);}public static ResponseVO error(int code, String message) {return vo(code, message, null);}public static ResponseVO error(ResponseStatus status) {return vo(status.getCode(), status.getMessage(), null);}public static ResponseVO error(String message) {return vo(CommonConst.DEFAULT_ERROR_CODE, message, null);}public static ResponseVO success(String message, Object data) {return vo(CommonConst.DEFAULT_SUCCESS_CODE, message, data);}public static ResponseVO success(String message) {return success(message, null);}public static ResponseVO success(ResponseStatus status) {return vo(status.getCode(), status.getMessage(), null);}public static ResponseVO vo(int code, String message, Object data) {return new ResponseVO<>(code, message, data);}public static PageResult tablePage(Long total, List<?> list) {return new PageResult(total, list);}public static PageResult tablePage(PageInfo info) {if (info == null) {return new PageResult(0L, new ArrayList());}return tablePage(info.getTotal(), info.getList());}}

返回前端工具类


package com.zyd.shiro.framework.object;import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.zyd.shiro.business.enums.ResponseStatus;
import lombok.Data;
import lombok.EqualsAndHashCode;import java.util.Collection;
import java.util.List;/*** controller返回json*/
@Data
@EqualsAndHashCode(callSuper = false)
public class ResponseVO<T> {private Integer status;private String message;private T data;public ResponseVO(Integer status, String message, T data) {this.status = status;this.message = message;this.data = data;}public ResponseVO(ResponseStatus status, T data) {this(status.getCode(), status.getMessage(), data);}public String toJson() {T t = this.getData();if (t instanceof List || t instanceof Collection) {return JSONObject.toJSONString(this, SerializerFeature.WriteNullListAsEmpty);} else {return JSONObject.toJSONString(this, SerializerFeature.WriteMapNullValue);}}
}

bootstrap table工具类

package com.zyd.shiro.framework.object;import lombok.Data;
import lombok.EqualsAndHashCode;import java.util.List;/*** bootstrap table用到的返回json格式*/
@Data
@EqualsAndHashCode(callSuper = false)
public class PageResult {private Long total;private List rows;public PageResult() {}public PageResult(Long total, List rows) {this.total = total;this.rows = rows;}}

12.Shiro-权限相关的业务处理mapper

1.通用mapper

package com.zyd.shiro.plugin;import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;/*** 通用Mapper {特别注意,该接口不能被扫描到,否则会出错}*/
public interface BaseMapper<T> extends Mapper<T>, MySqlMapper<T> {}

2.用户Mapper

package com.zyd.shiro.persistence.mapper;import com.zyd.shiro.business.vo.UserConditionVO;
import com.zyd.shiro.persistence.beans.SysUser;
import com.zyd.shiro.plugin.BaseMapper;
import org.springframework.stereotype.Repository;import java.util.List;/*** @author* @desc : SysUserMapper*/
@Repository
public interface SysUserMapper extends BaseMapper<SysUser> {/*** @Desc 分页查询用户* @Param*/List<SysUser> findPageBreakByCondition(UserConditionVO vo);/*** @Desc 分页查询用户角色* @Param*/List<SysUser> listByRoleId(Long roleId);}

3.角色Mapper


package com.zyd.shiro.persistence.mapper;import com.zyd.shiro.business.vo.RoleConditionVO;
import com.zyd.shiro.persistence.beans.SysRole;
import com.zyd.shiro.plugin.BaseMapper;
import org.springframework.stereotype.Repository;import java.util.List;/*** @author*/
@Repository
public interface SysRoleMapper extends BaseMapper<SysRole> {/*** 分页查询** @param vo* @return*/List<SysRole> findPageBreakByCondition(RoleConditionVO vo);/*** 该节代码参考自http://blog.csdn.net/poorcoder_/article/details/71374002* 感谢网友** @param userId* @return*/List<SysRole> queryRoleListWithSelected(Integer userId);List<SysRole> listRolesByUserId(Long userId);
}

4.资源mapper


package com.zyd.shiro.persistence.mapper;import com.zyd.shiro.business.vo.ResourceConditionVO;
import com.zyd.shiro.persistence.beans.SysResources;
import com.zyd.shiro.plugin.BaseMapper;
import org.springframework.stereotype.Repository;import java.util.List;
import java.util.Map;/*** @author* 资源mapper*/
@Repository
public interface SysResourceMapper extends BaseMapper<SysResources> {/*** 分页查询** @param vo* @return*/List<SysResources> findPageBreakByCondition(ResourceConditionVO vo);List<SysResources> listUserResources(Map<String, Object> map);/*** 该节代码参考自http://blog.csdn.net/poorcoder_/article/details/71374002* 感谢网友** @param rid* @return*/List<SysResources> queryResourcesListWithSelected(Long rid);List<SysResources> listUrlAndPermission();List<SysResources> listAllAvailableMenu();List<SysResources> listMenuResourceByPid(Long pid);List<SysResources> listByUserId(Long userId);
}

5.用户和角色mapper

package com.zyd.shiro.persistence.mapper;import com.zyd.shiro.persistence.beans.SysUserRole;
import com.zyd.shiro.plugin.BaseMapper;
import org.springframework.stereotype.Repository;import java.util.List;/*** @author*/
@Repository
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {List<Integer> findUserIdByRoleId(Integer roleId);
}

6.用户和资源mapper


package com.zyd.shiro.persistence.mapper;import com.zyd.shiro.persistence.beans.SysRoleResources;
import com.zyd.shiro.plugin.BaseMapper;
import org.springframework.stereotype.Repository;/*** @author zjjlive)* @version 1.0* @website https://www.foreknow.me* @date 2018/4/16 16:26* @since 1.0*/
@Repository
public interface SysRoleResourcesMapper extends BaseMapper<SysRoleResources> {
}

13.Shiro-权限相关的业务处理mapper.xml

SysUserMapper.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="com.zyd.shiro.persistence.mapper.SysUserMapper"><resultMap id="rm" type="com.zyd.shiro.persistence.beans.SysUser"><result property="id" jdbcType="BIGINT" column="id"/><result property="username" jdbcType="VARCHAR" column="username"/><result property="password" jdbcType="VARCHAR" column="password"/><result property="nickname" jdbcType="VARCHAR" column="nickname"/><result property="mobile" jdbcType="VARCHAR" column="mobile"/><result property="email" jdbcType="VARCHAR" column="email"/><result property="qq" jdbcType="VARCHAR" column="qq"/><result property="birthday" jdbcType="DATE" column="birthday"/><result property="gender" jdbcType="TINYINT" column="gender"/><result property="avatar" jdbcType="VARCHAR" column="avatar"/><result property="userType" jdbcType="CHAR" column="user_type"/><result property="regIp" jdbcType="VARCHAR" column="reg_ip"/><result property="lastLoginIp" jdbcType="VARCHAR" column="last_login_ip"/><result property="lastLoginTime" jdbcType="TIMESTAMP" column="last_login_time"/><result property="loginCount" jdbcType="INTEGER" column="login_count"/><result property="remark" jdbcType="VARCHAR" column="remark"/><result property="createTime" jdbcType="TIMESTAMP" column="create_time"/><result property="updateTime" jdbcType="TIMESTAMP" column="update_time"/></resultMap><select id="findPageBreakByCondition" parameterType="com.zyd.shiro.business.vo.UserConditionVO" resultMap="rm">SELECTs.*FROMsys_user sWHERE1 = 1<if test="keywords != null and keywords != '' ">AND(s.nickname LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') ors.mobile  LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') ors.username LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') ors.password LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') ors.email LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') ors.qq LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') ors.remark LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') or)</if><!-- 查询用户信息 --><if test="user != null"><if test="user.id!=null">AND s.id = #{user.id}</if><if test="user.gender!=null">AND s.gender = #{user.gender}</if><if test="user.userType!=null">AND s.user_type = #{user.userType}</if><if test="user.username!=null">AND s.username = #{user.username}</if><if test="user.password!=null">AND s.password = #{user.password}</if><if test="user.lastLoginIp!=null">AND s.last_login_ip = #{user.lastLoginIp}</if><if test="user.lastLoginTime!=null">AND s.last_login_time = #{user.lastLoginTime}</if></if>GROUP BYs.idORDER BYs.create_time DESC</select><select id="listByRoleId" parameterType="Long" resultMap="rm">SELECTs.id,s.username,s.nicknameFROMsys_user sINNER JOIN sys_user_role sur ON  sur.user_id = s.idWHEREsur.role_id = #{roleId}</select></mapper>

SysRoleMapper.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="com.zyd.shiro.persistence.mapper.SysRoleMapper"><resultMap id="rm" type="com.zyd.shiro.persistence.beans.SysRole"><result property="id" jdbcType="BIGINT" column="id"/><result property="name" jdbcType="VARCHAR" column="name"/><result property="description" jdbcType="VARCHAR" column="description"/><result property="available" jdbcType="BIT" column="available"/><result property="createTime" jdbcType="TIMESTAMP" column="create_time"/><result property="updateTime" jdbcType="TIMESTAMP" column="update_time"/></resultMap><select id="findPageBreakByCondition" parameterType="com.zyd.shiro.business.vo.RoleConditionVO" resultMap="rm">SELECTcom.*FROMsys_role comWHERE1 = 1<if test="keywords !=null and keywords != ''">AND (com.description LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%'))</if>ORDER BYcom.create_time DESC</select><select id="queryRoleListWithSelected" parameterType="Integer" resultMap="rm">SELECT r.id,r.name,r.description,(CASE WHEN (SELECT ur.role_idFROM sys_user_role urWHERE ur.user_id = #{userId}AND ur.role_id = r.id)THEN 1ELSE 0END) AS selectedFROM sys_role rWHERE r.available = 1</select><select id="listRolesByUserId" parameterType="Long" resultMap="rm">SELECT r.id,r.name,r.descriptionFROM sys_role rINNER JOIN sys_user_role ur ON ur.role_id = r.idWHERE ur.user_id = #{userId}AND r.available = 1</select>
</mapper>

SysResourceMapper.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="com.zyd.shiro.persistence.mapper.SysResourceMapper"><resultMap id="rm" type="com.zyd.shiro.persistence.beans.SysResources"><result property="id" jdbcType="BIGINT" column="id"/><result property="name" jdbcType="VARCHAR" column="name"/><result property="type" jdbcType="VARCHAR" column="type"/><result property="url" jdbcType="VARCHAR" column="url"/><result property="permission" jdbcType="VARCHAR" column="permission"/><result property="parentId" jdbcType="BIGINT" column="parent_id"/><result column="sort" jdbcType="INTEGER" property="sort"/><result property="external" jdbcType="BIT" column="external"/><result property="available" jdbcType="BIT" column="available"/><result property="icon" jdbcType="VARCHAR" column="icon"/><result property="checked" jdbcType="VARCHAR" column="checked"/><association property="parent" javaType="com.zyd.shiro.persistence.beans.SysResources"><result property="id" jdbcType="BIGINT" column="parent_id"/><result property="name" jdbcType="VARCHAR" column="parent_name"/><result property="type" jdbcType="VARCHAR" column="parent_type"/><result property="url" jdbcType="VARCHAR" column="parent_url"/><result property="parentId" jdbcType="BIGINT" column="parent_parent_id"/><result property="permission" jdbcType="VARCHAR" column="parent_permission"/><result property="available" jdbcType="BIT" column="parent_available"/><result property="icon" jdbcType="VARCHAR" column="parent_icon"/></association><collection property="nodes" column="node_id" javaType="ArrayList" ofType="com.zyd.shiro.persistence.beans.SysResources"><result property="id" jdbcType="BIGINT" column="node_id"/><result property="name" jdbcType="VARCHAR" column="node_name"/><result property="type" jdbcType="VARCHAR" column="node_type"/><result property="url" jdbcType="VARCHAR" column="node_url"/><result property="parentId" jdbcType="BIGINT" column="node_parent_id"/><result property="permission" jdbcType="VARCHAR" column="node_permission"/><result property="available" jdbcType="BIT" column="node_available"/><result property="external" jdbcType="BIT" column="node_external"/><result property="icon" jdbcType="VARCHAR" column="node_icon"/></collection></resultMap><select id="findPageBreakByCondition" parameterType="com.zyd.shiro.business.vo.ResourceConditionVO" resultMap="rm">SELECTcom.*,f.id parent_id,f.`name` parent_name,f.`icon` parent_icon,f.type parent_typeFROMsys_resources comLEFT JOIN sys_resources f ON com.parent_id = f.idWHERE1 = 1<if test="keywords !=null and keywords != ''">AND (com.name LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') ORcom.url LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') ORcom.permission LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%'))</if>ORDER bycom.parent_id ASC,com.name DESC</select><select id="listUrlAndPermission" resultMap="rm">SELECTurl,permissionFROMsys_resourcesWHEREurl IS NOT NULLORDER BYsort ASC</select><select id="listUserResources" parameterType="java.util.HashMap" resultMap="rm">SELECTre.id,re.`name`,re.parent_id,re.url,re.permission,re.icon,re.external,re.`available`,node.id AS node_id,node.`name` AS node_name,node.`type` AS node_type,node.`url` AS node_url,node.parent_id AS node_parent_id,node.`permission` AS node_permission,node.`available` AS node_available,node.`external` AS node_external,node.icon AS node_iconFROMsys_resources reLEFT JOIN sys_role_resources rr ON re.id = rr.resources_idLEFT JOIN sys_user_role ur ON rr.role_id = ur.role_idLEFT JOIN sys_resources node ON node.parent_id = re.id AND node.available = 1WHERE(re.parent_id = 0 OR re.parent_id IS NULL )AND re.available = 1AND ur.user_id = #{userId}<if test="type != null">AND re.type= #{type}</if>ORDER BYre.sort ASC,node.sort ASC</select><select id="queryResourcesListWithSelected" parameterType="Long" resultMap="rm">SELECTre.id,re.`name`,re.parent_id,re.url,re.type,re.icon,(CASEWHEN EXISTS (SELECT1FROMsys_role_resources rrWHERErr.resources_id = re.idAND rr.role_id = #{rid})THEN'true'ELSE'false'END) AS checkedFROMsys_resources reORDER BYre.sort ASC</select><select id="listAllAvailableMenu" resultMap="rm">SELECTr.id,r.`name`,node.id AS node_id,node.`name` AS node_name,node.parent_idFROMsys_resources rLEFT JOIN sys_resources node ON (node.parent_id = r.idAND node.available = 1AND node.type = 'menu')WHEREr.available = 1AND r.type = 'menu'AND (r.url IS NULL OR r.url = '')AND (r.permission IS NULL OR r.permission = '')ORDER BYr.sort ASC,node.sort ASC</select><select id="listMenuResourceByPid" parameterType="Long" resultMap="rm">SELECTre.id,re.`name`FROMsys_resources reWHERE re.parent_id = #{pid}ORDER BYre.sort ASC</select><select id="listByUserId" parameterType="Long" resultMap="rm">SELECTre.id,re.`name`,re.parent_id,re.url,re.permission,re.icon,re.sortFROMsys_resources reINNER JOIN sys_role_resources rr ON re.id = rr.resources_idINNER JOIN sys_user_role ur ON rr.role_id = ur.role_idWHEREur.user_id = #{userId}AND re.available = 1ORDER BYre.parent_id ASC,re.sort ASC</select>
</mapper>

SysUserRoleMapper.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="com.zyd.shiro.persistence.mapper.SysUserRoleMapper"><resultMap id="rm" type="com.zyd.shiro.persistence.beans.SysUserRole"><result property="id" jdbcType="BIGINT" column="id"/><result property="userId" jdbcType="BIGINT" column="user_id"/><result property="roleId" jdbcType="BIGINT" column="role_id"/><result property="createTime" jdbcType="TIMESTAMP" column="create_time"/><result property="updateTime" jdbcType="TIMESTAMP" column="update_time"/></resultMap><select id="findUserIdByRoleId" parameterType="Integer" resultType="Integer">SELECTuser_idFROMsys_user_roleWHERErole_id = #{roleId}</select>
</mapper>

14.Shiro-权限相关的业务处理Service

ShiroService接口和实现类

package com.zyd.shiro.business.service;import com.zyd.shiro.business.entity.User;import java.util.Map;/*** Shiro-权限相关的业务处理* @author )*/
public interface ShiroService {/*** 初始化权限*/Map<String, String> loadFilterChainDefinitions();/*** 重新加载权限*/void updatePermission();/*** 重新加载用户权限** @param user*/void reloadAuthorizingByUserId(User user);/*** 重新加载所有拥有roleId角色的用户的权限** @param roleId*/void reloadAuthorizingByRoleId(Long roleId);}
package com.zyd.shiro.business.service.impl;import com.zyd.shiro.business.entity.Resources;
import com.zyd.shiro.business.entity.User;
import com.zyd.shiro.business.service.ShiroService;
import com.zyd.shiro.business.service.SysResourcesService;
import com.zyd.shiro.business.service.SysUserService;
import com.zyd.shiro.business.shiro.realm.ShiroRealm;
import com.zyd.shiro.framework.holder.SpringContextHolder;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** Shiro-权限相关的业务处理*/
@Service
public class ShiroServiceImpl implements ShiroService {private static final Logger LOG = LoggerFactory.getLogger(ShiroService.class);@Autowiredprivate SysResourcesService resourcesService;@Autowiredprivate SysUserService userService;/*** 初始化权限*/@Overridepublic Map<String, String> loadFilterChainDefinitions() {/*配置访问权限- anon:所有url都都可以匿名访问- authc: 需要认证才能进行访问(此处指所有非匿名的路径都需要登陆才能访问)- user:配置记住我或认证通过可以访问*/Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了filterChainDefinitionMap.put("/passport/logout", "logout");filterChainDefinitionMap.put("/passport/login", "anon");filterChainDefinitionMap.put("/passport/signin", "anon");filterChainDefinitionMap.put("/favicon.ico", "anon");filterChainDefinitionMap.put("/error", "anon");filterChainDefinitionMap.put("/assets/**", "anon");// 加载数据库中配置的资源权限列表List<Resources> resourcesList = resourcesService.listUrlAndPermission();for (Resources resources : resourcesList) {if (!StringUtils.isEmpty(resources.getUrl()) && !StringUtils.isEmpty(resources.getPermission())) {String permission = "perms[" + resources.getPermission() + "]";filterChainDefinitionMap.put(resources.getUrl(), permission);}}// 本例子中并不存在什么特别关键的操作,所以直接使用user认证。如果有朋友是参考本例子的shiro开发其他安全功能(比如支付等)时,建议针对这类操作使用authc权限 by yadong.zhangfilterChainDefinitionMap.put("/**", "user");return filterChainDefinitionMap;}/*** 重新加载权限*/@Overridepublic void updatePermission() {ShiroFilterFactoryBean shirFilter = SpringContextHolder.getBean(ShiroFilterFactoryBean.class);synchronized (shirFilter) {AbstractShiroFilter shiroFilter = null;try {shiroFilter = (AbstractShiroFilter) shirFilter.getObject();} catch (Exception e) {throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!");}PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();// 清空老的权限控制manager.getFilterChains().clear();shirFilter.getFilterChainDefinitionMap().clear();shirFilter.setFilterChainDefinitionMap(loadFilterChainDefinitions());// 重新构建生成Map<String, String> chains = shirFilter.getFilterChainDefinitionMap();for (Map.Entry<String, String> entry : chains.entrySet()) {String url = entry.getKey();String chainDefinition = entry.getValue().trim().replace(" ", "");manager.createChain(url, chainDefinition);}}}/*** 重新加载用户权限** @param user*/@Overridepublic void reloadAuthorizingByUserId(User user) {RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();ShiroRealm shiroRealm = (ShiroRealm) rsm.getRealms().iterator().next();Subject subject = SecurityUtils.getSubject();String realmName = subject.getPrincipals().getRealmNames().iterator().next();SimplePrincipalCollection principals = new SimplePrincipalCollection(user, realmName);subject.runAs(principals);shiroRealm.getAuthorizationCache().remove(subject.getPrincipals());subject.releaseRunAs();LOG.info("用户[{}]的权限更新成功!!", user.getUsername());}/*** 重新加载所有拥有roleId角色的用户的权限** @param roleId*/@Overridepublic void reloadAuthorizingByRoleId(Long roleId) {List<User> userList = userService.listByRoleId(roleId);if (CollectionUtils.isEmpty(userList)) {return;}for (User user : userList) {reloadAuthorizingByUserId(user);}}}

AbstractService

package com.zyd.shiro.framework.object;import java.util.List;/***/
public interface AbstractService<T, PK> {/*** 保存一个实体,null的属性不会保存,会使用数据库默认值** @param entity* @return*/T insert(T entity);/*** 批量插入,支持批量插入的数据库可以使用,例如MySQL,H2等,另外该接口限制实体包含id属性并且必须为自增列** @param entities*/void insertList(List<T> entities);/*** 根据主键字段进行删除,方法参数必须包含完整的主键属性** @param primaryKey* @return*/boolean removeByPrimaryKey(PK primaryKey);/*** 根据主键更新实体全部字段,null值会被更新** @param entity* @return*/boolean update(T entity);/*** 根据主键更新属性不为null的值** @param entity* @return*/boolean updateSelective(T entity);/*** 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号** @param primaryKey* @return*/T getByPrimaryKey(PK primaryKey);/*** 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号** @param entity* @return*/T getOneByEntity(T entity);/*** 查询全部结果,listByEntity(null)方法能达到同样的效果** @return*/List<T> listAll();/*** 根据实体中的属性值进行查询,查询条件使用等号** @param entity* @return*/List<T> listByEntity(T entity);
}

SysUserService接口和实现类

package com.zyd.shiro.business.service;import com.github.pagehelper.PageInfo;
import com.zyd.shiro.business.entity.User;
import com.zyd.shiro.business.vo.UserConditionVO;
import com.zyd.shiro.framework.object.AbstractService;import java.util.List;/*** 用户* @author*/
public interface SysUserService extends AbstractService<User, Long> {/*** 分页查询** @param vo* @return*/PageInfo<User> findPageBreakByCondition(UserConditionVO vo);/*** 更新用户最后一次登录的状态信息** @param user* @return*/User updateUserLastLoginInfo(User user);/*** 根据用户名查找** @param userName* @return*/User getByUserName(String userName);/*** 通过角色Id获取用户列表** @param roleId* @return*/List<User> listByRoleId(Long roleId);}
package com.zyd.shiro.business.service.impl;import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zyd.shiro.business.entity.User;
import com.zyd.shiro.business.enums.UserStatusEnum;
import com.zyd.shiro.business.service.SysRoleService;
import com.zyd.shiro.business.service.SysUserService;
import com.zyd.shiro.business.vo.UserConditionVO;
import com.zyd.shiro.framework.exception.CustomException;
import com.zyd.shiro.framework.holder.RequestHolder;
import com.zyd.shiro.persistence.beans.SysUser;
import com.zyd.shiro.persistence.mapper.SysUserMapper;
import com.zyd.shiro.util.IpUtil;
import com.zyd.shiro.util.PasswordUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import java.util.ArrayList;
import java.util.Date;
import java.util.List;/*** 用户*/
@Service
public class SysUserServiceImpl implements SysUserService {private static final Logger LOGGER = LoggerFactory.getLogger(SysUserServiceImpl.class);@Autowiredprivate SysUserMapper sysUserMapper;@Autowiredprivate SysRoleService roleService;@Override@Transactional(rollbackFor = Exception.class)public User insert(User user) {Assert.notNull(user, "User不可为空!");user.setUpdateTime(new Date());user.setCreateTime(new Date());user.setRegIp(IpUtil.getRealIp(RequestHolder.getRequest()));user.setStatus(UserStatusEnum.NORMAL.getCode());sysUserMapper.insertSelective(user.getSysUser());return user;}@Override@Transactional(rollbackFor = Exception.class)public void insertList(List<User> users) {Assert.notNull(users, "Users不可为空!");List<SysUser> sysUsers = new ArrayList<>();String regIp = IpUtil.getRealIp(RequestHolder.getRequest());for (User user : users) {user.setUpdateTime(new Date());user.setCreateTime(new Date());user.setRegIp(regIp);sysUsers.add(user.getSysUser());}sysUserMapper.insertList(sysUsers);}/*** 根据主键字段进行删除,方法参数必须包含完整的主键属性** @param primaryKey* @return*/@Override@Transactional(rollbackFor = Exception.class)public boolean removeByPrimaryKey(Long primaryKey) {return sysUserMapper.deleteByPrimaryKey(primaryKey) > 0;}@Override@Transactional(rollbackFor = Exception.class)public boolean update(User user) {Assert.notNull(user, "User不可为空!");user.setUpdateTime(new Date());if (!StringUtils.isEmpty(user.getPassword())) {try {user.setPassword(PasswordUtil.encrypt(user.getPassword(), user.getUsername()));} catch (Exception e) {throw new CustomException("密码加密失败");}}return sysUserMapper.updateByPrimaryKey(user.getSysUser()) > 0;}@Override@Transactional(rollbackFor = Exception.class)public boolean updateSelective(User user) {Assert.notNull(user, "User不可为空!");user.setUpdateTime(new Date());if (!StringUtils.isEmpty(user.getPassword())) {try {user.setPassword(PasswordUtil.encrypt(user.getPassword(), user.getUsername()));} catch (Exception e) {e.printStackTrace();throw new CustomException("密码加密失败");}} else {user.setPassword(null);}return sysUserMapper.updateByPrimaryKeySelective(user.getSysUser()) > 0;}/*** 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号** @param primaryKey* @return*/@Overridepublic User getByPrimaryKey(Long primaryKey) {Assert.notNull(primaryKey, "PrimaryKey不可为空!");SysUser sysUser = sysUserMapper.selectByPrimaryKey(primaryKey);return null == sysUser ? null : new User(sysUser);}/*** 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号** @param entity* @return*/@Overridepublic User getOneByEntity(User entity) {Assert.notNull(entity, "User不可为空!");SysUser sysUser = sysUserMapper.selectOne(entity.getSysUser());return null == sysUser ? null : new User(sysUser);}@Overridepublic List<User> listAll() {List<SysUser> sysUsers = sysUserMapper.selectAll();if (CollectionUtils.isEmpty(sysUsers)) {return null;}List<User> users = new ArrayList<>();for (SysUser sysUser : sysUsers) {users.add(new User(sysUser));}return users;}@Overridepublic List<User> listByEntity(User user) {Assert.notNull(user, "User不可为空!");List<SysUser> sysUsers = sysUserMapper.select(user.getSysUser());if (CollectionUtils.isEmpty(sysUsers)) {return null;}List<User> users = new ArrayList<>();for (SysUser su : sysUsers) {users.add(new User(su));}return users;}/*** 分页查询** @param vo* @return*/@Overridepublic PageInfo<User> findPageBreakByCondition(UserConditionVO vo) {PageHelper.startPage(vo.getPageNumber(), vo.getPageSize());List<SysUser> sysUsers = sysUserMapper.findPageBreakByCondition(vo);if (CollectionUtils.isEmpty(sysUsers)) {return null;}List<User> users = new ArrayList<>();for (SysUser su : sysUsers) {users.add(new User(su));}PageInfo bean = new PageInfo<SysUser>(sysUsers);bean.setList(users);return bean;}/*** 更新用户最后一次登录的状态信息** @param user* @return*/@Overridepublic User updateUserLastLoginInfo(User user) {if (user != null) {user.setLoginCount(user.getLoginCount() + 1);user.setLastLoginTime(new Date());user.setLastLoginIp(IpUtil.getRealIp(RequestHolder.getRequest()));user.setPassword(null);this.updateSelective(user);}return user;}/*** 根据用户名查找** @param userName* @return*/@Overridepublic User getByUserName(String userName) {User user = new User(userName, null);return getOneByEntity(user);}/*** 通过角色Id获取用户列表** @param roleId* @return*/@Overridepublic List<User> listByRoleId(Long roleId) {List<SysUser> sysUsers = sysUserMapper.listByRoleId(roleId);if (CollectionUtils.isEmpty(sysUsers)) {return null;}List<User> users = new ArrayList<>();for (SysUser su : sysUsers) {users.add(new User(su));}return users;}}

角色接口和实现类

package com.zyd.shiro.business.service;import com.github.pagehelper.PageInfo;
import com.zyd.shiro.business.entity.Role;
import com.zyd.shiro.business.vo.RoleConditionVO;
import com.zyd.shiro.framework.object.AbstractService;import java.util.List;
import java.util.Map;/*** 角色** @author*/
public interface SysRoleService extends AbstractService<Role, Long> {/*** 获取ztree使用的角色列表** @param uid* @return*/List<Map<String, Object>> queryRoleListWithSelected(Integer uid);/*** 分页查询** @param vo* @return*/PageInfo<Role> findPageBreakByCondition(RoleConditionVO vo);/*** 获取用户的角色** @param userId* @return*/List<Role> listRolesByUserId(Long userId);
}

package com.zyd.shiro.business.service.impl;import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zyd.shiro.business.entity.Role;
import com.zyd.shiro.business.entity.User;
import com.zyd.shiro.business.service.SysRoleService;
import com.zyd.shiro.business.vo.RoleConditionVO;
import com.zyd.shiro.persistence.beans.SysRole;
import com.zyd.shiro.persistence.mapper.SysRoleMapper;
import com.zyd.shiro.persistence.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;import java.util.*;@Service
public class SysRoleServiceImpl implements SysRoleService {@Autowiredprivate SysRoleMapper roleMapper;@Autowiredprivate SysUserMapper sysUserMapper;/*** 获取ztree使用的角色列表** @param userId* @return*/@Overridepublic List<Map<String, Object>> queryRoleListWithSelected(Integer userId) {List<SysRole> sysRole = roleMapper.queryRoleListWithSelected(userId);if (CollectionUtils.isEmpty(sysRole)) {return null;}//getSelected=1当前选择角色List<Map<String, Object>> mapList = new ArrayList<Map<String, Object>>();Map<String, Object> map = null;for (SysRole role : sysRole) {map = new HashMap<String, Object>(16);map.put("id", role.getId());map.put("pId", 0);map.put("checked", role.getSelected() != null && role.getSelected() == 1);map.put("name", role.getDescription());mapList.add(map);}return mapList;}/*** 分页查询** @param vo* @return*/@Overridepublic PageInfo<Role> findPageBreakByCondition(RoleConditionVO vo) {PageHelper.startPage(vo.getPageNumber(), vo.getPageSize());List<SysRole> sysRoles = roleMapper.findPageBreakByCondition(vo);if (CollectionUtils.isEmpty(sysRoles)) {return null;}List<Role> roles = new ArrayList<>();for (SysRole r : sysRoles) {roles.add(new Role(r));}PageInfo bean = new PageInfo<SysRole>(sysRoles);bean.setList(roles);return bean;}/*** 获取用户的角色** @param userId* @return*/@Overridepublic List<Role> listRolesByUserId(Long userId) {List<SysRole> sysRoles = roleMapper.listRolesByUserId(userId);if (CollectionUtils.isEmpty(sysRoles)) {return null;}List<Role> roles = new ArrayList<>();for (SysRole r : sysRoles) {roles.add(new Role(r));}return roles;}/*** 保存一个实体,null的属性不会保存,会使用数据库默认值** @param entity* @return*/@Overridepublic Role insert(Role entity) {Assert.notNull(entity, "Role不可为空!");entity.setCreateTime(new Date());entity.setUpdateTime(new Date());roleMapper.insert(entity.getSysRole());return entity;}/*** 批量插入,支持批量插入的数据库可以使用,例如MySQL,H2等,另外该接口限制实体包含id属性并且必须为自增列** @param entities*/@Overridepublic void insertList(List<Role> entities) {Assert.notNull(entities, "entities不可为空!");List<SysRole> sysRole = new ArrayList<>();for (Role role : entities) {role.setUpdateTime(new Date());role.setCreateTime(new Date());sysRole.add(role.getSysRole());}roleMapper.insertList(sysRole);}/*** 根据主键字段进行删除,方法参数必须包含完整的主键属性** @param primaryKey* @return*/@Overridepublic boolean removeByPrimaryKey(Long primaryKey) {return roleMapper.deleteByPrimaryKey(primaryKey) > 0;}/*** 根据主键更新实体全部字段,null值会被更新** @param entity* @return*/@Overridepublic boolean update(Role entity) {Assert.notNull(entity, "Role不可为空!");entity.setUpdateTime(new Date());return roleMapper.updateByPrimaryKey(entity.getSysRole()) > 0;}/*** 根据主键更新属性不为null的值** @param entity* @return*/@Overridepublic boolean updateSelective(Role entity) {Assert.notNull(entity, "Role不可为空!");entity.setUpdateTime(new Date());return roleMapper.updateByPrimaryKeySelective(entity.getSysRole()) > 0;}/*** 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号** @param primaryKey* @return*/@Overridepublic Role getByPrimaryKey(Long primaryKey) {Assert.notNull(primaryKey, "PrimaryKey不可为空!");SysRole sysRole = roleMapper.selectByPrimaryKey(primaryKey);return null == sysRole ? null : new Role(sysRole);}/*** 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号** @param entity* @return*/@Overridepublic Role getOneByEntity(Role entity) {Assert.notNull(entity, "User不可为空!");SysRole sysRole = roleMapper.selectOne(entity.getSysRole());return null == sysRole ? null : new Role(sysRole);}/*** 查询全部结果,listByEntity(null)方法能达到同样的效果** @return*/@Overridepublic List<Role> listAll() {List<SysRole> sysRole = roleMapper.selectAll();return getRole(sysRole);}/*** 根据实体中的属性值进行查询,查询条件使用等号** @param entity* @return*/@Overridepublic List<Role> listByEntity(Role entity) {Assert.notNull(entity, "Role不可为空!");List<SysRole> sysRole = roleMapper.select(entity.getSysRole());return getRole(sysRole);}private List<Role> getRole(List<SysRole> sysRole) {if (CollectionUtils.isEmpty(sysRole)) {return null;}List<Role> roleList = new ArrayList<>();for (SysRole r : sysRole) {roleList.add(new Role(r));}return roleList;}
}

资源接口和实现类


package com.zyd.shiro.business.service;import com.github.pagehelper.PageInfo;
import com.zyd.shiro.business.entity.Resources;
import com.zyd.shiro.business.vo.ResourceConditionVO;
import com.zyd.shiro.framework.object.AbstractService;import java.util.List;
import java.util.Map;/*** 系统资源*/
public interface SysResourcesService extends AbstractService<Resources, Long> {/*** 分页查询** @param vo* @return*/PageInfo<Resources> findPageBreakByCondition(ResourceConditionVO vo);/*** 获取用户的资源列表** @param map* @return*/List<Resources> listUserResources(Map<String, Object> map);/*** 获取ztree使用的资源列表** @param rid* @return*/List<Map<String, Object>> queryResourcesListWithSelected(Long rid);/*** 获取资源的url和permission** @return*/List<Resources> listUrlAndPermission();/*** 获取所有可用的菜单资源** @return*/List<Resources> listAllAvailableMenu();/*** 获取父级资源下所有menu资源** @return*/List<Map<String, Object>> listChildMenuByPid(Long pid);/*** 获取用户关联的所有资源** @param userId* @return*/List<Resources> listByUserId(Long userId);
}
package com.zyd.shiro.business.service.impl;import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zyd.shiro.business.entity.Resources;
import com.zyd.shiro.business.service.SysResourcesService;
import com.zyd.shiro.business.vo.ResourceConditionVO;
import com.zyd.shiro.persistence.beans.SysResources;
import com.zyd.shiro.persistence.mapper.SysResourceMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;import java.util.*;/*** 系统资源*/
@Service
public class SysResourcesServiceImpl implements SysResourcesService {@Autowiredprivate SysResourceMapper resourceMapper;/*** 分页查询** @param vo* @return*/@Overridepublic PageInfo<Resources> findPageBreakByCondition(ResourceConditionVO vo) {PageHelper.startPage(vo.getPageNumber(), vo.getPageSize());List<SysResources> sysResources = resourceMapper.findPageBreakByCondition(vo);if (CollectionUtils.isEmpty(sysResources)) {return null;}List<Resources> resources = new ArrayList<>();for (SysResources r : sysResources) {resources.add(new Resources(r));}PageInfo bean = new PageInfo<SysResources>(sysResources);bean.setList(resources);return bean;}/*** 获取用户的资源列表** @param map* @return*/@Overridepublic List<Resources> listUserResources(Map<String, Object> map) {List<SysResources> sysResources = resourceMapper.listUserResources(map);if (CollectionUtils.isEmpty(sysResources)) {return null;}List<Resources> resources = new ArrayList<>();for (SysResources r : sysResources) {resources.add(new Resources(r));}return resources;}/*** 获取ztree使用的资源列表** @param rid* @return*/@Overridepublic List<Map<String, Object>> queryResourcesListWithSelected(Long rid) {List<SysResources> sysResources = resourceMapper.queryResourcesListWithSelected(rid);if (CollectionUtils.isEmpty(sysResources)) {return null;}List<Map<String, Object>> mapList = new ArrayList<Map<String, Object>>();Map<String, Object> map = null;for (SysResources resources : sysResources) {map = new HashMap<String, Object>(3);map.put("id", resources.getId());map.put("pId", resources.getParentId());map.put("checked", resources.getChecked());map.put("name", resources.getName());mapList.add(map);}return mapList;}/*** 获取资源的url和permission** @return*/@Overridepublic List<Resources> listUrlAndPermission() {List<SysResources> sysResources = resourceMapper.listUrlAndPermission();return getResources(sysResources);}/*** 获取所有可用的菜单资源** @return*/@Overridepublic List<Resources> listAllAvailableMenu() {List<SysResources> sysResources = resourceMapper.listAllAvailableMenu();return getResources(sysResources);}/*** 获取父级资源下所有menu资源** @param pid* @return*/@Overridepublic List<Map<String, Object>> listChildMenuByPid(Long pid) {List<SysResources> sysResources = resourceMapper.listMenuResourceByPid(pid);if(CollectionUtils.isEmpty(sysResources)){return null;}List<Map<String, Object>> result = new LinkedList<>();Map<String, Object> item = null;for (SysResources sysResource : sysResources) {item = new HashMap<>(2);item.put("value", sysResource.getId());item.put("text", sysResource.getName());result.add(item);}return result;}/*** 获取用户关联的所有资源** @param userId* @return*/@Overridepublic List<Resources> listByUserId(Long userId) {List<SysResources> sysResources = resourceMapper.listByUserId(userId);return getResources(sysResources);}/*** 保存一个实体,null的属性不会保存,会使用数据库默认值** @param entity* @return*/@Overridepublic Resources insert(Resources entity) {Assert.notNull(entity, "Resources不可为空!");entity.setCreateTime(new Date());entity.setUpdateTime(new Date());resourceMapper.insert(entity.getSysResources());return entity;}/*** 批量插入,支持批量插入的数据库可以使用,例如MySQL,H2等,另外该接口限制实体包含id属性并且必须为自增列** @param entities*/@Overridepublic void insertList(List<Resources> entities) {Assert.notNull(entities, "entities不可为空!");List<SysResources> sysResources = new ArrayList<>();for (Resources resources : entities) {resources.setUpdateTime(new Date());resources.setCreateTime(new Date());sysResources.add(resources.getSysResources());}resourceMapper.insertList(sysResources);}/*** 根据主键字段进行删除,方法参数必须包含完整的主键属性** @param primaryKey* @return*/@Overridepublic boolean removeByPrimaryKey(Long primaryKey) {return resourceMapper.deleteByPrimaryKey(primaryKey) > 0;}/*** 根据主键更新实体全部字段,null值会被更新** @param entity* @return*/@Overridepublic boolean update(Resources entity) {Assert.notNull(entity, "Resources不可为空!");entity.setUpdateTime(new Date());return resourceMapper.updateByPrimaryKey(entity.getSysResources()) > 0;}/*** 根据主键更新属性不为null的值** @param entity* @return*/@Overridepublic boolean updateSelective(Resources entity) {Assert.notNull(entity, "Resources不可为空!");entity.setUpdateTime(new Date());return resourceMapper.updateByPrimaryKeySelective(entity.getSysResources()) > 0;}/*** 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号** @param primaryKey* @return*/@Overridepublic Resources getByPrimaryKey(Long primaryKey) {Assert.notNull(primaryKey, "PrimaryKey不可为空!");SysResources sysResources = resourceMapper.selectByPrimaryKey(primaryKey);return null == sysResources ? null : new Resources(sysResources);}/*** 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号** @param entity* @return*/@Overridepublic Resources getOneByEntity(Resources entity) {Assert.notNull(entity, "User不可为空!");SysResources sysResources = resourceMapper.selectOne(entity.getSysResources());return null == sysResources ? null : new Resources(sysResources);}/*** 查询全部结果,listByEntity(null)方法能达到同样的效果** @return*/@Overridepublic List<Resources> listAll() {List<SysResources> sysResources = resourceMapper.selectAll();return getResources(sysResources);}/*** 根据实体中的属性值进行查询,查询条件使用等号** @param entity* @return*/@Overridepublic List<Resources> listByEntity(Resources entity) {Assert.notNull(entity, "Resources不可为空!");List<SysResources> sysResources = resourceMapper.select(entity.getSysResources());return getResources(sysResources);}private List<Resources> getResources(List<SysResources> sysResources) {if (CollectionUtils.isEmpty(sysResources)) {return null;}List<Resources> resources = new ArrayList<>();for (SysResources r : sysResources) {resources.add(new Resources(r));}return resources;}
}

用户角色接口和实现类

package com.zyd.shiro.business.service;import com.zyd.shiro.business.entity.UserRole;
import com.zyd.shiro.framework.object.AbstractService;/*** 用户角色*/
public interface SysUserRoleService extends AbstractService<UserRole, Long> {/*** 添加用户角色** @param userId* @param roleIds*/void addUserRole(Long userId, String roleIds);/*** 根据用户ID删除用户角色** @param userId*/void removeByUserId(Long userId);
}

package com.zyd.shiro.business.service.impl;import com.zyd.shiro.business.entity.UserRole;
import com.zyd.shiro.business.service.SysUserRoleService;
import com.zyd.shiro.persistence.beans.SysUserRole;
import com.zyd.shiro.persistence.mapper.SysUserRoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.Example;import java.util.ArrayList;
import java.util.Date;
import java.util.List;/*** 用户角色** @author*/
@Service
public class SysUserRoleServiceImpl implements SysUserRoleService {@Autowiredprivate SysUserRoleMapper resourceMapper;/*** 保存一个实体,null的属性不会保存,会使用数据库默认值** @param entity* @return*/@Overridepublic UserRole insert(UserRole entity) {Assert.notNull(entity, "UserRole不可为空!");entity.setCreateTime(new Date());entity.setUpdateTime(new Date());resourceMapper.insert(entity.getSysUserRole());return entity;}/*** 批量插入,支持批量插入的数据库可以使用,例如MySQL,H2等,另外该接口限制实体包含id属性并且必须为自增列** @param entities*/@Overridepublic void insertList(List<UserRole> entities) {Assert.notNull(entities, "entities不可为空!");if (CollectionUtils.isEmpty(entities)) {return;}List<SysUserRole> sysUserRole = new ArrayList<>();for (UserRole ur : entities) {ur.setUpdateTime(new Date());ur.setCreateTime(new Date());sysUserRole.add(ur.getSysUserRole());}resourceMapper.insertList(sysUserRole);}/*** 根据主键字段进行删除,方法参数必须包含完整的主键属性** @param primaryKey* @return*/@Overridepublic boolean removeByPrimaryKey(Long primaryKey) {return resourceMapper.deleteByPrimaryKey(primaryKey) > 0;}/*** 根据主键更新实体全部字段,null值会被更新** @param entity* @return*/@Overridepublic boolean update(UserRole entity) {Assert.notNull(entity, "UserRole不可为空!");entity.setUpdateTime(new Date());return resourceMapper.updateByPrimaryKey(entity.getSysUserRole()) > 0;}/*** 根据主键更新属性不为null的值** @param entity* @return*/@Overridepublic boolean updateSelective(UserRole entity) {Assert.notNull(entity, "UserRole不可为空!");entity.setUpdateTime(new Date());return resourceMapper.updateByPrimaryKeySelective(entity.getSysUserRole()) > 0;}/*** 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号** @param primaryKey* @return*/@Overridepublic UserRole getByPrimaryKey(Long primaryKey) {Assert.notNull(primaryKey, "PrimaryKey不可为空!");SysUserRole sysUserRole = resourceMapper.selectByPrimaryKey(primaryKey);return null == sysUserRole ? null : new UserRole(sysUserRole);}/*** 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号** @param entity* @return*/@Overridepublic UserRole getOneByEntity(UserRole entity) {Assert.notNull(entity, "User不可为空!");SysUserRole sysUserRole = resourceMapper.selectOne(entity.getSysUserRole());return null == sysUserRole ? null : new UserRole(sysUserRole);}/*** 查询全部结果,listByEntity(null)方法能达到同样的效果** @return*/@Overridepublic List<UserRole> listAll() {List<SysUserRole> sysUserRole = resourceMapper.selectAll();return getUserRole(sysUserRole);}/*** 根据实体中的属性值进行查询,查询条件使用等号** @param entity* @return*/@Overridepublic List<UserRole> listByEntity(UserRole entity) {Assert.notNull(entity, "UserRole不可为空!");List<SysUserRole> sysUserRole = resourceMapper.select(entity.getSysUserRole());return getUserRole(sysUserRole);}private List<UserRole> getUserRole(List<SysUserRole> sysUserRole) {if (CollectionUtils.isEmpty(sysUserRole)) {return null;}List<UserRole> urList = new ArrayList<>();for (SysUserRole r : sysUserRole) {urList.add(new UserRole(r));}return urList;}/*** 添加用户角色* 该节代码参考自http://blog.csdn.net/poorcoder_/article/details/71374002** @param userId* @param roleIds*/@Override@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = {Exception.class})public void addUserRole(Long userId, String roleIds) {//删除removeByUserId(userId);//添加String[] roleIdArr = roleIds.split(",");if (roleIdArr.length == 0) {return;}UserRole u = null;List<UserRole> roles = new ArrayList<>();for (String roleId : roleIdArr) {if (StringUtils.isEmpty(roleId)) {continue;}u = new UserRole();u.setUserId(userId);u.setRoleId(Long.parseLong(roleId));roles.add(u);}insertList(roles);}/*** 根据用户ID删除用户角色** @param userId*/@Override@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = {Exception.class})public void removeByUserId(Long userId) {Example example = new Example(SysUserRole.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("userId", userId);resourceMapper.deleteByExample(example);}
}

角色资源接口和实现类


package com.zyd.shiro.business.service;import com.zyd.shiro.business.entity.RoleResources;
import com.zyd.shiro.framework.object.AbstractService;/*** 角色资源*/
public interface SysRoleResourcesService extends AbstractService<RoleResources, Long> {/*** 添加角色资源** @param roleId* @param resourcesIds*/void addRoleResources(Long roleId, String resourcesIds);/*** 通过角色id批量删除** @param roleId*/void removeByRoleId(Long roleId);
}

package com.zyd.shiro.business.service.impl;import com.zyd.shiro.business.entity.RoleResources;
import com.zyd.shiro.business.service.SysRoleResourcesService;
import com.zyd.shiro.persistence.beans.SysRoleResources;
import com.zyd.shiro.persistence.mapper.SysRoleResourcesMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.Example;import java.util.ArrayList;
import java.util.Date;
import java.util.List;@Service
public class SysRoleResourcesServiceImpl implements SysRoleResourcesService {@Autowiredprivate SysRoleResourcesMapper resourceMapper;/*** 保存一个实体,null的属性不会保存,会使用数据库默认值** @param entity* @return*/@Overridepublic RoleResources insert(RoleResources entity) {Assert.notNull(entity, "RoleResources不可为空!");entity.setCreateTime(new Date());entity.setUpdateTime(new Date());resourceMapper.insert(entity.getSysRoleResources());return entity;}/*** 批量插入,支持批量插入的数据库可以使用,例如MySQL,H2等,另外该接口限制实体包含id属性并且必须为自增列** @param entities*/@Overridepublic void insertList(List<RoleResources> entities) {Assert.notNull(entities, "entities不可为空!");if (CollectionUtils.isEmpty(entities)) {return;}List<SysRoleResources> sysRoleResources = new ArrayList<>();for (RoleResources rr : entities) {rr.setUpdateTime(new Date());rr.setCreateTime(new Date());sysRoleResources.add(rr.getSysRoleResources());}resourceMapper.insertList(sysRoleResources);}/*** 根据主键字段进行删除,方法参数必须包含完整的主键属性** @param primaryKey* @return*/@Overridepublic boolean removeByPrimaryKey(Long primaryKey) {return resourceMapper.deleteByPrimaryKey(primaryKey) > 0;}/*** 根据主键更新实体全部字段,null值会被更新** @param entity* @return*/@Overridepublic boolean update(RoleResources entity) {Assert.notNull(entity, "RoleResources不可为空!");entity.setUpdateTime(new Date());return resourceMapper.updateByPrimaryKey(entity.getSysRoleResources()) > 0;}/*** 根据主键更新属性不为null的值** @param entity* @return*/@Overridepublic boolean updateSelective(RoleResources entity) {Assert.notNull(entity, "RoleResources不可为空!");entity.setUpdateTime(new Date());return resourceMapper.updateByPrimaryKeySelective(entity.getSysRoleResources()) > 0;}/*** 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号** @param primaryKey* @return*/@Overridepublic RoleResources getByPrimaryKey(Long primaryKey) {Assert.notNull(primaryKey, "PrimaryKey不可为空!");SysRoleResources sysRoleResources = resourceMapper.selectByPrimaryKey(primaryKey);return null == sysRoleResources ? null : new RoleResources(sysRoleResources);}/*** 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号** @param entity* @return*/@Overridepublic RoleResources getOneByEntity(RoleResources entity) {Assert.notNull(entity, "User不可为空!");SysRoleResources sysRoleResources = resourceMapper.selectOne(entity.getSysRoleResources());return null == sysRoleResources ? null : new RoleResources(sysRoleResources);}/*** 查询全部结果,listByEntity(null)方法能达到同样的效果** @return*/@Overridepublic List<RoleResources> listAll() {List<SysRoleResources> sysRoleResources = resourceMapper.selectAll();return getRoleResources(sysRoleResources);}/*** 根据实体中的属性值进行查询,查询条件使用等号** @param entity* @return*/@Overridepublic List<RoleResources> listByEntity(RoleResources entity) {Assert.notNull(entity, "RoleResources不可为空!");List<SysRoleResources> sysRoleResources = resourceMapper.select(entity.getSysRoleResources());return getRoleResources(sysRoleResources);}private List<RoleResources> getRoleResources(List<SysRoleResources> sysRoleResources) {if (CollectionUtils.isEmpty(sysRoleResources)) {return null;}List<RoleResources> rr = new ArrayList<>();for (SysRoleResources r : sysRoleResources) {rr.add(new RoleResources(r));}return rr;}/*** 添加角色资源* 该节代码参考自http://blog.csdn.net/poorcoder_/article/details/71374002** @param roleId* @param resourcesIds*/@Override@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = {Exception.class})public void addRoleResources(Long roleId, String resourcesIds) {//删除removeByRoleId(roleId);//添加if (!StringUtils.isEmpty(resourcesIds)) {String[] resourcesArr = resourcesIds.split(",");if (resourcesArr.length == 0) {return;}SysRoleResources r = null;List<SysRoleResources> roleResources = new ArrayList<>();for (String ri : resourcesArr) {if (StringUtils.isEmpty(ri)) {continue;}r = new SysRoleResources();r.setRoleId(roleId);r.setResourcesId(Long.parseLong(ri));r.setCreateTime(new Date());r.setUpdateTime(new Date());roleResources.add(r);}if (roleResources.size() == 0) {return;}resourceMapper.insertList(roleResources);}}/*** 通过角色id批量删除** @param roleId*/@Override@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = {Exception.class})public void removeByRoleId(Long roleId) {//删除Example example = new Example(SysRoleResources.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("roleId", roleId);resourceMapper.deleteByExample(example);}
}

15.Shiro-权限相关的业务处理controller

登录controoler


package com.zyd.shiro.controller;import com.zyd.shiro.framework.object.ResponseVO;
import com.zyd.shiro.util.ResultUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;/*** 登录相关*/
@Controller
@RequestMapping(value = "/passport")
public class LoginController {private static final Logger logger = LoggerFactory.getLogger(LoginController.class);@GetMapping("/login")public ModelAndView login(Model model) {return ResultUtil.view("/login");}/*** 登录** @param username* @param password* @param rememberMe* @param kaptcha* @return*/@PostMapping("/signin")@ResponseBodypublic ResponseVO submitLogin(String username, String password, boolean rememberMe, String kaptcha) {if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {return ResultUtil.error("用户名或密码不能为空!");}//获取当前的SubjectSubject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);try {// 在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查// 每个Realm都能在必要时对提交的AuthenticationTokens作出反应// 所以这一步在调用login(token)方法时,它会走到xxRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法subject.login(token);return ResultUtil.success("登录成功!");} catch (Exception e) {logger.error("登录失败,用户名[{}]", username, e);token.clear();return ResultUtil.error(e.getMessage());}}/*** 使用权限管理工具进行用户的退出,跳出登录,给出提示信息** @param redirectAttributes* @return*/@GetMapping("/logout")public ModelAndView logout(RedirectAttributes redirectAttributes) {// http://www.oschina.net/question/99751_91561// 此处有坑: 退出登录,其实不用实现任何东西,只需要保留这个接口即可,也不可能通过下方的代码进行退出// SecurityUtils.getSubject().logout();// 因为退出操作是由Shiro控制的redirectAttributes.addFlashAttribute("message", "您已安全退出");return ResultUtil.redirect("index");}
}

用户管理controoler


package com.zyd.shiro.controller;import com.github.pagehelper.PageInfo;
import com.zyd.shiro.business.entity.User;
import com.zyd.shiro.business.enums.ResponseStatus;
import com.zyd.shiro.business.service.SysUserRoleService;
import com.zyd.shiro.business.service.SysUserService;
import com.zyd.shiro.business.vo.UserConditionVO;
import com.zyd.shiro.framework.object.PageResult;
import com.zyd.shiro.framework.object.ResponseVO;
import com.zyd.shiro.util.PasswordUtil;
import com.zyd.shiro.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 用户管理** @author liuminglin*/
@RestController
@RequestMapping("/user")
public class RestUserController {@Autowiredprivate SysUserService userService;@Autowiredprivate SysUserRoleService userRoleService;/*** 通过用户id获取用户 getUserById*/@RequiresPermissions("user:edit")@PostMapping("/get/{id}")public ResponseVO getUserById(@PathVariable Long id) {return ResultUtil.success(null, this.userService.getByPrimaryKey(id));}/*** 用户管理->用户列表*/@RequiresPermissions("users")@PostMapping("/list")public PageResult list(UserConditionVO vo) {PageInfo<User> pageInfo = userService.findPageBreakByCondition(vo);return ResultUtil.tablePage(pageInfo);}/*** 添加用户*/@RequiresPermissions("user:add")@PostMapping(value = "/add")public ResponseVO add(User user) {User dbUser = userService.getByUserName(user.getUsername());if (dbUser != null) {return ResultUtil.error("用户[" + user.getUsername() + "]已存在!请更改用户名");}try {user.setPassword(PasswordUtil.encrypt(user.getPassword(), user.getUsername()));userService.insert(user);return ResultUtil.success("成功");} catch (Exception e) {e.printStackTrace();return ResultUtil.error("error");}}/*** 添加用户角色** @param userId* @param roleIds 用户角色,获取的参数的角色id是以 “,” 分隔的字符串* @return*/@RequiresPermissions("user:allotRole")@PostMapping("/saveUserRoles")public ResponseVO saveUserRoles(Long userId, String roleIds) {if (StringUtils.isEmpty(userId)) {return ResultUtil.error("error");}userRoleService.addUserRole(userId, roleIds);return ResultUtil.success("成功");}/*** 删除用户和角色*/@RequiresPermissions(value = {"user:batchDelete", "user:delete"}, logical = Logical.OR)@PostMapping(value = "/remove")public ResponseVO remove(Long[] ids) {if (null == ids) {return ResultUtil.error(500, "请至少选择一条记录");}for (Long id : ids) {userService.removeByPrimaryKey(id);userRoleService.removeByUserId(id);}return ResultUtil.success("成功删除 [" + ids.length + "] 个用户");}/*** 修改用户*/@RequiresPermissions("user:edit")@PostMapping("/edit")public ResponseVO edit(User user) {try {userService.updateSelective(user);} catch (Exception e) {e.printStackTrace();return ResultUtil.error("用户修改失败!");}return ResultUtil.success(ResponseStatus.SUCCESS);}}

系统角色管理


package com.zyd.shiro.controller;import com.github.pagehelper.PageInfo;
import com.zyd.shiro.business.entity.Role;
import com.zyd.shiro.business.enums.ResponseStatus;
import com.zyd.shiro.business.service.ShiroService;
import com.zyd.shiro.business.service.SysRoleResourcesService;
import com.zyd.shiro.business.service.SysRoleService;
import com.zyd.shiro.business.vo.RoleConditionVO;
import com.zyd.shiro.framework.object.PageResult;
import com.zyd.shiro.framework.object.ResponseVO;
import com.zyd.shiro.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** 系统角色管理** @author liuminglin*/
@RestController
@RequestMapping("/roles")
public class RestRoleController {@Autowiredprivate SysRoleService roleService;@Autowiredprivate SysRoleResourcesService roleResourcesService;@Autowiredprivate ShiroService shiroService;/*** 分页获取所有角色列表*/@RequiresPermissions("roles")@PostMapping("/list")public PageResult getAll(RoleConditionVO vo) {PageInfo<Role> pageInfo = roleService.findPageBreakByCondition(vo);return ResultUtil.tablePage(pageInfo);}/*** 分配用户角色*/@RequiresPermissions("user:allotRole")@PostMapping("/rolesWithSelected")public ResponseVO<List<Role>> rolesWithSelected(Integer uid) {return ResultUtil.success(null, roleService.queryRoleListWithSelected(uid));}/*** 分配角色资源*/@RequiresPermissions("role:allotResource")@PostMapping("/saveRoleResources")public ResponseVO saveRoleResources(Long roleId, String resourcesId) {if (StringUtils.isEmpty(roleId)) {return ResultUtil.error("error");}roleResourcesService.addRoleResources(roleId, resourcesId);// 重新加载所有拥有roleId的用户的权限信息shiroService.reloadAuthorizingByRoleId(roleId);return ResultUtil.success("成功");}/*** 新增角色*/@RequiresPermissions("role:add")@PostMapping(value = "/add")public ResponseVO add(Role role) {roleService.insert(role);return ResultUtil.success("成功");}/*** 批量删除角色*/@RequiresPermissions(value = {"role:batchDelete", "role:delete"}, logical = Logical.OR)@PostMapping(value = "/remove")public ResponseVO remove(Long[] ids) {if (null == ids) {return ResultUtil.error(500, "请至少选择一条记录");}for (Long id : ids) {roleService.removeByPrimaryKey(id);roleResourcesService.removeByRoleId(id);}return ResultUtil.success("成功删除 [" + ids.length + "] 个角色");}/*** 获取角色*/@RequiresPermissions("role:edit")@PostMapping("/get/{id}")public ResponseVO get(@PathVariable Long id) {return ResultUtil.success(null, this.roleService.getByPrimaryKey(id));}/*** 编辑角色*/@RequiresPermissions("role:edit")@PostMapping("/edit")public ResponseVO edit(Role role) {try {roleService.updateSelective(role);} catch (Exception e) {e.printStackTrace();return ResultUtil.error("角色修改失败!");}return ResultUtil.success(ResponseStatus.SUCCESS);}}

系统资源管理


package com.zyd.shiro.controller;import com.github.pagehelper.PageInfo;
import com.zyd.shiro.business.entity.Resources;
import com.zyd.shiro.business.enums.ResponseStatus;
import com.zyd.shiro.business.service.ShiroService;
import com.zyd.shiro.business.service.SysResourcesService;
import com.zyd.shiro.business.vo.ResourceConditionVO;
import com.zyd.shiro.framework.object.PageResult;
import com.zyd.shiro.framework.object.ResponseVO;
import com.zyd.shiro.util.ResultUtil;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 系统资源管理*/
@RestController
@RequestMapping("/resources")
public class SystemResourcesController {@Autowiredprivate SysResourcesService resourcesService;@Autowiredprivate ShiroService shiroService;/*** 资源管理*/@RequiresPermissions("resources")@PostMapping("/list")public PageResult getAll(ResourceConditionVO vo) {PageInfo<Resources> pageInfo = resourcesService.findPageBreakByCondition(vo);return ResultUtil.tablePage(pageInfo);}/*** 分配角色资源*/@RequiresPermissions("role:allotResource")@PostMapping("/resourcesWithSelected")public ResponseVO resourcesWithSelected(Long rid) {return ResultUtil.success(null, resourcesService.queryResourcesListWithSelected(rid));}/*** 添加角色资源*/@RequiresPermissions("resource:add")@PostMapping(value = "/add")public ResponseVO add(Resources resources) {resourcesService.insert(resources);//更新权限shiroService.updatePermission();return ResultUtil.success("成功");}/*** 删除角色资源*/@RequiresPermissions(value = {"resource:batchDelete", "resource:delete"}, logical = Logical.OR)@PostMapping(value = "/remove")public ResponseVO remove(Long[] ids) {if (null == ids) {return ResultUtil.error(500, "请至少选择一条记录");}for (Long id : ids) {resourcesService.removeByPrimaryKey(id);}//更新权限shiroService.updatePermission();return ResultUtil.success("成功删除 [" + ids.length + "] 个资源");}/*** 获取角色资源*/@RequiresPermissions("resource:edit")@PostMapping("/get/{id}")public ResponseVO get(@PathVariable Long id) {return ResultUtil.success(null, this.resourcesService.getByPrimaryKey(id));}/*** 编辑角色资源*/@RequiresPermissions("resource:edit")@PostMapping("/edit")public ResponseVO edit(Resources resources) {try {resourcesService.updateSelective(resources);} catch (Exception e) {e.printStackTrace();return ResultUtil.error("资源修改失败!");}return ResultUtil.success(ResponseStatus.SUCCESS);}
}

页面渲染相关


package com.zyd.shiro.controller;import com.zyd.shiro.util.ResultUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;/*** 页面渲染相关 -- 页面跳转*/
@Controller
public class RenderController {@RequiresAuthentication@GetMapping(value = {"", "/index"})public ModelAndView home() {return ResultUtil.view("index");}@RequiresPermissions("users")@GetMapping("/users")public ModelAndView user() {return ResultUtil.view("user/list");}@RequiresPermissions("resources")@GetMapping("/resources")public ModelAndView resources() {return ResultUtil.view("resources/list");}@RequiresPermissions("roles")@GetMapping("/roles")public ModelAndView roles() {return ResultUtil.view("role/list");}}

统一异常处理类


package com.zyd.shiro.controller;import com.zyd.shiro.business.consts.CommonConst;
import com.zyd.shiro.business.enums.ResponseStatus;
import com.zyd.shiro.framework.exception.CustomException;
import com.zyd.shiro.framework.object.ResponseVO;
import com.zyd.shiro.util.ResultUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;import java.lang.reflect.UndeclaredThrowableException;/*** 统一异常处理类<br>* 捕获程序所有异常,针对不同异常,采取不同的处理方式*/
@ControllerAdvice
public class ExceptionHandleController {private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandleController.class);@ExceptionHandler(value = Exception.class)@ResponseBodypublic ResponseVO handle(Throwable e) {if (e instanceof CustomException) {return ResultUtil.error(e.getMessage());}if (e instanceof UndeclaredThrowableException) {e = ((UndeclaredThrowableException) e).getUndeclaredThrowable();}ResponseStatus responseStatus = ResponseStatus.getResponseStatus(e.getMessage());if (responseStatus != null) {LOGGER.error(responseStatus.getMessage());return ResultUtil.error(responseStatus.getCode(), responseStatus.getMessage());}e.printStackTrace(); // 打印异常栈return ResultUtil.error(CommonConst.DEFAULT_ERROR_CODE, ResponseStatus.ERROR.getMessage());}}

错误页面控制器

package com.zyd.shiro.controller;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorController;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;/*** 错误页面控制器:重写BasicErrorController,主要负责系统的异常页面的处理以及错误信息的显示* <p>* 要注意,这个类里面的代码一定不能有异常或者潜在异常发生,否则可能会让程序陷入死循环。*/
@Controller
@RequestMapping("/error")
@EnableConfigurationProperties({ServerProperties.class})
public class ErrorPagesController implements ErrorController {private static final Logger LOG = LoggerFactory.getLogger(ErrorPagesController.class);private ErrorAttributes errorAttributes;@Autowiredprivate ServerProperties serverProperties;/*** 初始化ExceptionController** @param errorAttributes*/@Autowiredpublic ErrorPagesController(ErrorAttributes errorAttributes) {Assert.notNull(errorAttributes, "ErrorAttributes must not be null");this.errorAttributes = errorAttributes;}@RequestMapping("/404")public ModelAndView errorHtml404(HttpServletRequest request, HttpServletResponse response) {response.setStatus(HttpStatus.NOT_FOUND.value());Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));return new ModelAndView("error/404", model);}@RequestMapping("/403")public ModelAndView errorHtml403(HttpServletRequest request, HttpServletResponse response) {response.setStatus(HttpStatus.FORBIDDEN.value());// 404拦截规则,如果是静态文件发生的404则不记录到DBMap<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));if (!String.valueOf(model.get("path")).contains(".")) {model.put("status", HttpStatus.FORBIDDEN.value());}return new ModelAndView("error/403", model);}@RequestMapping("/400")public ModelAndView errorHtml400(HttpServletRequest request, HttpServletResponse response) {response.setStatus(HttpStatus.BAD_REQUEST.value());Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));return new ModelAndView("error/400", model);}@RequestMapping("/401")public ModelAndView errorHtml401(HttpServletRequest request, HttpServletResponse response) {response.setStatus(HttpStatus.UNAUTHORIZED.value());Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));return new ModelAndView("error/401", model);}@RequestMapping("/500")public ModelAndView errorHtml500(HttpServletRequest request, HttpServletResponse response) {response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));return new ModelAndView("error/500", model);}/*** Determine if the stacktrace attribute should be included.** @param request  the source request* @param produces the media type produced (or {@code MediaType.ALL})* @return if the stacktrace attribute should be included*/protected boolean isIncludeStackTrace(HttpServletRequest request,MediaType produces) {ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace();if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {return true;}return include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM && getTraceParameter(request);}/*** 获取错误的信息** @param request* @param includeStackTrace* @return*/private Map<String, Object> getErrorAttributes(HttpServletRequest request,boolean includeStackTrace) {RequestAttributes requestAttributes = new ServletRequestAttributes(request);return this.errorAttributes.getErrorAttributes(requestAttributes,includeStackTrace);}/*** 是否包含trace** @param request* @return*/private boolean getTraceParameter(HttpServletRequest request) {String parameter = request.getParameter("trace");return parameter != null && !"false".equalsIgnoreCase(parameter);}/*** 获取错误编码** @param request* @return*/private HttpStatus getStatus(HttpServletRequest request) {Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");if (statusCode == null) {return HttpStatus.INTERNAL_SERVER_ERROR;}try {return HttpStatus.valueOf(statusCode);} catch (Exception ex) {LOG.error("获取当前HttpStatus发生异常", ex);return HttpStatus.INTERNAL_SERVER_ERROR;}}/*** 实现错误路径,暂时无用** @return*/@Overridepublic String getErrorPath() {return "";}
}

16.Shiro-权限相关的bean

objectBean


import java.io.Serializable;/****/
public abstract class AbstractBO implements Serializable {private static final long serialVersionUID = -3737736141782545763L;
}
package com.zyd.shiro.framework.object;import lombok.Data;
import lombok.EqualsAndHashCode;import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
import java.util.Date;/****/
@Data
@EqualsAndHashCode(callSuper = false)
public abstract class AbstractDO implements Serializable {/*** @fieldName: serialVersionUID* @fieldType: long*/private static final long serialVersionUID = 5088697673359856350L;@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private Date createTime;private Date updateTime;}
package com.zyd.shiro.framework.object;import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;import java.util.Date;/*** @author zjjlive)* desc:基本条件*/
@Data
@EqualsAndHashCode(callSuper = false)
public class BaseConditionVO {//默认页面大小public final static int DEFAULT_PAGE_SIZE = 10;//页码private int pageNumber = 1;//页码private int pageSize = 0;//条数private int pageStart = 0;//排序字段private String orderField;//排序描述private String orderDirection;//关键词private String keywords;@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date startDate;@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date endDate;public int getPageSize() {return pageSize > 0 ? pageSize : DEFAULT_PAGE_SIZE;}public int getPageStart() {return pageNumber > 0 ? (pageNumber - 1) * getPageSize() : 0;}
}

Shiro-权限相关业务的bean

package com.zyd.shiro.persistence.beans;import com.zyd.shiro.framework.object.AbstractDO;
import lombok.Data;
import lombok.EqualsAndHashCode;import java.util.Date;/*** @author*/
@Data
@EqualsAndHashCode(callSuper = false)
public class SysUser extends AbstractDO {private String username;private String password;private String nickname;private String mobile;private String email;private String qq;private Date birthday;private Integer gender;private String avatar;private String userType;private String regIp;private String lastLoginIp;private Date lastLoginTime;private Integer loginCount;private String remark;private Integer status;
}

package com.zyd.shiro.persistence.beans;import com.zyd.shiro.framework.object.AbstractDO;
import lombok.Data;
import lombok.EqualsAndHashCode;import javax.persistence.Transient;/*** @author*/
@Data
@EqualsAndHashCode(callSuper = false)
public class SysRole extends AbstractDO {private String name;private String description;private Boolean available;@Transientprivate Integer selected;
}
package com.zyd.shiro.persistence.beans;import com.zyd.shiro.framework.object.AbstractDO;
import lombok.Data;
import lombok.EqualsAndHashCode;import javax.persistence.Transient;
import java.util.List;/*** @author*/
@Data
@EqualsAndHashCode(callSuper = false)
public class SysResources extends AbstractDO {private String name;private String type;private String url;private String permission;private Long parentId;private Integer sort;private Boolean external;private Boolean available;private String icon;@Transientprivate String checked;@Transientprivate SysResources parent;@Transientprivate List<SysResources> nodes;
}

package com.zyd.shiro.persistence.beans;import com.zyd.shiro.framework.object.AbstractDO;
import lombok.Data;
import lombok.EqualsAndHashCode;/*** @author*/
@Data
@EqualsAndHashCode(callSuper = false)
public class SysUserRole extends AbstractDO {private Long userId;private Long roleId;
}

package com.zyd.shiro.persistence.beans;import com.zyd.shiro.framework.object.AbstractDO;
import lombok.Data;
import lombok.EqualsAndHashCode;/*** @author*/
@Data
@EqualsAndHashCode(callSuper = false)
public class SysRoleResources extends AbstractDO {private Long roleId;private Long resourcesId;
}

Shiro-权限相关业务的entity


import com.fasterxml.jackson.annotation.JsonIgnore;
import com.zyd.shiro.business.enums.*;
import com.zyd.shiro.framework.object.AbstractBO;
import com.zyd.shiro.persistence.beans.SysUser;
import com.zyd.shiro.util.PasswordUtil;
import org.springframework.util.StringUtils;import java.util.Date;/*** 系统用户*/
public class User extends AbstractBO {private SysUser sysUser;public User() {this.sysUser = new SysUser();}public User(SysUser sysUser) {this.sysUser = sysUser;}public User(String loginname, String password) {this();setUsername(loginname);if (!StringUtils.isEmpty(password)) {try {setPassword(PasswordUtil.encrypt(password, loginname));} catch (Exception e) {e.printStackTrace();}}}@JsonIgnorepublic SysUser getSysUser() {return this.sysUser;}public Long getId() {return this.sysUser.getId();}public void setId(Long id) {this.sysUser.setId(id);}public String getNickname() {return this.sysUser.getNickname();}public void setNickname(String nickname) {this.sysUser.setNickname(nickname);}public String getMobile() {return this.sysUser.getMobile();}public void setMobile(String mobile) {this.sysUser.setMobile(mobile);}public String getUsername() {return this.sysUser.getUsername();}public void setUsername(String username) {this.sysUser.setUsername(username);}public String getPassword() {return this.sysUser.getPassword();}public void setPassword(String password) {this.sysUser.setPassword(password);}public String getEmail() {return this.sysUser.getEmail();}public void setEmail(String email) {this.sysUser.setEmail(email);}public String getQq() {return this.sysUser.getQq();}public void setQq(String qq) {this.sysUser.setQq(qq);}public Date getBirthday() {return this.sysUser.getBirthday();}public void setBirthday(Date birthday) {this.sysUser.setBirthday(birthday);}public Integer getGender() {return this.sysUser.getGender();}public void setGender(UserGenderEnum gender) {if (gender != null && gender.getCode() != -1) {this.sysUser.setGender(gender.getCode());}}public void setGender(Integer Gender) {this.sysUser.setGender(Gender);}public String getAvatar() {return this.sysUser.getAvatar();}public void setAvatar(String avatar) {this.sysUser.setAvatar(avatar);}public String getUserType() {return this.sysUser.getUserType();}public void setUserType(UserTypeEnum userTypeEnum) {if (null != userTypeEnum) {setUserType(userTypeEnum.toString());}}public void setUserType(String userType) {this.sysUser.setUserType(userType);}public UserTypeEnum getUserTypeEnum() {return UserTypeEnum.getByType(this.sysUser.getUserType());}public String getRegIp() {return this.sysUser.getRegIp();}public void setRegIp(String regIp) {this.sysUser.setRegIp(regIp);}public String getLastLoginIp() {return this.sysUser.getLastLoginIp();}public void setLastLoginIp(String lastLoginIp) {this.sysUser.setLastLoginIp(lastLoginIp);}public Date getLastLoginTime() {return this.sysUser.getLastLoginTime();}public void setLastLoginTime(Date lastLoginTime) {this.sysUser.setLastLoginTime(lastLoginTime);}public Integer getLoginCount() {return this.sysUser.getLoginCount();}public void setLoginCount(Integer loginCount) {this.sysUser.setLoginCount(loginCount);}public String getRemark() {return this.sysUser.getRemark();}public void setRemark(String remark) {this.sysUser.setRemark(remark);}public Integer getStatus() {return this.sysUser.getStatus();}public void setStatus(Integer status) {this.sysUser.setStatus(status);}public UserStatusEnum getStatusEnum() {return UserStatusEnum.get(this.sysUser.getStatus());}public Date getCreateTime() {return this.sysUser.getCreateTime();}public void setCreateTime(Date regTime) {this.sysUser.setCreateTime(regTime);}public Date getUpdateTime() {return this.sysUser.getUpdateTime();}public void setUpdateTime(Date updateTime) {this.sysUser.setUpdateTime(updateTime);}}

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.zyd.shiro.persistence.beans.SysRole;import java.util.Date;/*** 角色*/
public class Role {private SysRole sysRole;public Role() {this.sysRole = new SysRole();}public Role(SysRole sysRole) {this.sysRole = sysRole;}@JsonIgnorepublic SysRole getSysRole() {return this.sysRole;}public Long getId() {return this.sysRole.getId();}public void setId(Long id) {this.sysRole.setId(id);}public String getName() {return this.sysRole.getName();}public void setName(String name) {this.sysRole.setName(name);}public String getDescription() {return this.sysRole.getDescription();}public void setDescription(String description) {this.sysRole.setDescription(description);}public boolean isAvailable() {Boolean value = this.sysRole.getAvailable();return value != null ? value : false;}public void setAvailable(boolean available) {this.sysRole.setAvailable(available);}public Date getCreateTime() {return this.sysRole.getCreateTime();}public void setCreateTime(Date regTime) {this.sysRole.setCreateTime(regTime);}public Date getUpdateTime() {return this.sysRole.getUpdateTime();}public void setUpdateTime(Date updateTime) {this.sysRole.setUpdateTime(updateTime);}}
package com.zyd.shiro.business.entity;import com.fasterxml.jackson.annotation.JsonIgnore;
import com.zyd.shiro.business.enums.ResourceTypeEnum;
import com.zyd.shiro.persistence.beans.SysResources;import java.util.Date;
import java.util.List;/****/
public class Resources {private SysResources sysResources;public Resources() {this.sysResources = new SysResources();}public Resources(SysResources sysResources) {this.sysResources = sysResources;}@JsonIgnorepublic SysResources getSysResources() {return this.sysResources;}public Long getId() {return this.sysResources.getId();}public void setId(Long id) {this.sysResources.setId(id);}public String getName() {return this.sysResources.getName();}public void setName(String name) {this.sysResources.setName(name);}public ResourceTypeEnum getType() {return this.sysResources.getType() != null ? ResourceTypeEnum.valueOf(this.sysResources.getType()) : null;}public void setType(ResourceTypeEnum type) {this.sysResources.setType(type.toString());}public String getUrl() {return this.sysResources.getUrl();}public void setUrl(String url) {this.sysResources.setUrl(url);}public String getPermission() {return this.sysResources.getPermission();}public void setPermission(String permission) {this.sysResources.setPermission(permission);}public Long getParentId() {return this.sysResources.getParentId();}public void setParentId(Long parentId) {this.sysResources.setParentId(parentId);}public Integer getSort() {return this.sysResources.getSort();}public void setSort(Integer sort) {this.sysResources.setSort(sort);}public boolean isAvailable() {Boolean value = this.sysResources.getAvailable();return value != null ? value : false;}public void setAvailable(boolean available) {this.sysResources.setAvailable(available);}public Boolean getExternal() {Boolean value = this.sysResources.getExternal();return null == value ? false : value;}public void setExternal(Boolean external) {this.sysResources.setExternal(external);}public String getIcon() {return this.sysResources.getIcon();}public void setIcon(String icon) {this.sysResources.setIcon(icon);}public Date getCreateTime() {return this.sysResources.getCreateTime();}public void setCreateTime(Date regTime) {this.sysResources.setCreateTime(regTime);}public Date getUpdateTime() {return this.sysResources.getUpdateTime();}public void setUpdateTime(Date updateTime) {this.sysResources.setUpdateTime(updateTime);}public SysResources getParent() {return this.sysResources.getParent();}public void setParent(SysResources parent) {this.sysResources.setParent(parent);}public List<SysResources> getNodes() {return this.sysResources.getNodes();}public void setNodes(List<SysResources> nodes) {this.sysResources.setNodes(nodes);}
}

package com.zyd.shiro.business.entity;import com.fasterxml.jackson.annotation.JsonIgnore;
import com.zyd.shiro.persistence.beans.SysUserRole;import java.util.Date;/****/
public class UserRole {private SysUserRole sysUserRole;public UserRole() {this.sysUserRole = new SysUserRole();}public UserRole(SysUserRole sysUserRole) {this.sysUserRole = sysUserRole;}@JsonIgnorepublic SysUserRole getSysUserRole() {return this.sysUserRole;}public Long getUserId() {return this.sysUserRole.getUserId();}public void setUserId(Long userId) {this.sysUserRole.setUserId(userId);}public Long getRoleId() {return this.sysUserRole.getRoleId();}public void setRoleId(Long roleId) {this.sysUserRole.setRoleId(roleId);}public Date getCreateTime() {return this.sysUserRole.getCreateTime();}public void setCreateTime(Date regTime) {this.sysUserRole.setCreateTime(regTime);}public Date getUpdateTime() {return this.sysUserRole.getUpdateTime();}public void setUpdateTime(Date updateTime) {this.sysUserRole.setUpdateTime(updateTime);}
}
package com.zyd.shiro.business.entity;import com.fasterxml.jackson.annotation.JsonIgnore;
import com.zyd.shiro.persistence.beans.SysRoleResources;import java.util.Date;/****/
public class RoleResources {private SysRoleResources sysRoleResources;public RoleResources() {this.sysRoleResources = new SysRoleResources();}public RoleResources(SysRoleResources sysRoleResources) {this.sysRoleResources = sysRoleResources;}@JsonIgnorepublic SysRoleResources getSysRoleResources() {return this.sysRoleResources;}public Long getRoleId() {return this.sysRoleResources.getRoleId();}public void setRoleId(Long roleId) {this.sysRoleResources.setRoleId(roleId);}public Long getResourcesId() {return this.sysRoleResources.getResourcesId();}public void setResourcesId(Long resourcesId) {this.sysRoleResources.setResourcesId(resourcesId);}public Date getCreateTime() {return this.sysRoleResources.getCreateTime();}public void setCreateTime(Date regTime) {this.sysRoleResources.setCreateTime(regTime);}public Date getUpdateTime() {return this.sysRoleResources.getUpdateTime();}public void setUpdateTime(Date updateTime) {this.sysRoleResources.setUpdateTime(updateTime);}}

17.权限用到的常量类

package com.zyd.shiro.business.consts;/*** 程序中公用的常量类** @author*/
public class CommonConst {/*** 安全密码(UUID生成),作为盐值用于用户密码的加密*/public static final String ZYD_SECURITY_KEY = "929123f8f17944e8b0a531045453e1f1";/*** 程序默认的错误状态码*/public static final int DEFAULT_ERROR_CODE = 500;/*** 程序默认的成功状态码*/public static final int DEFAULT_SUCCESS_CODE = 200;}

package com.zyd.shiro.business.consts;/*** @author*/
public class SessionConst {/*** User 的 session key;k*/public static final String USER_SESSION_KEY = "user";
}

18.shiro权限管理

shiro的主要模块:身份认证,权限授权,加密,会话管理等功能

配置自定义Realm


package com.zyd.shiro.business.shiro.realm;import com.zyd.shiro.business.entity.Resources;
import com.zyd.shiro.business.entity.Role;
import com.zyd.shiro.business.entity.User;
import com.zyd.shiro.business.enums.UserStatusEnum;
import com.zyd.shiro.business.service.SysResourcesService;
import com.zyd.shiro.business.service.SysRoleService;
import com.zyd.shiro.business.service.SysUserService;
import org.apache.shiro.SecurityUtils;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import javax.annotation.Resource;
import java.util.List;/*** 自定义Realm*/
public class ShiroRealm extends AuthorizingRealm {@Resourceprivate SysUserService userService;@Resourceprivate SysResourcesService resourcesService;@Resourceprivate SysRoleService roleService;/*** 认证(登录时调用)(用户的角色信息集合)*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//获取用户的输入的账号.String username = (String) token.getPrincipal();User user = userService.getByUserName(username);if (user == null) {throw new UnknownAccountException("账号不存在!");}if (user.getStatus() != null && UserStatusEnum.DISABLE.getCode().equals(user.getStatus())) {throw new LockedAccountException("帐号已被锁定,禁止登录!");}// principal参数使用用户Id,方便动态刷新用户权限SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(),ByteSource.Util.bytes(username),//realm namegetName());// 当验证都通过后,把用户信息放在session里Session session = SecurityUtils.getSubject().getSession();session.setAttribute("userSession", user);session.setAttribute("userSessionId", user.getId());return authenticationInfo;}/*** 授权 权限认证,为当前登录的Subject授予角色和权限(角色的权限信息集合)*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {User user = (User) SecurityUtils.getSubject().getPrincipal();// 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();Long userId = user.getId();// 赋予角色List<Role> roleList = roleService.listRolesByUserId(userId);for (Role role : roleList) {info.addRole(role.getName());}// 赋予权限List<Resources> resourcesList = resourcesService.listByUserId(userId);if (!CollectionUtils.isEmpty(resourcesList)) {for (Resources resources : resourcesList) {String permission = resources.getPermission();System.out.println(resources.getName() + "   " + permission);if (!StringUtils.isEmpty(permission)) {info.addStringPermission(permission);}}}return info;}}

身份验证拦截器

package com.zyd.shiro.framework.interceptor;import com.zyd.shiro.business.consts.SessionConst;
import com.zyd.shiro.business.entity.User;
import com.zyd.shiro.business.service.SysUserService;
import com.zyd.shiro.util.PasswordUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/**** @desc 身份验证拦截器*/
@Slf4j
@Component
public class RememberAuthenticationInterceptor implements HandlerInterceptor {@Autowiredprivate SysUserService userService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {Subject subject = SecurityUtils.getSubject();if (subject.isAuthenticated()) {return true;}Session session = subject.getSession(true);if (session.getAttribute(SessionConst.USER_SESSION_KEY) != null) {return true;}if (!subject.isRemembered()) {log.warn("未设置“记住我”,跳转到登录页...");response.sendRedirect(request.getContextPath() + "/passport/login");return false;}try {Long userId = Long.parseLong(subject.getPrincipal().toString());User user = userService.getByPrimaryKey(userId);UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), PasswordUtil.decrypt(user.getPassword(), user.getUsername()), true);subject.login(token);session.setAttribute(SessionConst.USER_SESSION_KEY, user);log.info("[{}] - 已自动登录", user.getUsername());} catch (Exception e) {log.error("自动登录失败", e);response.sendRedirect(request.getContextPath() + "/passport/login");return false;}return true;}@Overridepublic void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {}
}

shiro认证:

认证就是验证用户。比如用户登录的时候验证账号密码是否正确。
我们可以把对登录的验证交给shiro。我们执行要查询相应的用户信息,并传给shiro。

如下代码则为用户登录:

  /*** 登录** @param username* @param password* @param rememberMe* @param kaptcha* @return*/@PostMapping("/signin")@ResponseBodypublic ResponseVO submitLogin(String username, String password, boolean rememberMe, String kaptcha) {if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {return ResultUtil.error("用户名或密码不能为空!");}//获取当前的SubjectSubject subject = SecurityUtils.getSubject();UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);try {// 在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查// 每个Realm都能在必要时对提交的AuthenticationTokens作出反应// 所以这一步在调用login(token)方法时,它会走到xxRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法subject.login(token);return ResultUtil.success("登录成功!");} catch (Exception e) {logger.error("登录失败,用户名[{}]", username, e);token.clear();return ResultUtil.error(e.getMessage());}}

用户登陆的代码主要就是 subject.login(token);调用后就会进去我们自定义的realm中的doGetAuthenticationInfo()方法。

 /*** 认证(登录时调用)(用户的角色信息集合)*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//获取用户的输入的账号.String username = (String) token.getPrincipal();User user = userService.getByUserName(username);if (user == null) {throw new UnknownAccountException("账号不存在!");}if (user.getStatus() != null && UserStatusEnum.DISABLE.getCode().equals(user.getStatus())) {throw new LockedAccountException("帐号已被锁定,禁止登录!");}// principal参数使用用户Id,方便动态刷新用户权限SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(),ByteSource.Util.bytes(username),//realm namegetName());// 当验证都通过后,把用户信息放在session里Session session = SecurityUtils.getSubject().getSession();session.setAttribute("userSession", user);session.setAttribute("userSessionId", user.getId());return authenticationInfo;}

我们在ShiroConfig中配置了凭证匹配器:

@Bean(name = "shiroRealm")public ShiroRealm shiroRealm(@Qualifier("credentialsMatcher") RetryLimitCredentialsMatcher matcher) {ShiroRealm shiroRealm = new ShiroRealm();shiroRealm.setCredentialsMatcher(credentialsMatcher());return shiroRealm;}/*** 凭证匹配器* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了* 所以我们需要修改下doGetAuthenticationInfo中的代码;*/@Bean(name = "credentialsMatcher")public RetryLimitCredentialsMatcher credentialsMatcher() {return new RetryLimitCredentialsMatcher();}

Shiro密码凭证匹配器:

package com.zyd.shiro.business.shiro.credentials;import com.zyd.shiro.util.PasswordUtil;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;/*** Shiro-密码凭证匹配器(验证密码有效性)** @author zjjlive)* @version 1.0* @website https://www.foreknow.me* @date 2018/4/16 16:26* @since 1.0*/
public class CredentialsMatcher extends SimpleCredentialsMatcher {@Overridepublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {UsernamePasswordToken utoken = (UsernamePasswordToken) token;//获得用户输入的密码:(可以采用加盐(salt)的方式去检验)String inPassword = new String(utoken.getPassword());//获得数据库中的密码String dbPassword = (String) info.getCredentials();try {dbPassword = PasswordUtil.decrypt(dbPassword, utoken.getUsername());} catch (Exception e) {e.printStackTrace();return false;}//进行密码的比对return this.equals(inPassword, dbPassword);}
}

所以在认证时的密码是加过密的,使用AES+md5散发将密码与盐值组合加密两次。则我们在增加用户的时候,对用户的密码则要进过相同规则的加密才行。
添加用户代码如下:

 /*** 添加用户*/@RequiresPermissions("user:add")@PostMapping(value = "/add")public ResponseVO add(User user) {User dbUser = userService.getByUserName(user.getUsername());if (dbUser != null) {return ResultUtil.error("用户[" + user.getUsername() + "]已存在!请更改用户名");}try {user.setPassword(PasswordUtil.encrypt(user.getPassword(), user.getUsername()));userService.insert(user);return ResultUtil.success("成功");} catch (Exception e) {e.printStackTrace();return ResultUtil.error("error");}}

Shiro-密码重试次数的匹配管理


package com.zyd.shiro.business.shiro.credentials;import com.zyd.shiro.business.consts.SessionConst;
import com.zyd.shiro.business.entity.User;
import com.zyd.shiro.business.service.SysUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;import java.util.concurrent.TimeUnit;/*** Shiro-密码输入错误的状态下重试次数的匹配管理*/
public class RetryLimitCredentialsMatcher extends CredentialsMatcher {private static final Logger LOGGER = LoggerFactory.getLogger(RetryLimitCredentialsMatcher.class);/*** 用户登录次数计数  redisKey 前缀*/private static final String SHIRO_LOGIN_COUNT = "shiro_login_count_";/*** 用户登录是否被锁定    一小时 redisKey 前缀*/private static final String SHIRO_IS_LOCK = "shiro_is_lock_";@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate SysUserService userService;@Overridepublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {User shiroUser = (User) info.getPrincipals().getPrimaryPrincipal();Long userId = shiroUser.getId();User user = userService.getByPrimaryKey(userId);String username = user.getUsername();// 访问一次,计数一次ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();String loginCountKey = SHIRO_LOGIN_COUNT + username;String isLockKey = SHIRO_IS_LOCK + username;opsForValue.increment(loginCountKey, 1);if (redisTemplate.hasKey(isLockKey)) {throw new ExcessiveAttemptsException("帐号[" + username + "]已被禁止登录!");}// 计数大于5时,设置用户被锁定一小时String loginCount = String.valueOf(opsForValue.get(loginCountKey));int retryCount = (5 - Integer.parseInt(loginCount));if (retryCount <= 0) {opsForValue.set(isLockKey, "LOCK");redisTemplate.expire(isLockKey, 1, TimeUnit.HOURS);redisTemplate.expire(loginCountKey, 1, TimeUnit.HOURS);throw new ExcessiveAttemptsException("由于密码输入错误次数过多,帐号[" + username + "]已被禁止登录!");}boolean matches = super.doCredentialsMatch(token, info);if (!matches) {String msg = retryCount <= 0 ? "您的账号一小时内禁止登录!" : "您还剩" + retryCount + "次重试的机会";throw new AccountException("帐号或密码不正确!" + msg);}//清空登录计数redisTemplate.delete(loginCountKey);try {userService.updateUserLastLoginInfo(user);} catch (Exception e) {e.printStackTrace();}// 当验证都通过后,把用户信息放在session里// 注:User必须实现序列化SecurityUtils.getSubject().getSession().setAttribute(SessionConst.USER_SESSION_KEY, user);return true;}
}

shiro授权

 /*** 授权 权限认证,为当前登录的Subject授予角色和权限(角色的权限信息集合)*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {User user = (User) SecurityUtils.getSubject().getPrincipal();// 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();Long userId = user.getId();// 赋予角色List<Role> roleList = roleService.listRolesByUserId(userId);for (Role role : roleList) {info.addRole(role.getName());}// 赋予权限List<Resources> resourcesList = resourcesService.listByUserId(userId);if (!CollectionUtils.isEmpty(resourcesList)) {for (Resources resources : resourcesList) {String permission = resources.getPermission();System.out.println(resources.getName() + "   " + permission);if (!StringUtils.isEmpty(permission)) {info.addStringPermission(permission);}}}return info;}

从以上代码中可以看出来,根据用户id查询出用户的权限,放入SimpleAuthorizationInfo。关联表user_role,role_resources,resources,三张表,根据用户所拥有的角色,角色所拥有的权限,查询出分配给该用户的所有权限的url。当访问的链接中配置在shiro中时,或者使用shiro标签,shiro权限注解时,则会访问该方法,判断该用户是否拥有相应的权限。

在ShiroConfig中有如下代码:

 /*** ShiroFilterFactoryBean 处理拦截资源文件问题。* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager* Filter Chain定义说明* 1、一个URL可以配置多个Filter,使用逗号分隔* 2、当设置多个过滤器时,全部验证通过,才视为通过* 3、部分过滤器可指定参数,如perms,roles*/@Bean(name = "shiroFilter")public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 必须设置 SecurityManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面shiroFilterFactoryBean.setLoginUrl("/passport/login/");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/index");// 未授权界面;shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");// 配置数据库中的resourceMap<String, String> filterChainDefinitionMap = shiroService.loadFilterChainDefinitions();shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}@Bean(name = "securityManager")public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm authRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 设置realm.securityManager.setRealm(authRealm);securityManager.setCacheManager(redisCacheManager());// 自定义session管理 使用redissecurityManager.setSessionManager(sessionManager());// 注入记住我管理器securityManager.setRememberMeManager(rememberMeManager());return securityManager;}

配置shiro的过滤器。以上代码将静态文件设置为任何权限都可访问

  // 加载数据库中配置的资源权限列表List<Resources> resourcesList = resourcesService.listUrlAndPermission();for (Resources resources : resourcesList) {if (!StringUtils.isEmpty(resources.getUrl()) && !StringUtils.isEmpty(resources.getPermission())) {String permission = "perms[" + resources.getPermission() + "]";filterChainDefinitionMap.put(resources.getUrl(), permission);}}

然后在数据中查询所有的资源,将该资源的url当作key,配置拥有该url权限的用户才可访问该url。
最后加入 filterChainDefinitionMap.put(“/*”, “authc”);表示其他没有配置的链接都需要认证才可访问。注意这个要放最后面,因为shiro的匹配是从上往下,如果匹配到就不继续匹配了,所以把 /放到最前面,则 后面的链接都无法匹配到了。
而这段代码是在项目启动的时候加载的。加载的数据是放到内存中的。但是当权限增加或者删除时,正常情况下不会重新启动,重新加载权限。所以需要调用以下代码的updatePermission()方法来重新加载权限。


package com.zyd.shiro.business.service.impl;import com.zyd.shiro.business.entity.Resources;
import com.zyd.shiro.business.entity.User;
import com.zyd.shiro.business.service.ShiroService;
import com.zyd.shiro.business.service.SysResourcesService;
import com.zyd.shiro.business.service.SysUserService;
import com.zyd.shiro.business.shiro.realm.ShiroRealm;
import com.zyd.shiro.framework.holder.SpringContextHolder;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** Shiro-权限相关的业务处理** @author zjjlive)* @version 1.0* @website https://www.foreknow.me* @date 2018/4/16 16:26* @since 1.0*/
@Service
public class ShiroServiceImpl implements ShiroService {private static final Logger LOG = LoggerFactory.getLogger(ShiroService.class);@Autowiredprivate SysResourcesService resourcesService;@Autowiredprivate SysUserService userService;/*** 初始化权限*/@Overridepublic Map<String, String> loadFilterChainDefinitions() {/*配置访问权限- anon:所有url都都可以匿名访问- authc: 需要认证才能进行访问(此处指所有非匿名的路径都需要登陆才能访问)- user:配置记住我或认证通过可以访问*/Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了filterChainDefinitionMap.put("/passport/logout", "logout");filterChainDefinitionMap.put("/passport/login", "anon");filterChainDefinitionMap.put("/passport/signin", "anon");filterChainDefinitionMap.put("/favicon.ico", "anon");filterChainDefinitionMap.put("/error", "anon");filterChainDefinitionMap.put("/assets/**", "anon");// 加载数据库中配置的资源权限列表List<Resources> resourcesList = resourcesService.listUrlAndPermission();for (Resources resources : resourcesList) {if (!StringUtils.isEmpty(resources.getUrl()) && !StringUtils.isEmpty(resources.getPermission())) {String permission = "perms[" + resources.getPermission() + "]";filterChainDefinitionMap.put(resources.getUrl(), permission);}}// 本例子中并不存在什么特别关键的操作,所以直接使用user认证。如果有朋友是参考本例子的shiro开发其他安全功能(比如支付等)时,建议针对这类操作使用authc权限 by yadong.zhangfilterChainDefinitionMap.put("/**", "user");return filterChainDefinitionMap;}/*** 重新加载权限*/@Overridepublic void updatePermission() {ShiroFilterFactoryBean shirFilter = SpringContextHolder.getBean(ShiroFilterFactoryBean.class);synchronized (shirFilter) {AbstractShiroFilter shiroFilter = null;try {shiroFilter = (AbstractShiroFilter) shirFilter.getObject();} catch (Exception e) {throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!");}PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();// 清空老的权限控制manager.getFilterChains().clear();shirFilter.getFilterChainDefinitionMap().clear();shirFilter.setFilterChainDefinitionMap(loadFilterChainDefinitions());// 重新构建生成Map<String, String> chains = shirFilter.getFilterChainDefinitionMap();for (Map.Entry<String, String> entry : chains.entrySet()) {String url = entry.getKey();String chainDefinition = entry.getValue().trim().replace(" ", "");manager.createChain(url, chainDefinition);}}}/*** 重新加载用户权限** @param user*/@Overridepublic void reloadAuthorizingByUserId(User user) {RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();ShiroRealm shiroRealm = (ShiroRealm) rsm.getRealms().iterator().next();Subject subject = SecurityUtils.getSubject();String realmName = subject.getPrincipals().getRealmNames().iterator().next();SimplePrincipalCollection principals = new SimplePrincipalCollection(user, realmName);subject.runAs(principals);shiroRealm.getAuthorizationCache().remove(subject.getPrincipals());subject.releaseRunAs();LOG.info("用户[{}]的权限更新成功!!", user.getUsername());}/*** 重新加载所有拥有roleId角色的用户的权限** @param roleId*/@Overridepublic void reloadAuthorizingByRoleId(Long roleId) {List<User> userList = userService.listByRoleId(roleId);if (CollectionUtils.isEmpty(userList)) {return;}for (User user : userList) {reloadAuthorizingByUserId(user);}}}

shiro会话管理

这个使用了redis保存session。这样可以实现集群的session共享。代码在ShiroConfig中

 @Beanpublic SecurityManager securityManager(){DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();//设置realm.securityManager.setRealm(myShiroRealm());// 自定义缓存实现 使用redis//securityManager.setCacheManager(cacheManager());// 自定义session管理 使用redissecurityManager.setSessionManager(sessionManager());return securityManager;}

自定义session

<dependency><groupId>org.crazycake</groupId><artifactId>shiro-redis</artifactId><version>2.4.2.1-RELEASE</version>
</dependency>

然后再配置

 /*** 配置shiro redisManager* 使用的是shiro-redis开源插件* @return*/public RedisManager redisManager() {RedisManager redisManager = new RedisManager();redisManager.setHost(host);redisManager.setPort(port);redisManager.setExpire(1800);// 配置缓存过期时间redisManager.setTimeout(timeout);// redisManager.setPassword(password);return redisManager;}/*** cacheManager 缓存 redis实现* 使用的是shiro-redis开源插件* @return*/public RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();redisCacheManager.setRedisManager(redisManager());return redisCacheManager;}/*** RedisSessionDAO shiro sessionDao层的实现 通过redis* 使用的是shiro-redis开源插件*/@Beanpublic RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();redisSessionDAO.setRedisManager(redisManager());return redisSessionDAO;}/*** shiro session的管理*/@Beanpublic DefaultWebSessionManager sessionManager() {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setSessionDAO(redisSessionDAO());return sessionManager;}

配置文件 application.properties中加入:

#redis
# Redis服务器地址
spring.redis.host= localhost
# Redis服务器连接端口
spring.redis.port= 6379
# 连接池中的最大空闲连接
spring.redis.pool.max-idle= 8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle= 0
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active= 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait= -1
# 连接超时时间(毫秒)
spring.redis.timeout= 0

当然运行的时候要先启动redis。将自己的redis配置在以上配置中。这样session就存在redis中了。
上面ShiroConfig中的securityManager()方法中,我把

//securityManager.setCacheManager(cacheManager());

这行代码注了,因为每次在需要验证的地方,比如在subject.hasRole(“admin”) 或 subject.isPermitted(“admin”)、@RequiresRoles(“admin”) 、 shiro:hasPermission=”/users/add”的时候都会调用MyShiroRealm中的doGetAuthorizationInfo()。但是以为这些信息不是经常变的,所以有必要进行缓存。把这行代码的注释打开,的时候都会调用MyShiroRealm中的doGetAuthorizationInfo()的返回结果会被redis缓存。但是这里稍微有个小问题,就是在刚修改用户的权限时,无法立即失效。本来我是使用了ShiroService中的clearUserAuthByUserId()想清除当前session存在的用户的权限缓存,但是没有效果。不知道什么原因。希望哪个大神看到后帮忙弄个解决方法。所以我干脆就把doGetAuthorizationInfo()的返回结果通过spring cache的方式加入缓存。

  @Cacheable(cacheNames="resources",key="#map['userid'].toString()+#map['type']")public List<Resources> loadUserResources(Map<String, Object> map) {return resourcesMapper.loadUserResources(map);}

这样也可以实现,然后在修改权限时加上注解

 @CacheEvict(cacheNames="resources", allEntries=true)

这样修改权限后可以立即生效。其实我感觉这样不好,因为清楚了我是清除了所有用户的权限缓存,其实只要修改当前session在线中被修改权限的用户就行了。 先这样吧,以后再研究下,修改得更好一点。

按钮控制

在前端页面,对按钮进行细粒度权限控制,只需要在按钮上加上shiro:hasPermission

  <button shiro:hasPermission="/users/add" type="button"  onclick="$('#addUser').modal();" class="btn btn-info" >新增</button>

19.前端页面

user页面

<#include "/layout/header.ftl"/>
<div class="clearfix"></div>
<div class="row"><div class="col-md-12 col-sm-12 col-xs-12"><ol class="breadcrumb"><li><a href="/">首页</a></li><li class="active">用户管理</li></ol><div class="x_panel"><div class="x_content"><div class="<#--table-responsive-->"><div class="btn-group hidden-xs" id="toolbar"><@shiro.hasPermission name="user:add"><button id="btn_add" type="button" class="btn btn-default" title="新增用户"><i class="fa fa-plus"></i> 新增用户</button></@shiro.hasPermission><@shiro.hasPermission name="user:batchDelete"><button id="btn_delete_ids" type="button" class="btn btn-default" title="删除选中"><i class="fa fa-trash-o"></i> 批量删除</button></@shiro.hasPermission></div><table id="tablelist"></table></div></div></div></div>
</div>
<#include "/layout/footer.ftl"/>
<!--弹框-->
<div class="modal fade bs-example-modal-sm" id="selectRole" tabindex="-1" role="dialog" aria-labelledby="selectRoleLabel"><div class="modal-dialog modal-sm" role="document"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button><h4 class="modal-title" id="selectRoleLabel">分配角色</h4></div><div class="modal-body"><form id="boxRoleForm"><div class="zTreeDemoBackground left"><ul id="treeDemo" class="ztree"></ul></div></form></div><div class="modal-footer"><button type="button" class="btn btn-default" data-dismiss="modal">关闭</button></div></div></div>
</div>
<!--/弹框-->
<!--添加用户弹框-->
<div class="modal fade" id="addOrUpdateModal" tabindex="-1" role="dialog" aria-labelledby="addroleLabel"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button><h4 class="modal-title" id="addroleLabel">添加用户</h4></div><div class="modal-body"><form id="addOrUpdateForm" class="form-horizontal form-label-left" novalidate><input type="hidden" name="id"><div class="item form-group"><label class="control-label col-md-3 col-sm-3 col-xs-12" for="username">用户名: <span class="required">*</span></label><div class="col-md-6 col-sm-6 col-xs-12"><input type="text" class="form-control col-md-7 col-xs-12" name="username" id="username" required="required" placeholder="请输入用户名"/></div></div><div class="item form-group"><label class="control-label col-md-3 col-sm-3 col-xs-12" for="password">密码: <span class="required">*</span></label><div class="col-md-6 col-sm-6 col-xs-12"><input type="password" class="form-control col-md-7 col-xs-12" id="password" name="password" required="required" placeholder="请输入密码 6位以上"/></div></div><div class="item form-group"><label class="control-label col-md-3 col-sm-3 col-xs-12" for="nickname">角色名称:</label><div class="col-md-6 col-sm-6 col-xs-12"><input type="text" class="form-control col-md-7 col-xs-12" name="nickname" id="nickname" placeholder="请输入昵称"/></div></div><div class="item form-group"><label class="control-label col-md-3 col-sm-3 col-xs-12" for="mobile">手机:</label><div class="col-md-6 col-sm-6 col-xs-12"><input type="tel" class="form-control col-md-7 col-xs-12" name="mobile" id="mobile" data-validate-length-range="8,20" placeholder="请输入手机号"/></div></div><div class="item form-group"><label class="control-label col-md-3 col-sm-3 col-xs-12" for="email">邮箱:</label><div class="col-md-6 col-sm-6 col-xs-12"><input type="email" class="form-control col-md-7 col-xs-12" name="email" id="email" placeholder="请输入邮箱"/></div></div><div class="item form-group"><label class="control-label col-md-3 col-sm-3 col-xs-12" for="qq">QQ:</label><div class="col-md-6 col-sm-6 col-xs-12"><input type="number" class="form-control col-md-7 col-xs-12" name="qq" id="qq" placeholder="请输入QQ"/></div></div></form></div><div class="modal-footer"><button type="button" class="btn btn-default" data-dismiss="modal">关闭</button><button type="button" class="btn btn-primary addOrUpdateBtn">保存</button></div></div></div>
</div>
<!--/添加用户弹框-->
<script>/*** 操作按钮* @param code* @param row* @param index* @returns {string}*/function operateFormatter(code, row, index) {var currentUserId = '${user.id}';var trUserId = row.id;var operateBtn = ['<@shiro.hasPermission name="user:edit"><a class="btn btn-xs btn-primary btn-update" data-id="' + trUserId + '"><i class="fa fa-edit"></i>编辑</a></@shiro.hasPermission>',];if (currentUserId != trUserId) {operateBtn.push('<@shiro.hasPermission name="user:delete"><a class="btn btn-xs btn-danger btn-remove" data-id="' + trUserId + '"><i class="fa fa-trash-o"></i>删除</a></@shiro.hasPermission>');operateBtn.push('<@shiro.hasPermission name="user:allotRole"><a class="btn btn-xs btn-info btn-allot" data-id="' + trUserId + '"><i class="fa fa-circle-thin"></i>分配角色</a></@shiro.hasPermission>')}return operateBtn.join('');}$(function () {var options = {url: "/user/list",getInfoUrl: "/user/get/{id}",updateUrl: "/user/edit",removeUrl: "/user/remove",createUrl: "/user/add",saveRolesUrl: "/user/saveUserRoles",columns: [{checkbox: true}, {field: 'username',title: '用户名',editable: false,}, {field: 'nickname',title: '角色',editable: true}, {field: 'email',title: '邮箱',editable: true}, {field: 'qq',title: 'qq',editable: true}, {field: 'userType',title: '用户类型',editable: false}, {field: 'statusEnum',title: '状态',editable: false}, {field: 'lastLoginTime',title: '最后登录时间',editable: false,formatter: function (code) {return new Date(code).format("yyyy-MM-dd hh:mm:ss")}}, {field: 'loginCount',title: '登录次数',editable: false}, {field: 'operate',title: '操作',formatter: operateFormatter //自定义方法,添加操作按钮}],modalName: "用户"};//1.初始化Table$.tableUtil.init(options);//2.初始化Button的点击事件$.buttonUtil.init(options);/* 分配用户角色 */$('#tablelist').on('click', '.btn-allot', function () {console.log("分配权限");var $this = $(this);var userId = $this.attr("data-id");$.ajax({async: false,type: "POST",data: {uid: userId},url: '/roles/rolesWithSelected',dataType: 'json',success: function (json) {var data = json.data;console.log(data);var setting = {check: {enable: true,chkboxType: {"Y": "ps", "N": "ps"},chkStyle: "radio"},data: {simpleData: {enable: true}},callback: {onCheck: function (event, treeId, treeNode) {console.log(treeNode.tId + ", " + treeNode.name + "," + treeNode.checked);var treeObj = $.fn.zTree.getZTreeObj(treeId);var nodes = treeObj.getCheckedNodes(true);var ids = new Array();for (var i = 0; i < nodes.length; i++) {//获取选中节点的值ids.push(nodes[i].id);}console.log(ids);console.log(userId);$.post(options.saveRolesUrl, {"userId": userId, "roleIds": ids.join(",")}, function (obj) {}, 'json');}}};var tree = $.fn.zTree.init($("#treeDemo"), setting, data);tree.expandAll(true);//全部展开$('#selectRole').modal('show');}});});});
</script>

20.权限用法

数据监控
数据报表    null
用户管理    null
系统配置    null
用户列表    users
新增用户    user:add
批量删除用户 user:batchDelete
编辑用户    user:edit
删除用户    user:delete
分配用户角色 user:allotRole
资源管理    resources
角色管理    roles
新增资源    resource:add
批量删除资源 resource:batchDelete
编辑资源    resource:edit
删除资源    resource:delete
新增角色    role:add
批量删除角色 role:batchDelete
编辑角色    role:edit
删除角色    role:delete
分配角色资源 role:allotResource
Druid监控   druid

21.源码下载

源码地址:shiro: Springboot Shiro权限管理系统

SpringBoot整合shiro实现细粒度动态权限相关推荐

  1. springboot整合shiro + jwt + redis实现权限认证(上手即用)

    目录 前言 项目结构 依赖导入 建数据库表 建表语句 使用插件生成增删改查 添加MyRealm 添加ShiroConfig 添加JwtFilter JWT相关得类 JwtToken JwtAudien ...

  2. SpringBoot 整合 Shiro Thymeleaf Mysql 动态授权

    文章目录 需求安排 一.前期准备 1. maven依赖 2. 创建数据库+初始化表数据 3. 实体类 4. mapper接口 5. mapper接口映射文件 6. service接口 7. servi ...

  3. SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例(转)...

    SpringBoot整合mybatis.shiro.redis实现基于数据库的细粒度动态权限管理系统实例 shiro 目录(?)[+] 前言 表结构 maven配置 配置Druid 配置mybatis ...

  4. SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例...

    SpringBoot整合mybatis.shiro.redis实现基于数据库的细粒度动态权限管理系统实例 shiro 目录(?)[+] 1.前言 本文主要介绍使用SpringBoot与shiro实现基 ...

  5. SpringBoot整合Shiro实现权限控制,验证码

    本文介绍 SpringBoot 整合 shiro,相对于 Spring Security 而言,shiro 更加简单,没有那么复杂. 目前我的需求是一个博客系统,有用户和管理员两种角色.一个用户可能有 ...

  6. SpringBoot整合Shiro搭建登录注册认证授权权限项目模板

    主要内容: 1 SpringBoot整合Shiro安全框架; 2 Shiro主要学习内容总结;(执行流程.主要对象接口.注意事项等) 3 Redis实现对权限信息缓存; ! 温馨提示: 想要快速搭Sh ...

  7. SpringBoot整合Shiro实现权限管理与登陆注册

    前言 Shiro解决了什么问题? 互联网无非就是一些用户C想要使用一些服务S的资源去完成某件事,S的资源不能说给谁用就给谁用,因此产生了权限的概念,即C必须有权限才能操作S的资源.S如何确定C就是C呢 ...

  8. springboot整合shiro(超详细,你想要的都在这了)

    Springboot整合Shiro 文章目录 pom依赖 前端页面(thymeleaf整合shiro) thymeleaf中shiro标签解释 数据库(整合mybatis) 理解shiro的几个组成部 ...

  9. springboot整合shiro使用shiro-spring-boot-web-starter

    此文章仅仅说明在springboot整合shiro时的一些坑,并不是教程 增加依赖 <!-- 集成shiro依赖 --> <dependency><groupId> ...

  10. springboot整合shiro和session的详细过程和自定义登录拦截器

    文章目录 1.shiro依赖 2.shiro配置 shiro过滤器配置: 关联自定义的其他管理器 自定义会话工厂: 3.登陆时记录用户信息 4.shiro一些工具类的学习 5.自定义登录拦截器 shi ...

最新文章

  1. 【学习笔记】智能制造之精益思想
  2. fiddler2抓包数据工具使用教程
  3. 不定宽高的div如何垂直居中
  4. php 导出excel类,php 导出excel类
  5. input文本框不可编辑的方法
  6. 视频编解码(九):FFMPEG操作总结一
  7. ArrayList和CopyOnWriteArrayList
  8. 【77.39%】【codeforces 734A】Anton and Danik
  9. GX Works2使用问题记录
  10. 计算机cad名词解释,CAD全部名词解释
  11. HTTP报文字段说明
  12. ASP.NET Core 和 EF Core 系列教程——迁移
  13. 抓取android log,一键抓取Android的Locat Log
  14. java arraylis 删除_Java ArrayList批量删除算法分析
  15. OSWorkflow(转载)
  16. 计算机青岛科技大学济南大学,山东考生在山东理工,济大,山东科技和青岛科技中该如何选择?...
  17. 抓包PC微信小程序失败解决方法
  18. AI三大主义:符号主义、联结主义、行为主义
  19. 计算机上静音快捷键是什么,电脑静音快捷键是什么(如何设置电脑一键静音?)...
  20. 虚拟机Centos,登陆之后又弹回到登陆界面,无法进入系统

热门文章

  1. 计算机考研828是什么意思,浙江大学828计算机程序设计基础考研复习经验
  2. python生成列表a到z_python实现 1-26=A-Z, then AA-AZ, BA-BZ...ZZA-ZZZ, AAAA, etc.
  3. 用c语言编程一个滑稽图案,滑稽,用C语言搞个鼠标连点器
  4. foxmail超大附件密码不对的解决办法
  5. OpenCV探索之路(二十二):制作一个类“全能扫描王”的简易扫描软件
  6. dotnet OpenXML PPT 动画框架入门
  7. 二、安装配置java和hadoop程序
  8. 互联网大厂校招大战:华为研究生年薪最高到45万,腾讯offer数增四成
  9. 计算机音量程序是哪个键,计算机键盘上的哪个键是音量
  10. 最经典的人生定律、法则、效应总结