spring cloud项目微服务间互相调用使用自定义标注进行鉴权方案

news2024/9/21 22:36:19

来吧,贴代码。

一、背景

我们有一个项目使用了spring cloud,有的微服务需要调用别的微服务,但这些调用没有鉴权;当初项目时间非常紧,同时这部分微服务有的对外也没有鉴权,在代码中设置了无须鉴权,可直接访问。近期客户进行安全性测评,查出了一堆安全性漏洞。你睇下:

@Override
public void configure(HttpSecurity http) throws Exception {
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            .and()
            .authorizeRequests()
            //添加放行接口,不进行OAuth2授权认证
            .antMatchers(
                    "/camera/**",
                    "/jcDeviceManufacturer/file/preview/**",
                    "/jcStationFile/**",
                    "/jcTimedTask/**",
                    "/jcDevice/**",
                    "/jcStation/**",
                    "/user/query/warning/**",
                    "/jcSenorDataCurrent/**",
                    "/jcSensorData003/**",
                    "/jcSensorData007/**",
                    "/jcSensorData008/**",
                    "/jcSensorData009/**",
                    "/jcSensorData012/**",
                    "/jcSensorData014/**",
                    "/jcSensorData015/**",
                    "/jcSensorData024/**",
                    "/jcSensorData025/**",
                    "/jcSensorData027/**",
                    "/jcSensorData034/**",
                    "/jcSensorGnssResolvedata/**",
                    "/jcSensorDataDxs/**",
                    "/jcSensorDataGnss/**",
                    "/jcStationMap/**",
                    "/jcWarnConfigDevice/**",
                    "/jcStationDeviceMap/**"
                    ).permitAll()
            // 指定监控访问权限
            .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
            .anyRequest().authenticated()
            .and()
            //认证鉴权错误处理
            .exceptionHandling()
            .accessDeniedHandler(new OpenAccessDeniedHandler())
            .authenticationEntryPoint(new OpenAuthenticationEntryPoint())
            .and()
            .csrf().disable();
}

如之奈何,计将安出?

二、思路

项目早就验收了,维护期也过期了。本着为客户着想,并幻想他们能再续期,丢个几万元让我们维护,所以我奋不顾身地维护一下。

我的指导原则是代码不要进行大的调整,尽量简单处理,毕竟量体裁衣,看菜吃饭。而且当时项目开发的人很多,我只负责其中几个模块,好多都不是我弄的。现在人员已经走得差不多了,维护任务就落到我头上。我只好硬着猪头皮,献上思路如下:

1)微服务间调用,检查请求头有无带上特定信息,有则通过,无则抛出异常
2)外部访问,设置白名单,检查发出请求的IP,符合则通过,否则抛出异常。这样第三方系统就不用更改了
3)但这些服务中,有一些前端也会请求。由于前端有登录,那么前端的请求应该不受上面的限制措施影响。
4)搞一个标注来完成这些鉴权动作,并且应用AOP,尽量将现有代码改动减到最小。

三、具体实现

1、标注@Inner,用于标记类

import java.lang.annotation.*;

/**
 * 微服务内部访问方法
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inner {
    /**
     * 是否AOP统一处理
     */
    boolean value() default true;
}

2、标注@InnerMethod,用于标记方法

import java.lang.annotation.*;

/**
 * 微服务内部访问方法
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InnerMethod {
    /**
     * 是否AOP统一处理
     */
    boolean value() default true;
}

3、AOP

1)InnerAspect.java

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Aspect
@Component
public class InnerAspect implements Ordered {
/**
配置文件内容:
inner.head.name=X-From
inner.head.value=internal
inner.white-ip=127.0.0.1,192.168.10.8,192.168.10.9
*/
    @Value(value = "${inner.head.name:X-From}")
    private String from;
    @Value(value = "${inner.head.value:internal}")
    private String fromIn;
    @Value(value = "${inner.white-ip:127.0.0.1}")
    private String whiteIps;

    private static List<String> whiteList = null;

    @Around("@within(inner)")  // Modified pointcut expression
    public Object around(ProceedingJoinPoint point, Inner inner) throws Throwable {
        if(!isValid(point,inner.value())){
            throw new AccessDeniedException("Access is denied");
        }
        return point.proceed();  // Proceed with the original method call
    }

