Fork me on GitLab

spring-boot+mybatis-plus+spring-security+jwt+OAuth+redis认证授权

1.首先了解oauth是什么东西,具体是用来干嘛的。

  OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。

  本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释。http://en.wikipedia.org/wiki/OAuth

        网上看到写的很好的博客:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

2.搭建一个spring-boot工程

  1.我的目录结构如下

  一个个的操作的话篇幅太多,不使用作者这种,一个spring-boot工程也是一样的。

2.添加必要的依赖

  mydome的maven包,一般创建springboot项目时会自动帮我们生成的

<?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>
    <packaging>pom</packaging>

    <modules>
        <module>com-dome-provider</module>
        <module>com-dome-model</module>
        <module>com-dome-common</module>
        <module>com-dome-biz</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>com</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>


    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                   <version>2.4.0</version>
                    <configuration>
                        <mainClass>com.dome.provider.App</mainClass>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>build-info</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>

                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <target>1.8</target>
                        <source>1.8</source>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

</project>
View Code

  com-dome-model模块的maven

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>demo</artifactId>
        <groupId>com</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>com-dome-model</artifactId>

    <dependencies>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <!--swagger ui-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

        <!--msql-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--校验工具-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- 代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.0</version>
        </dependency>

        <!-- 模板-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.1</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.1.0</version>
        </dependency>

    </dependencies>
</project>
View Code

  com-dome-common模块

<?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>demo</artifactId>
        <groupId>com</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>com-dome-common</artifactId>

    <dependencies>

        <dependency>
            <groupId>com</groupId>
            <artifactId>com-dome-model</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--redis集成-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--        jjwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <!-- 支持 @ConfigurationProperties 注解 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <!--aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!--   UserAgentUtils   -->
        <dependency>
            <groupId>eu.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
            <version>1.21</version>
        </dependency>

        <!-- 获取系统信息 -->
        <dependency>
            <groupId>com.github.oshi</groupId>
            <artifactId>oshi-core</artifactId>
            <version>4.4.2</version>
        </dependency>

        <!--        json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.71</version>
        </dependency>

        <!--验证码-->
        <dependency>
            <groupId>com.github.axet</groupId>
            <artifactId>kaptcha</artifactId>
            <version>0.0.9</version>
        </dependency>

<!--        excel-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.9</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.9</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.20</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>
</project>
View Code

  com-dome-biz模块

<?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>demo</artifactId>
        <groupId>com</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>com-dome-biz</artifactId>

    <dependencies>

        <dependency>
            <groupId>com</groupId>
            <artifactId>com-dome-model</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com</groupId>
            <artifactId>com-dome-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>
View Code

  com-dome-privoder模块

<?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>demo</artifactId>
        <groupId>com</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>com-dome-provider</artifactId>

    <dependencies>

        <dependency>
            <groupId>com</groupId>
            <artifactId>com-dome-model</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com</groupId>
            <artifactId>com-dome-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com</groupId>
            <artifactId>com-dome-biz</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <!--        热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <!--        安全框架-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.3.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.1.0</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.dome.provider.App</mainClass>
                    <finalName>com-dome-provider</finalName>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.21.0</version>
                <configuration>
                    <testFailureIgnore>true</testFailureIgnore>
                </configuration>
            </plugin>
        </plugins>

    </build>


</project>
View Code

  添加一个有趣的banner.txt,在com-dome-provider的resources中

//                          _ooOoo_                               //
//                         o8888888o                              //
//                         88" . "88                              //
//                         (| ^_^ |)                              //
//                         O\  =  /O                              //
//                      ____/`---'\____                           //
//                    .'  \\|     |//  `.                         //
//                   /  \\|||  :  |||//  \                        //
//                  /  _||||| -:- |||||-  \                       //
//                  |   | \\\  -  /// |   |                       //
//                  | \_|  ''\---/''  |   |                       //
//                  \  .-\__  `-`  ___/-. /                       //
//                ___`. .'  /--.--\  `. . ___                     //
//              ."" '<  `.___\_<|>_/___.'  >'"".                  //
//            | | :  `- \`.;`\ _ /`;.`/ - ` : | |                 //
//            \  \ `-.   \_ __\ /__ _/   .-` /  /                 //
//      ========`-.____`-.___\_____/___.-`____.-'========         //
//                           `=---='                              //
//      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        //
//            佛祖保佑       永不宕机     永无BUG                 //
View Code

  项目大致的结构已经搭建完成

