首页 > 其他分享 >实现一个自己的OpenFeign 远程调用验证协议--Springboot 自定义拦截器验证请求合法性--xunznux

实现一个自己的OpenFeign 远程调用验证协议--Springboot 自定义拦截器验证请求合法性--xunznux

时间:2024-07-25 22:25:46浏览次数:13  
标签:Feign 拦截器 String 自定义 验证 -- 调用 public 请求

Springboot 如何实现一个自定义的拦截器处理系统发出的请求以及接收到的请求(实现一个用于feign远程调用验证的简单协议)

文章目录

实现Feign拦截器的意义

通过 Spring Boot中的过滤器,可以处理HTTP请求并执行一些预处理逻辑,比如验证请求的合法性等。

  • 统一处理请求:
    拦截器允许你在所有Feign调用之前和之后执行代码,这使得你可以统一处理如添加请求头、日志记录、性能监控、错误处理等操作。
  • 权限校验:
    在微服务架构中,服务间通信的安全性非常重要。通过Feign拦截器,可以在调用远程服务前检查权限,确保只有授权的服务才能访问特定资源。
  • 认证和授权:
    当使用OAuth2或其他认证机制时,Feign拦截器可以自动添加必要的认证信息(如JWT Token)到请求头中,确保远程服务能够识别调用者的身份。
  • 请求/响应修改:
    拦截器可以修改请求参数或响应数据,例如,对敏感信息进行加密或解密,或者根据业务需求转换数据格式。
  • 性能优化:
    可以在拦截器中实现缓存逻辑,避免不必要的远程调用,提高应用性能。

实现细节&代码

具体可以看代码仓库的demo:https://gitee.com/zhou-xiujun/feign-xun-interceptor

首先添加 application.yaml 的配置

spring:
  application:
    name: FeignXunInterceptor
app:
  id: 999
  key: 666

xun:
  protocol:
    context-path: /xun

其中,app.id 和 app.key 是需要保密的,用于服务端验证客户端的请求是否合法。
context-path 是用于判断请求路径是否符合该远程调用的规定,如果URI 以该路径开头,则判定是远程调用,需要进行请求的处理。

添加配置类

XunConfig
@Component
@ConfigurationProperties(prefix = "app")
public class XunConfig {
    private String id;
    private String key;

    // 添加getters和setters
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}

该配置类用于获取 id 和 key。

XunProperties
@Component
@ConfigurationProperties(prefix = "xun.protocol")
public class XunProperties {
    private String contextPath;

    public String getContextPath() {
        return contextPath;
    }

    public void setContextPath(String contextPath) {
        this.contextPath = contextPath;
    }
}

该配置类用于获取 contextPath。

XunAutoConfiguration
@Configuration
public class XunAutoConfiguration {
    @Bean
    public RequestInterceptor requestInterceptor() {
        return new ClientInterceptor();
    }

    @Bean
    public OncePerRequestFilter serverInterceptor() {
        return new ServerInterceptor();
    }
}

其中的 ClientInterceptor 和 ServerInterceptor 都是后面定义的客户端拦截器和服务端拦截器。这里使用 bean 注入的方式应用拦截器。

ClientInterceptor 客户端拦截器的实现

public class ClientInterceptor implements RequestInterceptor {
    @Resource
    private XunConfig xunConfig;
    @Resource
    private XunProperties xunProperties;

    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";

    @Override
    public void apply(RequestTemplate template) {
        if (template.url().startsWith(xunProperties.getContextPath())) {
            String appId = xunConfig.getId(); // 从配置或服务中获取
            String appKey = xunConfig.getKey(); // 从配置或服务中获取
            byte[] body = template.body(); // 需要从template或其他地方获取请求体内容
            Boolean signUpperCase = true; // 根据需要设置

            String sign = xunSign(appId, appKey, Optional.ofNullable(body)
                    .map(Arrays::toString)
                    .orElse(""), signUpperCase);
            template.header("Content-Type", "application/json");
            template.header("Authorization", "Bearer " + sign);
            if (body != null) {
                template.header("data", Base64.getEncoder().encodeToString(body));

            }

            process(template, StandardCharsets.UTF_8, sign, body);
        }
    }