    /**
     * 注意 @Around("@annotation(innerMethod)")中的"innerMethod",
     * 名称要与aroundMethod(ProceedingJoinPoint point, InnerMethod innerMethod) 中的参数名称一致
     * @param point
     * @param innerMethod
     * @return
     * @throws Throwable
     */
    @Around("@annotation(innerMethod)")
    public Object aroundMethod(ProceedingJoinPoint point, InnerMethod innerMethod) throws Throwable {
        if(!isValid(point,innerMethod.value())){
            throw new AccessDeniedException("Access is denied");
        }
        return point.proceed();
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }

    private boolean isValid(ProceedingJoinPoint point, boolean innerHasValue){
        boolean yes = true;

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || "anonymousUser".equals(authentication.getPrincipal())){//尚未登录
            initWhiteList();
            Signature signature = point.getSignature();
            if (innerHasValue) {  // Check if AOP is enabled for the class
                HttpServletRequest request = ServletUtils.getRequest();
                String header = request.getHeader(from);
                String ipAddress = getOriginalIp(request);

                // Authorization check based on request header or IP address
                if (!fromIn.equals(header) && !whiteList.contains(ipAddress)) {
                    System.err.println(String.format("没有权限访问接口 %s", signature.getName()));
                    yes = false;
                }
            }
        }

        return yes;
    }
    private void initWhiteList(){
        if(whiteList == null || whiteList.size() == 0){
            whiteList = new ArrayList<>(Arrays.asList(whiteIps.split(",")));
        }
    }
    /**
     * 获取最原始的请求IP
     * 因为请求有可能经过nginx等转发
     * @param request
     * @return
     */
    private String getOriginalIp(HttpServletRequest request) {
        String originalIp = request.getHeader("X-Forwarded-For");
        if (originalIp == null || originalIp.isEmpty()) {
            originalIp = request.getRemoteAddr();
        } else {
            // 可能会有多个IP,获取第一个IP地址
            originalIp = originalIp.split(",")[0].trim();
        }
        return originalIp;
    }
}

其中,主要逻辑部分:

private boolean isValid(ProceedingJoinPoint point, boolean innerHasValue){
    boolean yes = true;

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication == null || "anonymousUser".equals(authentication.getPrincipal())){//尚未登录
        initWhiteList();
        Signature signature = point.getSignature();
        if (innerHasValue) {  // Check if AOP is enabled for the class
            HttpServletRequest request = ServletUtils.getRequest();
            String header = request.getHeader(from);
            String ipAddress = getOriginalIp(request);

            // Authorization check based on request header or IP address
            if (!fromIn.equals(header) && !whiteList.contains(ipAddress)) {
                System.err.println(String.format("没有权限访问接口 %s", signature.getName()));
                yes = false;
            }
        }
    }

    return yes;
}

首先看是否已经登录,未登录的话才进行考察。如果既无请求头,又不在白名单内,才抛出异常;否则都通过,宽松得很。

值得一提得是,@Around的写法。里面的参数要跟函数的参数保持一致:
在这里插入图片描述

2)自定义的HttpServletRequest.java

上面代码中用到这个自定义类。

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class ServletUtils {
    public static HttpServletRequest getRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }
}

四、使用

1、被调用的服务

1)类

@Api(value = "监测设备信息", tags = "监测设备信息")
@RestController
@RequestMapping("jcDevice")
@Inner
public class JcDeviceController implements IJcDeviceServiceClient {
。。。
}

2)方法

  @GetMapping("/file/preview")
  @InnerMethod
  public void previewDemo(HttpServletRequest request, HttpServletResponse response, @RequestParam("code") String code) {
。。。
  }

2、主动发起调用的服务

服务之间是通过Feign来调用的,只要在主动发起调用的微服务中实现Feign的拦截器即可:

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class InnerAuthInterceptor implements RequestInterceptor {
    @Value(value = "${inner.head.name:X-From}")
    private String from;
    @Value(value = "${inner.head.value:internal}")
    private String fromIn;

    @Override
    public void apply(RequestTemplate template) {
        template.header(from, fromIn);
    }
}