3.添加一下工具类和配置信息

  在@SpringBootApplication加上要扫描的包目录和@MapperScan指向存放dao的目录,添加yml配置。

server:
  port: 8080

spring:
  #时间戳统一转换
  jackson:
  date-format: yyyy-MM-dd HH:mm:ss
  time-zone: GMT+8

  #数据库配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/sprin_security?useSSL=false&useUnicode=true&nullCatalogMeansCurrent=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&maxReconnects=10
    password: yourpass
    username: root
    hikari:
      minimum-idle: 1
      maximum-pool-size: 10


  redis:
    host: 127.0.0.1
    port: 6379
    password: yourpass
    jedis:
      pool:
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: -1
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 10000ms
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池中的最小空闲连接
        min-idle: 0
    # 连接超时时间(毫秒)
    timeout: 10000ms

  servlet:
    multipart:
      # spring-boot 最大上传文件大小
      max-file-size: 30MB
      max-request-size: 30MB

  aop:
    auto: true

mybatis-plus:
  configuration:
    #配置日志 默认控制台输出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-value: 1
      logic-not-delete-value: 0
  mapper-locations: classpath*:/mapper/**/*.xml
  type-aliases-package: com.dome.model.po

jwt:
  tokenHeader: Authorization
  secret: iwqjhda8232bjgh432
  #JWT的超期限时间(60*60*24*7)
  expiration: 604800
  tokenHead: 'Bearer '
View Code

4.数据库

  作者使用的是MySQL5.7的数据库,用完这些就可以启动了。

