• Redis实战 - 01 Redis 和 SpringSecurity Oauth2 实现认证授权中心


    1. food-social-contact-parent 父项目

    创建父项目 food-social-contact-parent,导入公共依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.imooc</groupId>
        <artifactId>food-social-contact-parent</artifactId>
        <packaging>pom</packaging>
        <version>1.0-SNAPSHOT</version>
        <modules>
            <module>ms-registry</module>
            <module>ms-gateway</module>
            <module>ms-diners</module>
            <module>ms-oauth2-server</module>
            <module>commons</module>
        </modules>
    
        <!-- 可以集中定义依赖资源的版本信息 -->
        <properties>
            <spring-boot-version>2.3.5.RELEASE</spring-boot-version>
            <spring-cloud-version>Hoxton.SR8</spring-cloud-version>
            <lombok-version>1.18.16</lombok-version>
            <commons-lang-version>3.11</commons-lang-version>
            <mybatis-starter-version>2.1.3</mybatis-starter-version>
            <mysql-version>8.0.22</mysql-version>
            <swagger-starter-version>2.1.5-RELEASE</swagger-starter-version>
            <hutool-version>5.4.7</hutool-version>
            <guava-version>20.0</guava-version>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <!-- 集中定义依赖,不引入 -->
        <dependencyManagement>
            <dependencies>
                <!-- spring boot 依赖 -->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot-version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!-- spring cloud 依赖 -->
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud-version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <!-- lombok 依赖 -->
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <version>${lombok-version}</version>
                </dependency>
                <!-- common-lang3 依赖 -->
                <dependency>
                    <groupId>org.apache.commons</groupId>
                    <artifactId>commons-lang3</artifactId>
                    <version>${commons-lang-version}</version>
                </dependency>
                <!-- mybatis 依赖 -->
                <dependency>
                    <groupId>org.mybatis.spring.boot</groupId>
                    <artifactId>mybatis-spring-boot-starter</artifactId>
                    <version>${mybatis-starter-version}</version>
                </dependency>
                <!-- swagger 依赖 -->
                <dependency>
                    <groupId>com.battcn</groupId>
                    <artifactId>swagger-spring-boot-starter</artifactId>
                    <version>${swagger-starter-version}</version>
                </dependency>
                <!-- mysql 依赖 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>${mysql-version}</version>
                </dependency>
                <!-- hutool 依赖 -->
                <dependency>
                    <groupId>cn.hutool</groupId>
                    <artifactId>hutool-all</artifactId>
                    <version>${hutool-version}</version>
                </dependency>
                <!-- guava 依赖 -->
                <dependency>
                    <groupId>com.google.guava</groupId>
                    <artifactId>guava</artifactId>
                    <version>${guava-version}</version>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <!-- 集中定义项目所需插件 -->
        <build>
            <pluginManagement>
                <plugins>
                    <!-- spring boot maven 项目打包插件 -->
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112

    2. commons 公共服务

    在这里插入图片描述

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>food-social-contact-parentartifactId>
            <groupId>com.imoocgroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>commonsartifactId>
    
        <dependencies>
            
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
            dependency>
            
            <dependency>
                <groupId>cn.hutoolgroupId>
                <artifactId>hutool-allartifactId>
            dependency>
            
            <dependency>
                <groupId>com.google.guavagroupId>
                <artifactId>guavaartifactId>
            dependency>
            
            <dependency>
                <groupId>com.battcngroupId>
                <artifactId>swagger-spring-boot-starterartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.securitygroupId>
                <artifactId>spring-security-coreartifactId>
            dependency>
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    1. 全局常量类 ApiConstant

    /**
     * 全局常量类
     */
    public class ApiConstant {
        // 成功
        public static final int SUCCESS_CODE = 1;
        // 成功提示信息
        public static final String SUCCESS_MESSAGE = "Successful.";
        // 错误
        public static final int ERROR_CODE = 0;
        // 未登录
        public static final int NO_LOGIN_CODE = -100;
        // 请登录提示信息
        public static final String NO_LOGIN_MESSAGE = "Please login.";
        // 错误提示信息
        public static final String ERROR_MESSAGE = "Oops! Something was wrong.";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2. 全局异常类 ParameterException

    /**
     * 全局异常类
     */
    @Getter
    @Setter
    public class ParameterException extends RuntimeException {
    
        private Integer errorCode;
    
        public ParameterException() {
            super(ApiConstant.ERROR_MESSAGE);
            this.errorCode = ApiConstant.ERROR_CODE;
        }
    
        public ParameterException(Integer errorCode) {
            this.errorCode = errorCode;
        }
    
        public ParameterException(String message) {
            super(message);
            this.errorCode = ApiConstant.ERROR_CODE;
        }
    
        public ParameterException(Integer errorCode, String message) {
            super(message);
            this.errorCode = errorCode;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3. 实体对象公共属性 BaseModel

    /**
     * 实体对象公共属性
     */
    @Getter
    @Setter
    public class BaseModel implements Serializable {
    
        private Integer id;
        private Date createDate;
        private Date updateDate;
        private int isValid;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4. 公共返回对象 ResultInfo

    /**
     * 公共返回对象
     */
    @Getter
    @Setter
    @ApiModel(value = "返回说明")
    public class ResultInfo<T> implements Serializable {
        
        @ApiModelProperty(value = "成功标识0=失败,1=成功")
        private Integer code;
        @ApiModelProperty(value = "描述信息")
        private String message;
        @ApiModelProperty(value = "访问路径")
        private String path;
        @ApiModelProperty(value = "返回数据对象")
        private T data;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5. 食客实体类 Diners

    /**
     * 食客实体类
     */
    @Getter
    @Setter
    public class Diners extends BaseModel {
    
        // 主键
        private Integer id;
        // 用户名
        private String username;
        // 昵称
        private String nickname;
        // 密码
        private String password;
        // 手机号
        private String phone;
        // 邮箱
        private String email;
        // 头像
        private String avatarUrl;
        // 角色
        private String roles;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    6. 公共返回对象工具类 ResultInfoUtil

    /**
     * 公共返回对象工具类
     */
    public class ResultInfoUtil {
    
        /**
         * 请求出错返回
         *
         * @param path 请求路径
         * @param 
         * @return
         */
        public static <T> ResultInfo<T> buildError(String path) {
            ResultInfo<T> resultInfo = build(ApiConstant.ERROR_CODE,
                    ApiConstant.ERROR_MESSAGE, path, null);
            return resultInfo;
        }
    
        /**
         * 请求出错返回
         *
         * @param errorCode 错误代码
         * @param message   错误提示信息
         * @param path      请求路径
         * @param 
         * @return
         */
        public static <T> ResultInfo<T> buildError(int errorCode, String message, String path) {
            ResultInfo<T> resultInfo = build(errorCode, message, path, null);
            return resultInfo;
        }
    
        /**
         * 请求成功返回
         *
         * @param path 请求路径
         * @param 
         * @return
         */
        public static <T> ResultInfo<T> buildSuccess(String path) {
            ResultInfo<T> resultInfo = build(ApiConstant.SUCCESS_CODE,
                    ApiConstant.SUCCESS_MESSAGE, path, null);
            return resultInfo;
        }
    
        /**
         * 请求成功返回
         *
         * @param path 请求路径
         * @param data 返回数据对象
         * @param 
         * @return
         */
        public static <T> ResultInfo<T> buildSuccess(String path, T data) {
            ResultInfo<T> resultInfo = build(ApiConstant.SUCCESS_CODE,
                    ApiConstant.SUCCESS_MESSAGE, path, data);
            return resultInfo;
        }
    
        /**
         * 构建返回对象方法
         *
         * @param code
         * @param message
         * @param path
         * @param data
         * @param 
         * @return
         */
        public static <T> ResultInfo<T> build(Integer code, String message, String path, T data) {
            if (code == null) {
                code = ApiConstant.SUCCESS_CODE;
            }
            if (message == null) {
                message = ApiConstant.SUCCESS_MESSAGE;
            }
            ResultInfo resultInfo = new ResultInfo();
            resultInfo.setCode(code);
            resultInfo.setMessage(message);
            resultInfo.setPath(path);
            resultInfo.setData(data);
            return resultInfo;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    3. ms-registry 注册中心服务

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>food-social-contact-parentartifactId>
            <groupId>com.imoocgroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>ms-registryartifactId>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
            dependency>
        dependencies>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    server:
      port: 8080
    
    spring:
      application:
        name: ms-registry
    
    # 配置 Eureka Server 注册中心
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
          defaultZone: http://localhost:8080/eureka/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4. ms-oauth-server 认证授权中心服务

    在这里插入图片描述

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>food-social-contact-parentartifactId>
            <groupId>com.imoocgroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>ms-oauth2-serverartifactId>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
        properties>
    
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            dependency>
            
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
            dependency>
            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-securityartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-oauth2artifactId>
            dependency>
            
            <dependency>
                <groupId>com.imoocgroupId>
                <artifactId>commonsartifactId>
                <version>1.0-SNAPSHOTversion>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-configuration-processorartifactId>
                <optional>trueoptional>
            dependency>
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    server:
      port: 8082 # 端口
    
    spring:
      application:
        name: ms-oauth2-server # 应用名
      # 数据库
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: root
        url: jdbc:mysql://127.0.0.1:3306/orgnization?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false
      # Redis
      redis:
        port: 6379
        host: 192.168.38.22
        timeout: 3000
        database: 1
      # swagger
      swagger:
        base-package: com.hh.oauth2
        title: 美食社交食客API接口文档
    
    # Oauth2
    client:
      oauth2:
        client-id: appId # 客户端标识 ID
        secret: 123456 # 客户端安全码
        # 授权类型
        grant_types:
          - password
          - refresh_token
        # token 有效时间,单位秒
        token-validity-time: 3600
        refresh-token-validity-time: 3600
        # 客户端访问范围
        scopes:
          - api
          - all
    
    # 配置 Eureka Server 注册中心
    eureka:
      instance:
        prefer-ip-address: true
        instance-id: ${spring.cloud.client.ip-address}:${server.port}
      client:
        service-url:
          defaultZone: http://localhost:8080/eureka/
    
    # Mybatis
    mybatis:
      configuration:
        map-underscore-to-camel-case: true # 开启驼峰映射
    
    # 指标监控健康检查
    management:
      endpoints:
        web:
          exposure:
            include: "*" # 暴露的端点
    
    logging:
      pattern:
        console: '%d{HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    1. 自定义认证数据源 UserDetailsService

    自定义认证数据源从数据库中获取用户信息:

    @Service
    public class UserService implements UserDetailsService {
        @Resource
        private DinersMapper dinersMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            AssertUtil.isNotEmpty(username, "请输入用户名");
            Diners diners = dinersMapper.selectByAccountInfo(username);
            if (diners == null) {
                throw new UsernameNotFoundException("用户名或密码错误,请重新输入");
            }
            return new User(
                    username,
                    diners.getPassword(),
                    AuthorityUtils.commaSeparatedStringToAuthorityList(diners.getRoles())
            );
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    @Mapper
    public interface DinersMapper {
    
        /**
         * 根据用户名 or 手机号 or 邮箱查询用户信息
         */
        @Select("select id, username, nickname, phone, email, " +
                "password, avatar_url, roles, is_valid from t_diners where " +
                "(username = #{account} or phone = #{account} or email = #{account})")
        Diners selectByAccountInfo(@Param("account") String account);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. 配置类 SecurityConfiguration

    @Configuration
    @EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        // 注入 Redis 连接工厂
        @Resource
        private RedisConnectionFactory redisConnectionFactory;
    
        /**
         * 初始化 RedisTokenStore 用于将 token 存储至 Redis
         */
        @Bean
        public RedisTokenStore redisTokenStore() {
            RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
            // 设置key的层级前缀,方便查询
            redisTokenStore.setPrefix("TOKEN:");
            return redisTokenStore;
        }
    
        /**
         * 初始化密码编码器,用 MD5 加密密码
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new PasswordEncoder() {
                /**
                 * 密码加密
                 * @param rawPassword 原始密码
                 * @return 加密后的密码
                 */
                @Override
                public String encode(CharSequence rawPassword) {
                    return DigestUtil.md5Hex(rawPassword.toString());
                }
    
                /**
                 * 校验密码
                 * @param rawPassword       原始密码
                 * @param encodedPassword   加密密码
                 */
                @Override
                public boolean matches(CharSequence rawPassword, String encodedPassword) {
                    return DigestUtil.md5Hex(rawPassword.toString()).equals(encodedPassword);
                }
            };
        }
    
        /**
         * 初始化认证管理对象
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        /**
         * 放行和认证规则
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    // 放行的请求
                    .antMatchers("/oauth/**", "/actuator/**").permitAll()
                    // 其他请求必须认证才能访问
                    .anyRequest().authenticated()
                    .and()
                    .csrf().disable();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    3. 搭建授权服务器

    1. 客户端信息配置类 ClientOAuth2DataConfiguration
    # Oauth2
    client:
      oauth2:
        client-id: appId # 客户端标识 ID
        secret: 123456 # 客户端安全码
        # 授权类型
        grant_types:
          - password
          - refresh_token
        # token 有效时间,单位秒
        token-validity-time: 3600
        refresh-token-validity-time: 3600
        # 客户端访问范围
        scopes:
          - api
          - all
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    /**
     * 客户端配置类
     */
    @Component
    @ConfigurationProperties(prefix = "client.oauth2")
    @Data
    public class ClientOAuth2DataConfiguration {
    
        // 客户端标识 ID
        private String clientId;
    
        // 客户端安全码
        private String secret;
    
        // 授权类型
        private String[] grantTypes;
    
        // token有效期
        private int tokenValidityTime;
    
        // refresh-token有效期
        private int refreshTokenValidityTime;
    
        // 客户端访问范围
        private String[] scopes;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    2. 授权服务器配置类 AuthorizationServerConfiguration
    /**
     * 授权服务器配置类
     **/
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
        // RedisTokenSore
        @Resource
        private RedisTokenStore redisTokenStore;
    
        // 认证管理对象
        @Resource
        private AuthenticationManager authenticationManager;
    
        // 密码编码器
        @Resource
        private PasswordEncoder passwordEncoder;
    
        // 客户端配置类
        @Resource
        private ClientOAuth2DataConfiguration clientOAuth2DataConfiguration;
    
        // 登录校验
        @Resource
        private UserService userService;
    
        /**
         * 客户端配置 - 授权模型
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    // 客户端标识 ID
                    .withClient(clientOAuth2DataConfiguration.getClientId())
                    // 客户端安全码
                    .secret(passwordEncoder.encode(clientOAuth2DataConfiguration.getSecret()))
                    // 授权类型
                    .authorizedGrantTypes(clientOAuth2DataConfiguration.getGrantTypes())
                    // token 有效期
                    .accessTokenValiditySeconds(clientOAuth2DataConfiguration.getTokenValidityTime())
                    // 刷新 token 的有效期
                    .refreshTokenValiditySeconds(clientOAuth2DataConfiguration.getRefreshTokenValidityTime())
                    // 客户端访问范围
                    .scopes(clientOAuth2DataConfiguration.getScopes());
        }
    
        /**
         * 配置授权以及令牌的访问端点和令牌服务
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            // 认证器
            endpoints.authenticationManager(authenticationManager)
                    // 刷新令牌必须配置userDetailsService,用来刷新令牌时的认证
                    .userDetailsService(userService)
                    // token 存储的方式:Redis
                    .tokenStore(redisTokenStore);
        }
    
        /**
         * 配置令牌端点安全约束
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            // 允许访问 token 的公钥,默认 /oauth/token_key 是受保护的
            security.tokenKeyAccess("permitAll()")
                    // 允许检查 token 的状态,默认 /oauth/check_token 是受保护的
                    .checkTokenAccess("permitAll()");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    3. 启动项目测试

    查看注册中心是否注册 ms-oauth2-server服务:

    在这里插入图片描述

    SpringSecurity Oauth 的访问令牌接口:/oauth/token

    认证参数:
    在这里插入图片描述
    请求体参数:
    在这里插入图片描述

    4. 重构认证授权中心增强令牌返回结果

    1. 自定义/oauth/token控制器 OAuthController

    oauth2控制器,让/oauth/token请求走自己的控制器接口:

    /**
     * Oauth2 控制器
     */
    @RestController
    @RequestMapping("oauth")
    public class OAuthController {
    
        @Resource
        private TokenEndpoint tokenEndpoint;
    
        @Resource
        private HttpServletRequest request;
    
        @PostMapping("token")
        public ResultInfo postAccessToken(Principal principal, @RequestParam Map<String, String> parameters)
                throws HttpRequestMethodNotSupportedException {
            return custom(tokenEndpoint.postAccessToken(principal, parameters).getBody());
        }
    
        /**
         * 自定义 Token 返回对象
         */
        private ResultInfo custom(OAuth2AccessToken accessToken) {
            DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
            Map<String, Object> data = new LinkedHashMap(token.getAdditionalInformation());
            data.put("accessToken", token.getValue());
            data.put("expireIn", token.getExpiresIn());
            data.put("scopes", token.getScope());
            if (token.getRefreshToken() != null) {
                data.put("refreshToken", token.getRefreshToken().getValue());
            }
            return ResultInfoUtil.buildSuccess(request.getServletPath(), data);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    2. 认证用户实体类 SignInIdentity
    package com.hh.commons.model.domain;
    
    @Data
    public class SignInIdentity implements UserDetails {
        // 主键
        private Integer id;
        // 用户名
        private String username;
        // 昵称
        private String nickname;
        // 密码
        private String password;
        // 手机号
        private String phone;
        // 邮箱
        private String email;
        // 头像
        private String avatarUrl;
        // 角色
        private String roles;
        // 是否有效 0=无效 1=有效
        private int isValid;
        // 角色集合, 不能为空
        private List<GrantedAuthority> authorities;
    
        // 获取角色信息
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            if (StrUtil.isNotBlank(this.roles)) {
                // 获取数据库中的角色信息
                Lists.newArrayList();
                this.authorities = Stream.of(this.roles.split(",")).map(role -> {
                    return new SimpleGrantedAuthority(role);
                }).collect(Collectors.toList());
            } else {
                // 如果角色为空则设置为 ROLE_USER
                this.authorities = AuthorityUtils
                        .commaSeparatedStringToAuthorityList("ROLE_USER");
            }
            return this.authorities;
        }
    
        @Override
        public String getPassword() {
            return this.password;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return this.isValid == 0 ? false : true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    3. 授权服务器配置类 AuthorizationServerConfiguration
    /**
     * 授权服务
     */
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    
        // 省略....
    
        /**
         * 配置授权以及令牌的访问端点和令牌服务
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            // 认证器
            endpoints.authenticationManager(authenticationManager)
                    // 刷新令牌必须配置userDetailsService,用来刷新令牌时的认证
                    .userDetailsService(userService)
                    // token 存储的方式:Redis
                    .tokenStore(redisTokenStore)
                    // 令牌增强对象,增强返回的结果
                    .tokenEnhancer((accessToken, authentication) -> {
                        // 获取登录用户的信息,然后设置
                        SignInIndentity signInIdentity = (SignInIndentity) authentication.getPrincipal();
                        LinkedHashMap<String, Object> map = new LinkedHashMap<>();
                        map.put("nickname", signInIdentity.getNickname());
                        map.put("avatarUrl", signInIdentity.getAvatarUrl());
                        DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
                        token.setAdditionalInformation(map);
                        return token;
                    });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    启动项目测试:

    在这里插入图片描述

    目前已经在认证授权中心的登录校验已经写完了,网关路由到ms-dinners服务,在ms-dinners服务远程调用授权认证服务完成认证功能。

    5. ms-dinners 食客服务

    下面就是完成食客服务ms-dinners的登录业务逻辑,网关路由到ms-dinners服务,在ms-dinners服务远程调用授权认证服务完成认证功能。

    在这里插入图片描述

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>food-social-contact-parentartifactId>
            <groupId>com.imoocgroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>ms-dinersartifactId>
    
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            dependency>
            
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
            dependency>
            
            <dependency>
                <groupId>com.imoocgroupId>
                <artifactId>commonsartifactId>
                <version>1.0-SNAPSHOTversion>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-configuration-processorartifactId>
                <optional>trueoptional>
            dependency>
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    server:
      port: 8081 # 端口
    
    spring:
      application:
        name: ms-diners # 应用名
      # 数据库
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: root
        url: jdbc:mysql://127.0.0.1:3306/orgnization?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false
      # Redis
      redis:
        port: 6379
        host: 192.168.38.22
        timeout: 3000
        database: 1
      # swagger
      swagger:
        base-package: com.hh.oauth2
        title: 美食社交食客API接口文档
    
    
    # Oauth2 客户端信息
    oauth2:
      client:
        client-id: appId
        secret: 123456
        grant_type: password
        scope: api
    
    # oauth2 服务地址
    service:
      name:
        ms-oauth-server: http://ms-oauth2-server/
    
    # 配置 Eureka Server 注册中心
    eureka:
      instance:
        prefer-ip-address: true
        instance-id: ${spring.cloud.client.ip-address}:${server.port}
      client:
        service-url:
          defaultZone: http://localhost:8080/eureka/
    
    logging:
      pattern:
        console: '%d{HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    1. 客户端信息配置类 OAuth2ClientConfiguration

    SpringBoot 配置文件:

    # Oauth2 客户端信息
    oauth2:
      client:
        client-id: appId
        secret: 123456
        grant_type: password
        scope: api
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    /**
     * 客户端配置类
     */
    @Component
    @ConfigurationProperties(prefix = "oauth2.client")
    @Getter
    @Setter
    public class OAuth2ClientConfiguration {
    
        private String clientId;
        private String secret;
        private String grant_type;
        private String scope;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2. RestTemplateConfiguration

    RestTemplate是执行HTTP请求的同步阻塞式的客户端,RestTemplate类通过为HTTP方法(例如GET,POST,PUT,DELETE等)提供重载的方法,提供了一种非常方便的方法访问基于HTTP的Web服务。

    /**
     * Rest 配置类
     */
    @Configuration
    public class RestTemplateConfiguration {
    
        @LoadBalanced
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3. 食客服务控制层 DinersController

    /**
     * 食客服务控制层
     */
    @RestController
    @Api(tags = "食客相关接口")
    public class DinersController {
    
        @Resource
        private DinersService dinersService;
    
        @Resource
        private HttpServletRequest request;
    
        /**
         * 登录
         */
        @GetMapping("signin")
        public ResultInfo signIn(String account, String password) {
            return dinersService.signIn(account, password, request.getServletPath());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4. 食客服务业务逻辑层 DinersService

    /**
     * 食客服务业务逻辑层
     */
    @Service
    public class DinersService {
    
        @Resource
        private RestTemplate restTemplate;
    
        @Value("${service.name.ms-oauth-server}")
        private String oauthServerName;
    
        @Resource
        private OAuth2ClientConfiguration oAuth2ClientConfiguration;
    
        /**
         * 登录
         *
         * @param account  帐号:用户名或手机或邮箱
         * @param password 密码
         * @param path     请求路径
         */
        public ResultInfo signIn(String account, String password, String path) {
            // 参数校验
            AssertUtil.isNotEmpty(account, "请输入登录帐号");
            AssertUtil.isNotEmpty(password, "请输入登录密码");
    
            // 模拟使用postForObject模拟表单数据提交,即:提交x-www-form-urlencoded格式的数据
            // 构建请求头
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    
            // 构建请求体(请求参数)
            MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
            body.add("username", account);
            body.add("password", password);
            body.setAll(BeanUtil.beanToMap(oAuth2ClientConfiguration));
            HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
    
            // 设置 Authorization
            restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(
                            oAuth2ClientConfiguration.getClientId(),
                            oAuth2ClientConfiguration.getSecret()));
            
            // 发送请求:请求路径url,请求体,响应数据类型
            ResponseEntity<ResultInfo> result = restTemplate.postForEntity(oauthServerName + "oauth/token", entity, ResultInfo.class);
            
            // 处理返回结果
            AssertUtil.isTrue(result.getStatusCode() != HttpStatus.OK, "登录失败");
            ResultInfo resultInfo = result.getBody();
            if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
                // 登录失败
                resultInfo.setData(resultInfo.getMessage());
                return resultInfo;
            }
            // 这里的 Data 是一个 LinkedHashMap 转成了域对象 OAuthDinerInfo
            OAuthDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(),new OAuthDinerInfo(), false);
            
            // 根据业务需求返回视图对象
            LoginDinerInfo loginDinerInfo = new LoginDinerInfo();
            loginDinerInfo.setToken(dinerInfo.getAccessToken());
            loginDinerInfo.setAvatarUrl(dinerInfo.getAvatarUrl());
            loginDinerInfo.setNickname(dinerInfo.getNickname());
            return ResultInfoUtil.buildSuccess(path, loginDinerInfo);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    访问/oauth/token接口获取token的返回结果:

    package com.hh.diners.domain;
    
    @Getter
    @Setter
    public class OAuthDinerInfo implements Serializable {
        private String nickname;
        private String avatarUrl;
        private String accessToken;
        private String expireIn;
        private List<String> scopes;
        private String refreshToken;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    返回给前端的响应类:

    package com.hh.diners.vo;
    
    @Setter
    @Getter
    public class LoginDinerInfo implements Serializable {
        private String nickname;
        private String token;
        private String avatarUrl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    其实上面的操作就是使用RestTemplate发起HTTP POST请求访问oauth/token接口路径获取token的过程,即使用postForObject模拟表单数据提交x-www-form-urlencoded格式的数据,其中请求数据为:
    在这里插入图片描述

    在这里插入图片描述

    5. 启动项目测试

    启动 ms-registry 注册中心服务、ms-oauth-server 认证授权中心服务、ms-dinners 食客服务

    在这里插入图片描述

    6. ms-oauth2-server 获取登录用户信息

    1. 资源服务配置类 ResourceServerConfig

    /**
     * 资源服务
     */
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
        @Resource
        private MyAuthenticationEntryPoint authenticationEntryPoint;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            // 配置放行的资源
            http.authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                    .requestMatchers().antMatchers("/user/**");
        }
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.authenticationEntryPoint(authenticationEntryPoint);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    认证失败后的自定义处理逻辑:

    /**
     * 认证失败处理
     */
    @Component
    public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    
        @Resource
        private ObjectMapper objectMapper;
    
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                             AuthenticationException authException) throws IOException {
            // 返回 JSON
            response.setContentType("application/json;charset=utf-8");
            // 状态码 401
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            // 写出
            PrintWriter out = response.getWriter();
            String errorMessage = authException.getMessage();
            if (StringUtils.isBlank(errorMessage)) {
                errorMessage = "登录失效!";
            }
            ResultInfo result = ResultInfoUtil.buildError(ApiConstant.ERROR_CODE,
                    errorMessage, request.getRequestURI());
            out.write(objectMapper.writeValueAsString(result));
            out.flush();
            out.close();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2. 获取登录用户信息

    /**
     * 用户中心
     */
    @RestController
    public class UserController {
    
        @Resource
        private HttpServletRequest request;
    
        @Resource
        private RedisTokenStore redisTokenStore;
    
        @GetMapping("user/me")
        public ResultInfo getCurrentUser(Authentication authentication) {
            // 获取登录用户的信息,然后设置
            SignInIndentity signInIdentity = (SignInIndentity) authentication.getPrincipal();
            // 转为前端可用的视图对象
            SignInDinerInfo dinerInfo = new SignInDinerInfo();
            BeanUtils.copyProperties(signInIdentity, dinerInfo);
            return ResultInfoUtil.buildSuccess(request.getServletPath(), dinerInfo);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    package com.imooc.commons.model.vo;
    
    @Getter
    @Setter
    @ApiModel(value = "SignInDinerInfo", description = "登录用户信息")
    public class SignInDinerInfo implements Serializable {
    
        @ApiModelProperty("主键")
        private Integer id;
        @ApiModelProperty("用户名")
        private String username;
        @ApiModelProperty("昵称")
        private String nickname;
        @ApiModelProperty("手机号")
        private String phone;
        @ApiModelProperty("邮箱")
        private String email;
        @ApiModelProperty("头像")
        private String avatarUrl;
        @ApiModelProperty("角色")
        private String roles;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3. 启动项目测试

    ① 首先获取access_token :
    在这里插入图片描述

    ② 请求路径上携带 access_token 访问受限资源

    在这里插入图片描述

    ③ Authentication中配置access_token 访问受限资源

    在这里插入图片描述

    4. 退出登录

    /**
     * 用户中心
     */
    @RestController
    public class UserController {
    
        @Resource
        private HttpServletRequest request;
    
        @Resource
        private RedisTokenStore redisTokenStore;
    
        /**
         * 安全退出
         *
         * @param access_token
         * @param authorization
         * @return
         */
        @GetMapping("user/logout")
        public ResultInfo logout(String access_token, String authorization) {
            // 判断 access_token 是否为空,为空将 authorization 赋值给 access_token
            if (StringUtils.isBlank(access_token)) {
                access_token = authorization;
            }
            // 判断 authorization 是否为空
            if (StringUtils.isBlank(access_token)) {
                return ResultInfoUtil.buildSuccess(request.getServletPath(), "退出成功");
            }
            // 判断 bearer token 是否为空
            if (access_token.toLowerCase().contains("bearer ".toLowerCase())) {
                access_token = access_token.toLowerCase().replace("bearer ", "");
            }
            // 清除 redis token 信息
            OAuth2AccessToken oAuth2AccessToken = redisTokenStore.readAccessToken(access_token);
            if (oAuth2AccessToken != null) {
                redisTokenStore.removeAccessToken(oAuth2AccessToken);
                OAuth2RefreshToken refreshToken = oAuth2AccessToken.getRefreshToken();
                redisTokenStore.removeRefreshToken(refreshToken);
            }
            return ResultInfoUtil.buildSuccess(request.getServletPath(), "退出成功");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    在这里插入图片描述

    7. ms-gateway 网关服务

    所有的请求先到网关,在网关中做身份认证

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>food-social-contact-parentartifactId>
            <groupId>com.imoocgroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>ms-gatewayartifactId>
    
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-gatewayartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            
            <dependency>
                <groupId>com.imoocgroupId>
                <artifactId>commonsartifactId>
                <version>1.0-SNAPSHOTversion>
                
                <exclusions>
                    <exclusion>
                        <groupId>com.battcngroupId>
                        <artifactId>swagger-spring-boot-starterartifactId>
                    exclusion>
                exclusions>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-configuration-processorartifactId>
                <optional>trueoptional>
            dependency>
        dependencies>
    
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    server:
      port: 80
    
    spring:
      application:
        name: ms-gateway
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true # 开启配置注册中心进行路由功能
              lower-case-service-id: true # 将服务名称转小写
          routes:
            - id: ms-diners
              uri: lb://ms-diners
              predicates:
                - Path=/diners/**
              filters:
                - StripPrefix=1
    
            - id: ms-oauth2-server
              uri: lb://ms-oauth2-server
              predicates:
                - Path=/auth/**
              filters:
                - StripPrefix=1
    
    secure:
      ignore:
        urls: # 配置白名单路径
          - /actuator/**
          - /auth/oauth/**
          - /diners/signin
    
    # 配置 Eureka Server 注册中心
    eureka:
      instance:
        prefer-ip-address: true
        instance-id: ${spring.cloud.client.ip-address}:${server.port}
      client:
        service-url:
          defaultZone: http://localhost:8080/eureka/
    
    logging:
      pattern:
        console: '%d{HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    1. 网关白名单配置 IgnoreUrlsConfig

    secure:
      ignore:
        urls: # 配置白名单路径
          - /actuator/**
          - /auth/oauth/**
          - /diners/signin
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    /**
     * 网关白名单配置
     */
    @Data
    @Component
    @ConfigurationProperties(prefix = "secure.ignore")
    public class IgnoreUrlsConfig {
        private List<String> urls;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2. RestTemplateConfiguration

    @Configuration
    public class RestTemplateConfiguration {
        
        @LoadBalanced
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3. 网关全局过滤器 AuthGlobalFilter

    /**
     * 网关全局过滤器
     */
    @Component
    public class AuthGlobalFilter implements GlobalFilter, Ordered {
    
        @Resource
        private IgnoreUrlsConfig ignoreUrlsConfig;
        @Resource
        private RestTemplate restTemplate;
        @Resource
        private HandleException handleException;
    
        /**
         * 身份校验处理
         *
         * @param exchange
         * @param chain
         * @return
         */
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 判断当前的请求是否在白名单中
            AntPathMatcher pathMatcher = new AntPathMatcher();
            boolean flag = false;
            String path = exchange.getRequest().getURI().getPath();
            for (String url : ignoreUrlsConfig.getUrls()) {
                if (pathMatcher.match(url, path)) {
                    flag = true;
                    break;
                }
            }
            // 白名单放行
            if (flag) {
                return chain.filter(exchange);
            }
            
            // 获取 access_token
            String access_token = exchange.getRequest().getQueryParams().getFirst("access_token");
            // 判断 access_token 是否为空
            if (StringUtils.isBlank(access_token)) {
                return handleException.writeError(exchange, "请登录");
            }
            // 校验 token 是否有效
            String checkTokenUrl = "http://ms-oauth2-server/oauth/check_token?token=".concat(access_token);
            try {
                // 发送远程请求,验证 token
                ResponseEntity<String> entity = restTemplate.getForEntity(checkTokenUrl, String.class);
                // token 无效的业务逻辑处理
                if (entity.getStatusCode() != HttpStatus.OK) {
                    return handleException.writeError(exchange,
                            "Token was not recognised, token: ".concat(access_token));
                }
                if (StringUtils.isBlank(entity.getBody())) {
                    return handleException.writeError(exchange,
                            "This token is invalid: ".concat(access_token));
                }
            } catch (Exception e) {
                return handleException.writeError(exchange,
                        "Token was not recognised, token: ".concat(access_token));
            }
            // 放行
            return chain.filter(exchange);
        }
    
        /**
         * 网关过滤器的排序,数字越小优先级越高
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    @Component
    public class HandleException {
    
        @Resource
        private ObjectMapper objectMapper;
    
        public Mono<Void> writeError(ServerWebExchange exchange, String error) {
    
            ServerHttpResponse response = exchange.getResponse();
            ServerHttpRequest request = exchange.getRequest();
            response.setStatusCode(HttpStatus.OK);
            response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    
            // 用户没有登录,请登录
            ResultInfo resultInfo = ResultInfoUtil.buildError(ApiConstant.NO_LOGIN_CODE, ApiConstant.NO_LOGIN_MESSAGE, request.getURI().getPath());
    
            String resultInfoJson = null;
            DataBuffer buffer = null;
            try {
                resultInfoJson = objectMapper.writeValueAsString(resultInfo);
                buffer =  response.bufferFactory().wrap(resultInfoJson.getBytes(Charset.forName("UTF-8")));
            } catch (JsonProcessingException ex) {
                ex.printStackTrace();
            }
    
            return response.writeWith(Mono.just(buffer));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    4. 启动项目测试

    所有的请求都经过网关,网关根据请求路径路由到指定的服务。网关路由到ms-dinners服务完成认证登录获取令牌token。

    在这里插入图片描述

    网关路由到 ms-oauth2-server 服务获取用户登录信息:

    在这里插入图片描述

    在这里插入图片描述

    8. 认证授权时序图

    image-20201115164057228.png

    image-20201115164136838.png

  • 相关阅读:
    Zenmap - 可视化Nmap的使用
    linux-网站服务
    阿斯达年代记三强争霸账号怎么注册 游戏账号注册教程分享
    I.MXU6LL制作烧录SD卡详细步骤
    python基于django的汽车租赁系统nodejs+vue+element
    美团——小美的用户名
    PyTorch实现注意力机制及使用方法汇总,附30篇attention论文
    20. 有效的括号
    Vue中的计算属性和方法有什么区别?
    一阶段Linux整理
  • 原文地址:https://blog.csdn.net/qq_42764468/article/details/127132520