6004 字
约 20 分钟
30
appendix_K
无标签

附录K 网关服务模块源码详解

模块概览(面向 0 基础读者):

  • 基于 Spring Cloud Gateway 统一接入、鉴权与限流。
  • 集成 Spring Security、JWT 对请求做集中验证。
  • 支持配置化路由、跨域、熔断以及审计日志。

核心目录:

  • xuecheng-plus-gateway/src/main/java/com/xuecheng/gateway/config
  • xuecheng-plus-gateway/src/main/resources
  • xuecheng-plus-gateway/pom.xml

文件:xuecheng-plus-gateway/main/com/xuecheng/gateway/config/GatewayAuthFilter.java

package com.xuecheng.gateway.config; // 声明类所在的包路径

import com.alibaba.fastjson.JSON; // 导入 JSON 类
import lombok.extern.slf4j.Slf4j; // 导入 Slf4j 类
import org.apache.commons.lang.StringUtils; // 导入 StringUtils 类
import org.springframework.beans.factory.annotation.Autowired; // 导入 Autowired 类
import org.springframework.cloud.gateway.filter.GatewayFilterChain; // 导入 GatewayFilterChain 类
import org.springframework.cloud.gateway.filter.GlobalFilter; // 导入 GlobalFilter 类
import org.springframework.core.Ordered; // 导入 Ordered 类
import org.springframework.core.io.buffer.DataBuffer; // 导入 DataBuffer 类
import org.springframework.http.HttpStatus; // 导入 HttpStatus 类
import org.springframework.http.server.reactive.ServerHttpResponse; // 导入 ServerHttpResponse 类
import org.springframework.security.oauth2.common.OAuth2AccessToken; // 导入 OAuth2AccessToken 类
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; // 导入 InvalidTokenException 类
import org.springframework.security.oauth2.provider.token.TokenStore; // 导入 TokenStore 类
import org.springframework.stereotype.Component; // 导入 Component 类
import org.springframework.util.AntPathMatcher; // 导入 AntPathMatcher 类
import org.springframework.web.server.ServerWebExchange; // 导入 ServerWebExchange 类
import reactor.core.publisher.Mono; // 导入 Mono 类

import java.io.InputStream; // 导入 InputStream 类
import java.nio.charset.StandardCharsets; // 导入 StandardCharsets 类
import java.util.ArrayList; // 导入 ArrayList 类
import java.util.List; // 导入 List 类
import java.util.Properties; // 导入 Properties 类
import java.util.Set; // 导入 Set 类

/** // 保留原作者写的块注释
 * @author Mr.M // 保留原作者写的块注释
 * @version 1.0 // 保留原作者写的块注释
 * @description 网关认证过虑器 // 保留原作者写的块注释
 * @date 2022/9/27 12:10 // 保留原作者写的块注释
 */ // 保留原作者写的块注释
@Component // 使用注解 @Component
@Slf4j // 使用注解 @Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered { // 定义 class GatewayAuthFilter


    //白名单 // 保留原作者写的行内注释
    private static List<String> whitelist = null; // 把变量 whitelist 初始化为 null

    static { // 代码逻辑
        //加载白名单 // 保留原作者写的行内注释
        try ( // 尝试执行可能抛出异常的代码
                InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties"); // 把 GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties") 赋值给 resourceAsStream
        ) { // 代码逻辑
            Properties properties = new Properties(); // 创建 Properties 对象并赋值给 properties
            properties.load(resourceAsStream); // 执行语句
            Set<String> strings = properties.stringPropertyNames(); // 把 properties.stringPropertyNames() 的返回值赋给 strings
            whitelist= new ArrayList<>(strings); // 创建 ArrayList<> 对象并赋值给 whitelist

        } catch (Exception e) { // 定义方法 catch(...)
            log.error("加载/security-whitelist.properties出错:{}",e.getMessage()); // 执行语句
            e.printStackTrace(); // 执行语句
        } // 代码块结束


    } // 代码块结束

    @Autowired // 使用注解 @Autowired
    private TokenStore tokenStore; // 执行语句


    @Override // 使用注解 @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 定义方法 filter(...)
        //请求的url // 保留原作者写的行内注释
        String requestUrl = exchange.getRequest().getPath().value(); // 把 exchange.getRequest().getPath().value() 的返回值赋给 requestUrl
        AntPathMatcher pathMatcher = new AntPathMatcher(); // 创建 AntPathMatcher 对象并赋值给 pathMatcher
        //白名单放行 // 保留原作者写的行内注释
        for (String url : whitelist) { // 定义方法 方法(...)
            if (pathMatcher.match(url, requestUrl)) { // 定义方法 方法(...)
                return chain.filter(exchange); // 返回 chain.filter(exchange)
            } // 代码块结束
        } // 代码块结束

        //检查token是否存在 // 保留原作者写的行内注释
        String token = getToken(exchange); // 把 getToken(exchange) 赋值给 token
        if (StringUtils.isBlank(token)) { // 定义方法 方法(...)
            return buildReturnMono("没有认证",exchange); // 返回 buildReturnMono("没有认证",exchange)
        } // 代码块结束
        //判断是否是有效的token // 保留原作者写的行内注释
        OAuth2AccessToken oAuth2AccessToken; // 执行语句
        try { // 尝试执行可能抛出异常的代码
            oAuth2AccessToken = tokenStore.readAccessToken(token); // 把 tokenStore.readAccessToken(token) 赋值给 oAuth2AccessToken

            boolean expired = oAuth2AccessToken.isExpired(); // 把 oAuth2AccessToken.isExpired() 的返回值赋给 expired
            if (expired) { // 定义方法 方法(...)
                return buildReturnMono("认证令牌已过期",exchange); // 返回 buildReturnMono("认证令牌已过期",exchange)
            } // 代码块结束
            return chain.filter(exchange); // 返回 chain.filter(exchange)
        } catch (InvalidTokenException e) { // 定义方法 catch(...)
            log.info("认证令牌无效: {}", token); // 执行语句
            return buildReturnMono("认证令牌无效",exchange); // 返回 buildReturnMono("认证令牌无效",exchange)
        } // 代码块结束

    } // 代码块结束

    /** // 保留原作者写的块注释
     * 获取token // 保留原作者写的块注释
     */ // 保留原作者写的块注释
    private String getToken(ServerWebExchange exchange) { // 定义方法 getToken(...)
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization"); // 把 exchange.getRequest().getHeaders().getFirst("Authorization") 赋值给 tokenStr
        if (StringUtils.isBlank(tokenStr)) { // 定义方法 方法(...)
            return null; // 返回 null
        } // 代码块结束
        String token = tokenStr.split(" ")[1]; // 把 tokenStr.split(" ")[1] 赋值给 token
        if (StringUtils.isBlank(token)) { // 定义方法 方法(...)
            return null; // 返回 null
        } // 代码块结束
        return token; // 返回 token
    } // 代码块结束




    private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) { // 定义方法 buildReturnMono(...)
        ServerHttpResponse response = exchange.getResponse(); // 把 exchange.getResponse() 的返回值赋给 response
        String jsonString = JSON.toJSONString(new RestErrorResponse(error)); // 把 JSON.toJSONString(new RestErrorResponse(error)) 赋值给 jsonString
        byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8); // 把 jsonString.getBytes(StandardCharsets.UTF_8) 赋值给 bits
        DataBuffer buffer = response.bufferFactory().wrap(bits); // 把 response.bufferFactory().wrap(bits) 赋值给 buffer
        response.setStatusCode(HttpStatus.UNAUTHORIZED); // 执行语句
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); // 把 UTF-8") 赋值给 "application/json;charset
        return response.writeWith(Mono.just(buffer)); // 返回 response.writeWith(Mono.just(buffer))
    } // 代码块结束


    @Override // 使用注解 @Override
    public int getOrder() { // 定义方法 getOrder(...)
        return 0; // 返回 0
    } // 代码块结束
} // 代码块结束

