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

来吧,贴代码。

一、背景

我们有一个项目使用了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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

三连杆滑块机构运动学仿真 | 【Matlab源码+理论公式文本】|曲柄滑块 | 曲柄连杆 | 机械连杆

【程序简介】&#x1f4bb;&#x1f50d; 本程序通过matlab实现了三连杆滑块机构的运动学仿真编程&#xff0c;动态展现了三连杆机构的运动动画&#xff0c;同时给出了角位移、角速度和角加速度的时程曲线&#xff0c;除了程序本身&#xff0c;还提供了机构运动学公式推导文档…

【MySQL】MySQL用户管理

文章目录 一、用户1.用户信息2.创建用户3.删除用户4.修改用户密码 二、数据库的权限1.给用户授权2.回收权限 一、用户 如果我们只能使用root用户&#xff0c;这样存在安全隐患。这时&#xff0c;就需要使用MySQL的用户管理。 1.用户信息 我们安装mysql之后&#xff0c;会自动…

ATFX汇市:预期之外,瑞士央行率先降息!瑞郎对美元剧烈贬值

ATFX汇市&#xff1a;昨日&#xff0c;全球的交易者都在盯着美联储和英国央行的利率决议&#xff0c;期待在鲍威尔和贝利的讲话中窥见未来的利率路径。然而&#xff0c;被大部分人所忽略的瑞士央行&#xff0c;在昨日16:30的利率决议上&#xff0c;宣布降息25基点&#xff0c;将…

类对象的初始化过程与方法

类初始化过程与方法 一、类对象的初始化过程 1.初始化的过程 &#xff08;1&#xff09;对象在实例化的时候需要调用构造函数&#xff0c;如果对应的构造函数调用不了&#xff0c;这个对象是没有办法实例化的。 &#xff08;2&#xff09;构造函数的执行&#xff0c;是在内…

2024腾龙杯web签到题-初识jwt(签到:这是一个登录页面)

什么是 jwt? 它是 JSON Web Token 的缩写&#xff0c;是一个开放标准&#xff0c;定义了一种紧凑的、自包含的方式&#xff0c;用于作为JSON对象在各方之间安全地传输信息&#xff0c;该信息可以被验证和信任&#xff0c;因为它是数字签名的。它就是一种认证机制&#xff0c;…

接口自动化之操作mysql数据库!

在接口自动化测试过程中并不像UI自动化测试一样存在界面可以通过界面数据校验正确性&#xff0c;除了可以通过接口返回的数据校验还可以通过操作数据库校验数据&#xff0c;那么我们该如何做接口自动化的数据校验呢&#xff1f;这篇文章我们就讲解以mysql为例&#xff0c;讲解接…

项目系统使用异步业务流程(线程池详细实现)

❤ 作者主页&#xff1a;李奕赫揍小邰的博客 ❀ 个人介绍&#xff1a;大家好&#xff0c;我是李奕赫&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 记得点赞、收藏、评论⭐️⭐️⭐️ &#x1f4e3; 认真学习!!!&#x1f389;&#x1f389; 文章目录 异步化1.介绍…

雷卯有高强Ipp的多种电压ESD保护器件推荐

1.上海雷卯推出多种电压 高强Ipp ESD 防护器件 下表仅是部分展示。 3.USB PD 防浪涌方案 4.为什么需要高强Ipp ESD防护器件 硬件工程师常会遇到这种情况&#xff0c;我的PCB 板上电源也放ESD保护器件 了&#xff0c;但是当插拔电或上电瞬间ESD保护器件烧毁了&#xff0c;导致…

schweizer-electronic 公司 safedat2 操作使用说明

schweizer-electronic 公司 safedat2 操作使用说明

从上到下,深入理解LIN协议 及 进阶问题

目录 一、当LIN总线静默4s-10s时&#xff0c;节点自动进入休眠状态。这个“自动进入休眠状态”是怎么实现的&#xff1f;二、帧的架构设计2.1 问题提出2.2 帧设计 三、帧超时功能四、主节点如何接收从节点上报的数据&#xff1f;五、网络管理5.1 当功能开关关闭时&#xff0c;应…