/*
 Navicat Premium Data Transfer

 Source Server         : 阿里云
 Source Server Type    : MySQL
 Source Server Version : 50724
 Source Host           : 127.0.0.1:3306
 Source Schema         : sprin_security

 Target Server Type    : MySQL
 Target Server Version : 50724
 File Encoding         : 65001

 Date: 30/11/2020 16:01:47
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu`  (
  `id` bigint(36) NOT NULL AUTO_INCREMENT,
  `type` tinyint(2) NULL DEFAULT 1 COMMENT '菜单类型  1  =  1级  2  = 2级.......',
  `parent` bigint(36) NULL DEFAULT 0 COMMENT '父级菜单id',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单名称',
  `url` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '后台生成URL',
  `icon` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '菜单图标icon',
  `permission` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限  sys:user:query  =  系统管理:用户管理:查询',
  `sort` bigint(36) NULL DEFAULT NULL COMMENT '排序',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `create_name` bigint(36) NULL DEFAULT NULL COMMENT '创建人',
  `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
  `update_name` bigint(36) NULL DEFAULT NULL COMMENT '修改人',
  `version` bigint(36) NULL DEFAULT 1 COMMENT '版本号',
  `del_flag` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除0 = 存在 1 = 删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, 1, 0, '系统管理', '/sys', NULL, 'sys', NULL, '2020-11-19 15:36:32', 1, '2020-11-19 15:38:16', 1, 1, 0);
INSERT INTO `sys_menu` VALUES (2, 2, 1, '用户管理', '/sys/user', NULL, 'sys:user', NULL, '2020-11-19 15:36:56', 1, '2020-11-19 15:38:16', 1, 1, 0);
INSERT INTO `sys_menu` VALUES (3, 3, 2, '新增用户', '/sys/user/add', NULL, 'sys:user:add', NULL, '2020-11-19 15:37:30', 1, '2020-11-19 15:38:16', 1, 1, 0);
INSERT INTO `sys_menu` VALUES (4, 3, 2, '查询', '/sys/user/query', NULL, 'sys:user:query', NULL, '2020-11-19 15:38:10', 1, '2020-11-19 15:38:16', 1, 1, 0);
INSERT INTO `sys_menu` VALUES (6, 3, 2, '删除', '/sys/user/del', NULL, 'sys:user:del', NULL, '2020-11-19 15:38:43', 1, '2020-11-19 15:38:43', 1, 1, 0);
INSERT INTO `sys_menu` VALUES (7, 3, 2, '详情', '/sys/user/findById', NULL, 'sys:user:findById', NULL, '2020-11-27 09:48:43', NULL, '2020-11-27 09:48:59', NULL, 1, 0);

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` bigint(36) NOT NULL AUTO_INCREMENT,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色编码',
  `name` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '角色名称',
  `role_describe` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',
  `status` tinyint(1) NULL DEFAULT 0 COMMENT '角色状态0 =  启用 = 禁用',
  `create_name` bigint(36) NULL DEFAULT NULL COMMENT '创建人',
  `update_name` bigint(36) NULL DEFAULT NULL COMMENT '修改人',
  `version` bigint(36) NULL DEFAULT 1 COMMENT '版本号',
  `del_flag` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除0 = 存在   1 = 删除',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'gly', '管理员', '系统管理员', 0, 1, 1, 1, 0, '2020-11-19 15:35:33', '2020-11-19 15:35:33');

-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu`  (
  `id` bigint(36) NOT NULL AUTO_INCREMENT,
  `role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色id',
  `menu_id` bigint(20) NULL DEFAULT NULL COMMENT '菜单id',
  `create_name` bigint(255) NULL DEFAULT NULL COMMENT '创建人',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色菜单中间表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (1, 1, 1, 1, '2020-11-19 15:40:05');
INSERT INTO `sys_role_menu` VALUES (2, 1, 2, 1, '2020-11-19 15:40:07');
INSERT INTO `sys_role_menu` VALUES (3, 1, 3, 1, '2020-11-19 15:40:11');
INSERT INTO `sys_role_menu` VALUES (4, 1, 4, 1, '2020-11-19 15:40:12');
INSERT INTO `sys_role_menu` VALUES (5, 1, 6, 1, '2020-11-19 15:40:13');
INSERT INTO `sys_role_menu` VALUES (7, 1, 7, 1, '2020-11-30 15:06:04');

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` bigint(36) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码',
  `nick_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户昵称',
  `phone` decimal(12, 0) NULL DEFAULT NULL COMMENT '手机号',
  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `status` tinyint(1) NULL DEFAULT NULL COMMENT '用户状态  0  =  启用     1 =  禁用',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `create_name` bigint(36) NULL DEFAULT NULL COMMENT '创建人id',
  `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
  `update_name` bigint(36) NULL DEFAULT NULL COMMENT '修改人id',
  `version` bigint(36) NULL DEFAULT 1 COMMENT '版本号 ',
  `del_flag` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除0 = 存在 1 = 删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$Fu0pt5Mf1ggTgVXM32GHmuBWntgp8a1WyVEgufwzuzlgoXT.Ou7aK', '管理员', 1111111111, '1111@qq.com', 0, '2020-11-20 15:02:31', 1, '2020-11-27 11:24:39', 1, 1, 0);

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `id` bigint(36) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(36) NULL DEFAULT NULL COMMENT '用户id',
  `role_id` bigint(36) NULL DEFAULT NULL COMMENT '角色id',
  `create_name` bigint(255) NULL DEFAULT NULL COMMENT '创建人',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户角色中间表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1, 1, '2020-11-19 15:41:40');

SET FOREIGN_KEY_CHECKS = 1;
View Code

 3.用到的一些工具类和文件

  mybatis-plus的配置和代码生成配置:https://www.cnblogs.com/yiMro/p/13529094.html

  jwt工具类:https://www.cnblogs.com/yiMro/p/14061996.html

  全局异常捕获:https://www.cnblogs.com/yiMro/p/13672652.html

  redis工具:https://www.cnblogs.com/yiMro/p/13529150.html

  客服端硬盘号工具

package com.dome.common.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * @author YinXiaoWei
 * @date 2020/11/25 16:00
 * 获取客户端硬盘号
 */
public class HdSerialInfoUtil {

