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
使用说明
- 使用IDE导入本项目
- 新建数据库
CREATE DATABASE shiro;
- 导入数据库
docs/db/shiro.sql
- 修改(
resources/application.yml
)配置文件
- 数据库链接属性(可搜索
datasource
或定位到L.19) - redis配置(可搜索
redis
或定位到L.69)
- 运行项目(三种方式)
- 项目根目录下执行
mvn -X clean package -Dmaven.test.skip=true
编译打包,然后执行java -jar shiro-admin/target/shiro-admin.jar
- 项目根目录下执行
mvn springboot:run
- 直接运行
ShiroAdminApplication.java
- 浏览器访问
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 {
@Autowired
private DruidProperties druidProperties;
@Bean
public 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
*/
@Bean
public FilterRegistrationBean druidStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean(new WebStatFilter());
// 添加过滤规则
bean.addUrlPatterns("/*");
// 排除的url
bean.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 {
@Autowired
protected freemarker.template.Configuration configuration;
@Autowired
protected CustomTagDirective customTagDirective;
/**
* 添加自定义标签
*/
@PostConstruct
public 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";
@Autowired
private SysResourcesService resourcesService;
@Override
public 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
@Deprecated
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public 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")
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
//设置缓存过期时间:秒
cacheManager.setDefaultExpiration(30 * 24 * 60 * 60);
return cacheManager;
}
@Bean
public 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 {
@Autowired
private RememberAuthenticationInterceptor rememberAuthenticationInterceptor;
@Override
public 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 {
/**
* 自定义异常处理路径
*/
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public 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 {
@Autowired
private ShiroService shiroService;
@Autowired
private 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();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/passport/login/");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
// 配置数据库中的resource
Map<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管理 使用redis
securityManager.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注解支持.
* 使用代理方式;所以需要开启代码支持;
*/
@Bean
public 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开源插件
*/
//@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* shiro session的管理
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(redisProperties.getExpire() * 1000L);
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
@Bean
public RedisCacheManager redisCacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
@Bean
public 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 = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
// 记住我cookie生效时间30天 ,单位秒。 注释掉,默认永久不过期 2018-07-15
simpleCookie.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;
@Override
public 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解决方案
// - 用此法解决的
// - 留作参考吧
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(password.getBytes());
//AES 要求密钥长度为 128
kg.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);
/**
* 该节代码参考自
* 感谢网友
*
* @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);
/**
* 该节代码参考自
* 感谢网友
*
* @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">
SELECT
s.*
FROM
sys_user s
WHERE
1 = 1
<if test="keywords != null and keywords != '' ">
AND
(
s.nickname LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') or
s.mobile LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') or
s.username LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') or
s.password LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') or
s.email LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') or
s.qq LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') or
s.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 BY
s.id
ORDER BY
s.create_time DESC
</select>
<select id="listByRoleId" parameterType="Long" resultMap="rm">
SELECT
s.id,
s.username,
s.nickname
FROM
sys_user s
INNER JOIN sys_user_role sur ON sur.user_id = s.id
WHERE
sur.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">
SELECT
com.*
FROM
sys_role com
WHERE
1 = 1
<if test="keywords !=null and keywords != ''">
AND (
com.description LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%')
)
</if>
ORDER BY
com.create_time DESC
</select>
<select id="queryRoleListWithSelected" parameterType="Integer" resultMap="rm">
SELECT r.id,
r.name,
r.description,
(
CASE WHEN (
SELECT ur.role_id
FROM sys_user_role ur
WHERE ur.user_id = #{userId}
AND ur.role_id = r.id
)
THEN 1
ELSE 0
END
) AS selected
FROM sys_role r
WHERE r.available = 1
</select>
<select id="listRolesByUserId" parameterType="Long" resultMap="rm">
SELECT r.id,
r.name,
r.description
FROM sys_role r
INNER JOIN sys_user_role ur ON ur.role_id = r.id
WHERE 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">
SELECT
com.*,
f.id parent_id,
f.`name` parent_name,
f.`icon` parent_icon,
f.type parent_type
FROM
sys_resources com
LEFT JOIN sys_resources f ON com.parent_id = f.id
WHERE
1 = 1
<if test="keywords !=null and keywords != ''">
AND (
com.name LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') OR
com.url LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%') OR
com.permission LIKE CONCAT('%',#{keywords , jdbcType=VARCHAR},'%')
)
</if>
ORDER by
com.parent_id ASC,
com.name DESC
</select>
<select id="listUrlAndPermission" resultMap="rm">
SELECT
url,
permission
FROM
sys_resources
WHERE
url IS NOT NULL
ORDER BY
sort ASC
</select>
<select id="listUserResources" parameterType="java.util.HashMap" resultMap="rm">
SELECT
re.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_icon
FROM
sys_resources re
LEFT JOIN sys_role_resources rr ON re.id = rr.resources_id
LEFT JOIN sys_user_role ur ON rr.role_id = ur.role_id
LEFT JOIN sys_resources node ON node.parent_id = re.id AND node.available = 1
WHERE
(re.parent_id = 0 OR re.parent_id IS NULL )
AND re.available = 1
AND ur.user_id = #{userId}
<if test="type != null">
AND re.type= #{type}
</if>
ORDER BY
re.sort ASC,
node.sort ASC
</select>
<select id="queryResourcesListWithSelected" parameterType="Long" resultMap="rm">
SELECT
re.id,
re.`name`,
re.parent_id,
re.url,
re.type,
re.icon,
(
CASE
WHEN EXISTS (
SELECT
1
FROM
sys_role_resources rr
WHERE
rr.resources_id = re.id
AND rr.role_id = #{rid}
)
THEN
'true'
ELSE
'false'
END
) AS checked
FROM
sys_resources re
ORDER BY
re.sort ASC
</select>
<select id="listAllAvailableMenu" resultMap="rm">
SELECT
r.id,
r.`name`,
node.id AS node_id,
node.`name` AS node_name,
node.parent_id
FROM
sys_resources r
LEFT JOIN sys_resources node ON (
node.parent_id = r.id
AND node.available = 1
AND node.type = 'menu'
)
WHERE
r.available = 1
AND r.type = 'menu'
AND (r.url IS NULL OR r.url = '')
AND (r.permission IS NULL OR r.permission = '')
ORDER BY
r.sort ASC,
node.sort ASC
</select>
<select id="listMenuResourceByPid" parameterType="Long" resultMap="rm">
SELECT
re.id,
re.`name`
FROM
sys_resources re
WHERE re.parent_id = #{pid}
ORDER BY
re.sort ASC
</select>
<select id="listByUserId" parameterType="Long" resultMap="rm">
SELECT
re.id,
re.`name`,
re.parent_id,
re.url,
re.permission,
re.icon,
re.sort
FROM
sys_resources re
INNER JOIN sys_role_resources rr ON re.id = rr.resources_id
INNER JOIN sys_user_role ur ON rr.role_id = ur.role_id
WHERE
ur.user_id = #{userId}
AND re.available = 1
ORDER BY
re.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">
SELECT
user_id
FROM
sys_user_role
WHERE
role_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);
@Autowired
private SysResourcesService resourcesService;
@Autowired
private SysUserService userService;
/**
* 初始化权限
*/
@Override
public 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.zhang
filterChainDefinitionMap.put("/**", "user");
return filterChainDefinitionMap;
}
/**
* 重新加载权限
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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);
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private 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
*/
@Override
public User getByPrimaryKey(Long primaryKey) {
Assert.notNull(primaryKey, "PrimaryKey不可为空!");
SysUser sysUser = sysUserMapper.selectByPrimaryKey(primaryKey);
return null == sysUser ? null : new User(sysUser);
}
/**
* 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号
*
* @param entity
* @return
*/
@Override
public User getOneByEntity(User entity) {
Assert.notNull(entity, "User不可为空!");
SysUser sysUser = sysUserMapper.selectOne(entity.getSysUser());
return null == sysUser ? null : new User(sysUser);
}
@Override
public 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;
}
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public User getByUserName(String userName) {
User user = new User(userName, null);
return getOneByEntity(user);
}
/**
* 通过角色Id获取用户列表
*
* @param roleId
* @return
*/
@Override
public 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 {
@Autowired
private SysRoleMapper roleMapper;
@Autowired
private SysUserMapper sysUserMapper;
/**
* 获取ztree使用的角色列表
*
* @param userId
* @return
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public boolean removeByPrimaryKey(Long primaryKey) {
return roleMapper.deleteByPrimaryKey(primaryKey) > 0;
}
/**
* 根据主键更新实体全部字段,null值会被更新
*
* @param entity
* @return
*/
@Override
public boolean update(Role entity) {
Assert.notNull(entity, "Role不可为空!");
entity.setUpdateTime(new Date());
return roleMapper.updateByPrimaryKey(entity.getSysRole()) > 0;
}
/**
* 根据主键更新属性不为null的值
*
* @param entity
* @return
*/
@Override
public boolean updateSelective(Role entity) {
Assert.notNull(entity, "Role不可为空!");
entity.setUpdateTime(new Date());
return roleMapper.updateByPrimaryKeySelective(entity.getSysRole()) > 0;
}
/**
* 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
*
* @param primaryKey
* @return
*/
@Override
public Role getByPrimaryKey(Long primaryKey) {
Assert.notNull(primaryKey, "PrimaryKey不可为空!");
SysRole sysRole = roleMapper.selectByPrimaryKey(primaryKey);
return null == sysRole ? null : new Role(sysRole);
}
/**
* 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号
*
* @param entity
* @return
*/
@Override
public Role getOneByEntity(Role entity) {
Assert.notNull(entity, "User不可为空!");
SysRole sysRole = roleMapper.selectOne(entity.getSysRole());
return null == sysRole ? null : new Role(sysRole);
}
/**
* 查询全部结果,listByEntity(null)方法能达到同样的效果
*
* @return
*/
@Override
public List<Role> listAll() {
List<SysRole> sysRole = roleMapper.selectAll();
return getRole(sysRole);
}
/**
* 根据实体中的属性值进行查询,查询条件使用等号
*
* @param entity
* @return
*/
@Override
public 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 {
@Autowired
private SysResourceMapper resourceMapper;
/**
* 分页查询
*
* @param vo
* @return
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public List<Resources> listUrlAndPermission() {
List<SysResources> sysResources = resourceMapper.listUrlAndPermission();
return getResources(sysResources);
}
/**
* 获取所有可用的菜单资源
*
* @return
*/
@Override
public List<Resources> listAllAvailableMenu() {
List<SysResources> sysResources = resourceMapper.listAllAvailableMenu();
return getResources(sysResources);
}
/**
* 获取父级资源下所有menu资源
*
* @param pid
* @return
*/
@Override
public 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
*/
@Override
public List<Resources> listByUserId(Long userId) {
List<SysResources> sysResources = resourceMapper.listByUserId(userId);
return getResources(sysResources);
}
/**
* 保存一个实体,null的属性不会保存,会使用数据库默认值
*
* @param entity
* @return
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public boolean removeByPrimaryKey(Long primaryKey) {
return resourceMapper.deleteByPrimaryKey(primaryKey) > 0;
}
/**
* 根据主键更新实体全部字段,null值会被更新
*
* @param entity
* @return
*/
@Override
public boolean update(Resources entity) {
Assert.notNull(entity, "Resources不可为空!");
entity.setUpdateTime(new Date());
return resourceMapper.updateByPrimaryKey(entity.getSysResources()) > 0;
}
/**
* 根据主键更新属性不为null的值
*
* @param entity
* @return
*/
@Override
public boolean updateSelective(Resources entity) {
Assert.notNull(entity, "Resources不可为空!");
entity.setUpdateTime(new Date());
return resourceMapper.updateByPrimaryKeySelective(entity.getSysResources()) > 0;
}
/**
* 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
*
* @param primaryKey
* @return
*/
@Override
public Resources getByPrimaryKey(Long primaryKey) {
Assert.notNull(primaryKey, "PrimaryKey不可为空!");
SysResources sysResources = resourceMapper.selectByPrimaryKey(primaryKey);
return null == sysResources ? null : new Resources(sysResources);
}
/**
* 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号
*
* @param entity
* @return
*/
@Override
public Resources getOneByEntity(Resources entity) {
Assert.notNull(entity, "User不可为空!");
SysResources sysResources = resourceMapper.selectOne(entity.getSysResources());
return null == sysResources ? null : new Resources(sysResources);
}
/**
* 查询全部结果,listByEntity(null)方法能达到同样的效果
*
* @return
*/
@Override
public List<Resources> listAll() {
List<SysResources> sysResources = resourceMapper.selectAll();
return getResources(sysResources);
}
/**
* 根据实体中的属性值进行查询,查询条件使用等号
*
* @param entity
* @return
*/
@Override
public 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 {
@Autowired
private SysUserRoleMapper resourceMapper;
/**
* 保存一个实体,null的属性不会保存,会使用数据库默认值
*
* @param entity
* @return
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public boolean removeByPrimaryKey(Long primaryKey) {
return resourceMapper.deleteByPrimaryKey(primaryKey) > 0;
}
/**
* 根据主键更新实体全部字段,null值会被更新
*
* @param entity
* @return
*/
@Override
public boolean update(UserRole entity) {
Assert.notNull(entity, "UserRole不可为空!");
entity.setUpdateTime(new Date());
return resourceMapper.updateByPrimaryKey(entity.getSysUserRole()) > 0;
}
/**
* 根据主键更新属性不为null的值
*
* @param entity
* @return
*/
@Override
public boolean updateSelective(UserRole entity) {
Assert.notNull(entity, "UserRole不可为空!");
entity.setUpdateTime(new Date());
return resourceMapper.updateByPrimaryKeySelective(entity.getSysUserRole()) > 0;
}
/**
* 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
*
* @param primaryKey
* @return
*/
@Override
public UserRole getByPrimaryKey(Long primaryKey) {
Assert.notNull(primaryKey, "PrimaryKey不可为空!");
SysUserRole sysUserRole = resourceMapper.selectByPrimaryKey(primaryKey);
return null == sysUserRole ? null : new UserRole(sysUserRole);
}
/**
* 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号
*
* @param entity
* @return
*/
@Override
public UserRole getOneByEntity(UserRole entity) {
Assert.notNull(entity, "User不可为空!");
SysUserRole sysUserRole = resourceMapper.selectOne(entity.getSysUserRole());
return null == sysUserRole ? null : new UserRole(sysUserRole);
}
/**
* 查询全部结果,listByEntity(null)方法能达到同样的效果
*
* @return
*/
@Override
public List<UserRole> listAll() {
List<SysUserRole> sysUserRole = resourceMapper.selectAll();
return getUserRole(sysUserRole);
}
/**
* 根据实体中的属性值进行查询,查询条件使用等号
*
* @param entity
* @return
*/
@Override
public 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;
}
/**
* 添加用户角色
* 该节代码参考自
*
* @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 {
@Autowired
private SysRoleResourcesMapper resourceMapper;
/**
* 保存一个实体,null的属性不会保存,会使用数据库默认值
*
* @param entity
* @return
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public boolean removeByPrimaryKey(Long primaryKey) {
return resourceMapper.deleteByPrimaryKey(primaryKey) > 0;
}
/**
* 根据主键更新实体全部字段,null值会被更新
*
* @param entity
* @return
*/
@Override
public boolean update(RoleResources entity) {
Assert.notNull(entity, "RoleResources不可为空!");
entity.setUpdateTime(new Date());
return resourceMapper.updateByPrimaryKey(entity.getSysRoleResources()) > 0;
}
/**
* 根据主键更新属性不为null的值
*
* @param entity
* @return
*/
@Override
public boolean updateSelective(RoleResources entity) {
Assert.notNull(entity, "RoleResources不可为空!");
entity.setUpdateTime(new Date());
return resourceMapper.updateByPrimaryKeySelective(entity.getSysRoleResources()) > 0;
}
/**
* 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号
*
* @param primaryKey
* @return
*/
@Override
public RoleResources getByPrimaryKey(Long primaryKey) {
Assert.notNull(primaryKey, "PrimaryKey不可为空!");
SysRoleResources sysRoleResources = resourceMapper.selectByPrimaryKey(primaryKey);
return null == sysRoleResources ? null : new RoleResources(sysRoleResources);
}
/**
* 根据实体中的属性进行查询,只能有一个返回值,有多个结果时抛出异常,查询条件使用等号
*
* @param entity
* @return
*/
@Override
public RoleResources getOneByEntity(RoleResources entity) {
Assert.notNull(entity, "User不可为空!");
SysRoleResources sysRoleResources = resourceMapper.selectOne(entity.getSysRoleResources());
return null == sysRoleResources ? null : new RoleResources(sysRoleResources);
}
/**
* 查询全部结果,listByEntity(null)方法能达到同样的效果
*
* @return
*/
@Override
public List<RoleResources> listAll() {
List<SysRoleResources> sysRoleResources = resourceMapper.selectAll();
return getRoleResources(sysRoleResources);
}
/**
* 根据实体中的属性值进行查询,查询条件使用等号
*
* @param entity
* @return
*/
@Override
public 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;
}
/**
* 添加角色资源
* 该节代码参考自
*
* @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")
@ResponseBody
public ResponseVO submitLogin(String username, String password, boolean rememberMe, String kaptcha) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return ResultUtil.error("用户名或密码不能为空!");
}
//获取当前的Subject
Subject 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) {
//
// 此处有坑: 退出登录,其实不用实现任何东西,只需要保留这个接口即可,也不可能通过下方的代码进行退出
// 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 {
@Autowired
private SysUserService userService;
@Autowired
private 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 {
@Autowired
private SysRoleService roleService;
@Autowired
private SysRoleResourcesService roleResourcesService;
@Autowired
private 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 {
@Autowired
private SysResourcesService resourcesService;
@Autowired
private 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)
@ResponseBody
public 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;
@Autowired
private ServerProperties serverProperties;
/**
* 初始化ExceptionController
*
* @param errorAttributes
*/
@Autowired
public 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则不记录到DB
Map<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
*/
@Override
public 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;
@Transient
private 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;
@Transient
private String checked;
@Transient
private SysResources parent;
@Transient
private 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();
}
}
}
@JsonIgnore
public 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;
}
@JsonIgnore
public 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;
}
@JsonIgnore
public 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;
}
@JsonIgnore
public 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;
}
@JsonIgnore
public 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 {
@Resource
private SysUserService userService;
@Resource
private SysResourcesService resourcesService;
@Resource
private SysRoleService roleService;
/**
* 认证(登录时调用)(用户的角色信息集合)
*/
@Override
protected 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 name
getName()
);
// 当验证都通过后,把用户信息放在session里
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("userSession", user);
session.setAttribute("userSessionId", user.getId());
return authenticationInfo;
}
/**
* 授权 权限认证,为当前登录的Subject授予角色和权限(角色的权限信息集合)
*/
@Override
protected 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 {
@Autowired
private SysUserService userService;
@Override
public 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;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public 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")
@ResponseBody
public ResponseVO submitLogin(String username, String password, boolean rememberMe, String kaptcha) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return ResultUtil.error("用户名或密码不能为空!");
}
//获取当前的Subject
Subject 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()方法。
/**
* 认证(登录时调用)(用户的角色信息集合)
*/
@Override
protected 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 name
getName()
);
// 当验证都通过后,把用户信息放在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 {
@Override
public 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_";
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private SysUserService userService;
@Override
public 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授予角色和权限(角色的权限信息集合)
*/
@Override
protected 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();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/passport/login/");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
// 配置数据库中的resource
Map<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管理 使用redis
securityManager.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);
@Autowired
private SysResourcesService resourcesService;
@Autowired
private SysUserService userService;
/**
* 初始化权限
*/
@Override
public 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.zhang
filterChainDefinitionMap.put("/**", "user");
return filterChainDefinitionMap;
}
/**
* 重新加载权限
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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中
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
//securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.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开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* shiro session的管理
*/
@Bean
public 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">×</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">×</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