乐得瑞科技PD协议芯片:OTG与充电并行,引领数据交互

在科技日新月异的今天&#xff0c;数据交互的方式对于我们的日常生活和工作都起到了至关重要的作用。但在OTG技术诞生之前&#xff0c;这一过程却显得相当繁琐和耗时。想象一下&#xff0c;你需要将数码相机的照片导入到笔记本电脑中&#xff0c;却不得不频繁地拔出内存卡&…

✅技术社区—使用Redis BitMap实现签到与查询历史签到以及签到统计功能

一、前言 签到是一个很常见的功能&#xff0c;如果使用数据库实现&#xff0c;那么用户一次签到&#xff0c;就是一条记录&#xff0c;假如有100万用户&#xff0c;平均每个用户每年签到次数为30次&#xff0c;则这张表一年的数据量为 3000 万条&#xff0c;一般签到记录字段不…

揭秘项目管理多重益处:加速产品上市、优化资源利用,提升质量与满意度等秘诀

一、引言 在飞速发展的商业环境中&#xff0c;项目管理已经成为企业成功的关键要素之一。正如项目管理大师哈罗德科兹纳所言&#xff1a;“项目管理不仅仅是关于完成任务的工具&#xff0c;更是一种战略武器&#xff0c;能够帮助企业获得竞争优势。”项目管理的好处多种多样&am…

Apache FtpServer在Windows上下载安装与使用

Apache FtpServer在Windows上下载安装与使用 1、Apache Ftp Server下载 进入apache官网 https://mina.apache.org/ftpserver-project/old-downloads.html 下载自己使用的版本。 Apache FtpServer 1.1.1及以下的版本需要JDK1.7的支持 Apache FtpServer 1.1.1以上的版本需要JDK…

Linux--任务管理与守护进程

目录 任务管理 进程组概念 作业概念 会话概念 补充 守护进程 基本概念 守护进程的查看 守护进程的创建 自己手写守护进程 使用系统调用函数创建守护进程 任务管理 进程组概念 每一个进程除了有一个进程ID之外&#xff0c;还有一个进程组ID&#xff0c;进程组是一个或…

新零售SaaS架构:线上商城系统架构设计

零售商家为什么要建设线上商城&#xff1f; 传统的实体门店服务范围有限&#xff0c;只能吸引周边500米以内的消费者。因此&#xff0c;如何拓展服务范围&#xff0c;吸引更多的消费者到店&#xff0c;成为了店家迫切需要解决的问题。 缺乏忠实顾客&#xff0c;客户基础不稳&a…

clickhouse突然启动不起来问题排查

场景&#xff1a; 在实现postgreql数据迁移到clickhouse中&#xff0c;想使用MaterializedPostgreSQL的功能实现&#xff0c;但是中途clickhouse突然挂了&#xff0c;就再启动不了了。 现象&#xff1a; systemctl start clcikhouse-server启动报错 [rootlocalhost clickhous…

python矢量算法-三角形变化寻找对应点

1.算法需求描述 现有随机生成的两个三角形A与B&#xff0c;在三角形A中存在Pa&#xff0c;使用算法计算出三角形B中对应的点Pb 2.python代码 import numpy as np # 计算三角形A的面积 def area_triangle(vertices): return 0.5 * np.abs(np.dot(vertices[0] - vertices[…

用python爬取CSDN博客的总字数

一、下载pycahrm 此处推荐博客&#xff1a;PyCharm安装教程&#xff0c;图文教程&#xff08;超详细&#xff09;-CSDN博客 二、安装相应的库 pycharm安装库的步骤&#xff1a; 1、打开pycharm&#xff1b; 2、在菜单栏中&#xff0c;选择 "file">"setti…

d3dcompiler43.dll缺失怎么修复,教你五个方法快速搞定

在数字世界的深渊中&#xff0c;有一个名为d3dcompiler_43.dll的神秘文件&#xff0c;它就像一把打开现代科技之门的钥匙。这个文件是DirectX 12的一部分&#xff0c;由微软公司开发&#xff0c;用于编译和处理图形数据。 d3dcompiler_43.dll是一个动态链接库&#xff08;DLL&…