文件:xuecheng-plus-gateway/main/com/xuecheng/gateway/config/RestErrorResponse.java

package com.xuecheng.gateway.config; // 声明类所在的包路径

import java.io.Serializable; // 导入 Serializable 类

/** // 保留原作者写的块注释
 * 错误响应参数包装 // 保留原作者写的块注释
 */ // 保留原作者写的块注释
public class RestErrorResponse implements Serializable { // 定义 class RestErrorResponse

    private String errMessage; // 执行语句

    public RestErrorResponse(String errMessage){ // 定义方法 RestErrorResponse(...)
        this.errMessage= errMessage; // 把 errMessage 赋值给 this.errMessage
    } // 代码块结束

    public String getErrMessage() { // 定义方法 getErrMessage(...)
        return errMessage; // 返回 errMessage
    } // 代码块结束

    public void setErrMessage(String errMessage) { // 定义方法 setErrMessage(...)
        this.errMessage = errMessage; // 把 errMessage 赋值给 this.errMessage
    } // 代码块结束
} // 代码块结束

文件:xuecheng-plus-gateway/main/com/xuecheng/gateway/config/SecurityConfig.java

package com.xuecheng.gateway.config; // 声明类所在的包路径

import org.springframework.context.annotation.Bean; // 导入 Bean 类
import org.springframework.context.annotation.Configuration; // 导入 Configuration 类
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; // 导入 EnableWebFluxSecurity 类
import org.springframework.security.config.web.server.ServerHttpSecurity; // 导入 ServerHttpSecurity 类
import org.springframework.security.web.server.SecurityWebFilterChain; // 导入 SecurityWebFilterChain 类

/** // 保留原作者写的块注释
 * @description 安全配置类 // 保留原作者写的块注释
 * @author Mr.M // 保留原作者写的块注释
 * @date 2022/9/27 12:07 // 保留原作者写的块注释
 * @version 1.0 // 保留原作者写的块注释
 */ // 保留原作者写的块注释
 @EnableWebFluxSecurity // 使用注解 @EnableWebFluxSecurity
 @Configuration // 使用注解 @Configuration
 public class SecurityConfig { // 定义 class SecurityConfig


  //安全拦截配置 // 保留原作者写的行内注释
  @Bean // 使用注解 @Bean
  public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) { // 定义方法 webFluxSecurityFilterChain(...)

   return http.authorizeExchange() // 返回 http.authorizeExchange()
           .pathMatchers("/**").permitAll() // 代码逻辑
           .anyExchange().authenticated() // 代码逻辑
           .and().csrf().disable().build(); // 执行语句
  } // 代码块结束


 } // 代码块结束

文件:xuecheng-plus-gateway/main/com/xuecheng/gateway/config/TokenConfig.java

package com.xuecheng.gateway.config; // 声明类所在的包路径

import org.springframework.beans.factory.annotation.Autowired; // 导入 Autowired 类
import org.springframework.context.annotation.Bean; // 导入 Bean 类
import org.springframework.context.annotation.Configuration; // 导入 Configuration 类
import org.springframework.security.oauth2.provider.token.TokenStore; // 导入 TokenStore 类
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; // 导入 JwtAccessTokenConverter 类
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; // 导入 JwtTokenStore 类