    public static String getHdSerialInfo() {
        String line = "";
        //定义变量 硬盘序列号
        String HdSerial = "";
        try {
            //获取命令行参数
            Process proces = Runtime.getRuntime().exec("cmd /c dir c:");
            BufferedReader buffreader = new BufferedReader(new InputStreamReader(proces.getInputStream(),"gbk"));

            while ((line = buffreader.readLine()) != null) {
                //读取参数并获取硬盘序列号
                if (line.indexOf("卷的序列号是 ") != -1) {

                    HdSerial = line.substring(line.indexOf("卷的序列号是 ") + "卷的序列号是 ".length(), line.length());
                    break;
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        return HdSerial;
    }
}
View Code

  客服端ip工具

package com.dome.common.utils;

import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.UserAgent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @author YinXiaoWei
 * @date 2020/11/25 15:34
 * ip工具类
 */
public class IpUtil {

    private static final Logger logger = LoggerFactory.getLogger(IpUtil.class);

    /**
     * 获取客户端IP地址
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (ipAddress.equals("127.0.0.1")) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        logger.error("获取用户的主机发生异常",e);
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 ***.***.***.***".length()
            int ipAddressMax = 15;
            if (ipAddress != null && ipAddress.length() > ipAddressMax) {
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress="";
            logger.error("获取用户的ip地址发生异常",e);
        }

        //服务端和客户端在一台机器
        if(ipAddress.toString().contains( "0:0:0:0:0:0:0:1")) {
            ipAddress = "127.0.0.1";
        }
        return ipAddress;
    }

    /**
     * 获取浏览器信息
     * @param request
     * @return
     */
    public static String getBrowser(HttpServletRequest request){
        String ua = request.getHeader("User-Agent");
        //转成UserAgent对象
        UserAgent userAgent = UserAgent.parseUserAgentString(ua);
        Browser browser = userAgent.getBrowser();
        //浏览器名
        return browser.toString();
    }
}
View Code

  验证码生成器

package com.dome.provider.config;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

/**
 * @author YinXiaoWei
 * @date 2020/11/25 15:55
 * 图片验证码配置
 */
@Configuration
public class KaptchaConfig {

    @Bean
    public DefaultKaptcha producer(){
        Properties p =new Properties();
        //是否有边框
        p.put("kaptcha.border","no");
        //字体颜色
        p.put("kaptcha.textproducer.font.color","black");
        //图片宽度
        p.put("kaptcha.image.width","135");
        //使用哪些字符生成验证码
        p.put("kaptcha.textproducer.char.string","ABCDEFHKIJZPYRSTWXabcdefhkijzprstwx0123456789");
        //图片高度
        p.put("kaptcha.image.height","50");
        //字体大小
        p.put("kaptcha.textproducer.font.size","43");
        //字体颜色
        p.put("kaptcha.noise.color","blue");
        //字符的个数
        p.put("kaptcha.textproducer.char.length","4");
        //字体
        p.put("kaptcha.textproducer.font.names","Arial");
        Config config =new Config(p);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}
View Code
package com.dome.common.utils;


import com.google.code.kaptcha.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
 * @author YinXiaoWei
 * @date 2020/11/25 15:43
 * 验证码图片的工具类
 */
@Component
public class VerifyCodeUtil {

    private static final String AUTH_NAME = "AUTH_NAME";

    @Autowired
    private Producer producer;

    @Autowired
    private RedisUtil redisUtil;


    /**
     * 生成验证码
     * @return
     */
    public String getAuthCode() {
        String id = HdSerialInfoUtil.getHdSerialInfo();
        if (redisUtil.hasKey(AUTH_NAME + id)) {
            redisUtil.delKey(AUTH_NAME + id);
        }
        String code = producer.createText().toLowerCase();
        redisUtil.setObject(AUTH_NAME + id, code);
        return code;
    }

    /**
     * 校验验证码
     * @param code
     * @return
     */
    public boolean verifyAuthCode(String code) {
        String authCode = code.toLowerCase();
        String id = HdSerialInfoUtil.getHdSerialInfo();
        if (!redisUtil.hasKey(AUTH_NAME + id)) {
            return false;
        }
        if (!redisUtil.getObject(AUTH_NAME + id).toString().trim().equals(authCode)) {
            return false;
        }
        return true;
    }

    /**
     * 返回图片 io流
     * @param response
     * @param code
     */
    public void backImg(HttpServletResponse response, String code) {
        BufferedImage img = producer.createImage(code);
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            ImageIO.write(img,"jpg", outputStream);
        } catch (IOException e) {
            throw new RuntimeException("生成图片验证码错误!");
        }
    }
}
View Code

  在resources添加logback.xml文件用于生成日志文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
    <property name="LOG_HOME" value="/Logs"/>
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!-- 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/com-dome-provider.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 出错日志 appender  -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天回滚 daily -->
            <!-- log.dir 在maven profile里配置 -->
            <FileNamePattern>${LOG_HOME}/com-dome-provider-error-log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!-- 日志最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>


    <!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG"/>
    <logger name="org.hibernate.SQL" level="DEBUG"/>
    <logger name="org.hibernate.engine.QueryParameters" level="DEBUG"/>
    <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG"/>

    <!--myibatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>

    <logger name="com.bool.order.meeting.core.mapper" level="DEBUG"></logger>

    <!-- 日志输出级别 -->
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
        <appender-ref ref="ERROR_FILE" />
    </root>
</configuration>
View Code

4.添加controller、service、mapper层级代码代码

  添加各个层级的代码,建议自己手扣好一点。修改字段生成的代码:

  1.实体类中跟数据库tinyint位数为一的字段会是布尔类型改为Integer类型。

  2.在时间字段上加上@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")==>用于处理时间格式

  3.controller类上加上@Slf4j日志 采用lombok提供的@RequiredArgsConstructor(onConstructor = @__(@Autowired) 并使用(final)的方式注入 private final RoleService roleService;

  4.@MapperScan和yml中mybatis-plus下的mapper-lications、type-aliases-package一定要指向正确,不然就会出现xxx找不到的问题。

  5.如果确定配置没有写错还是出现xxx找不到的问题(作者是自动生成的所以不存在层级关系和代码名称的问题),那看一下xml文件的位置,如果不在resources下springboot是没有办法识别 的,在maven添加

<build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
View Code

6.写一个hellWord接口测试访问(security的maven依赖注释掉,不然就会跳转到登录页面。用户名:user 密码:项目启动在控制台中生成)

    

 5.security认证授权

  Swagger

  相信无论是前端还是后端开发,都或多或少地被接口文档折磨过。前端经常抱怨后端给的接口文档与实际情况不一致。后端又觉得编写及维护接口文档会耗费不少精力,经常来不及更新。其实无论是前端调用后端,还是后端调用后端,都期望有一个好的接口文档。但是这个接口文档对于程序员来说,就跟注释一样,经常会抱怨别人写的代码没有写注释,然而自己写起代码起来,最讨厌的,也是写注释。所以仅仅只通过强制来规范大家是不够的,随着时间推移,版本迭代,接口文档往往很容易就跟不上代码了。
swagger详情介绍:https://www.jianshu.com/p/349e130e40d5
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.List;

/**
 * @author YinXiaoWei
 * @date 2020/11/24 14:26
 * swagger
 */
@Configuration
@EnableSwagger2
@EnableKnife4j
public class SwaggerConfig {

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build()
                .globalOperationParameters(getParameterList());
    }

    private ApiInfo apiInfo() {
        Contact contact = new Contact("spring-security", "http://www.spring-security.com/", "15289@qq.com");
        return new ApiInfoBuilder()
                .title("com.dome.spring")
                .description("前端API接口")
                .contact(contact)
                .version("1.0.0")
                .build();
    }

    private List<Parameter> getParameterList(){
        List<Parameter> parameters = new ArrayList<>();
        parameters.add(new ParameterBuilder()
                .name(this.tokenHeader)
                .description("令牌")
                .defaultValue(this.tokenHead)
                .modelRef(new ModelRef("string"))
                .parameterType("header")
                .required(false)
                .build());
        return parameters;
    }
}
View Code

推荐Knife4j,在swagger-ui的基础上导入maven依赖在到SwaggerConfig文件上加上@EnableKnife4j注解就可以访问很好看的Knife4j页面了。

Knife4j访问地址:http://ip:host/doc.html#/home  swagger-ui访问地址:http://ip:host/swagger-ui.html

 <!--整合Knife4j-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.4</version>
        </dependency>

 

  认证授权

根据自己的要求自定义UserDetails实体类

package com.dome.provider.config.security.dto;

import com.dome.model.po.role.Role;
import com.dome.model.po.user.User;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @author YinXiaoWei
 * @date 2020/11/26 9:33
 */
@Data
public class JwtUserDto implements UserDetails {

    /**
     * 用户数据
     */
    private User myUser;

    private List<Role> roleInfo;
    /**
     * 用户权限的集合
     */
    @JsonIgnore
    private List<GrantedAuthority> authorities;

    public List<String> getRoles() {
        return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
    }


    /**
     * 加密后的密码
     * @return
     */
    @Override
    public String getPassword() {
        return myUser.getPassword();
    }


    /**
     * 用户名
     * @return
     */
    @Override
    public String getUsername() {
        return myUser.getUserName();
    }


    /**
     * 是否过期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }


    /**
     * 是否锁定
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }


    /**
     * 凭证是否过期
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }


    /**
     * 是否可用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return myUser.getStatus() == 0 ? true : false;
    }




    public JwtUserDto(User myUser, List<GrantedAuthority> authorities) {
        this.myUser = myUser;
        this.authorities = authorities;
    }
}
View Code

添加需要用到的dao方法,附上MenuMapper.xml代码和VO类,因为集成了mybatis-plus所以写了一个基本就够用了。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dome.biz.mapper.menu.MenuMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.dome.model.po.menu.Menu">
        <id column="id" property="id" />
        <result column="type" property="type" />
        <result column="parent" property="parent" />
        <result column="name" property="name" />
        <result column="url" property="url" />
        <result column="icon" property="icon" />
        <result column="permission" property="permission" />
        <result column="sort" property="sort" />
        <result column="create_time" property="createTime" />
        <result column="create_name" property="createName" />
        <result column="update_time" property="updateTime" />
        <result column="update_name" property="updateName" />
        <result column="version" property="version" />
        <result column="del_flag" property="delFlag" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, type, parent, name, url, icon, permission, sort, create_time, create_name, update_time, update_name, version, del_flag
    </sql>

<!--    菜单信息列-->
    <sql id="Menu_By_Id">
        m.id as id,  m.parent,  m.name as title,m.icon,m.url as href,  m.type, m.permission, m.sort
    </sql>

    <!--    通过用户id返回菜单-->
    <select id="listByUserId" resultType="com.dome.model.vo.menu.MenuIndexVo">
        SELECT DISTINCT
        <include refid="Menu_By_Id"></include>
        FROM
        sys_user_role ru
        INNER JOIN sys_role_menu rm ON rm.role_id = ru.role_id
        LEFT JOIN sys_menu m ON rm.menu_id = m.id
        WHERE 1 = 1
        <if test="userId != null">
            AND ru.user_id = #{userId}
        </if>
        ORDER BY
        ifnull(m.sort, 0)
    </select>

</mapper>
View Code
package com.dome.model.vo.menu;

import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.List;

/**
 * @author YinXiaoWei
 * @date 2020/11/26 10:34
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="MenuIndexVo对象", description="菜单信息返回表")
public class MenuIndexVo implements Serializable {

    private Integer id;

    private Integer parentId;

    private String title;

    private String icon;

    private Integer type;

    private String href;

    private String permission;

    private List<MenuIndexVo> children;
}
View Code

创建UserDetailsServiceImpl类,重写UserDetailsService方法,动态连接数据库

package com.dome.provider.config.security;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dome.biz.service.menu.MenuService;
import com.dome.biz.service.role.RoleService;
import com.dome.biz.service.user.UserRoleService;
import com.dome.biz.service.user.UserService;
import com.dome.common.utils.RedisUtil;
import com.dome.model.po.role.Role;
import com.dome.model.po.user.User;
import com.dome.model.po.user.UserRole;
import com.dome.model.vo.menu.MenuIndexVo;
import com.dome.provider.config.security.dto.JwtUserDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author YinXiaoWei
 * @date 2020/11/26 10:25
 * 重写登录实现类
 */
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private MenuService menuService;

    @Override
    public JwtUserDto loadUserByUsername(String userName) {
        //根据用户名获取用户
        User user = userService.existUser(userName);
        if (user == null ){
            throw new BadCredentialsException("用户名或密码错误");
        }else if (user.getStatus().equals(User.Status.VALID)) {
            throw new LockedException("用户被锁定,请联系管理员解锁");
        }
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>(15);
        List<MenuIndexVo> list = menuService.listByUserId(user.getId().toString());
        List<String> collect = list.stream().map(MenuIndexVo::getPermission).collect(Collectors.toList());
        for (String authority : collect){
            if (!StringUtils.isEmpty(authority)){
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority);
                grantedAuthorities.add(grantedAuthority);
            }
        }
        //将用户所拥有的权限加入GrantedAuthority集合中
        JwtUserDto loginUser =new JwtUserDto(user,grantedAuthorities);
        loginUser.setRoleInfo(getRoleInfo(user));
        return loginUser;
    }

    /**
     * 获得用户所有角色
     * @param user
     * @return
     */
    public List<Role> getRoleInfo(User user) {
        QueryWrapper<UserRole> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(UserRole::getUserId, user.getId());
        List<UserRole> userRoleList = userRoleService.getBaseMapper().selectList(wrapper);
        List<Role> roleList = new ArrayList<>();
        for (UserRole item : userRoleList) {
            Role role = roleService.getBaseMapper().selectById(item.getRoleId());
            roleList.add(role);
        }
        return roleList;
    }
}
View Code

完成这一步就可以登录了,在securityConfig中添加UserDetailsServiceImpl和加密方法(数据库的密码现在好像必须要加密,有兴趣可以了解下CSDN密码泄露事件)。

package com.dome.provider.config.security.config;

import com.dome.provider.config.security.UserDetailsServiceImpl;
import com.dome.provider.config.security.filter.JwtAuthenticationTokenFilter;
import com.dome.provider.config.security.handler.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


/**
 * @author YinXiaoWei
 * @date 2020/11/25 17:07
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 登录逻辑
     */
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    /**
     * 登录成功逻辑
     */
    @Autowired
    private MyAuthenticationSuccessHandler authenticationSuccessHandler;

    /**
     * 登出成功逻辑
     */
    @Autowired
    private AjaxLogoutSuccessHandler logoutSuccessHandler;


    /**
     * 登录失败逻辑
     */
    @Autowired
    private MyAuthenticationFailureHandler authenticationFailureHandler;

    /**
     * 无权限拦截器
     */
    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
    /**
     * 无权访问 JSON 格式的数据
     */
    @Autowired
    private RestfulAccessDeniedHandler accessDeniedHandler;

    /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 放行静态资源
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers(HttpMethod.GET,
                        "/swagger-resources/**",
                        "/PearAdmin/**",
                        "/component/**",
                        "/admin/**",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/swagger-ui.html",
                        "/webjars/**",
                        "/v2/**",
                        "/druid/**");
    }