    private void process(RequestTemplate template, Charset charset, String key, byte[] data) {
        template.removeHeader("Content-Type");
        template.header("Content-Type", "application/json");
        template.body(data, charset);
    }

    private String encode(String string, Charset charset) {
        try {
            return URLEncoder.encode(string, charset.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Failed to encode URL", e);
        }
    }

    /**
     * 生成签名
     * @param appId 应用ID
     * @param appKey 应用密钥
     * @param bodyStr 请求体字符串
     * @param signUpperCase 是否大写签名
     * @return 生成的签名
     */
    private String xunSign(String appId, String appKey, String bodyStr, Boolean signUpperCase) {
        try {
            String data = appId + (bodyStr != null ? bodyStr : "");
            Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
            SecretKeySpec secretKey = new SecretKeySpec(appKey.getBytes(StandardCharsets.UTF_8), HMAC_SHA256_ALGORITHM);
            mac.init(secretKey);
            byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            String signature = Base64.getEncoder().encodeToString(hash);
            return signUpperCase ? signature.toUpperCase() : signature;
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate signature", e);
        }
    }

}

apply方法,用于对RequestTemplate对象进行处理。具体功能如下:

  1. 首先判断template的URL是否以xunProperties.getContextPath()开头,如果是则执行以下步骤,否则直接返回。
  2. 从配置或服务中获取appId和appKey。
  3. 从template中获取请求体内容body。
  4. 根据需要设置signUpperCase为true。
  5. 调用xunSign方法生成签名sign。
  6. 设置请求头Content-Type为application/json,Authorization为Bearer + sign。
  7. 如果请求体body不为空,则将body进行Base64编码,并设置请求头data为编码后的字符串。
  8. 最后调用process方法对template进行进一步处理,传入UTF-8编码、sign和body作为参数。

该函数主要用于对请求进行签名认证和设置请求头,以便进行安全的API调用。这里的实现较为简单,可以个人对此进行优化改进,保证更好的安全性和合理性。主要展示是是这个处理流程。

ServerInterceptor 服务端拦截器

public class ServerInterceptor extends OncePerRequestFilter {
    @Resource
    private XunConfig xunConfig;
    @Resource
    private XunProperties xunProperties;

    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 认证放行与协议验证鉴权逻辑
        String authorization = request.getHeader("Authorization");
        String requestURI = request.getRequestURI();
        if (requestURI.startsWith(xunProperties.getContextPath()) && authorization != null && authorization.startsWith("Bearer ")) {
            String appId = xunConfig.getId(); // 从配置或服务中获取
            String appKey = xunConfig.getKey(); // 从配置或服务中获取
            Boolean signUpperCase = true; // 根据需要设置
            String generatedSign = xunSign(appId, appKey, getRequestBody(request), signUpperCase);
            String token = authorization.substring(7);
            if (generatedSign.equals(token)) {
                filterChain.doFilter(request, response);
            } else {
                // 验证不通过
                throw new RuntimeException("验证不通过");
            }
        }
        // 放行
        filterChain.doFilter(request, response);
    }

    private String xunSign(String appId, String appKey, String bodyStr, Boolean signUpperCase) {
        try {
            String data = appId + (bodyStr != null ? bodyStr : "");
            Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
            SecretKeySpec secretKey = new SecretKeySpec(appKey.getBytes(StandardCharsets.UTF_8), HMAC_SHA256_ALGORITHM);
            mac.init(secretKey);
            byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            String signature = Base64.getEncoder().encodeToString(hash);
            return signUpperCase ? signature.toUpperCase() : signature;
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate signature", e);
        }
    }

    private String getRequestBody(HttpServletRequest request) throws IOException {
        StringBuilder requestBody = new StringBuilder();
        try (BufferedReader reader = request.getReader()) {
            String line;
            while ((line = reader.readLine()) != null) {
                requestBody.append(line);
            }
        }
        return requestBody.toString();
    }
}