/** // 保留原作者写的块注释
 * @author Administrator // 保留原作者写的块注释
 * @version 1.0 // 保留原作者写的块注释
 **/ // 保留原作者写的块注释
@Configuration // 使用注解 @Configuration
public class TokenConfig { // 定义 class TokenConfig

    String SIGNING_KEY = "mq123"; // 把 "mq123" 赋值给 SIGNING_KEY


//    @Bean // 保留原作者写的行内注释
//    public TokenStore tokenStore() { // 保留原作者写的行内注释
//        //使用内存存储令牌(普通令牌) // 保留原作者写的行内注释
//        return new InMemoryTokenStore(); // 保留原作者写的行内注释
//    } // 保留原作者写的行内注释

    @Autowired // 使用注解 @Autowired
    private JwtAccessTokenConverter accessTokenConverter; // 执行语句

    @Bean // 使用注解 @Bean
    public TokenStore tokenStore() { // 定义方法 tokenStore(...)
        return new JwtTokenStore(accessTokenConverter()); // 返回 new JwtTokenStore(accessTokenConverter())
    } // 代码块结束

    @Bean // 使用注解 @Bean
    public JwtAccessTokenConverter accessTokenConverter() { // 定义方法 accessTokenConverter(...)
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 创建 JwtAccessTokenConverter 对象并赋值给 converter
        converter.setSigningKey(SIGNING_KEY); // 执行语句
        return converter; // 返回 converter
    } // 代码块结束


} // 代码块结束

文件:xuecheng-plus-gateway/main/com/xuecheng/GatewayApplication.java

package com.xuecheng; // 声明类所在的包路径

import org.springframework.boot.SpringApplication; // 导入 SpringApplication 类
import org.springframework.boot.autoconfigure.SpringBootApplication; // 导入 SpringBootApplication 类

@SpringBootApplication // 使用注解 @SpringBootApplication
public class GatewayApplication { // 定义 class GatewayApplication

    public static void main(String[] args) { // 定义方法 main(...)
        SpringApplication.run(GatewayApplication.class, args); // 执行语句
    } // 代码块结束

} // 代码块结束

文件:xuecheng-plus-gateway/pom.xml