    /**
     * 开启加密
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 身份认证接口
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

    /**
     * 核心配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭csrf
        http.csrf().disable()
                // 基于token,所以不需要session
                 .sessionManagement()
                 .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                 .and()
                //未登陆时返回 JSON 格式的数据给前端
                .httpBasic()
                .authenticationEntryPoint(restAuthenticationEntryPoint)
                .and()
                .authorizeRequests()
                // 对于登录login 验证码 允许匿名访问
                .antMatchers("/authentication/getAuthCode", "/authentication/existAuthCode/**","/login")
                .anonymous()
                .antMatchers("/profile/**").permitAll()
                .antMatchers("/common/download**").anonymous()
                .antMatchers("/common/download/resource**").anonymous()
                .antMatchers("/swagger-ui.html").anonymous()
                .antMatchers("/swagger-resources/**").anonymous()
                .antMatchers("/webjars/**").anonymous()
                .antMatchers("/*/api-docs").anonymous()
                .antMatchers("/doc.html").permitAll()
                // 其余请求都需要认证
                .anyRequest().authenticated()
                .and()
                .formLogin()
               // 登录页面 不设限访问
              // .loginPage("/login.html")
                // .loginProcessingUrl("/login")
                // 登录成功
                .successHandler(authenticationSuccessHandler)
                // 登录失败
                .failureHandler(authenticationFailureHandler)
                .permitAll()
                .and()
                // 注销行为
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler)
                .and()
                //  允许通过remember-me登录的用户访问
                .rememberMe()
                .rememberMeParameter("remember-me")
                 .and()
                 .headers()
                // 禁用缓存
                .cacheControl();
        // 防止iframe 造成跨域
        http.headers().frameOptions().disable();
        // 添加的token拦截器
        http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 无权访问 JSON 格式的数据
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
    }
}
View Code

