oauth2demo实现(springboot+mybatis+redis)(代码较多,比较完整,仅供参考)

一、新建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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yuan</groupId>
    <artifactId>oauth2demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>oauth2demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
        <fastjson>1.2.54</fastjson>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 新增依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <groupId>org.springframework.boot</groupId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <groupId>org.springframework.cloud</groupId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>4.1.5</version>
        </dependency>

        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.22.0-GA</version>
        </dependency>

        <dependency>
            <artifactId>springfox-swagger2</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>swagger-annotations</artifactId>
                    <groupId>io.swagger</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>swagger-models</artifactId>
                    <groupId>io.swagger</groupId>
                </exclusion>
            </exclusions>
            <groupId>io.springfox</groupId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <artifactId>springfox-swagger-ui</artifactId>
            <groupId>io.springfox</groupId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <artifactId>swagger-annotations</artifactId>
            <groupId>io.swagger</groupId>
            <version>1.5.21</version>
        </dependency>
        <dependency>
            <artifactId>swagger-models</artifactId>
            <groupId>io.swagger</groupId>
            <version>1.5.21</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <artifactId>spring-cloud-dependencies</artifactId>
                <groupId>org.springframework.cloud</groupId>
                <scope>import</scope>
                <type>pom</type>
                <version>${spring-cloud.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>
pom.xml

二、resources下添加配置文件

spring:
  application:
    name: oauth2demo
  ##解决jvm使用的冲突,tomcat中不同的server 注册jmx必须使用不同的jvm
  jmx:
    default-domain: oauth2demo
  profiles:
    active: dev
  authorization:
    client-id: yuan
    client-secret: yuan_secret
    validity-hour: 4

## mybatis配置
mybatis:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.yuan.oauth2demo.model

## 服务监听的端口号
server:
  port: 8199
  tomcat:
    uri-encoding: utf-8
logging:
  level:
    root: info
    com.yuan.oauth2demo.mapper: debug

resource:
  server:
    configuration:
      permitted:
        - /v2/api-docs
        - /swagger-resources/**
        - /swagger-ui.html
        - /oauth/*
      authenticated:
        - /**/**

##api文档配置
swagger:
  config:
    enable: true
    path: com.yuan.oauth2demo
    host: 127.0.0.1:8199
    token-required: false
  api:
    title: oauth2demo服务Api文档
    terms-of-service-url: http://127.0.0.1:8199/

oatu2demo:
  redis:
    enable: true
application.yml
<?xml version="1.0" encoding="UTF-8"?><!-- configuration 中设置debug=true,在springboot启动前会产生辣鸡日志 -->
<configuration>

    <property name="LOG_PATH" value="oauth2demo" />

    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%red(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-6level) %green([ %-25thread ])
                %boldMagenta(%-50logger ->%5L) : %cyan(%msg) %n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- 全部级别的日志-->
    <!-- 按照每天生成日志文件 -->
    <appender name="ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">>
            <!-- 格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <!--<pattern>%red(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) %green([ %thread ]) %boldMagenta(%-50logger ->%5L) : %cyan(%msg) %n</pattern>-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-10level [ %thread ] %-50logger ->%5L : %msg %n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志输出文件名定义 -->
            <fileNamePattern>./logBack/${LOG_PATH}/all/%d{yyyy-MM-dd}_all.log</fileNamePattern>
            <!-- 文件保留天数 -->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <!-- 文件最大大小 -->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!--error 级别的日志-->
    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <!--<pattern>%red(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) %green([ %thread ]) %boldMagenta(%-50logger ->%5L) : %cyan(%msg) %n</pattern>-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [ %thread ] %-50logger ->%5L : %msg %n
            </pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志输出文件名定义 -->
            <fileNamePattern>./logBack/${LOG_PATH}/error/%d{yyyy-MM-dd}_error.log</fileNamePattern>
            <!-- 文件保留天数 -->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <!-- 文件最大大小 -->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- 输出原始sql -->
    <logger name="java.sql" level="DEBUG" />
    <logger name="org.apache.ibatis" level="DEBUG" />
    <logger name="org.springframework" level="warn" />
    <logger name="ch.qos.logback" level="DEBUG" />
    <!-- 关闭netflix 远程心跳日志 --><logger name="com.netflix.discovery.shared.resolver.aws.ConfigClusterResolver" level="OFF" />

    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="ALL" />
        <appender-ref ref="ERROR" />
    </root>

</configuration>
logback-spring.xml
spring:
  # redis configuration
  redis:
    host: xxx
    lettuce:
      pool:
        max-active: 100
        max-idle: 30
        max-wait: -1ms
        min-idle: 5
      shutdown-timeout: 200
    password: Vdaifu
    port: 6379
    timeout: 6000
    database: 1
  ##数据源配置  ${server.ip}  useUnicode=true&characterEncoding=UTF-8&useTimezone=true&serverTimezone=GMT%2B8
  datasource:
    url: jdbc:mysql://xxx:3306/xxx?useSSL=false&useUnicode=true&characterEncoding=utf-8&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
    username: xxx
    password: xxx
    driver-class-name: com.mysql.cj.jdbc.Driver
application-dev.yml

三、common及utils

package com.yuan.oauth2demo.common.encrypt;

public enum EncryptionPrefix {
    /**
     * 枚举常见的加密算法的前缀
     */
    BCRYPT("bcrypt", "{bcrypt}"),
    MD5("MD5", "{MD5}"),
    NOOP("noop", "{noop}"),
    SHA_256("SHA-256", "{SHA-256}");

    public final String name;

    public final String prefix;

    EncryptionPrefix(String name, String prefix) {
        this.name = name;
        this.prefix = prefix;
    }
}
EncryptionPrefix.java
package com.yuan.oauth2demo.common.exception;

@SuppressWarnings("serial")
public class AesException extends Exception {

    public final static int OK = 0;
    public final static int VALIDATE_SIGNATURE_ERROR = -40001;
    public final static int PARSE_XML_ERROR = -40002;
    public final static int COMPUTE_SIGNATURE_ERROR = -40003;
    public final static int ILLEGAL_AES_KEY = -40004;
    public final static int VALIDATE_APPID_ERROR = -40005;
    public final static int ENCRYPT_AES_ERROR = -40006;
    public final static int DECRYPT_AES_ERROR = -40007;
    public final static int ILLEGAL_BUFFER = -40008;

    private int code;

    public AesException(int code) {
        super(getMessage(code));
        this.code = code;
    }

    private static String getMessage(int code) {
        switch (code) {
            case VALIDATE_SIGNATURE_ERROR:
                return "签名验证错误";
            case PARSE_XML_ERROR:
                return "xml解析失败";
            case COMPUTE_SIGNATURE_ERROR:
                return "sha加密生成签名失败";
            case ILLEGAL_AES_KEY:
                return "SymmetricKey非法";
            case VALIDATE_APPID_ERROR:
                return "appid校验失败";
            case ENCRYPT_AES_ERROR:
                return "aes加密失败";
            case DECRYPT_AES_ERROR:
                return "aes解密失败";
            case ILLEGAL_BUFFER:
                return "解密后得到的buffer非法";
            default:
                // cannot be
                return null;
        }
    }

    public int getCode() {
        return code;
    }

}
AesException.java
package com.yuan.oauth2demo.common.exception;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ProtocolException;
import java.nio.file.AccessDeniedException;
import java.sql.SQLException;

public enum ExceptionCodes {
    //RuntimeException 类型异常
    RUNTIME_EXCEPTION("运行时异常", RuntimeException.class),
    NULL_POINT_EXCEPTION("空指针异常", NullPointerException.class),
    ARITHMETIC_EXCEPTION("数学错误,被0除", ArithmeticException.class),
    INDEX_OUT_OF_BOUNDS_EXCEPTION("当某对象的索引超出范围时抛出异常", IndexOutOfBoundsException.class),
    ARRAY_INDEX_OUT_OF_EXCEPTION("数组下标越界", ArrayIndexOutOfBoundsException.class),
    CLASS_CAST_EXCEPTION("强制转换异常", ClassCastException.class),
    ILLEGAL_ARGUMENT_EXCEPTION("非法转换", IllegalArgumentException.class),
    NUMBER_FORMAT_EXCEPTION("字符串转换为数字异常类", NumberFormatException.class),
    PROTOCOL_EXCEPTION("网络协议有错误", ProtocolException.class),

    //IOException 类型异常
    IO_EXCEPTION("IO 流异常", IOException.class),
    FILE_NOT_FOUND_EXCEPTION("文件找不到", FileNotFoundException.class),

    //数据库 sql 操作异常
    SQL_EXCEPTION("操作数据库异常", SQLException.class),

    //其他相关异常
    REFLECTIVE_OPERATION_EXCEPTION("", ReflectiveOperationException.class),
    ACESS_REFUSED_EXCEPTION("没有权限访问!", AccessDeniedException.class),
    ILLEGAL_ACESS_EXCEPTION("访问某类被拒绝时抛出的异常", IllegalAccessException.class),


    // 定制化异常  如微信三方业务异常
    //WE_CHAT_EXCEPTION(40001, "微信三方异常", WeChatE)
    ;

    private Integer code;

    private String msg;

    private Class<? extends Exception> klass;

    ExceptionCodes(String msg, Class<? extends Exception> klass) {
        this.msg = msg;
        this.klass = klass;
    }

    ExceptionCodes(Integer code, String msg, Class<? extends Exception> klass) {
        this.code = code;
        this.msg = msg;
        this.klass = klass;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return this.msg;
    }

    public Class<? extends Exception> getKlass() {
        return this.klass;
    }

    /**
     * 根据异常类获取异常信息
     **/
    public static Integer getCodeByKlass(Class<? extends Exception> klass) {
        for (ExceptionCodes exceptionCodes : ExceptionCodes.values()) {
            if (exceptionCodes.getKlass() == klass) {
                return exceptionCodes.getCode();
            }
        }
        return null;
    }

    /**
     * 根据异常类获取异常信息
     **/
    public static String getMsgByKlass(Class<? extends Exception> klass) {
        for (ExceptionCodes exceptionCodes : ExceptionCodes.values()) {
            if (exceptionCodes.getKlass() == klass) {
                return exceptionCodes.getMsg();
            }
        }
        return null;
    }

}
ExceptionCodes.java
package com.yuan.oauth2demo.common.exception;

import java.io.Serializable;


public class ServiceException extends RuntimeException implements Serializable {

    private static final long SERIALIZABLE_UID = 1L;

    private int code;

    private String msg;

    public ServiceException() {
        super();
    }

    public ServiceException(String msg) {
        this.msg = msg;
    }

    public ServiceException(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ServiceException(String message, int code, String msg) {
        super(message);
        this.code = code;
        this.msg = msg;
    }

    public static long getSerializableUid() {
        return SERIALIZABLE_UID;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    //------------------------------------------

    /**
     * 业务枚举
     **/
    public enum Problems {
        /**
         * 服务异常
         */
        EXCEPTION(3000, "内部服务异常"),
        /**
         * 小程序登录授权失败异常
         */
        SMALL_LOGIN_AUTHORIZE_FAIL_EXCEPTION(400003, "登录授权失败"),

        /**
         * 小程序授权过期
         */
        AUTHORIZE_EXPIRE_EXCEPTION_CODE(400002, "授权过期"),
        /**
         * 用户没有绑定手机号码
         */
        USER_NOT_BIND_MOBILE_EXCEPTION_CODE(400001, "微信用户未绑定手机号码"),
        /**
         * 请求太频繁
         */
        FREQUENT_REQUESTS_EXCEPTION(400006, "请求太频繁"),

        /**
         * 提现失败
         */
        CASH_WITHDRAWAL_EXCEPTION(400007, "提现失败");

        int code;
        String msg;

        Problems(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }

        public int getCode() {
            return this.code;
        }

        public String getMsg() {
            return this.msg;
        }
    }
}
ServiceExceptionl.java
package com.yuan.oauth2demo.common.exception;

import org.springframework.security.access.AccessDeniedException;

import javax.websocket.DecodeException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ProtocolException;
import java.sql.SQLException;


public enum SystemExceptionCodes {
    //RuntimeException 类型异常
    RUNTIME_EXCEPTION("运行时异常", RuntimeException.class),
    NULL_POINT_EXCEPTION("空指针异常", NullPointerException.class),
    ARITHMETIC_EXCEPTION("数学错误,被0除", ArithmeticException.class),
    INDEX_OUT_OF_BOUNDS_EXCEPTION("当某对象的索引超出范围时抛出异常", IndexOutOfBoundsException.class),
    ARRAY_INDEX_OUT_OF_EXCEPTION("数组下标越界", ArrayIndexOutOfBoundsException.class),
    CLASS_CAST_EXCEPTION("强制转换异常", ClassCastException.class),
    ILLEGAL_ARGUMENT_EXCEPTION("非法转换", IllegalArgumentException.class),
    NUMBER_FORMAT_EXCEPTION("字符串转换为数字异常类", NumberFormatException.class),
    PROTOCOL_EXCEPTION("网络协议有错误", ProtocolException.class),

    //IOException 类型异常
    IO_EXCEPTION("IO 流异常", IOException.class),
    FILE_NOT_FOUND_EXCEPTION("文件找不到", FileNotFoundException.class),

    //数据库 sql 操作异常
    SQL_EXCEPTION("操作数据库异常", SQLException.class),

    //其他相关异常
    REFLECTIVE_OPERATION_EXCEPTION("", ReflectiveOperationException.class),
    ACESS_REFUSED_EXCEPTION("没有权限访问!", AccessDeniedException.class),
    DECODE_EXCEPTION("feign 客户端之间的请求响应类型转换异常", DecodeException.class),
    ILLEGAL_ACESS_EXCEPTION("访问某类被拒绝时抛出的异常", IllegalAccessException.class),


    // 定制化异常  如微信三方业务异常
    //WE_CHAT_EXCEPTION(40001, "微信三方异常", WeChatE)
    ;

    private Integer code;

    private String msg;

    private Class<? extends Exception> klass;

    SystemExceptionCodes(String msg, Class<? extends Exception> klass) {
        this.msg = msg;
        this.klass = klass;
    }

    SystemExceptionCodes(Integer code, String msg, Class<? extends Exception> klass) {
        this.code = code;
        this.msg = msg;
        this.klass = klass;
    }

    /**
     * 根据异常类获取异常信息
     **/
    public static Integer getCodeByKlass(Class<? extends Exception> klass) {
        for (SystemExceptionCodes systemExceptionCodes : SystemExceptionCodes.values()) {
            if (systemExceptionCodes.getKlass() == klass) {
                return systemExceptionCodes.getCode();
            }
        }
        return null;
    }

    /**
     * 根据异常类获取异常信息
     **/
    public static String getMsgByKlass(Class<? extends Exception> klass) {
        for (SystemExceptionCodes systemExceptionCodes : SystemExceptionCodes.values()) {
            if (systemExceptionCodes.getKlass() == klass) {
                return systemExceptionCodes.getMsg();
            }
        }
        return null;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return this.msg;
    }

    public Class<? extends Exception> getKlass() {
        return this.klass;
    }

}
SystemExceptionCodes.java
package com.yuan.oauth2demo.common.http;


public enum HttpStatus {

    /**
     * http 状态码枚举
     */
    CONTINUE(100, "Continue", "请继续发送请求的剩余部分"),
    SWITCHING_PROTOCOLS(101, "Switching Protocols", "协议切换"),
    PROCESSING(102, "Processing", "请求将继续执行"),
    // for 103 https://news.ycombinator.com/item?id=15590049
    CHECKPOINT(103, "Checkpoint", "可以预加载"),
    OK(200, "OK", "请求已经成功处理"),
    CREATED(201, "Created", "请求已经成功处理,并创建了资源"),
    ACCEPTED(202, "Accepted", "请求已经接受,等待执行"),
    NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information", "请求已经成功处理,但是信息不是原始的"),
    NO_CONTENT(204, "No Content", "请求已经成功处理,没有内容需要返回"),
    RESET_CONTENT(205, "Reset Content", "请求已经成功处理,请重置视图"),
    PARTIAL_CONTENT(206, "Partial Content", "部分Get请求已经成功处理"),
    MULTI_STATUS(207, "Multi-Status", "请求已经成功处理,将返回XML消息体"),
    ALREADY_REPORTED(208, "Already Reported", "请求已经成功处理,一个DAV的绑定成员被前一个请求枚举,并且没有被再一次包括"),
    IM_USED(226, "IM Used", "请求已经成功处理,将响应一个或者多个实例"),
    MULTIPLE_CHOICES(300, "Multiple Choices", "提供可供选择的回馈"),
    MOVED_PERMANENTLY(301, "Moved Permanently", "请求的资源已经永久转移"),
    FOUND(302, "Found", "请重新发送请求"),
    // MOVED_TEMPORARILY(302, "Moved Temporarily", "") 已经过时
    SEE_OTHER(303, "See Other", "请以Get方式请求另一个URI"),
    NOT_MODIFIED(304, "Not Modified", "资源未改变"),
    USE_PROXY(305, "Use Proxy", "请通过Location域中的代理进行访问"),
    // 306在新版本的规范中被弃用
    TEMPORARY_REDIRECT(307, "Temporary Redirect", "请求的资源临时从不同的URI响应请求"),
    RESUME_INCOMPLETE(308, "Resume Incomplete", "请求的资源已经永久转移"),
    BAD_REQUEST(400, "Bad Request", "请求错误,请修正请求"),
    UNAUTHORIZED(401, "Unauthorized", "没有被授权或者授权已经失效"),
    PAYMENT_REQUIRED(402, "Payment Required", "预留状态"),
    FORBIDDEN(403, "Forbidden", "请求被理解,但是拒绝执行"),
    NOT_FOUND(404, "Not Found", "资源未找到"),
    METHOD_NOT_ALLOWED(405, "Method Not Allowed", "请求方法不允许被执行"),
    NOT_ACCEPTABLE(406, "Not Acceptable", "请求的资源不满足请求者要求"),
    PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required", "请通过代理进行身份验证"),
    REQUEST_TIMEOUT(408, "Request Timeout", "请求超时"),
    CONFLICT(409, "Conflict", "请求冲突"),
    GONE(410, "Gone", "请求的资源不可用"),
    LENGTH_REQUIRED(411, "Length Required", "Content-Length未定义"),
    PRECONDITION_FAILED(412, "Precondition Failed", "不满足请求的先决条件"),
    REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large", "请求发送的实体太大"),
    REQUEST_URI_TOO_LONG(414, "Request-URI Too Long", "请求的URI超长"),
    UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type", "请求发送的实体类型不受支持"),
    REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable", "Range" +
            "指定的范围与当前资源可用范围不一致"),
    EXPECTATION_FAILED(417, "Expectation Failed", "请求头Expect中指定的预期内容无法被服务器满足"),
    // I_AM_A_TEAPOT(418, "I'm a teapot", ""), 该代码没有被服务器实现

    // INSUFFICIENT_SPACE_ON_RESOURCE(419, "Insufficient Space On Resource", ""),  已经过时

    // METHOD_FAILURE(420, "Method Failure", ""),  已经过时

    // DESTINATION_LOCKED(421, "Destination Locked", ""),  已经过时
    UNPROCESSABLE_ENTITY(422, "Unprocessable Entity", "请求格式正确,但是由于含有语义错误,无法响应"),
    LOCKED(423, "Locked", "当前资源被锁定"),
    FAILED_DEPENDENCY(424, "Failed Dependency", "由于之前的请求发生错误,导致当前请求失败"),
    UPGRADE_REQUIRED(426, "Upgrade Required", "客户端需要切换到TLS1.0"),
    PRECONDITION_REQUIRED(428, "Precondition Required", "请求需要提供前置条件"),
    TOO_MANY_REQUESTS(429, "Too Many Requests", "请求过多"),
    REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large", "请求头超大,拒绝请求"),
    INTERNAL_SERVER_ERROR(500, "Internal Server Error", "服务器内部错误"),
    NOT_IMPLEMENTED(501, "Not Implemented", "服务器不支持当前请求的部分功能"),
    BAD_GATEWAY(502, "Bad Gateway", "响应无效"),
    SERVICE_UNAVAILABLE(503, "Service Unavailable", "服务器维护或者过载,拒绝服务"),
    GATEWAY_TIMEOUT(504, "Gateway Timeout", "上游服务器超时"),
    HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported", "不支持的HTTP版本"),
    VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates", "服务器内部配置错误"),
    INSUFFICIENT_STORAGE(507, "Insufficient Storage", "服务器无法完成存储请求所需的内容"),
    LOOP_DETECTED(508, "Loop Detected", "服务器处理请求时发现死循环"),
    BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded", "服务器达到带宽限制"),
    NOT_EXTENDED(510, "Not Extended", "获取资源所需的策略没有被满足"),
    NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required", "需要进行网络授权");

    /**
     * 枚举服务器接口响应的多种状态
     */
    private static final int
            INFORMATIONAL = 1,
            SUCCESSFUL = 2,
            REDIRECTION = 3,
            CLIENT_ERROR = 4,
            SERVER_ERROR = 5;
    /**
     * 状态码
     */
    private final int code;

    /**
     * 美式解释短语
     */
    private final String reasonPhraseUs;

    /**
     * 中文解释短语
     */
    private final String reasonPhraseCn;

    HttpStatus(int code, String reasonPhraseUs, String reasonPhraseCn) {
        this.code = code;
        this.reasonPhraseUs = reasonPhraseUs;
        this.reasonPhraseCn = reasonPhraseCn;
    }

    public static HttpStatus valueOf(int code) {
        for (HttpStatus httpStatus : values()) {
            if (httpStatus.code() == code) {
                return httpStatus;
            }
        }
        throw new IllegalArgumentException("No matching constant for [" + code + "]");
    }

    public int code() {
        return code;
    }

    public String reasonPhraseUs() {
        return reasonPhraseUs;
    }

    public String reasonPhraseCn() {
        return reasonPhraseCn;
    }

    public boolean isInformational() {
        return type() == INFORMATIONAL;
    }

    public boolean isSuccessful() {
        return type() == SUCCESSFUL;
    }

    public boolean isRedirection() {
        return type() == REDIRECTION;
    }

    public boolean isClientError() {
        return type() == CLIENT_ERROR;
    }

    public boolean isServerError() {
        return type() == SERVER_ERROR;
    }

    private int type() {
        return code / 100;
    }
}
HttpStatus.java
package com.yuan.oauth2demo.common.http;

import org.springframework.http.HttpStatus;

public class ResponseResult<T> {

    private int code;

    private T data;

    private String msg;

    public ResponseResult() {

    }

    public ResponseResult(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResponseResult(int code, T data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    public static <T> ResponseResult<T> build(T data) {
        ResponseResult<T> result = new ResponseResult<T>();
        result.setData(data);
        return result;
    }

    public ResponseResult<T> success() {
        this.code = HttpStatus.OK.value();
        this.msg = HttpStatus.OK.getReasonPhrase();
        return this;
    }

    public ResponseResult<T> fail() {
        this.code = 3000;
        this.msg = "操作失败!";
        return this;
    }

    public ResponseResult<T> empty() {
        this.code = HttpStatus.OK.value();
        this.data = null;
        this.msg = "查询到的结果为空!";
        return this;
    }

    public ResponseResult<T> success(T data) {
        this.code = HttpStatus.OK.value();
        this.data = data;
        this.msg = HttpStatus.OK.getReasonPhrase();
        return this;
    }

    public ResponseResult<T> fail(String msg) {
        this.code = 3000;
        this.msg = msg;
        return this;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

}
ResponseResult.java

package com.yuan.oauth2demo.utils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;

public class DateUtils {

    /**
     * 日期格式,年份,例如:2004,2008
     */
    public static final String DATE_FORMAT_YYYY = "yyyy";

    // ==格式到年==
    /**
     * 日期格式,年份和月份,例如:200707,200808
     */
    public static final String DATE_FORMAT_YYYYMM = "yyyyMM";


    // ==格式到年月 ==
    /**
     * 日期格式,年份和月份,例如:200707,2008-08
     */
    public static final String DATE_FORMAT_YYYY_MM = "yyyy-MM";
    /**
     * 日期格式,年月日,例如:050630,080808
     */
    public static final String DATE_FORMAT_YYMMDD = "yyMMdd";


    // ==格式到年月日==
    /**
     * 日期格式,年月日,用横杠分开,例如:06-12-25,08-08-08
     */
    public static final String DATE_FORMAT_YY_MM_DD = "yy-MM-dd";
    /**
     * 日期格式,年月日,例如:20050630,20080808
     */
    public static final String DATE_FORMAT_YYYYMMDD = "yyyyMMdd";
    /**
     * 日期格式,年月日,用横杠分开,例如:2006-12-25,2008-08-08
     */
    public static final String DATE_FORMAT_YYYY_MM_DD = "yyyy-MM-dd";
    /**
     * 日期格式,年月日,例如:2016.10.05
     */
    public static final String POINT_YYYY_MMU_DD = "yyyy.MM.dd";
    /**
     * 日期格式,月日,例如:10.05
     */
    public static final String POINT_MMU_DD = "MM.dd";

    /**
     * 日期格式,月 例如:10
     */
    public static final String POINT_MMU = "MM";

    /**
     * 日期格式,年月日,例如:2016年10月05日
     */
    public static final String CHINA_YYYY_MMU_DD = "yyyy年MM月dd日";
    /**
     * 日期格式,年月日,例如:2016年10月05日 12:00
     */
    public static final String CHINA_YYYYMMDD_HHU_MM = "yyyy年MM月dd日 HH:mm";
    /**
     * 日期格式,年月日,例如:2016100512:00:99
     */
    public static final String YYYY_MMU_DD_HHU_MM_SS1 = "yyyyMMddHH:mm:ss";
    /**
     * 日期格式,年月日时分,例如:200506301210,200808081210
     */
    public static final String YYYY_MMU_DD_HHU_MM = "yyyyMMddHHmm";

    // ==格式到年月日 时分 ==
    /**
     * 日期格式,年月日时分,例如:20001230 12:00,20080808 20:08
     */
    public static final String YYYY_MMU_DD__HHU_MM1 = "yyyyMMdd HH:mm";
    /**
     * 日期格式,年月日时分,例如:2000-12-30 12:00,2008-08-08 20:08
     */
    public static final String YYYY_MMU_DD__HHU_MM2 = "yyyy-MM-dd HH:mm";
    /**
     * 日期格式,年月日时分秒,例如:20001230120000,20080808200808
     */
    public static final String YYYY_MMU_DD_HHU_MM_SS2 = "yyyyMMddHHmmss";


    // ==格式到年月日 时分秒==
    /**
     * 日期格式,年月日时分秒,年月日用横杠分开,时分秒用冒号分开
     * 例如:2005-05-10 23:20:00,2008-08-08 20:08:08
     */
    public static final String YYYY_MMU_DD__HHU_MM_SS = "yyyy-MM-dd HH:mm:ss";
    /**
     * 日期格式,年月日时分秒毫秒,例如:20001230120000123,20080808200808456
     */
    public static final String YYYY_MMU_DD_HHU_MM_SS_SSSU = "yyyyMMddHHmmssSSS";


    // ==格式到年月日 时分秒 毫秒==
    /**
     * 日期格式,月日时分,例如:10-05 12:00
     */
    public static final String MMU_DD__HHU_MM = "MM-dd HH:mm";


    // ==特殊格式==
    /**
     * 日期格式,天/月 ,比如 :四月十二号 : 12/4
     */
    public static final String D_M = "d/M";
    /**
     * 时间格式 小时:分钟 ,比如 十三点十五分 :13:15
     */
    public static final String HHU_MM = "HH:mm";
    public final static DateTimeFormatter FORMAT =
            DateTimeFormatter.ofPattern(DateUtils.CHINA_YYYYMMDD_HHU_MM);
    public final static DateTimeFormatter FORMAT_112 =
            DateTimeFormatter.ofPattern(DateUtils.YYYY_MMU_DD__HHU_MM_SS);
    public final static DateTimeFormatter FORMAT_113 =
            DateTimeFormatter.ofPattern(DateUtils.YYYY_MMU_DD_HHU_MM_SS1);
    public final static DateTimeFormatter FORMAT_114 =
            DateTimeFormatter.ofPattern(DateUtils.DATE_FORMAT_YYYY_MM_DD);
    private static final Logger logger = LoggerFactory.getLogger(DateUtils.class);


    /* ************工具方法***************   */

    /**
     * 格式化Date时间
     *
     * @param time         Date类型时间
     * @param pattern      String类型格式
     * @param defaultValue 默认值为当前时间Date
     * @return 格式化后的字符串
     */
    public static String format(ZonedDateTime time, String pattern,
                                final ZonedDateTime defaultValue) {
        try {
            return format(time, pattern);
        } catch (Exception e) {
            if (defaultValue != null) {
                return format(defaultValue, pattern);
            } else {
                return format(ZonedDateTime.now(), pattern);
            }
        }
    }

    /**
     * 格式化Date时间
     *
     * @param time         Date类型时间
     * @param timeFormat   String类型格式
     * @param defaultValue 默认时间值String类型
     * @return 格式化后的字符串
     */
    public static String format(ZonedDateTime time, String timeFormat, final String defaultValue) {
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(timeFormat);
            return formatter.format(time);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * 格式化时间戳{@see Timestamp} 如果格式化抛出异常将会返回空串
     *
     * @param timestamp  时间戳
     * @param timeFormat 格式化模板
     * @return 返回一个格式化后的字符串
     * @see Timestamp
     */
    public static String format(Timestamp timestamp, String timeFormat) {
        return format(timestamp, timeFormat, StringUtils.EMPTY);
    }

    public static String format(Timestamp timestamp, String timeFormat, String defaultValue) {
        if (timestamp == null
                || StringUtils.isEmpty(timeFormat)) {
            return StringUtils.EMPTY;
        }
        ZoneId zone = ZoneId.systemDefault();
        ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(timestamp.toInstant(),
                zone);
        return format(zonedDateTime, timeFormat, defaultValue);
    }

    /**
     * 格式化String时间
     *
     * @param time          String类型时间
     * @param formatPattern String类型格式
     * @return 格式化后的Date日期
     */
    public static ZonedDateTime parse(String time, String formatPattern) {
        if (StringUtils.isEmpty(time)) {
            return null;
        }

        DateTimeFormatter deteFormatter = DateTimeFormatter.ofPattern(formatPattern);
        //需要确认时区 因为使用的 ZonedDateTime 时区信息从ZoneId中拿到
        return LocalDate.parse(time, deteFormatter).atStartOfDay(ZoneId.systemDefault());
    }

    /**
     * 格式化String时间
     *
     * @param strTime       String类型时间
     * @param formatPattern String类型格式
     * @param defaultValue  异常时返回的默认值
     * @return 返回格式化时间
     */
    public static ZonedDateTime parse(String strTime, String formatPattern,
                                      ZonedDateTime defaultValue) {
        try {
            return parse(strTime, formatPattern);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * 格式化日期对象 通过 formatPattern
     *
     * @param date          日期对象
     * @param formatPattern 格式化模板
     * @return 返回格式化结果 如果格式化抛出异常将会返回空串
     * @see StringUtils
     * {@link #format(ZonedDateTime, String, String)}
     */
    public static String format(ZonedDateTime date, String formatPattern) {
        return format(date, formatPattern, StringUtils.EMPTY);
    }

    /**
     * 格式化日期对象 通过 formatPattern
     *
     * @param date       日期对象
     * @param dateFormat 格式化日期的工具类
     * @return 返回格式化结果
     */
    public static String format(ZonedDateTime date, DateTimeFormatter dateFormat) {
        return dateFormat.format(date);
    }

    public static String format(String target, String fromPattern, String toPattern) {
        if (StringUtils.isEmpty(target)
                || StringUtils.isEmpty(fromPattern)
                || StringUtils.isEmpty(toPattern)) {
            logger.error("method signature: format(String target, String fromPattern, String " +
                    "toPattern) :format 字符串=" + target + "失败!!返回原值 cause:入参有空值 目标字符串 = " + target + " fromPattern = " + fromPattern + " toPattern = " + toPattern);
            return target;
        }
        ZonedDateTime dateTime = parse(target, fromPattern);
        return format(dateTime, toPattern);
    }

    public static String formatDate(Date date,String formatPattern){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(formatPattern);
        return simpleDateFormat.format(date);
    }

    public static Calendar getCalendar(Date date) {
        if (date == null) {
            return null;
        }
        DateFormat df = DateFormat.getDateInstance();
        df.format(date);
        return df.getCalendar();
    }

    /**
     * 获取时间的小时
     *
     * @param date
     * @return
     */
    public static Integer getHour(Date date) {
        if (date == null) {
            return null;
        }
        return getCalendar(date).get(Calendar.HOUR_OF_DAY);
    }

    /**
     * @Description: date转为string
     * @Author: Paul
     * @Date: 2019/12/4/004 16:32
     */
    public static String parseToString(Date date, String style) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
        simpleDateFormat.applyPattern(style);
        String str;
        if (date == null) {
            return null;
        } else {
            str = simpleDateFormat.format(date);
            return str;
        }
    }

    public static Date parseToDate(String s, String style) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
        simpleDateFormat.applyPattern(style);
        Date date;
        if (s == null || s.length() < 8) {
            return null;
        }
        try {
            date = simpleDateFormat.parse(s);
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
        return date;
    }

    /**
     * 判断输入的日期是否在下周区间
     *
     * @return false:不是下周;true:下周
     * @author nemowang
     */
    public static boolean isNextWeek(Date date) {
        // 设置时间格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Calendar calendar1 = Calendar.getInstance();
        Calendar calendar2 = Calendar.getInstance();
        int dayOfWeek = calendar1.get(Calendar.DAY_OF_WEEK) - 1;
        int offset1 = 1 - dayOfWeek;
        int offset2 = 7 - dayOfWeek;
        calendar1.add(Calendar.DATE, offset1 + 7);
        calendar2.add(Calendar.DATE, offset2 + 7);
        // 下周星期一
        String monday = sdf.format(calendar1.getTime());
        //System.out.println(monday);
        // 下周星期日
        String sunday = sdf.format(calendar2.getTime());
        //System.out.println(sunday);

        String format = sdf.format(date);
        try {
            Date parse = sdf.parse(format);
            Date mon = sdf.parse(monday);
            Date sun = sdf.parse(sunday);
            return parse.compareTo(mon) >= 0 && sun.compareTo(parse) >= 0;
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 计算年龄,根据生日日期
     *
     * @param birDate 生日
     * @return 年龄
     */
    public static String calculateAge(Date birDate) {
        LocalDate today = LocalDate.now();
        Instant instant = birDate.toInstant();
        ZoneId zoneId = ZoneId.systemDefault();
        // atZone()方法返回在指定时区从此Instant生成的ZonedDateTime。
        LocalDate localDate = instant.atZone(zoneId).toLocalDate();
        return String.valueOf(ChronoUnit.YEARS.between(localDate, today));
    }

    /**
     * 计算年龄,根据生日日期
     *
     * @param birDateStr 生日日期字符串
     * @param pattern    use in format {@code birDateStr}
     * @return 年龄
     */
    public static String calculateAge(String birDateStr, String pattern) {
        LocalDate today = LocalDate.now();
        ZonedDateTime zonedDateTime = parse(birDateStr, pattern);
        return String.valueOf(ChronoUnit.YEARS.between(Objects.requireNonNull(zonedDateTime).toLocalDate(), today));
    }

    /**
     * 获取星期几 通过日期
     *
     * @param date   日期
     * @param prefix 前缀:周,星期
     * @return 返回星期几 example :星期一
     */
    public static String getWeekXQ(Date date, String prefix) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int day = calendar.get(Calendar.DAY_OF_WEEK);
        return getWeekXQ(day, prefix);
    }

    private static String getWeekXQ(int dayOfWeek, String prefix) {
        String week = prefix;
        switch (dayOfWeek) {
            case 2:
                week += "一";
                break;
            case 3:
                week += "二";
                break;
            case 4:
                week += "三";
                break;
            case 5:
                week += "四";
                break;
            case 6:
                week += "五";
                break;
            case 7:
                week += "六";
                break;
            case 1:
                week += "日";
                break;
            default:
                break;
        }
        return week;
    }

    /**
     * 获取星期几 通过日期
     *
     * @param dateStr 日期字符串
     * @param prefix  前缀:周,星期
     * @return 返回星期几 example :星期一
     */
    public static String getWeekXQ(String dateStr, String pattern, String prefix) {
        Date dateTime;
        dateTime = parseToDate(dateStr, pattern);
        return dateTime == null ? StringUtils.EMPTY : getWeekXQ(dateTime, prefix);

    }

    /**
     * 比较 {@code target} 跟{@code subject}日期字符串的大小
     *
     * @param target     目标日期字符
     * @param subject    比较主体日期字符串
     * @param subPattern {@code target}日期模板
     * @param tarPattern {@code subject}日期模板
     * @return 如果 {@code target}在{{@code subject}之前则返回true
     */
    public static Boolean isBefore(String target, String tarPattern, String subject,
                                   String subPattern) {
        ZonedDateTime dateTime = parse(target, tarPattern);
        ZonedDateTime subDateTime = parse(subject, subPattern);
        return Objects.requireNonNull(dateTime).isBefore(Objects.requireNonNull(subDateTime));
    }

    /**
     * 比较 {@code target} 跟{@code subject}日期字符串的大小
     *
     * @param target  目标日期字符
     * @param subject 比较主体日期字符串
     * @param pattern 日期模板
     * @return 如果 {@code target}在{{@code subject}之前则返回true
     */
    public static Boolean isBefore(String target, String subject, String pattern) {
        ZonedDateTime dateTime = parse(target, pattern);
        ZonedDateTime subDateTime = parse(subject, pattern);
        return Objects.requireNonNull(dateTime).isBefore(Objects.requireNonNull(subDateTime));
    }
}
DateUtils.java
package com.yuan.oauth2demo.utils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;

import java.util.HashMap;
import java.util.Map;


public class RedisHelper {

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

    private RedisConnectionFactory redisConnectionFactory;

    public RedisHelper(RedisConnectionFactory redisConnectionFactory) {
        this.redisConnectionFactory = redisConnectionFactory;
    }

    /**
     * 获取redis connection 实例连接
     *
     * @return RedisConnection
     */
    private RedisConnection getConnection() {
        return this.redisConnectionFactory.getConnection();
    }

    /**
     * 关闭jedis 实例连接
     *
     * @param connect RedisConnection
     */
    public void closeConnection(RedisConnection connect) {
        if (null != connect) {
            connect.close();
        }
    }

    //--------------------------------------------------基于Hash 数据的操作
    public String hget(String key, String field) {
        RedisConnection conn = null;
        try {
            conn = this.getConnection();
            byte[] bytes = conn.hGet(key.getBytes(), field.getBytes());
            if (bytes != null) {
                return new String(bytes);
            }
        } finally {
            this.closeConnection(conn);
        }
        return StringUtils.EMPTY;
    }

    public Boolean hset(String key, String field, String value) {
        RedisConnection conn = null;
        try {
            conn = this.getConnection();
            return conn.hSet(key.getBytes(), field.getBytes(), value.getBytes());
        } finally {
            this.closeConnection(conn);
        }
    }

    public void hdel(String key, String field) {
        RedisConnection conn = null;
        try {
            conn = this.getConnection();
            conn.hDel(key.getBytes(), field.getBytes());
        } finally {
            this.closeConnection(conn);
        }
    }

    public void hmset(String key, Map<String, String> params) {
        RedisConnection conn = null;
        HashMap<byte[], byte[]> map = new HashMap<>();
        params.forEach((k, v) -> {
            map.put(k.getBytes(), v.getBytes());
        });
        try {
            conn = this.getConnection();
            conn.hMSet(key.getBytes(), map);
        } finally {
            this.closeConnection(conn);
        }
    }


    //--------------------------------------------------基于String 数据的操作
    public String get(String key) {
        RedisConnection conn = null;
        try {
            conn = this.getConnection();
            byte[] bytes = conn.get(key.getBytes());
            if (bytes != null) {
                return new String(bytes);
            }
        } finally {
            this.closeConnection(conn);
        }
        return StringUtils.EMPTY;
    }

    public void set(String key, String value) {
        RedisConnection conn = null;
        try {
            conn = this.getConnection();
            conn.set(key.getBytes(), value.getBytes());
        } finally {
            this.closeConnection(conn);
        }
    }

    // key 过期方面的设置
    public void expire(String key, int second) {
        RedisConnection conn = null;
        try {
            conn = this.getConnection();
            conn.expire(key.getBytes(), second);
        } finally {
            this.closeConnection(conn);
        }
    }


}
RedisHelper.java

四、user表及相关服务

1.建表语句及插入数据语句

create table `user` (
    `user_id` varchar (192),
    `user_name` varchar (135),
    `user_pwd` varchar (135),
    `create_time` datetime 
); 
insert into `user` (`user_id`, `user_name`, `user_pwd`, `create_time`) values('1','yuan','1483177275','2020-01-01 00:00:00');
user.sql

2.model

package com.yuan.oauth2demo.model.system.entity;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
import javax.persistence.*;
import lombok.Data;

@ApiModel(value="com-yuan-oauth2demo-model-system-entity-User")
@Data
@Table(name = "`user`")
public class User {
    /**
     * 主键id
     */
    @Id
    @Column(name = "user_id")
    @ApiModelProperty(value="主键id")
    private String userId;

    /**
     * 用户名
     */
    @Column(name = "user_name")
    @ApiModelProperty(value="用户名")
    private String userName;

    /**
     * 密码
     */
    @Column(name = "user_pwd")
    @ApiModelProperty(value="密码")
    private String userPwd;

    /**
     * 记录创建日期时间
     */
    @Column(name = "create_time")
    @ApiModelProperty(value="记录创建日期时间")
    private Date createTime;
}
User.java
package com.yuan.oauth2demo.model.system.bean;

import lombok.Data;

import java.util.List;


@Data
public class UserDetails {

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String pwd;

    /**
     * 角色
     */
    private List<String> roles;

}
UserDetails.java

3.mapper及service

package com.yuan.oauth2demo.mapper.demo.system;

import com.yuan.oauth2demo.mapper.BaseMapper;
import com.yuan.oauth2demo.model.system.bean.UserDetails;
import com.yuan.oauth2demo.model.system.entity.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {

    UserDetails findUserDetailsByUsername(@Param("username") String username);

    User findUserByUsername(@Param("username") String username);

}
UserMapper.java
<?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.yuan.oauth2demo.mapper.demo.system.UserMapper">
  <resultMap id="BaseResultMap" type="com.yuan.oauth2demo.model.system.entity.User">
    <!--@mbg.generated generated on Fri Aug 28 10:48:18 GMT+08:00 2020.-->
    <!--@Table `user`-->
    <result column="user_id" jdbcType="VARCHAR" property="userId" />
    <result column="user_name" jdbcType="VARCHAR" property="userName" />
    <result column="user_pwd" jdbcType="VARCHAR" property="userPwd" />
    <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
  </resultMap>
  <sql id="Base_Column_List">
    <!--@mbg.generated generated on Fri Aug 28 10:48:18 GMT+08:00 2020.-->
    user_id, user_name, user_pwd, create_time
  </sql>

  <resultMap id="UserDetailsResultMap" type="com.yuan.oauth2demo.model.system.bean.UserDetails">
    <id column="username" property="username" jdbcType="VARCHAR" />
    <result column="password" property="pwd" jdbcType="VARCHAR" />
    <collection property="roles" ofType="string" javaType="java.util.List">
      <constructor>
        <arg column="role" javaType="java.lang.String" jdbcType="VARCHAR" />
      </constructor>
    </collection>
  </resultMap>

  <select id="findUserDetailsByUsername" resultMap="UserDetailsResultMap">
    SELECT user_name username,user_pwd password,null role
    FROM USER
    <where>
      <if test="username != null and username != ''">
        and user_name = #{username}
      </if>
    </where>
  </select>

  <select id="findUserByUsername" resultMap="BaseResultMap">
    SELECT *
    FROM USER
    <where>
      <if test="username != null and username != ''">
        and user_name = #{username}
      </if>
    </where>
  </select>

</mapper>
UserMapper.xml
package com.yuan.oauth2demo.service;

import com.yuan.oauth2demo.common.http.ResponseResult;


public interface UserService {

    ResponseResult findUserByUsername(String username);

}
UserService.java
package com.yuan.oauth2demo.service.impl;

import com.yuan.oauth2demo.common.http.ResponseResult;
import com.yuan.oauth2demo.mapper.demo.system.UserMapper;
import com.yuan.oauth2demo.model.system.entity.User;
import com.yuan.oauth2demo.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;


@Service
public class UserServiceImpl implements UserService {

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

    private final UserMapper userMapper;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public ResponseResult findUserByUsername(String username) {
        User user = userMapper.findUserByUsername(username);
        if(user != null){
            return ResponseResult.build(user).success();
        }
        return ResponseResult.build(null).fail();
    }
}
UserServiceImpl.java

4.UserDetailServiceImpl(重点)

package com.yuan.oauth2demo.service.impl;

import com.yuan.oauth2demo.common.encrypt.EncryptionPrefix;
import com.yuan.oauth2demo.mapper.demo.system.UserMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;


@Service
public class UserDetailServiceImpl implements UserDetailsService {

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

    private final UserMapper userMapper;

    private final PasswordEncoder passwordEncoder;

    public UserDetailServiceImpl(UserMapper userMapper,
                                 @Qualifier("bCryptPasswordEncoder")PasswordEncoder passwordEncoder) {
        this.userMapper = userMapper;
        this.passwordEncoder = passwordEncoder;
    }


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        com.yuan.oauth2demo.model.system.bean.UserDetails userDetail = userMapper.findUserDetailsByUsername(username);
        if(userDetail == null){
            logger.info("用户信息查询为空");
            return null;
        }
        return new org.springframework.security.core.userdetails.User(userDetail.getUsername(),
                EncryptionPrefix.BCRYPT.prefix + passwordEncoder.encode(userDetail.getPwd()),
                AuthorityUtils.createAuthorityList(userDetail.getRoles() == null ? new String[0] :
                        userDetail.getRoles().toArray(new String[0])));
    }
}
UserDetailServiceImpl.java

五、conf相关配置类

package com.yuan.oauth2demo.conf;

import com.alibaba.fastjson.JSON;

import com.yuan.oauth2demo.common.exception.ServiceException;
import com.yuan.oauth2demo.common.http.ResponseResult;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

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

@Component
public class AuthExceptionEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) {
        Throwable cause = authException.getCause();
        response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
            if (cause instanceof InvalidTokenException) {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.getWriter()
                        .write(JSON.toJSONString(
                                new ResponseResult<String>(ServiceException.Problems.EXCEPTION.getCode(),
                                        "无效的token"))
                        );
            } else {
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                response.getWriter()
                        .write(JSON.toJSONString(
                                new ResponseResult<String>(ServiceException.Problems.EXCEPTION.getCode(), "服务器异常"))
                        );
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
AuthExceptionEntryPoint.java
package com.yuan.oauth2demo.conf;

import com.yuan.oauth2demo.common.encrypt.EncryptionPrefix;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import java.security.Principal;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
@Configuration
@EnableAuthorizationServer
@ConfigurationProperties(prefix = "spring.authorization")
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements InitializingBean {

    /**
     * 密码模式授权模式
     */
    public static final String GRANT_TYPE_PASSWORD = "password";
    /**
     * {@link TokenEndpoint#postAccessToken(Principal, Map)}
     * 授权token 参数中的用户名 参数key
     */
    public static final String USERNAME = "username";
    /**
     * 支持刷新token
     */
    public static final String REFRESH_TOKEN = "refresh_token";
    /**
     * 授权类型key
     */
    public static final String GRANT_TYPE = "grant_type";

    /**
     * 简化授权模式
     */
    private static final String IMPLICIT = "implicit";
    /**
     * 客户端凭证模式,支持客户端无需携带账号密码校验,只需要设置固定的 client id and client secret 来校验
     */
    private static final String CLIENT_CREDENTIALS = "client_credentials";


    /**
     * clientId
     */
    private String clientId;
    /**
     * clientSecret
     */
    private String clientSecret;
    /**
     * 有效期 unit:小时
     */
    private int validityHour;

    public final int getValidityHour() {
        return validityHour;
    }

    public final void setValidityHour(int validityHour) {
        this.validityHour = validityHour;
    }

    public final String getClientId() {
        return clientId;
    }

    public final void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public final String getClientSecret() {
        return clientSecret;
    }

    public final void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }


    @Autowired
    private LettuceConnectionFactory lettuceConnectionFactory;
    @Autowired
    @Qualifier("bCryptPasswordEncoder")
    private PasswordEncoder passwordEncoder;
    /**
     * 认证管理器
     */
    @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthExceptionEntryPoint authExceptionEntryPoint;

    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;

    @Autowired
    private AuthorizationTokenResponseExceptionTranslator exceptionTranslator;
    private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() {
        @Override
        public <T> T postProcess(T object) {
            return object;
        }
    };

    /*private static KeyPair getKeyPair() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(
                keyStoreFile), keyStorePassword.toCharArray());
        return keyStoreKeyFactory.getKeyPair(keyPairAlias);
    }*/


    @Bean
    @ConditionalOnMissingBean(TokenStore.class)
    public TokenStore redisTokenStore() {
        return new RedisTokenStore(lettuceConnectionFactory);
    }

    /*@Primary
    @ConditionalOnMissingBean(TokenStore.class)
    private TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }*/

    /*@Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(getKeyPair());
        return converter;
    }*/

    /**
     * 采用内存模型
     *
     * @param clients 客户端详情服务配置器
     * @throws Exception 配置客户端详情异常
     * @apiNote clients.withClientDetails(clientDetailsService);通过该方法可以注入自定义的客户端详情服务
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient(clientId)
                .scopes("xx")
                .secret(EncryptionPrefix.BCRYPT.prefix + passwordEncoder.encode(clientSecret))
                .authorizedGrantTypes(GRANT_TYPE_PASSWORD, REFRESH_TOKEN)
                .authorizedGrantTypes(CLIENT_CREDENTIALS)
                .accessTokenValiditySeconds((int) TimeUnit.MINUTES.toSeconds(30));
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        /*tokenEnhancerChain.setTokenEnhancers(
                Collections.singletonList(jwtAccessTokenConverter()));*/
        // 1小时
        endpoints.tokenStore(redisTokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager);
        // 配置tokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        //token store
        tokenServices.setTokenStore(endpoints.getTokenStore());
        //支持refresh_token
        tokenServices.setSupportRefreshToken(true);
        //ClientDetailsService
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        //token converter
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds(getAccessTokenValiditySeconds());
        endpoints.tokenServices(tokenServices)
                .exceptionTranslator(exceptionTranslator);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        // 允许表单认证
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .authenticationEntryPoint(authExceptionEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);
    }

    private int getAccessTokenValiditySeconds() {
        if (validityHour != 0) {
            return (int) TimeUnit.HOURS.toSeconds(validityHour);
        }
        return 0;
    }

    @Autowired
    private void basicAuthenticationFilter(ClientDetailsService clientDetailsService) {
        try {
            ClientDetailsUserDetailsService clientDetailsUserDetailsService =
                    new ClientDetailsUserDetailsService(clientDetailsService);
            clientDetailsUserDetailsService.setPasswordEncoder(passwordEncoder);
            AuthenticationManagerBuilder managerBuilder =
                    new AuthenticationManagerBuilder(objectPostProcessor);
            managerBuilder.userDetailsService(clientDetailsUserDetailsService)
                    .addObjectPostProcessor(objectPostProcessor);
/*
            BasicAuthenticationFilter
                    .setAuthenticationManager(managerBuilder.build());
*/
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    public void afterPropertiesSet() throws Exception {

    }
}
AuthorizationServerConfiguration.java
package com.yuan.oauth2demo.conf;



import com.yuan.oauth2demo.common.exception.ServiceException;
import com.yuan.oauth2demo.common.http.ResponseResult;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.stereotype.Component;


@SuppressWarnings("unchecked")
@Component
public class AuthorizationTokenResponseExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception> {

    private static final String INVALID_REFRESH_TOKEN = "Invalid refresh token";

    @Override
    public ResponseEntity translate(Exception e) {
        String message = e.getMessage();
        if (e instanceof InvalidGrantException) {
            if (message.contains(INVALID_REFRESH_TOKEN)) {
                return new ResponseEntity<>(new ResponseResult<>(ServiceException.Problems.EXCEPTION.getCode(),
                        "refresh token不可用"),
                        HttpStatus.UNAUTHORIZED);
            }
            return new ResponseEntity<>(new ResponseResult<>(ServiceException.Problems.EXCEPTION.getCode(),
                    "账户信息有误"),
                    HttpStatus.UNAUTHORIZED);
        }
        if (e instanceof UnsupportedGrantTypeException) {
            return new ResponseEntity<>(new ResponseResult<>(ServiceException.Problems.EXCEPTION.getCode(),
                    "不支持授权类型:" + message.substring(message.indexOf(":") + 1)),
                    HttpStatus.UNAUTHORIZED);
        }
        if (e instanceof InternalAuthenticationServiceException) {
            if (String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()).contains(message)) {
                return new ResponseEntity<>(new ResponseResult<>(ServiceException.Problems.EXCEPTION.getCode(), "当前机构未注册"),
                        HttpStatus.UNAUTHORIZED);
            }
            return new ResponseEntity<>(new ResponseResult<>(ServiceException.Problems.EXCEPTION.getCode(), "访问超时,请重试"),
                    HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return new ResponseEntity<>(new ResponseResult<>(ServiceException.Problems.EXCEPTION.getCode(), "未知异常"),
                HttpStatus.UNAUTHORIZED);
    }
}
AuthorizationTokenResponseExceptionTranslator.java
package com.yuan.oauth2demo.conf;

import com.alibaba.fastjson.JSON;
import com.yuan.oauth2demo.common.exception.ServiceException;
import com.yuan.oauth2demo.common.http.ResponseResult;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

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


@Component("customAccessDeniedHandler")
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
            response.getWriter()
                    .write(JSON.toJSONString(
                            new ResponseResult<String>(ServiceException.Problems.EXCEPTION.getCode(), "访问被拒绝")));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
CustomAccessDeniedHandler.java
package com.yuan.oauth2demo.conf;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yuan.oauth2demo.utils.RedisHelper;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;


@Configuration
@ConditionalOnProperty(name = "oatu2demo.redis.enable", havingValue = "true")
public class RedisClientConfiguration {

    public static final String REDIS_TEMPLATE_FOR_OBJECT = "redisTemplateForObject";
    private static final Logger lg = LoggerFactory.getLogger(RedisClientConfiguration.class);
    @Resource
    private RedisProperties redisProperties;

    @Bean("redisHelper")
    public RedisHelper redisHelper() {
        return new RedisHelper(createRedisConnectionFactory());
    }

    @Bean("redisConnectionFactory")
    @ConditionalOnMissingBean(LettuceConnectionFactory.class)
    public LettuceConnectionFactory createRedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration =
                new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(redisProperties.getHost());
        redisStandaloneConfiguration.setPort(redisProperties.getPort());
        redisStandaloneConfiguration.setDatabase(redisProperties.getDatabase());
        redisStandaloneConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
        LettuceClientConfiguration.LettuceClientConfigurationBuilder lettuceClientConfigurationBuilder
                = createBuilder(redisProperties.getLettuce().getPool());
        //  connection timeout
        lettuceClientConfigurationBuilder.commandTimeout(redisProperties.getTimeout());
        return new LettuceConnectionFactory(redisStandaloneConfiguration,
                lettuceClientConfigurationBuilder.build());
    }

    private LettuceClientConfiguration.LettuceClientConfigurationBuilder createBuilder(RedisProperties.Pool pool) {
        if (pool == null) {
            return LettuceClientConfiguration.builder();
        }
        return LettucePoolingClientConfiguration.builder()
                .poolConfig(getPoolConfig(pool));
    }

    private <T> GenericObjectPoolConfig<T> getPoolConfig(RedisProperties.Pool properties) {
        GenericObjectPoolConfig<T> config = new GenericObjectPoolConfig<>();
        config.setMaxTotal(properties.getMaxActive());
        config.setMaxIdle(properties.getMaxIdle());
        config.setMinIdle(properties.getMinIdle());
        if (properties.getTimeBetweenEvictionRuns() != null) {
            config.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRuns().toMillis());
        }
        if (properties.getMaxWait() != null) {
            config.setMaxWaitMillis(properties.getMaxWait().toMillis());
        }
        return config;
    }

    @Bean(REDIS_TEMPLATE_FOR_OBJECT)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        if (null == redisConnectionFactory) {
            lg.error("Redis Template Service is not available");
            return null;
        }
        //设置序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
                new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置redisTemplate
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        RedisSerializer stringSerializer = new StringRedisSerializer();
        // key序列化
        redisTemplate.setKeySerializer(stringSerializer);
        // value序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // Hash key序列化
        redisTemplate.setHashKeySerializer(stringSerializer);
        // Hash value序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
RedisClientConfiguration.java
package com.yuan.oauth2demo.conf;



import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
@EnableConfigurationProperties(ResourceServerConfigurationProperties.class)
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    private final TokenStore tokenStore;
    private final AuthExceptionEntryPoint exceptionEntryPoint;

    private final ResourceServerConfigurationProperties configurationProperties;

    @Autowired
    public ResourceServerConfiguration(@Qualifier("redisTokenStore") TokenStore tokenStore,
                                       AuthExceptionEntryPoint exceptionEntryPoint,
                                       ResourceServerConfigurationProperties configurationProperties) {
        this.tokenStore = tokenStore;
        this.exceptionEntryPoint = exceptionEntryPoint;
        this.configurationProperties = configurationProperties;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers(configurationProperties.getPermitted()).permitAll()
                .antMatchers(configurationProperties.getAuthenticated()).authenticated();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.tokenStore(tokenStore);
        resources.authenticationEntryPoint(exceptionEntryPoint);
    }
}
ResourceServerConfiguration.java
package com.yuan.oauth2demo.conf;

import org.springframework.boot.context.properties.ConfigurationProperties;


@ConfigurationProperties(prefix = "resource.server.configuration")
public class ResourceServerConfigurationProperties {

    /**
     * 允许不需要认证的url虚拟路径后缀列表
     *
     * @apiNote 默认没有允许的路径
     */
    private String[] permitted = new String[0];

    /**
     * 需要认证的url虚拟路径后缀列表
     *
     * @apiNote 默认所有路径都需要认证
     */
    private String[] authenticated = new String[]{"/**"};

    public final String[] getPermitted() {
        return permitted;
    }

    public final void setPermitted(String[] permitted) {
        this.permitted = permitted;
    }

    public final String[] getAuthenticated() {
        return authenticated;
    }

    public final void setAuthenticated(String[] authenticated) {
        this.authenticated = authenticated;
    }
}
ResourceServerConfigurationProperties.java
package com.yuan.oauth2demo.conf;



import com.yuan.oauth2demo.mapper.demo.system.UserMapper;
import com.yuan.oauth2demo.service.impl.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;


@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final UserMapper userMapper;

    @Autowired
    public SecurityConfiguration(UserMapper userMapper) {
        this.userMapper = userMapper;
    }


    @Bean("delegatingPasswordEncoder")
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean("bCryptPasswordEncoder")
    PasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**", "/v2/api-docs").permitAll()
                .antMatchers("/**").permitAll()
                .antMatchers("/**").authenticated();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService(userMapper)).passwordEncoder(passwordEncoder());
    }

    @Bean
    public UserDetailsService userDetailService(UserMapper userMapper) {
        return new UserDetailServiceImpl(userMapper, bCryptPasswordEncoder());
    }
}
SecurityConfiguration.java

六、controller  用于测试

package com.yuan.oauth2demo.controller;

import com.yuan.oauth2demo.common.http.ResponseResult;
import com.yuan.oauth2demo.controller.base.BaseController;
import com.yuan.oauth2demo.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;


@RestController
@RequestMapping("/user")
public class UserController extends BaseController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/detail")
    ResponseResult findUserByUsername(Principal principal,
                                      @RequestParam String username){
        return userService.findUserByUsername(username);
    }

}
UserController.java

七、项目总体目录结构

八、调试

1. 获取token

  2.使用token,调controller里的方法

3.换取token

完毕!

posted @ 2020-09-01 14:38  KyleCool  阅读(767)  评论(0)    收藏  举报