<?xml version="1.0" encoding="UTF-8"?> <!-- XML 声明 -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <!-- SQL 语句片段 -->
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <!-- SQL 语句片段 -->
    <modelVersion>4.0.0</modelVersion> <!-- SQL 语句片段 -->
    <parent> <!-- SQL 语句片段 -->
        <groupId>com.xuecheng</groupId> <!-- SQL 语句片段 -->
        <artifactId>xuecheng-plus-parent</artifactId> <!-- SQL 语句片段 -->
        <version>0.0.1-SNAPSHOT</version> <!-- SQL 语句片段 -->
        <relativePath>../xuecheng-plus-parent</relativePath> <!-- SQL 语句片段 -->
    </parent> <!-- SQL 语句片段 -->
    <artifactId>xuecheng-plus-gateway</artifactId> <!-- SQL 语句片段 -->

    <dependencies> <!-- SQL 语句片段 -->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>org.springframework.cloud</groupId> <!-- SQL 语句片段 -->
            <artifactId>spring-cloud-starter-security</artifactId> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>org.springframework.cloud</groupId> <!-- SQL 语句片段 -->
            <artifactId>spring-cloud-starter-oauth2</artifactId> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>org.projectlombok</groupId> <!-- SQL 语句片段 -->
            <artifactId>lombok</artifactId> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>com.alibaba</groupId> <!-- SQL 语句片段 -->
            <artifactId>fastjson</artifactId> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->
        <!--网关-->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>org.springframework.cloud</groupId> <!-- SQL 语句片段 -->
            <artifactId>spring-cloud-starter-gateway</artifactId> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->

        <!--服务发现中心-->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>com.alibaba.cloud</groupId> <!-- SQL 语句片段 -->
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>com.alibaba.cloud</groupId> <!-- SQL 语句片段 -->
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>com.alibaba</groupId> <!-- SQL 语句片段 -->
            <artifactId>fastjson</artifactId> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>org.projectlombok</groupId> <!-- SQL 语句片段 -->
            <artifactId>lombok</artifactId> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->
        <!-- 排除 Spring Boot 依赖的日志包冲突 -->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>org.springframework.boot</groupId> <!-- SQL 语句片段 -->
            <artifactId>spring-boot-starter</artifactId> <!-- SQL 语句片段 -->
            <exclusions> <!-- SQL 语句片段 -->
                <exclusion> <!-- SQL 语句片段 -->
                    <groupId>org.springframework.boot</groupId> <!-- SQL 语句片段 -->
                    <artifactId>spring-boot-starter-logging</artifactId> <!-- SQL 语句片段 -->
                </exclusion> <!-- SQL 语句片段 -->
            </exclusions> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->

        <!-- Spring Boot 集成 log4j2 -->
        <dependency> <!-- SQL 语句片段 -->
            <groupId>org.springframework.boot</groupId> <!-- SQL 语句片段 -->
            <artifactId>spring-boot-starter-log4j2</artifactId> <!-- SQL 语句片段 -->
        </dependency> <!-- SQL 语句片段 -->


    </dependencies> <!-- SQL 语句片段 -->

    <build> <!-- SQL 语句片段 -->
        <finalName>${project.artifactId}-${project.version}</finalName> <!-- SQL 语句片段 -->
        <plugins> <!-- SQL 语句片段 -->
            <plugin> <!-- SQL 语句片段 -->
                <groupId>org.springframework.boot</groupId> <!-- SQL 语句片段 -->
                <artifactId>spring-boot-maven-plugin</artifactId> <!-- SQL 语句片段 -->
                <version>${spring-boot.version}</version> <!-- SQL 语句片段 -->
                <executions> <!-- SQL 语句片段 -->
                    <execution> <!-- SQL 语句片段 -->
                        <goals> <!-- SQL 语句片段 -->
                            <goal>repackage</goal> <!-- SQL 语句片段 -->
                        </goals> <!-- SQL 语句片段 -->
                    </execution> <!-- SQL 语句片段 -->
                </executions> <!-- SQL 语句片段 -->
            </plugin> <!-- SQL 语句片段 -->
            <plugin> <!-- SQL 语句片段 -->
                <groupId>com.spotify</groupId> <!-- SQL 语句片段 -->
                <artifactId>docker-maven-plugin</artifactId> <!-- SQL 语句片段 -->
                <version>1.2.2</version> <!-- SQL 语句片段 -->
                <configuration> <!-- SQL 语句片段 -->
                    <!--修改imageName节点的内容,改为私有仓库地址和端口,再加上镜像id和 TAG,我们要直接传到私服-->
                    <!--配置最后生成的镜像名,docker images里的,我们这边取项目名:版本-->
                    <!--<imageName>${project.artifactId}:${project.version}</imageName>-->
                    <imageName>192.168.101.65:5000/${project.artifactId}:${project.version}</imageName> <!-- SQL 语句片段 -->
                    <!--也可以通过以下方式定义image的tag信息。 -->
                    <!-- <imageTags>
                         <imageTag>${project.version}</imageTag> <!-- SQL 语句片段 -->
                         &lt;!&ndash;build 时强制覆盖 tag,配合 imageTags 使用&ndash;&gt; <!-- SQL 语句片段 -->
                         <forceTags>true</forceTags> <!-- SQL 语句片段 -->
                         &lt;!&ndash;build 完成后,push 指定 tag 的镜像,配合 imageTags 使用&ndash;&gt; <!-- SQL 语句片段 -->
                         <pushImageTag>true</pushImageTag> <!-- SQL 语句片段 -->
                     </imageTags>--> <!-- SQL 语句片段 -->
                    <baseImage>java:8u20</baseImage> <!-- SQL 语句片段 -->
                    <maintainer>docker_maven [email protected]</maintainer> <!-- SQL 语句片段 -->
                    <workdir>/root</workdir> <!-- SQL 语句片段 -->
                    <cmd>["java", "-version"]</cmd> <!-- SQL 语句片段 -->
                    <!--来指明Dockerfile文件的所在目录,如果配置了dockerDirectory则忽略baseImage,maintainer等配置-->
                    <!--<dockerDirectory>./</dockerDirectory>-->
                    <!--2375是docker的远程端口,插件生成镜像时连接docker,这里需要指定docker远程端口-->
                    <dockerHost>http://192.168.101.65:2375</dockerHost> <!-- SQL 语句片段 -->
                    <!--入口点,project.build.finalName就是project标签下的build标签下 的filename标签内容,testDocker-->
                    <!--相当于启动容器后,会自动执行java -jar ...-->
                    <entryPoint>["java", "-Dfile.encoding=utf-8","-jar", "/root/${project.build.finalName}.jar"]</entryPoint> <!-- SQL 语句片段 -->
                    <!--是否推送到docker私有仓库,旧版本插件要配置maven的settings文件。 -->
                    <pushImage>true</pushImage> <!-- SQL 语句片段 -->
                    <registryUrl>192.168.101.65:5000</registryUrl>  <!-- 这里是复制 jar 包到 docker 容器指定目录配置 --> <!-- SQL 语句片段 -->
                    <resources> <!-- SQL 语句片段 -->
                        <resource> <!-- SQL 语句片段 -->
                            <targetPath>/root</targetPath> <!-- SQL 语句片段 -->
                            <directory>${project.build.directory}</directory> <!-- SQL 语句片段 -->
                            <!--把哪个文件上传到docker,相当于Dockerfile里的add app.jar /-->
                            <include>${project.build.finalName}.jar</include> <!-- 引用公共 SQL 片段 -->
                        </resource> <!-- SQL 语句片段 -->
                    </resources> <!-- SQL 语句片段 -->
                </configuration> <!-- SQL 语句片段 -->
            </plugin> <!-- SQL 语句片段 -->
        </plugins> <!-- SQL 语句片段 -->
    </build> <!-- SQL 语句片段 -->
</project> <!-- SQL 语句片段 -->

文件:xuecheng-plus-gateway/resources/bootstrap.yml

#微服务配置
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.101.65:8848
      discovery:
        namespace: dev402
        group: xuecheng-plus-project
      config:
        namespace: dev402
        group: xuecheng-plus-project
        file-extension: yaml
        refresh-enabled: true
        shared-configs:
          - data-id: logging-${spring.profiles.active}.yaml
            group: xuecheng-plus-common
            refresh: true


  profiles:
    active: dev

