113 KiB
icon | title | date | category | tag | star | description | sticky | |||
---|---|---|---|---|---|---|---|---|---|---|
simple-icons:spring | SpringSecurity | 2025-05-07 |
|
|
10 | 若依的SpringSecurity案例 | true |
若依的SpringSecurity案例
一、准备
1. 导入依赖
1. 父依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modules>
<module>01-druid</module>
<module>02-SpringSecurity</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mangmang</groupId>
<artifactId>learn-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>learn-test</name>
<packaging>pom</packaging>
<description>依赖版本控制</description>
<properties>
<java.version>1.8</java.version>
<mybatis-plus.version>3.4.3.1</mybatis-plus.version>
<mybatis-plus-generator.version>3.4.1</mybatis-plus-generator.version>
<mybatis-velocity.version>2.3</mybatis-velocity.version>
<druid.version>1.2.4</druid.version>
<jjwt.version>0.9.1</jjwt.version>
<knife4j.version>2.0.8</knife4j.version>
<userAgentUtils.version>1.21</userAgentUtils.version>
<mybatisplus-plus.version>1.5.1-RELEASE</mybatisplus-plus.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- 解析客户端操作系统、浏览器等 -->
<!-- 获取浏览器信息-->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>${userAgentUtils.version}</version>
</dependency>
<!--接口文档生成-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!--token生成验证-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!--mybatis-plus复合主键得使用-->
<dependency>
<groupId>com.github.jeffreyning</groupId>
<artifactId>mybatisplus-plus</artifactId>
<version>${mybatisplus-plus.version}</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--添加代码生成器依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-generator.version}</version>
</dependency>
<!--添加模板引擎依赖,MyBatis-Plus支持Velocity(默认)、Freemarker、Beetl
用户可以选择自己熟悉的模板引擎,如果都不满足您的要求,可以采用自定义模板引擎
-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${mybatis-velocity.version}</version>
</dependency>
<!--druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<!-- 工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.8</version>
</dependency>
<!-- redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- configuration-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.0.RELEASE</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 模块使用依赖
<?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">
<parent>
<artifactId>learn-test</artifactId>
<groupId>com.mangmang</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>02-SpringSecurity</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--mybatis-plus复合主键得使用-->
<dependency>
<groupId>com.github.jeffreyning</groupId>
<artifactId>mybatisplus-plus</artifactId>
</dependency>
<!--用户代理数据-->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
</dependency>
<!--接口文档生成-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
<!--token生成验证-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!-- spring security 安全认证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--添加代码生成器依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</dependency>
<!--添加模板引擎依赖,MyBatis-Plus支持Velocity(默认)、Freemarker、Beetl
用户可以选择自己熟悉的模板引擎,如果都不满足您的要求,可以采用自定义模板引擎
-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<!--druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
</dependencies>
<!-- 项目打包时会将java目录中的*.xml文件也进行打包 -->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
2. 配置YML
1. application.yml
spring:
profiles:
active: local
server:
port: 8000
swagger:
enabled: true
pathMapping:
token:
header: token
secret: asdaswqesdzxwr3123
expire-time: 30
2. application-local.yml
spring:
datasource:
# 配置项目数据源为druid
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
url: jdbc:mysql://127.0.0.1:3306/book?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
initial-size: 5
# 最小连接池数量
min-idle: 10
# 最大连接池数量
max-active: 20
# 配置获取连接等待超时的时间
max-wait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
max-evictable-idle-time-millis: 900000
# 配置检测连接是否有效
validation-query: SELECT 1 FROM DUAL
# 官方推荐配置
test-while-idle: true
test-on-borrow: false
test-on-return: false
#监控配置
web-stat-filter:
# 是否启用StatFilter默认值true
enabled: true
stat-view-servlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: root
login-password: liujing
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
redis:
port: 6379
host: 127.0.0.1
# MyBatis Plus配置
mybatis-plus:
# 搜索指定包别名
typeAliasesPackage: com.mangmang.**.domain
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
configuration:
# SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
#开启二级缓存
cache-enabled: true
#配置默认的执行器
default-executor-type: reuse
# 开启驼峰命名
map-underscore-to-camel-case: true
# 允许 JDBC 支持自动生成主键
use-generated-keys: true
#关闭logo
global-config:
banner: false
db-config:
# 全局逻辑删除的实体字段名
logic-delete-field: isDeleted
# 逻辑已删除值(默认为 1)
logic-delete-value: 1
# 逻辑未删除值(默认为 0)
logic-not-delete-value: 0
3. 配置Mybatis-Plus
/**
* @Date: 2021-08-10-17:31
* @Author lj
*/
@Configuration
@EnableTransactionManagement
@MapperScan("com.mangmang.security.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(paginationInnerInterceptor());
// 乐观锁插件
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
// 阻断插件
interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
return interceptor;
}
/**
* 分页插件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html
*/
public PaginationInnerInterceptor paginationInnerInterceptor() {
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 设置数据库类型为mysql
paginationInnerInterceptor.setDbType(DbType.MYSQL);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(-1L);
return paginationInnerInterceptor;
}
/**
* 乐观锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html
*/
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}
/**
* 如果是对全表的删除或更新操作,就会终止该操作 https://baomidou.com/guide/interceptor-block-attack.html
*/
public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
return new BlockAttackInnerInterceptor();
}
}
4. 配置Knife4j
/**
* @Date: 2021-08-10-18:14
* @Author lj
*/
@EnableSwagger2WebMvc
@Configuration
@EnableKnife4j
public class SwaggerConfig {
/**
* 是否开启swagger
*/
@Value("${swagger.enabled}")
private boolean enabled;
/**
* 设置请求的统一前缀
*/
@Value("${swagger.pathMapping}")
private String pathMapping;
/**
* 创建API
* .pathMapping("test")设置访问路径统一前缀
*/
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
// 是否启用Swagger
.enable(enabled)
// 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
.apiInfo(apiInfo())
// 设置哪些接口暴露给Swagger展示
.select()
// 扫描所有有注解的api,用这种方式更灵活
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
/* 设置安全模式,swagger可以设置访问token */
.securitySchemes(securitySchemes())
.securityContexts(securityContexts())
.pathMapping(pathMapping);
}
/**
* 安全模式,这里指定token通过Authorization头请求头传递
*/
private List<ApiKey> securitySchemes() {
List<ApiKey> apiKeyList = new ArrayList<>();
apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
return apiKeyList;
}
/**
* 安全上下文
*/
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("^(?!auth).*$"))
.build());
return securityContexts;
}
/**
* 默认的安全上引用
*/
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
/**
* 添加摘要信息
*/
private ApiInfo apiInfo() {
// 用ApiInfoBuilder进行定制
return new ApiInfoBuilder()
// 设置标题
.title("标题:后端接口")
// 描述
.description("描述:测试")
// 作者信息
.contact(new Contact("氓氓编程", null, null))
// 版本
.version("版本号:" + "0.0.1")
.build();
}
}
5. 配置RedisTemplate序列化规则
/**
* @author a3621
*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 创建一个模板
RedisTemplate<String, Object> template = new RedisTemplate<>();
//创建一个字符串序列化器
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
//创建Jackson 序列化器
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
//Jackson得ObjectMapper对象
ObjectMapper om = new ObjectMapper();
//这将使所有成员字段无需进一步注释即可序列化,而不仅仅是公共字段(默认设置)。
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//开启后序列化得类会带上全类名
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//om配置给jackson2JsonRedisSerializer
jackson2JsonRedisSerializer.setObjectMapper(om);
//RedisTemplate配置factory
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
//返回该模板
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
//创建一个字符串序列化器
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
//创建Jackson 序列化器
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
//这将使所有成员字段无需进一步注释即可序列化,而不仅仅是公共字段(默认设置)。
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//开启后序列化得类会带上全类名
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//om配置给jackson2JsonRedisSerializer
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
6. sql(复制到文本命名为.sql,然后导入数据库)
/*
Navicat Premium Data Transfer
Source Server : 127.0.0.1
Source Server Type : MySQL
Source Server Version : 50730
Source Host : localhost:3306
Source Schema : book
Target Server Type : MySQL
Target Server Version : 50730
File Encoding : 65001
Date: 29/08/2021 18:07:23
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`menu_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单名称',
`parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父菜单ID',
`order_num` int(4) NULL DEFAULT 0 COMMENT '显示顺序',
`path` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '路由地址',
`component` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '组件路径',
`is_frame` int(1) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)',
`is_cache` int(1) NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)',
`menu_type` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
`visible` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
`perms` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限标识',
`icon` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '#' COMMENT '菜单图标',
`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '备注',
PRIMARY KEY (`menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1061 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 1, 'system', NULL, 1, 0, 'M', '0', '0', '', 'system', 'admin', '2021-04-30 17:27:28', '', NULL, '系统管理目录');
INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 2, 'monitor', NULL, 1, 0, 'M', '0', '0', '', 'monitor', 'admin', '2021-04-30 17:27:28', '', NULL, '系统监控目录');
INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 3, 'tool', NULL, 1, 0, 'M', '0', '0', '', 'tool', 'admin', '2021-04-30 17:27:28', '', NULL, '系统工具目录');
INSERT INTO `sys_menu` VALUES (4, '若依官网', 0, 4, 'http://ruoyi.vip', NULL, 0, 0, 'M', '0', '0', '', 'guide', 'admin', '2021-04-30 17:27:28', '', NULL, '若依官网地址');
INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', '2021-04-30 17:27:28', '', NULL, '用户管理菜单');
INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', '2021-04-30 17:27:28', '', NULL, '角色管理菜单');
INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', '2021-04-30 17:27:28', '', NULL, '菜单管理菜单');
INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', '2021-04-30 17:27:28', '', NULL, '部门管理菜单');
INSERT INTO `sys_menu` VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', '2021-04-30 17:27:28', '', NULL, '岗位管理菜单');
INSERT INTO `sys_menu` VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', '2021-04-30 17:27:28', '', NULL, '字典管理菜单');
INSERT INTO `sys_menu` VALUES (106, '参数设置', 1, 7, 'config', 'system/config/index', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', '2021-04-30 17:27:28', '', NULL, '参数设置菜单');
INSERT INTO `sys_menu` VALUES (107, '通知公告', 1, 8, 'notice', 'system/notice/index', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', '2021-04-30 17:27:28', '', NULL, '通知公告菜单');
INSERT INTO `sys_menu` VALUES (108, '日志管理', 1, 9, 'log', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', '2021-04-30 17:27:28', '', NULL, '日志管理菜单');
INSERT INTO `sys_menu` VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', '2021-04-30 17:27:28', '', NULL, '在线用户菜单');
INSERT INTO `sys_menu` VALUES (110, '定时任务', 2, 2, 'job', 'monitor/job/index', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', '2021-04-30 17:27:28', '', NULL, '定时任务菜单');
INSERT INTO `sys_menu` VALUES (111, '数据监控', 2, 3, 'druid', 'monitor/druid/index', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', '2021-04-30 17:27:28', '', NULL, '数据监控菜单');
INSERT INTO `sys_menu` VALUES (112, '服务监控', 2, 4, 'server', 'monitor/server/index', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', '2021-04-30 17:27:28', '', NULL, '服务监控菜单');
INSERT INTO `sys_menu` VALUES (113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', '2021-04-30 17:27:28', '', NULL, '缓存监控菜单');
INSERT INTO `sys_menu` VALUES (114, '表单构建', 3, 1, 'build', 'tool/build/index', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', '2021-04-30 17:27:28', '', NULL, '表单构建菜单');
INSERT INTO `sys_menu` VALUES (115, '代码生成', 3, 2, 'gen', 'tool/gen/index', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', '2021-04-30 17:27:28', '', NULL, '代码生成菜单');
INSERT INTO `sys_menu` VALUES (116, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', '2021-04-30 17:27:28', '', NULL, '系统接口菜单');
INSERT INTO `sys_menu` VALUES (500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', '2021-04-30 17:27:28', '', NULL, '操作日志菜单');
INSERT INTO `sys_menu` VALUES (501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', '2021-04-30 17:27:28', '', NULL, '登录日志菜单');
INSERT INTO `sys_menu` VALUES (1001, '用户查询', 100, 1, '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1002, '用户新增', 100, 2, '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1003, '用户修改', 100, 3, '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1004, '用户删除', 100, 4, '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1005, '用户导出', 100, 5, '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1006, '用户导入', 100, 6, '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1007, '重置密码', 100, 7, '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1008, '角色查询', 101, 1, '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1009, '角色新增', 101, 2, '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1010, '角色修改', 101, 3, '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1011, '角色删除', 101, 4, '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1012, '角色导出', 101, 5, '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1013, '菜单查询', 102, 1, '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1014, '菜单新增', 102, 2, '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1015, '菜单修改', 102, 3, '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1016, '菜单删除', 102, 4, '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1017, '部门查询', 103, 1, '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1018, '部门新增', 103, 2, '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1019, '部门修改', 103, 3, '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1020, '部门删除', 103, 4, '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1021, '岗位查询', 104, 1, '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1022, '岗位新增', 104, 2, '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1023, '岗位修改', 104, 3, '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1024, '岗位删除', 104, 4, '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1025, '岗位导出', 104, 5, '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1026, '字典查询', 105, 1, '#', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1027, '字典新增', 105, 2, '#', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1028, '字典修改', 105, 3, '#', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1029, '字典删除', 105, 4, '#', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1030, '字典导出', 105, 5, '#', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1031, '参数查询', 106, 1, '#', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1032, '参数新增', 106, 2, '#', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1033, '参数修改', 106, 3, '#', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1034, '参数删除', 106, 4, '#', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1035, '参数导出', 106, 5, '#', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1036, '公告查询', 107, 1, '#', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1037, '公告新增', 107, 2, '#', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1038, '公告修改', 107, 3, '#', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1039, '公告删除', 107, 4, '#', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1040, '操作查询', 500, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1041, '操作删除', 500, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1042, '日志导出', 500, 4, '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1043, '登录查询', 501, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1044, '登录删除', 501, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1045, '日志导出', 501, 3, '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1046, '在线查询', 109, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1047, '批量强退', 109, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1048, '单条强退', 109, 3, '#', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1049, '任务查询', 110, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1050, '任务新增', 110, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1051, '任务修改', 110, 3, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1052, '任务删除', 110, 4, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1053, '状态修改', 110, 5, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1054, '任务导出', 110, 7, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1055, '生成查询', 115, 1, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1056, '生成修改', 115, 2, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1057, '生成删除', 115, 3, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1058, '导入代码', 115, 2, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1059, '预览代码', 115, 4, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
INSERT INTO `sys_menu` VALUES (1060, '生成代码', 115, 5, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2021-04-30 17:27:28', '', NULL, '');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`role_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称',
`role_key` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色权限字符串',
`role_sort` int(4) NOT NULL COMMENT '显示顺序',
`data_scope` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)',
`menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示',
`dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示',
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '超级管理员', 'admin', 1, '1', 1, 1, '0', '0', 'admin', '2021-04-30 17:27:28', '', NULL, '超级管理员');
INSERT INTO `sys_role` VALUES (2, '普通角色', 'common', 2, '2', 1, 1, '0', '0', 'admin', '2021-04-30 17:27:28', '', NULL, '普通角色');
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
PRIMARY KEY (`role_id`, `menu_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (2, 1);
INSERT INTO `sys_role_menu` VALUES (2, 2);
INSERT INTO `sys_role_menu` VALUES (2, 3);
INSERT INTO `sys_role_menu` VALUES (2, 4);
INSERT INTO `sys_role_menu` VALUES (2, 100);
INSERT INTO `sys_role_menu` VALUES (2, 101);
INSERT INTO `sys_role_menu` VALUES (2, 102);
INSERT INTO `sys_role_menu` VALUES (2, 103);
INSERT INTO `sys_role_menu` VALUES (2, 104);
INSERT INTO `sys_role_menu` VALUES (2, 105);
INSERT INTO `sys_role_menu` VALUES (2, 106);
INSERT INTO `sys_role_menu` VALUES (2, 107);
INSERT INTO `sys_role_menu` VALUES (2, 108);
INSERT INTO `sys_role_menu` VALUES (2, 109);
INSERT INTO `sys_role_menu` VALUES (2, 110);
INSERT INTO `sys_role_menu` VALUES (2, 111);
INSERT INTO `sys_role_menu` VALUES (2, 112);
INSERT INTO `sys_role_menu` VALUES (2, 113);
INSERT INTO `sys_role_menu` VALUES (2, 114);
INSERT INTO `sys_role_menu` VALUES (2, 115);
INSERT INTO `sys_role_menu` VALUES (2, 116);
INSERT INTO `sys_role_menu` VALUES (2, 500);
INSERT INTO `sys_role_menu` VALUES (2, 501);
INSERT INTO `sys_role_menu` VALUES (2, 1000);
INSERT INTO `sys_role_menu` VALUES (2, 1001);
INSERT INTO `sys_role_menu` VALUES (2, 1002);
INSERT INTO `sys_role_menu` VALUES (2, 1003);
INSERT INTO `sys_role_menu` VALUES (2, 1004);
INSERT INTO `sys_role_menu` VALUES (2, 1005);
INSERT INTO `sys_role_menu` VALUES (2, 1006);
INSERT INTO `sys_role_menu` VALUES (2, 1007);
INSERT INTO `sys_role_menu` VALUES (2, 1008);
INSERT INTO `sys_role_menu` VALUES (2, 1009);
INSERT INTO `sys_role_menu` VALUES (2, 1010);
INSERT INTO `sys_role_menu` VALUES (2, 1011);
INSERT INTO `sys_role_menu` VALUES (2, 1012);
INSERT INTO `sys_role_menu` VALUES (2, 1013);
INSERT INTO `sys_role_menu` VALUES (2, 1014);
INSERT INTO `sys_role_menu` VALUES (2, 1015);
INSERT INTO `sys_role_menu` VALUES (2, 1016);
INSERT INTO `sys_role_menu` VALUES (2, 1017);
INSERT INTO `sys_role_menu` VALUES (2, 1018);
INSERT INTO `sys_role_menu` VALUES (2, 1019);
INSERT INTO `sys_role_menu` VALUES (2, 1020);
INSERT INTO `sys_role_menu` VALUES (2, 1021);
INSERT INTO `sys_role_menu` VALUES (2, 1022);
INSERT INTO `sys_role_menu` VALUES (2, 1023);
INSERT INTO `sys_role_menu` VALUES (2, 1024);
INSERT INTO `sys_role_menu` VALUES (2, 1025);
INSERT INTO `sys_role_menu` VALUES (2, 1026);
INSERT INTO `sys_role_menu` VALUES (2, 1027);
INSERT INTO `sys_role_menu` VALUES (2, 1028);
INSERT INTO `sys_role_menu` VALUES (2, 1029);
INSERT INTO `sys_role_menu` VALUES (2, 1030);
INSERT INTO `sys_role_menu` VALUES (2, 1031);
INSERT INTO `sys_role_menu` VALUES (2, 1032);
INSERT INTO `sys_role_menu` VALUES (2, 1033);
INSERT INTO `sys_role_menu` VALUES (2, 1034);
INSERT INTO `sys_role_menu` VALUES (2, 1035);
INSERT INTO `sys_role_menu` VALUES (2, 1036);
INSERT INTO `sys_role_menu` VALUES (2, 1037);
INSERT INTO `sys_role_menu` VALUES (2, 1038);
INSERT INTO `sys_role_menu` VALUES (2, 1039);
INSERT INTO `sys_role_menu` VALUES (2, 1040);
INSERT INTO `sys_role_menu` VALUES (2, 1041);
INSERT INTO `sys_role_menu` VALUES (2, 1042);
INSERT INTO `sys_role_menu` VALUES (2, 1043);
INSERT INTO `sys_role_menu` VALUES (2, 1044);
INSERT INTO `sys_role_menu` VALUES (2, 1045);
INSERT INTO `sys_role_menu` VALUES (2, 1046);
INSERT INTO `sys_role_menu` VALUES (2, 1047);
INSERT INTO `sys_role_menu` VALUES (2, 1048);
INSERT INTO `sys_role_menu` VALUES (2, 1049);
INSERT INTO `sys_role_menu` VALUES (2, 1050);
INSERT INTO `sys_role_menu` VALUES (2, 1051);
INSERT INTO `sys_role_menu` VALUES (2, 1052);
INSERT INTO `sys_role_menu` VALUES (2, 1053);
INSERT INTO `sys_role_menu` VALUES (2, 1054);
INSERT INTO `sys_role_menu` VALUES (2, 1055);
INSERT INTO `sys_role_menu` VALUES (2, 1056);
INSERT INTO `sys_role_menu` VALUES (2, 1057);
INSERT INTO `sys_role_menu` VALUES (2, 1058);
INSERT INTO `sys_role_menu` VALUES (2, 1059);
INSERT INTO `sys_role_menu` VALUES (2, 1060);
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID',
`user_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户昵称',
`user_type` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '00' COMMENT '用户类型(00系统用户)',
`email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '用户邮箱',
`phonenumber` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '手机号码',
`sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '头像地址',
`password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '密码',
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`login_ip` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间',
`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2021-04-30 17:27:28', 'admin', '2021-04-30 17:27:28', '', NULL, '管理员');
INSERT INTO `sys_user` VALUES (2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2021-04-30 17:27:28', 'admin', '2021-04-30 17:27:28', '', NULL, '测试员');
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`email` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'admin', 'admin', 'admin@atguigu.com');
INSERT INTO `t_user` VALUES (2, 'test', 'test', 'test@test.com');
INSERT INTO `t_user` VALUES (3, 'wzg168', '123456', 'wzg168@qq.com');
INSERT INTO `t_user` VALUES (4, 'bbj168', '666666', 'bbj168@qq.com');
INSERT INTO `t_user` VALUES (5, 'abc168', '666666', 'abc168@qq.com');
INSERT INTO `t_user` VALUES (6, 'admin250', '123456', '362165265@qq.com');
INSERT INTO `t_user` VALUES (7, 'admin235', '123456', '362165265@qq.com');
INSERT INTO `t_user` VALUES (8, 'aaaaaaaaa', 'aaaaaa', '362165265@qq.com');
INSERT INTO `t_user` VALUES (9, 'aaaaa', 'aaaaa', '362165265@qq.com');
-- ----------------------------
-- Table structure for test
-- ----------------------------
DROP TABLE IF EXISTS `test`;
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`seqno` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`update_time` datetime NULL DEFAULT NULL,
`create_time` datetime NULL DEFAULT NULL,
`some` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of test
-- ----------------------------
INSERT INTO `test` VALUES (1, NULL, '2021-08-27 15:12:29', '2021-08-27 15:12:29', 'some');
-- ----------------------------
-- Table structure for test07
-- ----------------------------
DROP TABLE IF EXISTS `test07`;
CREATE TABLE `test07` (
`k1` int(11) NOT NULL,
`k2` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`col1` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`col2` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`k1`, `k2`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of test07
-- ----------------------------
INSERT INTO `test07` VALUES (1, '111', 'xxxx', NULL);
-- ----------------------------
-- Table structure for test2
-- ----------------------------
DROP TABLE IF EXISTS `test2`;
CREATE TABLE `test2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`refid` int(11) NOT NULL,
`some2` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of test2
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;
7. mybatis-plus 自动生成代码
二、SpringSecurity认证
1. SpringSecurity的工作流程
一个web请求会经过一条过滤器链,在经过过滤器链的过程中会完成认证与授权,如果中间发现这条请求未认证或者未授权,会根据被保护API的权限去抛出异常,然后由异常处理器去处理这些异常。
图片显示流程
/**
如上图:
1. 一个请求想要访问到API就会以从左到右的形式经过蓝线框框里面的过滤器
- 其中绿色部分是我们本篇主要讲的负责认证的过滤器
- 蓝色部分负责异常处理
- 橙色部分则是负责授权
2. 图中的这两个绿色过滤器是Spring Security对form表单认证和Basic认证内置的两个Filter,JWT认证方式用不上。
3. Spring Security配置中有两个叫formLogin和httpBasic的配置项,在配置中打开了它俩就对应着打开了上面的过滤器。
4. formLogin对应着你form表单认证方式,即UsernamePasswordAuthenticationFilter。
5. httpBasic对应着Basic认证方式,即BasicAuthenticationFilter。
6. 换言之,你配置了这两种认证方式,过滤器链中才会加入它们,否则它们是不会被加到过滤器链中去的。
7. 因为Spring Security自带的过滤器中是没有针对JWT这种认证方式的,所以我们会写一个JWT的认证过滤器,然后放在绿色的位置进行认证工作。
2. SpringSecurity的重要概念
1. SecurityContext:上下文对象,Authentication对象会放在里面。
2. SecurityContextHolder:用于拿到上下文对象的静态工具类。
3. Authentication:认证接口,定义了认证对象的数据形式。
4. AuthenticationManager:用于校验Authentication,返回一个认证完成后• Authentication对象。
1. SecurityContext
上下文对象,认证后的数据就放在这里面,接口定义如下:
public interface SecurityContext extends Serializable {
// 获取Authentication对象
Authentication getAuthentication();
// 放入Authentication对象
void setAuthentication(Authentication authentication);
}
2. SecurityContextHolder
可以说是SecurityContext的工具类,用于get 或 set 或者 clear SecurityContext,默认会把数据都存储到当前线程中。
public class SecurityContextHolder {
public static void clearContext() {
strategy.clearContext();
}
public static SecurityContext getContext() {
return strategy.getContext();
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
}
3. Authentication
Authentication只是定义了一种在SpringSecurity进行认证过的数据的数据形式应该是怎么样的,要有权限,要有密码,要有身份信息,要有额外信息。
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
/**
1. getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。
2. getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。
3. getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)。
4. getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails。
6. isAuthenticated: 获取当前 Authentication 是否已认证。
7. setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。
4. AuthenticationManager
AuthenticationManager定义了一个认证方法,它将一个未认证的Authentication传入,返回一个已认证的Authentication,默认使用的实现类为:ProviderManager。
public interface AuthenticationManager {
// 认证方法
Authentication authenticate(Authentication authentication)throws AuthenticationException;
}
5. Spring Security进行认证的流程:
/**
1. 先是一个请求带着身份信息进来
2. 经过AuthenticationManager的认证,
3. 再通过SecurityContextHolder获取SecurityContext,
4. 最后将认证后的信息放入到SecurityContext。
3. 必要组件
1. 定义加密器Bean
这个Bean是不必可少的,Spring Security在认证操作时会使用我们定义的这个加密器,如果没有则会出现异常。
/**
* spring容器中注入加密算法
*
* @return 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
2.定义AuthenticationManager
这里将Spring Security自带的authenticationManager声明成Bean,声明它的作用是用它帮我们进行认证操作,调用这个Bean的authenticate方法会由Spring Security自动帮我们做认证。
/**
* 解决 无法直接注入 AuthenticationManager
*
* @return return
* @throws Exception e
*/
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
3. 实现UserDetailsService
实现UserDetailsService的抽象方法并返回一个UserDetails对象,认证过程中SpringSecurity会调用这个方法访问数据库进行对用户的搜索,逻辑什么都可以自定义,无论是从数据库中还是从缓存中,但是我们需要将我们查询出来的用户信息和权限信息组装成一个UserDetails返回。
UserDetails也是一个定义了数据形式的接口,用于保存我们从数据库中查出来的数据,其功能主要是验证账号状态和获取权限
/**
* 自定义用户认证逻辑并交给spring管理
*
* @Date: 2021-08-27-13:50
* @Author lj
*/
@Slf4j
@Service("userDetailsService")
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserService userService;
private final PermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("②-用户信息认证");
//认证 判断用户是否存在于数据库内
User user = userService.getOne(new QueryWrapper<User>().eq("user_name", username));
if (Validator.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new RuntimeException("登录用户:" + username + "不存在");
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
log.info("登录用户:{} 已被删除.", username);
throw new RuntimeException("登录用户:" + username + "已被删除");
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new RuntimeException("登录用户:" + username + "已被停用");
}
//满足以上信息认证通过
//设置用户权限和当前用户基本信息
Set<String> permissions = permissionService.getMenuPermission(user);
//角色权限信息
Set<String> rolePermission = permissionService.getRolePermission(user);
user.setRoles(rolePermission);
//返回安全框架需要得用户信息和自定义得一些信息
return new SecurityUser(permissions, user);
}
}
4. TokenUtil
由于我们是JWT的认证模式,所以我们也需要一个帮我们操作Token的工具类,一般来说它具有以下三个方法就够了
- 创建token
- 验证token
- 反解析token中的信息
package com.mangmang.security.securities.jwt;
import cn.hutool.core.lang.Validator;
import com.mangmang.security.securities.domain.SecurityUser;
import com.mangmang.security.utils.AddressUtils;
import com.mangmang.security.utils.Constants;
import com.mangmang.security.utils.IpUtils;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @Date: 2021-08-26-15:00
* @Author lj
*/
@Data
@Component
@ConfigurationProperties("token")
@RequiredArgsConstructor
public class TokenService {
private final RedisTemplate<String, Object> redisTemplate;
/**
* 令牌自定义标识
*/
private String header;
/**
* 令牌密钥
*/
private String secret;
/**
* 令牌有效期(默认30分钟)
*/
private int expireTime;
/**
* 根据token获取用户信息
*
* @param request 请求
* @return 用户信息
*/
public SecurityUser getSecurityUser(HttpServletRequest request) {
//获取token
String token = request.getHeader(this.header);
if (Validator.isNotEmpty(token)) {
//获取用户唯一标识
String uniquelyIdentifies = getUniquelyIdentifiesByToken(token);
//返回该用户信息
return (SecurityUser) redisTemplate.opsForValue().get(Constants.LOGIN_TOKEN_KEY + uniquelyIdentifies);
}
return null;
}
/**
* 生成一个token
*
* @param securityUser 安全框架需要得用户信息
* @param request 请求
* @return token
*/
public String createToken(SecurityUser securityUser, HttpServletRequest request) {
//设置用户唯一标识
securityUser.setUniquelyIdentifies(UUID.randomUUID().toString());
//获取用户ip地址
String ipAddr = IpUtils.getIpAddr(request);
securityUser.setIpaddr(ipAddr);
//获取客户端信息
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
//根据ip获取用户真实地址
securityUser.setLoginLocation(AddressUtils.getRealAddressByIp(ipAddr));
//设置登录浏览器信息
securityUser.setBrowser(userAgent.getBrowser().getName());
//设置操作系统信息
securityUser.setOs(userAgent.getOperatingSystem().getName());
//用户信息存入redis
refreshRedisTokenTime(securityUser);
//token得主体信息;
HashMap<String, Object> tokenInfo = new HashMap<>(64);
tokenInfo.put(Constants.LOGIN_USER_KEY, securityUser.getUniquelyIdentifies());
//生成token
return Jwts.builder()
//jwt头使用默认
//有效载荷
.setClaims(tokenInfo)
//签名哈希
//让上面两部分数据签名通过HS256算法生成哈希,确保数据不会被篡改
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
/**
* 刷新存放在redis中用户登录相关信息时间
*/
public void refreshRedisTokenTime(SecurityUser securityUser) {
//设置当前时间为登录时间
securityUser.setLoginTime(System.currentTimeMillis());
//登录时间+30分钟为过期时间
securityUser.setExpireTime(securityUser.getLoginTime() + expireTime * Constants.MILLIS_MINUTE);
//把用户登录信息存入缓存expireTime分钟后过期
redisTemplate
.opsForValue()
.set(Constants.LOGIN_TOKEN_KEY + securityUser.getUniquelyIdentifies(), securityUser, expireTime, TimeUnit.MINUTES);
}
/**
* 小于10分钟更新存放在redis中token过期时间
* 判断redis中token过期时间是否小于10分钟
*
* @param securityUser 安全用户信息
*/
public void verifyToken(SecurityUser securityUser) {
if (securityUser.getExpireTime() - securityUser.getLoginTime() <= Constants.MILLIS_MINUTE_TEN) {
refreshRedisTokenTime(securityUser);
}
}
/**
* 通过token获取用户唯一标识
*
* @param token token
* @return 用户唯一标识
*/
public String getUniquelyIdentifiesByToken(String token) {
return (String) Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody()
.get(Constants.LOGIN_USER_KEY);
}
/**
* 根据用户唯一标识删除用户信息
*
* @param uniquelyIdentifies 用户唯一标识;
*/
public void deleteSecurity(String uniquelyIdentifies) {
if (Validator.isNotEmpty(uniquelyIdentifies)) {
//删除redis中数据
redisTemplate.delete(Constants.LOGIN_TOKEN_KEY + uniquelyIdentifies);
}
}
}
5. 自定义UserDetails
package com.mangmang.security.securities.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.mangmang.security.entity.User;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
/**
* security框架得用户信息
*
* @Date: 2021-08-26-16:18
* @Author lj
*/
@NoArgsConstructor
@Data
@ToString
public class SecurityUser implements UserDetails, Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户唯一标识
*/
private String uniquelyIdentifies;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 权限列表
*/
private Set<String> permissions;
/**
* 用户信息
*/
private User user;
@Getter(AccessLevel.NONE)
private String username;
/**
* 用户信息和权限信息
*
* @param permissions 权限信息
* @param user 用户信息
*/
public SecurityUser(Set<String> permissions, User user) {
this.permissions = permissions;
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@JsonIgnore
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUserName();
}
/**
* 账户是否未过期,过期无法验证
*
* @return 未过期
*/
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* @return 已解锁
*/
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return 未过期
*/
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return 可用
*/
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
4. 具体实现
1. 认证方法和认证源码分析
访问一个系统,一般最先访问的是认证方法,这里我写了最简略的认证需要的几个步骤,因为实际系统中我们还要写登录记录啊,前台密码解密啊这些操作。
/**
* security登录接口
*
* @Date: 2021-08-29-17:05
* @Author lj
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class LoginService {
private final AuthenticationManager authenticationManager;
private final TokenService tokenService;
private RedisTemplate<String, Object> redisTemplate;
public String login(String username, String password, HttpServletRequest request) {
Authentication authentication = null;
try {
//1. 开始认证
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (Exception e) {
if (e instanceof BadCredentialsException) {
log.error("用户密码不匹配");
throw new RuntimeException("用户密码不匹配");
} else {
e.printStackTrace();
}
}
assert authentication != null;
//2. 获取认证成功后得用户信息
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
System.out.println(securityUser);
//3. 缓存用户信息并生成token
return tokenService.createToken(securityUser, request);
}
}
/**
1. 传入用户名和密码创建了一个UsernamePasswordAuthenticationToken对象,这是我们前面说过的Authentication的实现类,传入用户名和密码做构造参数,这个对象就是我们创建出来的未认证的Authentication对象。使用我们先前已经声明过的Bean-authenticationManager调用它的authenticate方法进行认证,返回一个认证完成的Authentication对象。
2. 从Authentication对象中拿到我们的UserDetails对象,之前我们说过,认证后的Authentication对象调用它的getPrincipal()方法就可以拿到我们先前数据库查询后组装出来的UserDetails对象,
3. 然后创建token。把UserDetails对象放入缓存中,方便后面过滤器使用。
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
});
//还未认证成功过去用户名
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
//从缓存中去查用户名为XXX的对象
UserDetails user = this.userCache.getUserFromCache(username);
//为空
if (user == null) {
cacheWasUsed = false;
try {
//调用我们重写UserDetailsService的loadUserByUsername方法
// 拿到我们自己组装好的UserDetails对象
//AbstractUserDetailsAuthenticationProvider得实现类为DaoAuthenticationProvider由于retrieveUser为抽象方法所以调用子类方法
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User '" + username + "' not found");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
throw var6;
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
// 校验账号是否禁用
this.preAuthenticationChecks.check(user);
// 校验数据库查出来的密码,和我们传入的密码是否一致
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
2. jwt认证过滤器
有了token之后,我们要把过滤器放在过滤器链中,用于解析token,因为我们没有session,所以我们每次去辨别这是哪个用户的请求的时候,都是根据请求中的token来解析出来当前是哪个用户。
addFilterBefore的语义是添加一个Filter到XXXFilter之前,放在这里就是把
JwtAuthenticationTokenFilter
放在UsernamePasswordAuthenticationFilter之前,因为filter的执行也是有顺序的,我们必须要把我们的filter放在过滤器链中绿色的部分才会起到自动认证的效果。具体实现如下:
/**
* token过滤器 验证token有效性
*
* @Date: 2021-08-29-14:08
* @Author lj
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private final TokenService tokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("①=========jwt认证");
//1.之前登录过 所以这里会获取到redis中完整得用户信息
//1. 根据token获取用户唯一标识查询用户信息
SecurityUser securityUser = tokenService.getSecurityUser(request);
//2. 获取上下文中是否有 authentication认证接口,定义了认证对象的数据形式。(本次请求得认证接口,包含当前访问接口的用户信息)
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (Validator.isNotNull(securityUser) && Validator.isNull(authentication)) {
//3 如果redis中数据小于10分钟刷新
tokenService.verifyToken(securityUser);
//4. 组装一个authentication对象,把它放在上下文对象中,这样后面的过滤器看到我们上下文对象中有authentication对象,就相当于我们已经认证过了
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(securityUser, securityUser.getPassword(), securityUser.getAuthorities());
//5. 设置网页详情,ip地址,sessionId等信息
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//6. 将authentication信息放入到上下文对象中
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
//放行
filterChain.doFilter(request, response);
}
}
这样的话,每一个带有正确token的请求进来之后,都会找到它的账号信息,并放在上下文对象中,我们可以使用
SecurityContextHolder
很方便的拿到上下文对象中的Authentication
对象。完成之后,启动我们的demo,可以看到过滤器链中有以下过滤器,其中我们自定义的是第5个登录👉拿到token👉请求带上token👉JWT过滤器拦截👉校验token👉将从缓存中查出来的对象放到上下文中
5. 代码优化
1.认证失败处理器
当用户未登录或者token解析失败时会触发这个处理器,返回一个非法访问的结果。
/**
* 认证失败处理类 返回未授权
*
* @Date: 2021-08-29-13:10
* @Author lj
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
String msg = String.format("请求访问:%s,认证失败,无法访问系统资源", request.getRequestURI());
try {
// 设置返回的数据内容的数据类型和编码
response.setContentType("text/html; charset=utf-8");
response.getWriter().write(msg);
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
2. 自定义退出处理
/**
* 自定义退出处理类 返回成功
*
* @Date: 2021-08-29-13:51
* @Author lj
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
private final TokenService tokenService;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException {
//获取认证得用户
SecurityUser securityUser = tokenService.getSecurityUser(request);
//不为空
if (Validator.isNotNull(securityUser)) {
//获取用户姓名
String userName = securityUser.getUser().getUserName();
//删除redis缓存数据
tokenService.deleteSecurity(securityUser.getUniquelyIdentifies());
//删除上下文中的用户信息,只删除当前线程用户数据
SecurityContextHolder.clearContext();
//记录退出日志
log.info("用户 {} 退出了系统", userName);
}
// 设置返回的数据内容的数据类型和编码
response.setContentType("text/html; charset=utf-8");
response.getWriter().write("用户为空");
}
}
3. 权限不足处理器
/**
* 权限不足处理器
*
* @Date: 2021-08-31-13:50
* @Author lj
*/
@Slf4j
@Component
public class InsufficientPermissionsHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
String msg = String.format("请求访问:%s,失败,无权限,请联系管理员", request.getRequestURI());
response.setHeader("Cache-Control", "no-cache");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
try {
response.getWriter().println(msg);
response.getWriter().flush();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
6. security完整配置
/**
* 自动配置时会判断是否存在WebSecurityConfigurerAdapter类,
*
* @Date: 2021-08-27-13:48
* @Author lj
*/
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 用户认证逻辑spring自动注入加入容器实现该接口得方法
*/
private final UserDetailsService userDetailsService;
/**
* 认证失败处理类
*/
private final AuthenticationEntryPointImpl authenticationEntryPoint;
/**
* 无权限处理器
*/
private final InsufficientPermissionsHandler insufficientPermissionsHandler;
/**
* 退出成功处理
*/
private final LogoutSuccessHandlerImpl logoutSuccessHandler;
/**
* jwt认证过滤器
*/
private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
/**
* 配置security密码加密方式和自定义认证流程
*
* @param auth 认证
* @throws Exception 异常
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
/**
* spring容器中注入加密算法
*
* @return 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 解决 无法直接注入 AuthenticationManager
*
* @return return
* @throws Exception e
*/
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOrigin("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 设置访问源请求方法
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*
* @param http HttpSecurity
* @throws Exception e
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http // CSRF禁用,因为不使用session
.csrf().disable()
//认证失败处理
.exceptionHandling()
//没认证
.authenticationEntryPoint(authenticationEntryPoint)
//无权限
.accessDeniedHandler(insufficientPermissionsHandler)
//session管理
.and()
.sessionManagement()
//基于token,所以不需要session,设置为无状态
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//过滤请求
.and()
.authorizeRequests()
//静态资源用户可以任意访问
.antMatchers(HttpMethod.GET, URL_STATIC).permitAll()
//允许匿名访问得url
.antMatchers(URL_WHITELIST).anonymous()
//除以上配置路径外全部需要鉴权认证
.anyRequest().authenticated()
//配置完全开启x-frame-options
.and()
.headers()
.frameOptions()
.disable()
//退出成功处理
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler)
//添加滤器
.and()
//添加token认证过滤器到表单认证过滤器得前面
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
//添加跨域配置
.addFilterBefore(corsFilter(), JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter(), LogoutFilter.class);
}
/**
* 需要放行的静态资源
*/
private static final String[] URL_STATIC = {
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js"
};
/**
* 需要放行的url地址
*/
private static final String[] URL_WHITELIST = {
"/swagger-ui.html",
"/swagger-resources/**",
"/webjars/**",
"/*/api-docs",
"/druid/**",
"/login"
};
}
7. 其它工具类
1. 枚举
/**
* 用户状态
*
* @Date: 2021-08-27-17:55
* @Author lj
*/
public enum UserStatus {
/**
* 相当于创建UserStatus对象
* 正常
*/
OK("0", "正常"),
/**
* 禁用
*/
DISABLE("1", "停用"),
/**
* 删除
*/
DELETED("2", "删除");
/**
* 代号
*/
private final String code;
/**
* 信息
*/
private final String info;
UserStatus(String code, String info) {
this.code = code;
this.info = info;
}
public String getCode() {
return code;
}
public String getInfo() {
return info;
}
}
2. 静态常量
/**
* 静态常量
*
* @Date: 2021-08-26-15:35
* @Author lj
*/
public class Constants {
/**
* 毫秒
*/
public static final long MILLIS_SECOND = 1000;
/**
* 1分钟
*/
public static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
/**
* 10分钟
*/
public static final Long MILLIS_MINUTE_TEN = 10 * 60 * 1000L;
/**
* 未知
*/
public static final String UNKNOWN = "unknown";
/**
* 2
*/
public static final Integer TWO = 2;
/**
* 4
*/
public static final Integer FOUR = 4;
/**
* GBK
*/
public static final String GBK = "GBK";
/**
* UTF-8
*/
public static final String UTF8 = "UTF-8";
/**
* redis中登录用户唯一标识得key
*/
public static final String LOGIN_TOKEN_KEY = "login_tokens:";
/**
* token中用户唯一标识
*/
public static final String LOGIN_USER_KEY = "login_user_key";
/**
* 401 未授权
*/
public static final Integer UNAUTHORIZED = 401;
}
3. ip解析
/**
* @Date: 2021-08-27-8:43
* @Author lj
*/
@Slf4j
public class IpUtils {
/**
* 获取请求得ip地址
*
* @param request 请求
* @return ip地址
*/
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : EscapeUtil.clean(ip);
}
/**
* 判断ip是否为内网ip
*
* @param ip ip
* @return 是或否
*/
public static boolean internalIp(String ip) {
//将IPv4地址转换成字节
byte[] addr = textToNumericFormatV4(ip);
return internalIp(addr) || "127.0.0.1".equals(ip);
}
/**
* 判断该该字节是否为内网ip
*
* @param addr 字节内网ip
* @return 是或否
*/
private static boolean internalIp(byte[] addr) {
if (Validator.isNull(addr) || addr.length < Constants.TWO) {
return true;
}
final byte b0 = addr[0];
final byte b1 = addr[1];
// 10.x.x.x/8
final byte section1 = 0x0A;
// 172.16.x.x/12
final byte section2 = (byte) 0xAC;
final byte section3 = (byte) 0x10;
final byte section4 = (byte) 0x1F;
// 192.168.x.x/16
final byte section5 = (byte) 0xC0;
final byte section6 = (byte) 0xA8;
switch (b0) {
case section1:
return true;
case section2:
if (b1 >= section3 && b1 <= section4) {
return true;
}
case section5:
if (b1 == section6) {
return true;
}
default:
return false;
}
}
/**
* 将IPv4地址转换成字节
*
* @param text IPv4地址
* @return byte 字节
*/
public static byte[] textToNumericFormatV4(String text) {
long number1 = 4294967295L;
long number2 = 255L;
long number3 = 16777215L;
long number4 = 65535L;
if (text.length() == 0) {
return null;
}
byte[] bytes = new byte[4];
String[] elements = text.split("\\.", -1);
try {
long l;
int i;
switch (elements.length) {
case 1:
l = Long.parseLong(elements[0]);
if ((l < 0L) || (l > number1)) {
return null;
}
bytes[0] = (byte) (int) (l >> 24 & 0xFF);
bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 2:
l = Integer.parseInt(elements[0]);
if ((l < 0L) || (l > number2)) {
return null;
}
bytes[0] = (byte) (int) (l & 0xFF);
l = Integer.parseInt(elements[1]);
if ((l < 0L) || (l > number3)) {
return null;
}
bytes[1] = (byte) (int) (l >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 3:
for (i = 0; i < Constants.TWO; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
l = Integer.parseInt(elements[2]);
if ((l < 0L) || (l > number4)) {
return null;
}
bytes[2] = (byte) (int) (l >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 4:
for (i = 0; i < Constants.FOUR; ++i) {
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L)) {
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
break;
default:
return null;
}
} catch (NumberFormatException e) {
return null;
}
return bytes;
}
/**
* 获取本机ip地址
*
* @return ip地址
*/
public static String getHostIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error(e.getMessage());
}
return "127.0.0.1";
}
/**
* 获取本机名称
*
* @return 主机名称
*/
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.error("UnknownHostException", e);
}
return "未知";
}
}
package com.mangmang.security.utils;
import cn.hutool.http.HTMLFilter;
/**
* @Date: 2021-08-27-8:44
* @Author lj
*/
public class EscapeUtil {
public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)";
private static final char[][] TEXT = new char[64][];
/**
* 清除所有HTML标签,但是不删除标签内的内容
*
* @param content 文本
* @return 清除标签后的文本
*/
public static String clean(String content) {
return new HTMLFilter().filter(content);
}
}
4. HTTP请求发送
package com.mangmang.security.utils;
import lombok.extern.slf4j.Slf4j;
import javax.net.ssl.*;
import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
/**
* @Date: 2021-08-27-9:22
* @Author lj
*/
@Slf4j
public class HttpUtils {
/**
* 向指定 URL 发送GET方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
return sendGet(url, param, Constants.UTF8);
}
/**
* 向指定 URL 发送GET方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @param contentType 编码类型
* @return 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param, String contentType) {
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
log.info("sendGet - {}", urlNameString);
URL realUrl = new URL(urlNameString);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
log.info("recv - {}", result);
} catch (ConnectException e) {
log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
} catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
} catch (IOException e) {
log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
} catch (Exception e) {
log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception ex) {
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
}
}
return result.toString();
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
StringBuilder result = new StringBuilder();
try {
log.info("sendPost - {}", url);
URL realUrl = new URL(url);
URLConnection conn = realUrl.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true);
conn.setDoInput(true);
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
log.info("recv - {}", result);
} catch (ConnectException e) {
result.setLength(0);
result.append("fail");
log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
} catch (SocketTimeoutException e) {
result.setLength(0);
result.append("fail");
log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
} catch (IOException e) {
result.setLength(0);
result.append("fail");
log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
} catch (Exception e) {
result.setLength(0);
result.append("fail");
log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
}
}
return result.toString();
}
public static String sendSslPost(String url, String param) {
StringBuilder result = new StringBuilder();
String urlNameString = url + "?" + param;
try {
log.info("sendSSLPost - {}", urlNameString);
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
URL console = new URL(urlNameString);
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setSSLSocketFactory(sc.getSocketFactory());
conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
conn.connect();
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String ret;
while ((ret = br.readLine()) != null) {
if (!"".equals(ret.trim())) {
result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
}
}
log.info("recv - {}", result);
conn.disconnect();
br.close();
} catch (ConnectException e) {
log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
} catch (SocketTimeoutException e) {
log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
} catch (IOException e) {
log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
} catch (Exception e) {
log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
}
return result.toString();
}
private static class TrustAnyTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
}
三 、SpringSecurity授权
1. 获取用户权限信息
package com.mangmang.security.securities.service;
import com.mangmang.security.entity.User;
import com.mangmang.security.securities.domain.SecurityUser;
import com.mangmang.security.service.MenuService;
import com.mangmang.security.service.RoleService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/**
* 用户权限处理
*
* @Date: 2021-08-30-17:26
* @Author lj
*/
@Component
@RequiredArgsConstructor
public class PermissionService {
private final RoleService roleService;
private final MenuService menuService;
/**
* 获取角色权限
*
* @param user 用户信息
* @return 角色权限集合
*/
public Set<String> getRolePermission(User user) {
// 需要返回得权限集合
Set<String> roles = new HashSet<>();
//如果为超级用户添加超级用户
if (user.judgeIsAdmin()) {
roles.add("admin");
} else {
//否则添加其它角色
roles.addAll(roleService.findRolePermissionByUserId(user.getUserId()));
}
return roles;
}
/**
* 获取菜单权限
*
* @param user 用户户信息
* @return 菜单权限集合
*/
public Set<String> getMenuPermission(User user) {
// 需要返回得权限集合
Set<String> menus = new HashSet<>();
//如果为超级用户添加超级用户
if (user.judgeIsAdmin()) {
menus.add("*:*:*");
} else {
//添加两个集合得并集
menus.addAll(menuService.findAllMenu(user.getUserId()));
}
return menus;
}
}
2. 自定义权限判断
package com.mangmang.security.securities.service;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import com.mangmang.security.entity.Role;
import com.mangmang.security.securities.domain.SecurityUser;
import com.mangmang.security.securities.jwt.TokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
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 java.util.Set;
/**
* 判断是否具有某个权限
* 自定义权限实现,ss取自SpringSecurity首字母
*
* @Date: 2021-08-30-18:21
* @Author lj
*/
@Service("ss")
@RequiredArgsConstructor
public class PermissionJudgeService {
/**
* 拥有所有菜单权限标识
*/
private static final String ALL_PERMISSION = "*:*:*";
/**
* 拥有管理员角色权限标识
*/
private static final String SUPER_ADMIN = "admin";
/**
* 分隔符
*/
private static final String DELIMETER = ",";
/**
* token处理
*/
private final TokenService tokenService;
/**
* 获取request
*/
public static HttpServletRequest getRequest() {
//获取当前线程得request
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) attributes;
assert requestAttributes != null;
return requestAttributes.getRequest();
}
/**
* 验证用户是否具备某权限
*
* @param permission 需要判断的权限字符串
* @return 是或否
*/
public boolean hasPermi(String permission) {
//如果为null拒绝访问
if (Validator.isEmpty(permission)) {
return false;
}
//获取用户信息
SecurityUser securityUser = tokenService.getSecurityUser(getRequest());
//如果用户或权限为空拒绝访问
if (Validator.isNull(securityUser) || Validator.isNull(securityUser.getPermissions())) {
return false;
}
return hasPermissions(securityUser.getPermissions(), permission);
}
/**
* 验证不具备某个权限
*
* @param permission 需要判断的权限字符串
* @return true=没有该权限 false=有该权限
*/
public boolean lacksPermi(String permission) {
return !hasPermi(permission);
}
/**
* 验证用户是否具备以下任意权限
*
* @param permissions 以逗号为分隔符的权限列表
* @return true=有该权限 false=无该权限
*/
public boolean hasAnyPermi(String permissions) {
if (Validator.isEmpty(permissions)) {
return false;
}
//获取用户信息
SecurityUser securityUser = tokenService.getSecurityUser(getRequest());
//如果用户或权限为空拒绝访问
if (Validator.isNull(securityUser) || Validator.isNull(securityUser.getPermissions())) {
return false;
}
//获取权限列表
Set<String> authorities = securityUser.getPermissions();
//遍历权限判断是否具有某一个权限
for (String permission : permissions.split(DELIMETER)) {
if (permission != null && hasPermissions(authorities, permission)) {
return true;
}
}
return false;
}
/**
* 公用判断是否拥有某个权限
*/
private boolean hasPermissions(Set<String> permissions, String permission) {
return permissions.contains(ALL_PERMISSION) || permissions.contains(StrUtil.trim(permission));
}
/**
* 判断用户是否具有某一角色
*
* @param role 需要判断得角色
* @return true=有这个权限 false=没有这个权限
*/
public boolean hasRole(String role) {
if (Validator.isEmpty(role)) {
return false;
}
SecurityUser securityUser = tokenService.getSecurityUser(getRequest());
if (Validator.isNull(securityUser) || Validator.isNull(securityUser.getUser().getRoles())) {
return false;
}
for (String rolePerm : securityUser.getUser().getRoles()) {
if (SUPER_ADMIN.equals(rolePerm) || rolePerm.equals(StrUtil.trim(role))) {
return true;
}
}
return false;
}
/**
* 验证用户是否不具备某角色,与hasRole逻辑相反。
*
* @param role 角色名称
* @return 用户是否不具备某角色
*/
public boolean lacksRole(String role) {
return !hasRole(role);
}
/**
* 验证用户是否具有以下任意一个角色
*
* @param roles 以逗号为分隔符的角色列表
* @return 用户是否具有以下任意一个角色
*/
public boolean hasAnyRoles(String roles) {
if (Validator.isEmpty(roles)) {
return false;
}
SecurityUser securityUser = tokenService.getSecurityUser(getRequest());
if (Validator.isNull(securityUser) || Validator.isEmpty(securityUser.getUser().getRoles())) {
return false;
}
//roles以逗号分割后循环有匹配得数据直接返回true
for (String role : roles.split(DELIMETER)) {
if (hasRole(role)) {
return true;
}
}
return false;
}
}
3. 测试类
package com.mangmang.security.controller;
import com.mangmang.security.securities.service.LoginService;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @Date: 2021-08-29-16:51
* @Author lj
*/
@RestController
@RequiredArgsConstructor
public class LoginController {
private final LoginService loginService;
@ApiOperation("登录接口")
@GetMapping("/login")
public String login(String username, String password, HttpServletRequest request) {
return loginService.login(username, password, request);
}
@PreAuthorize("@ss.hasPermi('system:menu:list')")
@ApiOperation("test是否具有system:menu:list权限")
@GetMapping("/vip1")
public String test() {
return "访问成功";
}
@PreAuthorize("@ss.hasPermi('system:menu:test')")
@ApiOperation("test是否具有system:menu:test权限")
@GetMapping("/vip2")
public String test2() {
return "访问成功";
}
}
4. 查询权限得2个方法
package com.mangmang.security.service.impl;
import cn.hutool.core.lang.Validator;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.api.R;
import com.mangmang.security.entity.Menu;
import com.mangmang.security.entity.Role;
import com.mangmang.security.entity.RoleMenu;
import com.mangmang.security.entity.UserRole;
import com.mangmang.security.mapper.MenuMapper;
import com.mangmang.security.mapper.RoleMapper;
import com.mangmang.security.mapper.RoleMenuMapper;
import com.mangmang.security.mapper.UserRoleMapper;
import com.mangmang.security.service.MenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* <p>
* 菜单权限表 服务实现类
* </p>
*
* @author 氓氓编程
* @since 2021-08-26
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
private final RoleMapper roleMapper;
private final UserRoleMapper userRoleMapper;
private final RoleMenuMapper roleMenuMapper;
@Override
public Set<String> findAllMenu(Long userId) {
//查询该用户所有角色
List<UserRole> userRoleList = userRoleMapper.selectList(new QueryWrapper<UserRole>().eq("user_id", userId));
if (userRoleList.size() <= 0) {
return null;
}
Set<Long> roleIdSet = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toSet());
//查询该角色中未被禁用角色
QueryWrapper<Role> roleWrapper = new QueryWrapper<>();
roleWrapper.in("role_id", roleIdSet);
roleWrapper.eq("status", 0);
List<Role> rolesIdList = roleMapper.selectList(roleWrapper);
Set<Long> roleIds = rolesIdList.stream().map(Role::getRoleId).collect(Collectors.toSet());
//查询用户角色所有菜单
List<RoleMenu> roleMenuList = roleMenuMapper.selectList(new QueryWrapper<RoleMenu>().in("role_id", roleIds));
if (roleMenuList.size() <= 0) {
return null;
}
Set<Long> menuIdSet = roleMenuList.stream().map(RoleMenu::getMenuId).collect(Collectors.toSet());
//查询用户所有菜单中未禁用菜单
QueryWrapper<Menu> menuWrapper = new QueryWrapper<>();
menuWrapper.select("perms");
menuWrapper.eq("status", 0);
menuWrapper.in("menu_id", menuIdSet);
List<Menu> menus = baseMapper.selectList(menuWrapper);
//创建权限集合
Set<String> permsSet = new HashSet<>();
for (Menu menu : menus) {
if (Validator.isNotEmpty(menu.getPerms())) {
permsSet.addAll(Arrays.asList(menu.getPerms().split(",")));
}
}
log.info("{}", permsSet);
return permsSet;
}
}
package com.mangmang.security.service.impl;
import cn.hutool.core.lang.Validator;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mangmang.security.entity.Role;
import com.mangmang.security.entity.UserRole;
import com.mangmang.security.mapper.RoleMapper;
import com.mangmang.security.mapper.UserRoleMapper;
import com.mangmang.security.service.RoleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mangmang.security.service.UserRoleService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* <p>
* 角色信息表 服务实现类
* </p>
*
* @author 氓氓编程
* @since 2021-08-26
*/
@Service
@RequiredArgsConstructor
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
private final UserRoleMapper userRoleMapper;
@Override
public Set<String> findRolePermissionByUserId(Long userId) {
//获取该用户所有角色信息
List<UserRole> userRoleList = userRoleMapper.selectList(new QueryWrapper<UserRole>().eq("user_id", userId));
if (userRoleList.size() <= 0) {
return null;
}
//获取角色id
Set<Long> roleIdSet = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toSet());
//查询该角色中未被禁用角色
QueryWrapper<Role> roleWrapper = new QueryWrapper<>();
roleWrapper.in("role_id", roleIdSet);
roleWrapper.eq("status", 0);
List<Role> perms = baseMapper.selectList(roleWrapper);
//遍历角色
Set<String> permsSet = new HashSet<>();
for (Role perm : perms) {
if (Validator.isNotNull(perm)) {
permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(",")));
}
}
return permsSet;
}
}