做到这一步可以尝试下登录,http://ip:host/login(将上一步中多余的代码删掉)

以下是自定义的一些拦截器,把这些添加代码里面基本都有注释我就不介绍了。

package com.dome.provider.config.security.handler;

import com.alibaba.fastjson.JSON;
import com.dome.common.utils.RedisUtil;
import com.dome.common.utils.model.ResponseModel;
import com.dome.provider.config.security.dto.JwtUserDto;
import com.dome.provider.config.security.jwt.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author YinXiaoWei
 * @date 2020/11/26 9:29
 * 登录成功
 */
@Slf4j
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private JwtUtils jwtUtils;

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Value("${jwt.expiration}")
    private Long expiration;

    @Autowired
    private RedisUtil redisUtil;

    private static final String TOKEN_KEY = "USER_TOKEN";

    private static final String USER_KEY = "USER_INFO";

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //拿到登录用户信息
         JwtUserDto userDetails = (JwtUserDto)authentication.getPrincipal();
        //生成token
         String jwtToken = jwtUtils.generateToken(userDetails.getUsername());
         // 存入redis中
        if (redisUtil.hasKey(TOKEN_KEY + userDetails.getUsername())) {
            redisUtil.delKey(TOKEN_KEY + userDetails.getUsername());
        }
        if (redisUtil.hasKey(USER_KEY + userDetails.getUsername())) {
            redisUtil.delKey(USER_KEY + userDetails.getUsername());
        }
        redisUtil.setObject(USER_KEY + userDetails.getUsername(), userDetails);
        redisUtil.setObjectTime(TOKEN_KEY + userDetails.getUsername(), jwtToken, this.expiration);
        // 返回token信息
        ResponseModel success = ResponseModel.success("登录成功", jwtToken);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json");
        //输出结果
        httpServletResponse.getWriter().write(JSON.toJSONString(success));
    }
}
View Code
package com.dome.provider.config.security.handler;

