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"?>

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    4.0.0
    pom

    
        com-dome-provider
        com-dome-model
        com-dome-common
        com-dome-biz
    

    
        org.springframework.boot
        spring-boot-starter-parent
        2.4.0
         
    

    com
    demo
    0.0.1-SNAPSHOT
    com
    Demo project for Spring Boot

    
        1.8
    


    
        
            
                
                    org.springframework.boot
                    spring-boot-maven-plugin
                   2.4.0
                    
                        com.dome.provider.App
                    
                    
                        
                            
                                build-info
                            
                        
                    
                

                
                    maven-compiler-plugin
                    
                        1.8
                        1.8
                        UTF-8
                    
                
            
        
    

    com-dome-model模块的maven

<?xml version="1.0" encoding="UTF-8"?>

         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">
    
        demo
        com
        0.0.1-SNAPSHOT
    
    4.0.0

    com-dome-model

    
        
        
            io.springfox
            springfox-swagger2
            2.9.2
        

        
        
            io.springfox
            springfox-swagger-ui
            2.9.2
        

        
        
            org.springframework.boot
            spring-boot-starter-jdbc
        

        
            mysql
            mysql-connector-java
        

        
        
            org.projectlombok
            lombok
        

        
        
            org.springframework.boot
            spring-boot-starter-validation
        

        
        
            com.baomidou
            mybatis-plus-generator
            3.3.0
        

        
        
            org.apache.velocity
            velocity-engine-core
            2.1
        

        
            com.baomidou
            mybatis-plus-boot-starter
            3.1.0
        

        
            com.baomidou
            mybatis-plus-extension
            3.1.0
        

    

    com-dome-common模块

<?xml version="1.0" encoding="UTF-8"?>

         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">
    
        demo
        com
        0.0.1-SNAPSHOT
    
    4.0.0

    com-dome-common

    

        
            com
            com-dome-model
            0.0.1-SNAPSHOT
        

        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

        
        
            io.jsonwebtoken
            jjwt
            0.9.1
        

        
        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        

        
        
            org.springframework.boot
            spring-boot-starter-aop
        

        
        
            eu.bitwalker
            UserAgentUtils
            1.21
        

        
        
            com.github.oshi
            oshi-core
            4.4.2
        

        
        
            com.alibaba
            fastjson
            1.2.71
        

        
        
            com.github.axet
            kaptcha
            0.0.9
        


        
            org.apache.poi
            poi
            3.9
        

        
            org.apache.poi
            poi-ooxml
            3.9
        
        
            org.projectlombok
            lombok
        
        
            io.swagger
            swagger-annotations
            1.5.20
            compile
        
        
            jakarta.validation
            jakarta.validation-api
        
        
            com.google.guava
            guava
            20.0
            compile
        

    

    com-dome-biz模块