文件:xuecheng-plus-gateway/resources/log4j2-dev.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="180" packages="">
    <properties>
        <property name="logdir">logs</property>
        <property name="PATTERN">%date{YYYY-MM-dd HH:mm:ss,SSS} %level [%thread][%file:%line] - %msg%n%throwable</property>
    </properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${PATTERN}"/>
        </Console>

        <RollingFile name="ErrorAppender" fileName="${logdir}/error.log"
            filePattern="${logdir}/$${date:yyyy-MM-dd}/error.%d{yyyy-MM-dd-HH}.log" append="true">
            <PatternLayout pattern="${PATTERN}"/>
            <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
            </Policies>
        </RollingFile>

        <RollingFile name="DebugAppender" fileName="${logdir}/info.log"
            filePattern="${logdir}/$${date:yyyy-MM-dd}/info.%d{yyyy-MM-dd-HH}.log" append="true">
            <PatternLayout pattern="${PATTERN}"/>
            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true" />
            </Policies>
        </RollingFile>
        
        <!--异步appender-->
         <Async name="AsyncAppender" includeLocation="true">
            <AppenderRef ref="ErrorAppender"/>
            <AppenderRef ref="DebugAppender"/>
        </Async>
    </Appenders>
    
    <Loggers>
         <!--过滤掉spring和mybatis的一些无用的debug信息-->
        <logger name="org.springframework" level="INFO">
        </logger>
        <logger name="org.mybatis" level="INFO">
        </logger>
        <logger name="cn.itcast.wanxinp2p.consumer.mapper" level="DEBUG">
        </logger>

        <logger name="springfox" level="INFO">
        </logger>
		<logger name="org.apache.http" level="INFO">
        </logger>
        <logger name="com.netflix.discovery" level="INFO">
        </logger>
        
        <logger name="RocketmqCommon"  level="INFO" >
		</logger>
		
		<logger name="RocketmqRemoting" level="INFO"  >
		</logger>
		
		<logger name="RocketmqClient" level="WARN">
		</logger>

        <logger name="org.dromara.hmily" level="WARN">
        </logger>

        <logger name="org.dromara.hmily.lottery" level="WARN">
        </logger>

        <logger name="org.dromara.hmily.bonuspoint" level="WARN">
        </logger>
		
        <!--OFF   0-->
        <!--FATAL   100-->
        <!--ERROR   200-->
        <!--WARN   300-->
        <!--INFO   400-->
        <!--DEBUG   500-->
        <!--TRACE   600-->
        <!--ALL   Integer.MAX_VALUE-->
        <Root level="DEBUG" includeLocation="true">
            <AppenderRef ref="AsyncAppender"/>
            <AppenderRef ref="Console"/>
            <AppenderRef ref="DebugAppender"/>
        </Root>
    </Loggers>
</Configuration>

文件:xuecheng-plus-gateway/resources/security-whitelist.properties

/**=\u4E34\u65F6\u5168\u90E8\u6253\u5F00
/auth/**=\u8BA4\u8BC1\u5730\u5740
/content/open/**=\u5185\u5BB9\u7BA1\u7406\u516C\u5F00\u8BBF\u95EE\u63A5\u53E3
/media/open/**=\u5A92\u8D44\u7BA1\u7406\u516C\u5F00\u8BBF\u95EE\u63A5\u53E3

文件:xuecheng-plus-gateway/src/main/java/com/xuecheng/gateway/config/GatewayAuthFilter.java

package com.xuecheng.gateway.config; // 声明类所在的包路径

import com.alibaba.fastjson.JSON; // 导入 JSON 类
import lombok.extern.slf4j.Slf4j; // 导入 Slf4j 类
import org.apache.commons.lang.StringUtils; // 导入 StringUtils 类
import org.springframework.beans.factory.annotation.Autowired; // 导入 Autowired 类
import org.springframework.cloud.gateway.filter.GatewayFilterChain; // 导入 GatewayFilterChain 类
import org.springframework.cloud.gateway.filter.GlobalFilter; // 导入 GlobalFilter 类
import org.springframework.core.Ordered; // 导入 Ordered 类
import org.springframework.core.io.buffer.DataBuffer; // 导入 DataBuffer 类
import org.springframework.http.HttpStatus; // 导入 HttpStatus 类
import org.springframework.http.server.reactive.ServerHttpResponse; // 导入 ServerHttpResponse 类
import org.springframework.security.oauth2.common.OAuth2AccessToken; // 导入 OAuth2AccessToken 类
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; // 导入 InvalidTokenException 类
import org.springframework.security.oauth2.provider.token.TokenStore; // 导入 TokenStore 类
import org.springframework.stereotype.Component; // 导入 Component 类
import org.springframework.util.AntPathMatcher; // 导入 AntPathMatcher 类
import org.springframework.web.server.ServerWebExchange; // 导入 ServerWebExchange 类
import reactor.core.publisher.Mono; // 导入 Mono 类

import java.io.InputStream; // 导入 InputStream 类
import java.nio.charset.StandardCharsets; // 导入 StandardCharsets 类
import java.util.ArrayList; // 导入 ArrayList 类
import java.util.List; // 导入 List 类
import java.util.Properties; // 导入 Properties 类
import java.util.Set; // 导入 Set 类

/** // 保留原作者写的块注释
 * @author Mr.M // 保留原作者写的块注释
 * @version 1.0 // 保留原作者写的块注释
 * @description 网关认证过虑器 // 保留原作者写的块注释
 * @date 2022/9/27 12:10 // 保留原作者写的块注释
 */ // 保留原作者写的块注释
