--- icon: simple-icons:spring title: SpringSecurity date: 2025-05-07 category: - JAVA tag: - Spring - SpringSecurity star: 10 description: 若依的SpringSecurity案例 # 此页面会在文章列表置顶 sticky: true --- 若依的SpringSecurity案例 # 一、准备 ## 1. 导入依赖 ### 1. 父依赖 ```xml 4.0.0 01-druid 02-SpringSecurity org.springframework.boot spring-boot-starter-parent 2.3.0.RELEASE com.mangmang learn-test 0.0.1-SNAPSHOT learn-test pom 依赖版本控制 1.8 3.4.3.1 3.4.1 2.3 1.2.4 0.9.1 2.0.8 1.21 1.5.1-RELEASE eu.bitwalker UserAgentUtils ${userAgentUtils.version} com.github.xiaoymin knife4j-spring-boot-starter ${knife4j.version} io.jsonwebtoken jjwt ${jjwt.version} com.github.jeffreyning mybatisplus-plus ${mybatisplus-plus.version} com.baomidou mybatis-plus-boot-starter ${mybatis-plus.version} com.baomidou mybatis-plus-generator ${mybatis-plus-generator.version} org.apache.velocity velocity-engine-core ${mybatis-velocity.version} com.alibaba druid-spring-boot-starter ${druid.version} com.alibaba fastjson 1.2.75 cn.hutool hutool-all 5.5.8 org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools runtime true mysql mysql-connector-java runtime org.springframework.boot spring-boot-configuration-processor true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin 2.3.0.RELEASE org.projectlombok lombok ``` ### 2. 模块使用依赖 ```xml learn-test com.mangmang 0.0.1-SNAPSHOT 4.0.0 02-SpringSecurity 8 8 com.github.jeffreyning mybatisplus-plus eu.bitwalker UserAgentUtils com.github.xiaoymin knife4j-spring-boot-starter io.jsonwebtoken jjwt org.springframework.boot spring-boot-starter-security com.baomidou mybatis-plus-boot-starter com.baomidou mybatis-plus-generator org.apache.velocity velocity-engine-core com.alibaba druid-spring-boot-starter src/main/java **/*.xml false ``` ## 2. 配置YML ### 1. application.yml ```yml spring: profiles: active: local server: port: 8000 swagger: enabled: true pathMapping: token: header: token secret: asdaswqesdzxwr3123 expire-time: 30 ``` ### 2. application-local.yml ```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 ```java /** * @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 ```java /** * @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 securitySchemes() { List apiKeyList = new ArrayList<>(); apiKeyList.add(new ApiKey("Authorization", "Authorization", "header")); return apiKeyList; } /** * 安全上下文 */ private List securityContexts() { List securityContexts = new ArrayList<>(); securityContexts.add( SecurityContext.builder() .securityReferences(defaultAuth()) .forPaths(PathSelectors.regex("^(?!auth).*$")) .build()); return securityContexts; } /** * 默认的安全上引用 */ private List defaultAuth() { AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; authorizationScopes[0] = authorizationScope; List 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序列化规则 ```java /** * @author a3621 */ @EnableCaching @Configuration public class RedisConfig extends CachingConfigurerSupport { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { // 创建一个模板 RedisTemplate template = new RedisTemplate<>(); //创建一个字符串序列化器 RedisSerializer redisSerializer = new StringRedisSerializer(); //创建Jackson 序列化器 Jackson2JsonRedisSerializer 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 redisSerializer = new StringRedisSerializer(); //创建Jackson 序列化器 Jackson2JsonRedisSerializer 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,然后导入数据库) ```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的权限去抛出异常,然后由异常处理器去处理这些异常。** 图片显示流程 [//]: # (![springSecurity](../../img/20210829181406.png)) [//]: # () [//]: # (![v2-511a6a997997ea5f393d3ca89fe6f54c_720w](../../img/20210829181436.jpg)) [//]: # () [//]: # (![v2-4bee6e527edb95fba811f9fd6a4f58e6_720w](../../img/20210829181849.jpg)) ```java /** 如上图: 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的重要概念 ```apl 1. SecurityContext:上下文对象,Authentication对象会放在里面。 2. SecurityContextHolder:用于拿到上下文对象的静态工具类。 3. Authentication:认证接口,定义了认证对象的数据形式。 4. AuthenticationManager:用于校验Authentication,返回一个认证完成后• Authentication对象。 ``` ### 1. SecurityContext > 上下文对象,认证后的数据就放在这里面,接口定义如下: ```java public interface SecurityContext extends Serializable { // 获取Authentication对象 Authentication getAuthentication(); // 放入Authentication对象 void setAuthentication(Authentication authentication); } ``` ### 2. SecurityContextHolder > 可以说是SecurityContext的工具类,用于get 或 set 或者 clear SecurityContext,**默认会把数据都存储到当前线程中**。 ```java 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进行认证过的数据的数据形式应该是怎么样的,要有权限,要有密码,要有身份信息,要有额外信息。 ```java public interface Authentication extends Principal, Serializable { Collection getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; } ``` ```java /** 1. getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。 2. getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。 3. getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)。 4. getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails。 6. isAuthenticated: 获取当前 Authentication 是否已认证。 7. setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。 ``` ### 4. AuthenticationManager > AuthenticationManager定义了一个认证方法,它将一个未认证的Authentication传入,返回一个已认证的Authentication,默认使用的实现类为:ProviderManager。 ```java public interface AuthenticationManager { // 认证方法 Authentication authenticate(Authentication authentication)throws AuthenticationException; } ``` ### 5. Spring Security进行认证的流程: ```java /** 1. 先是一个请求带着身份信息进来 2. 经过AuthenticationManager的认证, 3. 再通过SecurityContextHolder获取SecurityContext, 4. 最后将认证后的信息放入到SecurityContext。 ``` ## 3. 必要组件 ### 1. 定义加密器Bean > 这个Bean是不必可少的,Spring Security在认证操作时会使用我们定义的这个加密器,如果没有则会出现异常。 ```java /** * spring容器中注入加密算法 * * @return 强散列哈希加密实现 */ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } ``` ### 2.定义AuthenticationManager >这里将Spring Security自带的authenticationManager声明成Bean,声明它的作用是用它帮我们进行认证操作,调用这个Bean的authenticate方法会由Spring Security自动帮我们做认证。 ```java /** * 解决 无法直接注入 AuthenticationManager * * @return return * @throws Exception e */ @Bean @Override protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } ``` ### 3. 实现UserDetailsService >实现UserDetailsService的抽象方法并返回一个UserDetails对象,认证过程中SpringSecurity会调用这个方法访问数据库进行对用户的搜索,逻辑什么都可以自定义,无论是从数据库中还是从缓存中,但是我们需要将我们查询出来的用户信息和权限信息组装成一个UserDetails返回。 > >UserDetails也是一个定义了数据形式的接口,用于保存我们从数据库中查出来的数据,其功能主要是验证账号状态和获取权限 ```java /** * 自定义用户认证逻辑并交给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().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 permissions = permissionService.getMenuPermission(user); //角色权限信息 Set rolePermission = permissionService.getRolePermission(user); user.setRoles(rolePermission); //返回安全框架需要得用户信息和自定义得一些信息 return new SecurityUser(permissions, user); } } ``` ### 4. TokenUtil > 由于我们是JWT的认证模式,所以我们也需要一个帮我们操作Token的工具类,一般来说它具有以下三个方法就够了 > > - 创建token > - 验证token > - 反解析token中的信息 ```java 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 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 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 ```java 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 permissions; /** * 用户信息 */ private User user; @Getter(AccessLevel.NONE) private String username; /** * 用户信息和权限信息 * * @param permissions 权限信息 * @param user 用户信息 */ public SecurityUser(Set permissions, User user) { this.permissions = permissions; this.user = user; } @Override public Collection 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. 认证方法和认证源码分析 > 访问一个系统,一般最先访问的是认证方法,这里我写了最简略的认证需要的几个步骤,因为实际系统中我们还要写登录记录啊,前台密码解密啊这些操作。 ```java /** * 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 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); } } ``` ```java /** 1. 传入用户名和密码创建了一个UsernamePasswordAuthenticationToken对象,这是我们前面说过的Authentication的实现类,传入用户名和密码做构造参数,这个对象就是我们创建出来的未认证的Authentication对象。使用我们先前已经声明过的Bean-authenticationManager调用它的authenticate方法进行认证,返回一个认证完成的Authentication对象。 2. 从Authentication对象中拿到我们的UserDetails对象,之前我们说过,认证后的Authentication对象调用它的getPrincipal()方法就可以拿到我们先前数据库查询后组装出来的UserDetails对象, 3. 然后创建token。把UserDetails对象放入缓存中,方便后面过滤器使用。 ``` ```java 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放在过滤器链中绿色的部分才会起到自动认证的效果。 > >具体实现如下: ```java /** * 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解析失败时会触发这个处理器,返回一个非法访问的结果。 ```java /** * 认证失败处理类 返回未授权 * * @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. 自定义退出处理 ```java /** * 自定义退出处理类 返回成功 * * @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. 权限不足处理器 ```java /** * 权限不足处理器 * * @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完整配置 ```java /** * 自动配置时会判断是否存在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. 枚举 ```java /** * 用户状态 * * @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. 静态常量 ```java /** * 静态常量 * * @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解析 ```java /** * @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 "未知"; } } ``` ```java 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请求发送 ```java 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. 获取用户权限信息 ```java 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 getRolePermission(User user) { // 需要返回得权限集合 Set roles = new HashSet<>(); //如果为超级用户添加超级用户 if (user.judgeIsAdmin()) { roles.add("admin"); } else { //否则添加其它角色 roles.addAll(roleService.findRolePermissionByUserId(user.getUserId())); } return roles; } /** * 获取菜单权限 * * @param user 用户户信息 * @return 菜单权限集合 */ public Set getMenuPermission(User user) { // 需要返回得权限集合 Set menus = new HashSet<>(); //如果为超级用户添加超级用户 if (user.judgeIsAdmin()) { menus.add("*:*:*"); } else { //添加两个集合得并集 menus.addAll(menuService.findAllMenu(user.getUserId())); } return menus; } } ``` ## 2. 自定义权限判断 ```java 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 authorities = securityUser.getPermissions(); //遍历权限判断是否具有某一个权限 for (String permission : permissions.split(DELIMETER)) { if (permission != null && hasPermissions(authorities, permission)) { return true; } } return false; } /** * 公用判断是否拥有某个权限 */ private boolean hasPermissions(Set 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. 测试类 ```java 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个方法 ```java 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; /** *