import com.alibaba.fastjson.JSON;
import com.dome.common.utils.model.ResponseModel;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author YinXiaoWei
 * @date 2020/11/26 10:12
 * 登录失败
 */
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("application/json");
        if (e instanceof BadCredentialsException){
            httpServletResponse.getWriter().write(JSON.toJSONString(ResponseModel.error("用户名或密码错误")));
        }else {
            httpServletResponse.getWriter().write(JSON.toJSONString(ResponseModel.error(e.getMessage())));
        }
    }
}
View Code
package com.dome.provider.config.security.handler;

import com.alibaba.fastjson.JSON;
import com.dome.common.utils.model.ResponseModel;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author YinXiaoWei
 * @date 2020/11/30 10:39
 * 退出成功
 */
@Component
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSON.toJSONString(ResponseModel.success("登出成功!")));
        response.getWriter().flush();
    }
}
View Code
package com.dome.provider.config.security.handler;

import com.alibaba.fastjson.JSON;
import com.dome.common.utils.model.ResponseModel;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author YinXiaoWei
 * @date 2020/11/26 10:19
 * 当未登录或者token失效访问接口时,自定义的返回结果
 */
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSON.toJSONString(ResponseModel.error("尚未登录,或者登录过期   " + authException.getMessage())));
        response.getWriter().flush();
    }
}
View Code
package com.dome.provider.config.security.handler;