@Component // 使用注解 @Component
@Slf4j // 使用注解 @Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered { // 定义 class GatewayAuthFilter


    //白名单 // 保留原作者写的行内注释
    private static List<String> whitelist = null; // 把变量 whitelist 初始化为 null

    static { // 代码逻辑
        //加载白名单 // 保留原作者写的行内注释
        try ( // 尝试执行可能抛出异常的代码
                InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties"); // 把 GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties") 赋值给 resourceAsStream
        ) { // 代码逻辑
            Properties properties = new Properties(); // 创建 Properties 对象并赋值给 properties
            properties.load(resourceAsStream); // 执行语句
            Set<String> strings = properties.stringPropertyNames(); // 把 properties.stringPropertyNames() 的返回值赋给 strings
            whitelist= new ArrayList<>(strings); // 创建 ArrayList<> 对象并赋值给 whitelist

        } catch (Exception e) { // 定义方法 catch(...)
            log.error("加载/security-whitelist.properties出错:{}",e.getMessage()); // 执行语句
            e.printStackTrace(); // 执行语句
        } // 代码块结束


    } // 代码块结束

    @Autowired // 使用注解 @Autowired
    private TokenStore tokenStore; // 执行语句


    @Override // 使用注解 @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 定义方法 filter(...)
        //请求的url // 保留原作者写的行内注释
        String requestUrl = exchange.getRequest().getPath().value(); // 把 exchange.getRequest().getPath().value() 的返回值赋给 requestUrl
        AntPathMatcher pathMatcher = new AntPathMatcher(); // 创建 AntPathMatcher 对象并赋值给 pathMatcher
        //白名单放行 // 保留原作者写的行内注释
        for (String url : whitelist) { // 定义方法 方法(...)
            if (pathMatcher.match(url, requestUrl)) { // 定义方法 方法(...)
                return chain.filter(exchange); // 返回 chain.filter(exchange)
            } // 代码块结束
        } // 代码块结束

        //检查token是否存在 // 保留原作者写的行内注释
        String token = getToken(exchange); // 把 getToken(exchange) 赋值给 token
        if (StringUtils.isBlank(token)) { // 定义方法 方法(...)
            return buildReturnMono("没有认证",exchange); // 返回 buildReturnMono("没有认证",exchange)
        } // 代码块结束
        //判断是否是有效的token // 保留原作者写的行内注释
        OAuth2AccessToken oAuth2AccessToken; // 执行语句
        try { // 尝试执行可能抛出异常的代码
            oAuth2AccessToken = tokenStore.readAccessToken(token); // 把 tokenStore.readAccessToken(token) 赋值给 oAuth2AccessToken

            boolean expired = oAuth2AccessToken.isExpired(); // 把 oAuth2AccessToken.isExpired() 的返回值赋给 expired
            if (expired) { // 定义方法 方法(...)
                return buildReturnMono("认证令牌已过期",exchange); // 返回 buildReturnMono("认证令牌已过期",exchange)
            } // 代码块结束
            return chain.filter(exchange); // 返回 chain.filter(exchange)
        } catch (InvalidTokenException e) { // 定义方法 catch(...)
            log.info("认证令牌无效: {}", token); // 执行语句
            return buildReturnMono("认证令牌无效",exchange); // 返回 buildReturnMono("认证令牌无效",exchange)
        } // 代码块结束

    } // 代码块结束

    /** // 保留原作者写的块注释
     * 获取token // 保留原作者写的块注释
     */ // 保留原作者写的块注释
    private String getToken(ServerWebExchange exchange) { // 定义方法 getToken(...)
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization"); // 把 exchange.getRequest().getHeaders().getFirst("Authorization") 赋值给 tokenStr
        if (StringUtils.isBlank(tokenStr)) { // 定义方法 方法(...)
            return null; // 返回 null
        } // 代码块结束
        String token = tokenStr.split(" ")[1]; // 把 tokenStr.split(" ")[1] 赋值给 token
        if (StringUtils.isBlank(token)) { // 定义方法 方法(...)
            return null; // 返回 null
        } // 代码块结束
        return token; // 返回 token
    } // 代码块结束




    private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) { // 定义方法 buildReturnMono(...)
        ServerHttpResponse response = exchange.getResponse(); // 把 exchange.getResponse() 的返回值赋给 response
        String jsonString = JSON.toJSONString(new RestErrorResponse(error)); // 把 JSON.toJSONString(new RestErrorResponse(error)) 赋值给 jsonString
        byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8); // 把 jsonString.getBytes(StandardCharsets.UTF_8) 赋值给 bits
        DataBuffer buffer = response.bufferFactory().wrap(bits); // 把 response.bufferFactory().wrap(bits) 赋值给 buffer
        response.setStatusCode(HttpStatus.UNAUTHORIZED); // 执行语句
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); // 把 UTF-8") 赋值给 "application/json;charset
        return response.writeWith(Mono.just(buffer)); // 返回 response.writeWith(Mono.just(buffer))
    } // 代码块结束


    @Override // 使用注解 @Override
    public int getOrder() { // 定义方法 getOrder(...)
        return 0; // 返回 0
    } // 代码块结束
} // 代码块结束

文件:xuecheng-plus-gateway/src/main/java/com/xuecheng/gateway/config/RestErrorResponse.java

package com.xuecheng.gateway.config; // 声明类所在的包路径

import java.io.Serializable; // 导入 Serializable 类

/** // 保留原作者写的块注释
 * 错误响应参数包装 // 保留原作者写的块注释
 */ // 保留原作者写的块注释
public class RestErrorResponse implements Serializable { // 定义 class RestErrorResponse