* 菜单权限表 服务实现类 *

* * @author 氓氓编程 * @since 2021-08-26 */ @Slf4j @Service @RequiredArgsConstructor public class MenuServiceImpl extends ServiceImpl implements MenuService { private final RoleMapper roleMapper; private final UserRoleMapper userRoleMapper; private final RoleMenuMapper roleMenuMapper; @Override public Set findAllMenu(Long userId) { //查询该用户所有角色 List userRoleList = userRoleMapper.selectList(new QueryWrapper().eq("user_id", userId)); if (userRoleList.size() <= 0) { return null; } Set roleIdSet = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toSet()); //查询该角色中未被禁用角色 QueryWrapper roleWrapper = new QueryWrapper<>(); roleWrapper.in("role_id", roleIdSet); roleWrapper.eq("status", 0); List rolesIdList = roleMapper.selectList(roleWrapper); Set roleIds = rolesIdList.stream().map(Role::getRoleId).collect(Collectors.toSet()); //查询用户角色所有菜单 List roleMenuList = roleMenuMapper.selectList(new QueryWrapper().in("role_id", roleIds)); if (roleMenuList.size() <= 0) { return null; } Set menuIdSet = roleMenuList.stream().map(RoleMenu::getMenuId).collect(Collectors.toSet()); //查询用户所有菜单中未禁用菜单 QueryWrapper menuWrapper = new QueryWrapper<>(); menuWrapper.select("perms"); menuWrapper.eq("status", 0); menuWrapper.in("menu_id", menuIdSet); List menus = baseMapper.selectList(menuWrapper); //创建权限集合 Set permsSet = new HashSet<>(); for (Menu menu : menus) { if (Validator.isNotEmpty(menu.getPerms())) { permsSet.addAll(Arrays.asList(menu.getPerms().split(","))); } } log.info("{}", permsSet); return permsSet; } } ``` ```java 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; /** *

* 角色信息表 服务实现类 *

* * @author 氓氓编程 * @since 2021-08-26 */ @Service @RequiredArgsConstructor public class RoleServiceImpl extends ServiceImpl implements RoleService { private final UserRoleMapper userRoleMapper; @Override public Set findRolePermissionByUserId(Long userId) { //获取该用户所有角色信息 List userRoleList = userRoleMapper.selectList(new QueryWrapper().eq("user_id", userId)); if (userRoleList.size() <= 0) { return null; } //获取角色id Set roleIdSet = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toSet()); //查询该角色中未被禁用角色 QueryWrapper roleWrapper = new QueryWrapper<>(); roleWrapper.in("role_id", roleIdSet); roleWrapper.eq("status", 0); List perms = baseMapper.selectList(roleWrapper); //遍历角色 Set permsSet = new HashSet<>(); for (Role perm : perms) { if (Validator.isNotNull(perm)) { permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); } } return permsSet; } } ```