import com.alibaba.fastjson.JSON;
import com.dome.common.utils.model.ResponseModel;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author YinXiaoWei
 * @date 2020/11/26 10:23
 * 当访问接口没有权限时,自定义的返回结果
 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSON.toJSONString(ResponseModel.error(e.getMessage())));
        response.getWriter().flush();
    }
}
View Code
package com.dome.provider.config.security.filter;

import com.dome.common.utils.RedisUtil;
import com.dome.provider.config.security.UserDetailsServiceImpl;
import com.dome.provider.config.security.jwt.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author YinXiaoWei
 * @date 2020/11/26 16:54
 * token过滤器
 */
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private RedisUtil redisUtil;

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    private static final String TOKEN_KEY = "USER_TOKEN";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取header令牌
        String authHeader = request.getHeader(this.tokenHeader);
        if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
            //  token由"Bearer "开头
            String authToken = authHeader.substring(this.tokenHead.length());
            //解析token获取用户名
            String username = jwtUtils.getUserNameFromToken(authToken);
            log.info("checking username:{}", username);
            // 判断redis有没有token
            if (redisUtil.hasKey(TOKEN_KEY + username) && authToken.equals(redisUtil.getObject(TOKEN_KEY + username))) {
                // 存放authentication到SecurityContextHolder
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                    if (userDetails != null) {
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
                                null, userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        log.info("authenticated user:{}", username);
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
        }
        filterChain.doFilter(request, response);
    }
}
View Code

在接口上添加@PreAuthorize(value = "hasAnyAuthority('对应的权限')"),在SecurityConfig加上@EnableGlobalMethodSecurity(prePostEnabled = true)

在PostMan中测试如下:

测试其他接口是需要加上token才能访问,若是不需要token就在SecurityConfig中添加antMatchers()。token前要带上Bearer 字段(在swagger中配置了默认输入)

 

 

 

posted @ 2020-11-30 16:06  隐琳琥  阅读(315)  评论(0编辑  收藏  举报