该函数是一个过滤器,用于对请求进行认证和授权验证。首先,它从请求头中获取Authorization信息和请求的URI。然后,判断请求的URI是否以某个特定的路径开头,并且Authorization信息是否以Bearer开头。如果是,则从配置或服务中获取appId和appKey,并根据需要生成一个签名。接着,从Authorization信息中获取token,并将其与生成的签名进行比较。如果两者相等,则放行请求,否则抛出一个运行时异常,表示验证不通过。最后,无论认证和授权验证是否通过,都会放行请求。
同样的,这里还可以再进行优化改进。

以上这两部分就是实现拦截器的核心部分。
接下来是使用部分。

服务端提供的接口方法

@RestController
@RequestMapping("/xun")
public class ServerController {

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

客户端的Feign调用方法

@RestController
@RequestMapping("/client")
public class ClientController {
    @Resource
    private SampleClient sampleClient;

    @GetMapping("/hello")
    public String hello() {
        // 调用远程服务
        return sampleClient.hello();
    }
}
@FeignClient(name = "server", url = "http://localhost:8080", configuration = XunAutoConfiguration.class)
public interface SampleClient {
	// 这定义了一个公共接口SampleClient,该接口将被Feign客户端实现,从而允许调用远程服务的方法。
    @GetMapping("/xun/hello")
    String hello();
}

@FeignClient 注解是Spring Cloud Feign提供的,用于声明一个Feign客户端。

  • name = “server” 指定了这个Feign客户端的名字,通常这个名字会被用来识别一个服务实例,特别是在使用服务注册与发现机制如Eureka或Consul时。但是在这个例子中,因为指定了URL,name可能不会用于服务发现。
  • url = “http://localhost:8080” 直接指定了目标服务的URL,这意味着这个Feign客户端将直接调用位于http://localhost:8080的微服务,而不是通过服务发现机制寻找服务位置。
  • configuration = XunAutoConfiguration.class 指定了一个配置类,该配置类包含了Feign客户端的额外配置,比如编码器、解码器、日志级别等。这使得可以为特定的Feign客户端定制行为。
  • @GetMapping 是Spring MVC的注解,用于映射HTTP GET请求到特定的方法上。在这里,它将/xun/hello的GET请求映射到了hello()方法。
  • String hello() 是一个无参数的方法,当远程服务的/xun/hello端点被调用时,它期望返回一个字符串类型的结果。

综上所述,SampleClient是一个Feign客户端接口,它可以调用位于http://localhost:8080/xun/hello的远程服务端点,并期待返回一个字符串类型的响应。在Spring Boot应用中,你可以像调用本地方法一样调用SampleClient的hello()方法,Feign框架会自动处理HTTP请求和响应的序列化与反序列化。

Springboot 启动类增加客户端

@SpringBootApplication
@EnableFeignClients(clients = {SampleClient.class})
public class FeignXunInterceptorApplication {

    public static void main(String[] args) {
        SpringApplication.run(FeignXunInterceptorApplication.class, args);
    }
}

总结

至此,一个简单的用于 OpenFeign 远程调用的验证协议就完成了,可以将其作为一个项目打包为jar包,然后引入其他项目中使用,这里将不做介绍。当然,拦截器的使用也不仅仅是可以只用于 OpenFeign 的远程调用验证,可以用于任何 HTTP 请求的拦截与验证。

之前的文章有对Springboot 启动时Bean的创建与注入这个过程的讲解以及对应的源码解读,感兴趣的可以去看看:
Springboot 启动时Bean的创建与注入(一)-源码解读-xunznux
Springboot 启动时Bean的创建与注入(二)-源码解读-xunznux
Springboot 的Bean生命周期五步、七步、十步详解以及框架源码解读

标签:Feign,拦截器,String,自定义,验证,--,调用,public,请求
From: https://blog.csdn.net/weixin_44615954/article/details/140620540

相关文章

