blog/src/programming/java/框架/SpringSecurity.md
2025-05-08 17:47:04 +08:00

113 KiB
Raw Blame History

icon title date category tag star description sticky
simple-icons:spring SpringSecurity 2025-05-07
JAVA
Spring
SpringSecurity
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认证内置的两个FilterJWT认证方式用不上
	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;
    }
}