<?xml version="1.0" encoding="UTF-8"?>

         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">
    
        demo
        com
        0.0.1-SNAPSHOT
    
    4.0.0

    com-dome-biz

    

        
            com
            com-dome-model
            0.0.1-SNAPSHOT
        

        
            com
            com-dome-common
            0.0.1-SNAPSHOT
        

    

    
        
            
                src/main/resources
                true
            
            
                src/main/java
                
                    **/*.xml
                
            
        
    

    com-dome-privoder模块

<?xml version="1.0" encoding="UTF-8"?>

         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">
    
        demo
        com
        0.0.1-SNAPSHOT
    
    4.0.0

    com-dome-provider

    

        
            com
            com-dome-model
            0.0.1-SNAPSHOT
        

        
            com
            com-dome-common
            0.0.1-SNAPSHOT
        

        
            com
            com-dome-biz
            0.0.1-SNAPSHOT
        

        
        
            org.springframework.boot
            spring-boot-devtools
        

        
        
            org.springframework.boot
            spring-boot-starter-security
        
        
            com.baomidou
            mybatis-plus-extension
            3.3.0
            compile
        
        
            com.baomidou
            mybatis-plus-extension
            3.3.0
            compile
        
        
            com.baomidou
            mybatis-plus-extension
            3.1.0
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    com.dome.provider.App
                    com-dome-provider
                
            

            
                org.apache.maven.plugins
                maven-surefire-plugin
                2.21.0
                
                    true
                
            
        

    


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

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

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

  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 '

  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;

 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;
    }
}

  客服端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();
    }
}

  验证码生成器

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;
    }
}
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("生成图片验证码错误!");
        }
    }
}

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

<?xml version="1.0" encoding="UTF-8"?>
"false">
    
    "LOG_HOME" value="/Logs"/>
    
    "STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
        
    
    
    "FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            
            ${LOG_HOME}/com-dome-provider.log.%d{yyyy-MM-dd}.log
            
            30
        
        class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
        
        
        class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            10MB
        
    

    
    "ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            
            
            ${LOG_HOME}/com-dome-provider-error-log.%d{yyyy-MM-dd}.log
            
            60
        
        class="ch.qos.logback.classic.filter.ThresholdFilter">
            WARN
        
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
        
    


    
    "org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
    "org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG"/>
    "org.hibernate.SQL" level="DEBUG"/>
    "org.hibernate.engine.QueryParameters" level="DEBUG"/>
    "org.hibernate.engine.query.HQLQueryPlan" level="DEBUG"/>

    
    "com.apache.ibatis" level="TRACE"/>
    "java.sql.Connection" level="DEBUG"/>
    "java.sql.Statement" level="DEBUG"/>
    "java.sql.PreparedStatement" level="DEBUG"/>

    "com.bool.order.meeting.core.mapper" level="DEBUG">

    
    "INFO">
        ref ref="STDOUT"/>
        ref ref="FILE"/>
        ref ref="ERROR_FILE" />
    

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

    添加各个层级的代码,建议自己手扣好一点。

    修改字段生成的代码:

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

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

        3.controller类上加上@Slf4j==>日志

                 @RequiredArgsConstructor(onConstructor = @__(@Autowired))==>可使用     private final RoleService roleService;(final)的方式注入

                 @Api(value = "系统管理 角色管理", tags = "系统管理 角色管理")==>swagger注解

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

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


        
            
                src/main/resources
                true
            
            
                src/main/java
                
                    **/*.xml
                
            
        
    

        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 getParameterList(){
        List 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;
    }
}

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

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

 
        
            com.github.xiaoymin
            knife4j-spring-boot-starter
            2.0.4
        

  认证授权

根据自己的要求自定义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 roleInfo;
    /**
     * 用户权限的集合
     */
    @JsonIgnore
    private List authorities;

    public List 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 authorities) {
        this.myUser = myUser;
        this.authorities = authorities;
    }
}

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

<?xml version="1.0" encoding="UTF-8"?>



    
    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

    
    
        id, type, parent, name, url, icon, permission, sort, create_time, create_name, update_time, update_name, version, del_flag
    


    
        m.id as id,  m.parent,  m.name as title,m.icon,m.url as href,  m.type, m.permission, m.sort
    

    
    

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 children;
}

创建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 grantedAuthorities = new ArrayList<>(15);
        List list = menuService.listByUserId(user.getId().toString());
        List 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 getRoleInfo(User user) {
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(UserRole::getUserId, user.getId());
        List userRoleList = userRoleService.getBaseMapper().selectList(wrapper);
        List roleList = new ArrayList<>();
        for (UserRole item : userRoleList) {
            Role role = roleService.getBaseMapper().selectById(item.getRoleId());
            roleList.add(role);
        }
        return roleList;
    }
}

完成这一步就可以登录了,在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);
    }
}

做到这一步可以尝试下登录,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));
    }
}
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())));
        }
    }
}
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();
    }
}
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();
    }
}
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();
    }
}
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);
    }
}

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

在PostMan中测试如下:

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

相关