五、小结

上述代码中,IP白名单在本地是没有问题的。但请求的转发是vue开发环境下实现的。部署到生产服务器nginx上,就拿不到最原始的请求IP,拿到的都是nginx服务器的IP。这个问题下周有时间再看看。

但不一定有时间。公司没啥活,员工却还是那么忙,搞不懂。
在这里插入图片描述
在这里插入图片描述

参考文章:
服务之间调用还需要鉴权?

Feign的拦截器RequestInterceptor

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1536955.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

真机笔记(2)项目分析

目录 1. 项目&#xff1a; 2. 网络工程师工作流程 3. 实验 设备命名 登录密码 使用SSH协议 1. 项目&#xff1a; 竞标方&#xff1a;集成商、厂商、代理商、服务商、监理检测公司 在一个网络项目中&#xff0c;不同的角色承担着不同的职责和任务。以下是集成商、厂商、代…

程序人生——Java异常使用建议

目录 引出异常建议110&#xff1a;提倡异常封装&#xff1b;建议111&#xff1a;采用异常链传递异常 建议112&#xff1a;受检异常尽可能转化为非受检异常建议113&#xff1a;不要在finally块中处理返回值 建议114&#xff1a;不要在构造函数中抛异常建议115&#xff1a;使用Th…

VMD + CEEMDAN 二次分解,CNN-Transformer预测模型

往期精彩内容&#xff1a; 时序预测&#xff1a;LSTM、ARIMA、Holt-Winters、SARIMA模型的分析与比较-CSDN博客 风速预测&#xff08;一&#xff09;数据集介绍和预处理-CSDN博客 风速预测&#xff08;二&#xff09;基于Pytorch的EMD-LSTM模型-CSDN博客 风速预测&#xff…

Data.olllo:一键数据“分组统计”!

引言&#xff1a; 数据统计是数据分析中的重要环节&#xff0c;而如何快速、准确地进行数据分组统计是许多数据工作者关注的焦点。现在&#xff0c;借助Data.olllo的神奇功能&#xff0c;您可以轻松进行一键式的数据分组统计&#xff0c;为您的数据分析提供更强大的支持&…

什么是浏览器指纹识别?指纹浏览器有用吗?

浏览器指纹识别是好是坏&#xff1f;这现在确实是一个有争议的话题。83%的消费者经常或偶尔会根据浏览历史记录看到广告。其实这就是利用了浏览器指纹技术。 如果您想了解浏览器指纹识别是什么&#xff0c;那就看下去&#xff01; 一、什么是浏览器指纹识别 浏览器指纹是指无…

Quartz完全开发手册(一篇学会Quartz所有知识点)

目录 一、Quartz概念 1.1、Quartz介绍 1.2、使用场景 1.3、特点 二、Quartz运行环境 三、Quartz设计模式 四、Quartz学习的核心概念 4.1、任务Job 4.2、触发器Trigger 4.3、调度器Scheduler 五、Quartz的体系结构与工作流程 5.1、体系结构 5.2、工作流程 六、Quar…

【Mock|JS】Mock的get传参+获取参数信息