    private String errMessage; // 执行语句

    public RestErrorResponse(String errMessage){ // 定义方法 RestErrorResponse(...)
        this.errMessage= errMessage; // 把 errMessage 赋值给 this.errMessage
    } // 代码块结束

    public String getErrMessage() { // 定义方法 getErrMessage(...)
        return errMessage; // 返回 errMessage
    } // 代码块结束

    public void setErrMessage(String errMessage) { // 定义方法 setErrMessage(...)
        this.errMessage = errMessage; // 把 errMessage 赋值给 this.errMessage
    } // 代码块结束
} // 代码块结束

文件:xuecheng-plus-gateway/src/main/java/com/xuecheng/gateway/config/SecurityConfig.java

package com.xuecheng.gateway.config; // 声明类所在的包路径

import org.springframework.context.annotation.Bean; // 导入 Bean 类
import org.springframework.context.annotation.Configuration; // 导入 Configuration 类
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; // 导入 EnableWebFluxSecurity 类
import org.springframework.security.config.web.server.ServerHttpSecurity; // 导入 ServerHttpSecurity 类
import org.springframework.security.web.server.SecurityWebFilterChain; // 导入 SecurityWebFilterChain 类

/** // 保留原作者写的块注释
 * @description 安全配置类 // 保留原作者写的块注释
 * @author Mr.M // 保留原作者写的块注释
 * @date 2022/9/27 12:07 // 保留原作者写的块注释
 * @version 1.0 // 保留原作者写的块注释
 */ // 保留原作者写的块注释
 @EnableWebFluxSecurity // 使用注解 @EnableWebFluxSecurity
 @Configuration // 使用注解 @Configuration
 public class SecurityConfig { // 定义 class SecurityConfig


  //安全拦截配置 // 保留原作者写的行内注释
  @Bean // 使用注解 @Bean
  public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) { // 定义方法 webFluxSecurityFilterChain(...)

   return http.authorizeExchange() // 返回 http.authorizeExchange()
           .pathMatchers("/**").permitAll() // 代码逻辑
           .anyExchange().authenticated() // 代码逻辑
           .and().csrf().disable().build(); // 执行语句
  } // 代码块结束


 } // 代码块结束

文件:xuecheng-plus-gateway/src/main/java/com/xuecheng/gateway/config/TokenConfig.java

package com.xuecheng.gateway.config; // 声明类所在的包路径

import org.springframework.beans.factory.annotation.Autowired; // 导入 Autowired 类
import org.springframework.context.annotation.Bean; // 导入 Bean 类
import org.springframework.context.annotation.Configuration; // 导入 Configuration 类
import org.springframework.security.oauth2.provider.token.TokenStore; // 导入 TokenStore 类
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; // 导入 JwtAccessTokenConverter 类
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; // 导入 JwtTokenStore 类

/** // 保留原作者写的块注释
 * @author Administrator // 保留原作者写的块注释
 * @version 1.0 // 保留原作者写的块注释
 **/ // 保留原作者写的块注释
@Configuration // 使用注解 @Configuration
public class TokenConfig { // 定义 class TokenConfig

    String SIGNING_KEY = "mq123"; // 把 "mq123" 赋值给 SIGNING_KEY


//    @Bean // 保留原作者写的行内注释
//    public TokenStore tokenStore() { // 保留原作者写的行内注释
//        //使用内存存储令牌(普通令牌) // 保留原作者写的行内注释
//        return new InMemoryTokenStore(); // 保留原作者写的行内注释
//    } // 保留原作者写的行内注释

    @Autowired // 使用注解 @Autowired
    private JwtAccessTokenConverter accessTokenConverter; // 执行语句

    @Bean // 使用注解 @Bean
    public TokenStore tokenStore() { // 定义方法 tokenStore(...)
        return new JwtTokenStore(accessTokenConverter()); // 返回 new JwtTokenStore(accessTokenConverter())
    } // 代码块结束

    @Bean // 使用注解 @Bean
    public JwtAccessTokenConverter accessTokenConverter() { // 定义方法 accessTokenConverter(...)
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 创建 JwtAccessTokenConverter 对象并赋值给 converter
        converter.setSigningKey(SIGNING_KEY); // 执行语句
        return converter; // 返回 converter
    } // 代码块结束


} // 代码块结束

文件:xuecheng-plus-gateway/src/main/java/com/xuecheng/GatewayApplication.java

package com.xuecheng; // 声明类所在的包路径

import org.springframework.boot.SpringApplication; // 导入 SpringApplication 类
import org.springframework.boot.autoconfigure.SpringBootApplication; // 导入 SpringBootApplication 类

@SpringBootApplication // 使用注解 @SpringBootApplication
public class GatewayApplication { // 定义 class GatewayApplication

    public static void main(String[] args) { // 定义方法 main(...)
        SpringApplication.run(GatewayApplication.class, args); // 执行语句
    } // 代码块结束

} // 代码块结束

文件:xuecheng-plus-gateway/src/main/resources/bootstrap.yml

#微服务配置
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.101.65:8848
      discovery:
        namespace: dev402
        group: xuecheng-plus-project
      config:
        namespace: dev402
        group: xuecheng-plus-project
        file-extension: yaml
        refresh-enabled: true
        shared-configs:
          - data-id: logging-${spring.profiles.active}.yaml
            group: xuecheng-plus-common
            refresh: true


  profiles:
    active: dev

文件:xuecheng-plus-gateway/src/main/resources/log4j2-dev.xml