  • 11 个接口性能优化技巧(下)【送源码】
     7.锁粒度在某些业务场景中,为了防止多个线程并发修改某个共享数据,造成数据异常。为了解决并发场景下,多个线程同时修改数据,造成数据不一致的情况。通常情况下,我们会:加锁。但如果锁加得不好,导致锁的粒度太粗,也会非常影响接口性能。7.1synchronized在java中提供了synchron......
  • Python——异常捕获,传递及其抛出操作
    01.异常的概念1.程序在运行时,如果python解释器遇到一个错误,会停止程序的执行,并且提示一些错误信息,这就是异常。2.程序停止执行并且提示错误信息这个动作,我们通常称之为:抛出(raise)异常。 程序开发时,很难将所有的特殊情况都处理的面面俱到,通过异常捕获可以针对突发事件做......
  • Springboot 的Bean生命周期五步、七步、十步详解以及框架源码解读生命周期-面试热点-x
    文章目录Springboot的Bean生命周期五步、七步、十步详解以及框架源码解读生命周期为什么要知道Bean的生命周期Bean的生命周期之五步堆栈信息:代码验证执行结果为Bean生命周期之七步执行结果Bean生命周期之十步增加的三步测试代码如下:执行结果:Bean的生命周期总结其他......
  • 手动部署?NONONO,动态上传热部署才是王道!!
    近期开发系统过程中遇到的一个需求,系统给定一个接口,用户可以自定义开发该接口的实现,并将实现打成jar包,上传到系统中。系统完成热部署,并切换该接口的实现。定义简单的接口这里以一个简单的计算器功能为例,接口定义比较简单,直接上代码。public interface Calculator {  ......
  • AI外包团队 Unity3D结合AI教育大模型 开发AI教师 AI外教 AI英语教师 AI课堂案例
    自2022年底ChatGPT引爆全球之后,大模型技术便迎来了一段崭新的快速发展期,由其在GPT4.0发布后,AI与教育领域结合产品研发、已成为教育+AI科技竞争的新高地、未来产业的新赛道、经济发展的新引擎和新产品的诞生地。据不完全统计,目前国内已有包括科大讯飞、百度、阿里、华为、网易在......
  • 进制的转换
    任意进制转化成十进制的公式都是系数基数的权次幂相加系数指的是每一位上对应的数字基数就是几进制对应的数字权从右到左依次是01234……1.二进制转十进制(二进制在代码中前面要加上0b)取值为0~1比如说0b1011转成十进制就是120+1*21+022+1*23=11。那么这里还有一个方法就......
  • Java中的日志管理:SLF4J与Logback
    Java中的日志管理:SLF4J与Logback大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!本文将介绍如何在Java中使用SLF4J与Logback进行日志管理,帮助您在项目中实现高效的日志记录和管理。一、SLF4J与Logback简介SLF4J(SimpleLoggingFacadeforJava)是一种简单......
  • 使用Apache Camel进行Java企业集成
    使用ApacheCamel进行Java企业集成大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!本文将介绍如何使用ApacheCamel进行Java企业集成,帮助您在企业应用中实现高效的数据交换和流程自动化。一、ApacheCamel简介ApacheCamel是一个强大的开源集成框架,它提......
  • 常见日志输出目标(Logback | Log4j2 | Java Util Logging)
    常见日志输出目标控制台:日志可以被输出到控制台(终端),通常用于开发和调试阶段。在日志框架中,控制台输出通常由ConsoleAppender(例如Log4j、Logback)配置。日志文件:日志也可以被写入到日志文件中,以便于长期存储和分析。在日志框架中,文件输出通常由FileAppender(例如Log4j、......
  • 市场上最好的需求缺陷管理工具有哪些?
    国内外主流的10款需求缺陷管理工具对比:PingCode、Worktile、禅道、Teambition、TAPD、Trello、简道云、Jira、Bugzilla、Asana。在选择需求缺陷管理工具时,面对众多选项,许多人可能感到困惑。不管是初创公司还是大型企业,都需要一个高效的工具来管理需求和缺陷,这不仅能节省时间,还......