mockjs的get传参 前端请求 const { data } await axios("/video/childcomments", {params: {sort: 1,start: 2,count: 5,childCount: 6,commenIndex: 0,},});后端获取参数 使用正则匹配url /*** # 根据url获取query参数* param {Url} urlStr get请求获取参数 eg:…

鸿蒙Harmony应用开发—ArkTS-全局UI方法(时间滑动选择器弹窗)

以24小时的时间区间创建时间滑动选择器&#xff0c;展示在弹窗上。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 本模块功能依赖UI的执行上下文&#xff0c;不可在UI上下文不明确的地方使用&…

vuecli创建vue3项目

第一步&#xff1a; 在文件夹中输入 vue create xxx 第二步&#xff1a; 勾选下面带有*号的&#xff0c;经验最好把Linter/Formatter勾掉&#xff0c;不然会出现eslint报错 第三步&#xff1a; 选择3.x 第四步&#xff1a; 意思为是否用history模式来创建路由&#xff0…

把 Taro 项目作为一个完整分包,Taro项目里分包的样式丢失

现象&#xff1a; 当我们把 Taro 项目作为原生微信小程序一个完整分包时&#xff0c;Taro项目里分包的样式丢失&#xff0c;示意图如下&#xff1a; 原因&#xff1a; 在node_modules/tarojs/plugin-indie/dist/index.js文件里&#xff0c;限制了只有pages目录下会被引入app.w…

The 2023 Guangdong Provincial Collegiate Programming Contest

I. Path Planning 嗯&#xff0c;怎么说呢&#xff0c;一般二维图&#xff0c;数据不是很大的比如n*m*log级别允许的&#xff0c;如果一眼不是bfs&#xff0c;可以考虑结合一下二分 本题可知&#xff0c;只能向下或者向右&#xff0c;那么我们就像如果答案为x&#xff0c;那么…

【重温设计模式】访问者模式及其Java示例

访问者模式的基本概念 访问者模式&#xff0c;一种行为型设计模式&#xff0c;其基本定义是&#xff1a;允许一个或者多个操作应用到一组对象上&#xff0c;解耦操作和对象的具体类&#xff0c;使得操作的添加可以独立于对象的类结构变化。在面向对象编程中&#xff0c;访问者…

sqllab第35-45关通关笔记

35关知识点&#xff1a; 宽字节注入数值型注入错误注入 payload:id1andextractvalue(1,concat(0x7e,database(),0x7e))0--联合注入 payload:id0unionselect1,database(),version()-- 36关知识点&#xff1a; 字符型注入宽字节注入错误注入 payload:id1%df%27andextractvalue(…

什么是浏览器指纹识别?Maskfog指纹浏览器有用吗?

浏览器指纹识别是好是坏&#xff1f;这现在确实是一个有争议的话题。83%的消费者经常或偶尔会根据浏览历史记录看到广告。其实这就是利用了浏览器指纹技术。 如果您想了解浏览器指纹识别是什么&#xff0c;那就看下去&#xff01; 一、什么是浏览器指纹识别 浏览器指纹是指无…

Linux学习之自定义协议

前言&#xff1a; 首先对于Tcp的socket的通信&#xff0c;我们已经大概了解了&#xff0c;但是其中其实是由一个小问题&#xff0c;我们目前是不得而知得&#xff0c;在我们客户端与服务端连接成功后&#xff0c;服务端读取来自客户端得消息&#xff0c;我们使用的是函数read,…

线程池相关详解

1.线程池的核心参数 线程池核心参数主要参考ThreadPoolExecutor这个类的7个参数的构造函数&#xff1a; corePoolSize核心线程数目 maximumPoolSize最大线程数目&#xff08;核心线程救急线程的最大数目&#xff09; keepAliveTime生存时间:救急线程的生存时间&#xff0c;生…

Pytorch入门实战 P3-天气识别

目录 一、前期准备 1、查看设备 2、导入本地数据 3、测试下获取到的天气数据 4、图像预处理 5、划分数据集 6、加载数据集 二、搭建简单的CNN网络&#xff08;特征提取分类&#xff09; 三、训练模型 1、设置超参数 2、编写训练函数 3、编写测试函数 4、正式训练 …

4、类加载器

2.4.1 什么是类加载器 类加载器&#xff08;ClassLoader&#xff09;是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术&#xff0c;类加载器只参与加载过程中的字节码获取并加载到内存这一部分。 类加载器会通过二进制流的方式获取到字节码文件的内容&#xff0c…

Visual Studio配置libtorch(cuda安装一步到位)

Visual Studio配置libtorch visual Studio安装cuDNN安装CUDAToolkit安装libtorch下载Visual Studio配置libtorch(cuda版本配置) visual Studio安装 visual Studio点击安装 具体的安装和配置过程这里就不进行细讲了&#xff0c;可以参考我这篇博客Visual Studio配置OpenCV(保姆…

【嵌入式学习】Qtday03.21

一、思维导图 二、练习 自由发挥登录窗口的应用场景&#xff0c;实现一个登录窗口界面。&#xff08;不要使用课堂上的图片和代码&#xff0c;自己发挥&#xff0c;有利于后面项目的完成&#xff09; 要求&#xff1a; 1. 需要使用Ui界面文件进行界面设计 2. ui界面上的组件…