<?xml version="1.0" encoding="UTF-8"?> <!-- XML 声明 -->
<Configuration monitorInterval="180" packages=""> <!-- SQL 语句片段 -->
    <properties> <!-- SQL 语句片段 -->
        <property name="logdir">logs</property> <!-- SQL 语句片段 -->
        <property name="PATTERN">%date{YYYY-MM-dd HH:mm:ss,SSS} %level [%thread][%file:%line] - %msg%n%throwable</property> <!-- SQL 语句片段 -->
    </properties> <!-- SQL 语句片段 -->
    <Appenders> <!-- SQL 语句片段 -->
        <Console name="Console" target="SYSTEM_OUT"> <!-- SQL 语句片段 -->
            <PatternLayout pattern="${PATTERN}"/> <!-- SQL 语句片段 -->
        </Console> <!-- SQL 语句片段 -->

        <RollingFile name="ErrorAppender" fileName="${logdir}/error.log" <!-- SQL 语句片段 -->
            filePattern="${logdir}/$${date:yyyy-MM-dd}/error.%d{yyyy-MM-dd-HH}.log" append="true"> <!-- SQL 语句片段 -->
            <PatternLayout pattern="${PATTERN}"/> <!-- SQL 语句片段 -->
            <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/> <!-- SQL 语句片段 -->
            <Policies> <!-- SQL 语句片段 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <!-- SQL 语句片段 -->
            </Policies> <!-- SQL 语句片段 -->
        </RollingFile> <!-- SQL 语句片段 -->

        <RollingFile name="DebugAppender" fileName="${logdir}/info.log" <!-- SQL 语句片段 -->
            filePattern="${logdir}/$${date:yyyy-MM-dd}/info.%d{yyyy-MM-dd-HH}.log" append="true"> <!-- SQL 语句片段 -->
            <PatternLayout pattern="${PATTERN}"/> <!-- SQL 语句片段 -->
            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/> <!-- SQL 语句片段 -->
            <Policies> <!-- SQL 语句片段 -->
                <TimeBasedTriggeringPolicy interval="1" modulate="true" /> <!-- SQL 语句片段 -->
            </Policies> <!-- SQL 语句片段 -->
        </RollingFile> <!-- SQL 语句片段 -->
        
        <!--异步appender-->
         <Async name="AsyncAppender" includeLocation="true"> <!-- SQL 语句片段 -->
            <AppenderRef ref="ErrorAppender"/> <!-- SQL 语句片段 -->
            <AppenderRef ref="DebugAppender"/> <!-- SQL 语句片段 -->
        </Async> <!-- SQL 语句片段 -->
    </Appenders> <!-- SQL 语句片段 -->
    
    <Loggers> <!-- SQL 语句片段 -->
         <!--过滤掉spring和mybatis的一些无用的debug信息-->
        <logger name="org.springframework" level="INFO"> <!-- SQL 语句片段 -->
        </logger> <!-- SQL 语句片段 -->
        <logger name="org.mybatis" level="INFO"> <!-- SQL 语句片段 -->
        </logger> <!-- SQL 语句片段 -->
        <logger name="cn.itcast.wanxinp2p.consumer.mapper" level="DEBUG"> <!-- SQL 语句片段 -->
        </logger> <!-- SQL 语句片段 -->

        <logger name="springfox" level="INFO"> <!-- SQL 语句片段 -->
        </logger> <!-- SQL 语句片段 -->
		<logger name="org.apache.http" level="INFO"> <!-- SQL 语句片段 -->
        </logger> <!-- SQL 语句片段 -->
        <logger name="com.netflix.discovery" level="INFO"> <!-- SQL 语句片段 -->
        </logger> <!-- SQL 语句片段 -->
        
        <logger name="RocketmqCommon"  level="INFO" > <!-- SQL 语句片段 -->
		</logger> <!-- SQL 语句片段 -->
		
		<logger name="RocketmqRemoting" level="INFO"  > <!-- SQL 语句片段 -->
		</logger> <!-- SQL 语句片段 -->
		
		<logger name="RocketmqClient" level="WARN"> <!-- SQL 语句片段 -->
		</logger> <!-- SQL 语句片段 -->

        <logger name="org.dromara.hmily" level="WARN"> <!-- SQL 语句片段 -->
        </logger> <!-- SQL 语句片段 -->

        <logger name="org.dromara.hmily.lottery" level="WARN"> <!-- SQL 语句片段 -->
        </logger> <!-- SQL 语句片段 -->

        <logger name="org.dromara.hmily.bonuspoint" level="WARN"> <!-- SQL 语句片段 -->
        </logger> <!-- SQL 语句片段 -->
		
        <!--OFF   0-->
        <!--FATAL   100-->
        <!--ERROR   200-->
        <!--WARN   300-->
        <!--INFO   400-->
        <!--DEBUG   500-->
        <!--TRACE   600-->
        <!--ALL   Integer.MAX_VALUE-->
        <Root level="DEBUG" includeLocation="true"> <!-- SQL 语句片段 -->
            <AppenderRef ref="AsyncAppender"/> <!-- SQL 语句片段 -->
            <AppenderRef ref="Console"/> <!-- SQL 语句片段 -->
            <AppenderRef ref="DebugAppender"/> <!-- SQL 语句片段 -->
        </Root> <!-- SQL 语句片段 -->
    </Loggers> <!-- SQL 语句片段 -->
</Configuration> <!-- SQL 语句片段 -->
appendix_K
https://clxhxhhr.top/posts/4378/
作者
clxstart
发布于
2026-09-26
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。