• Feign 调用出现异常:feign.FeignException: status 401 reading xxx#xxx(xxx)


    异常描述

    微服务 Controller 层方法通过 Feign 调用某个微服务方法,出现以下异常:

    feign.FeignException: status 401 reading UserFeign#updateLoginTime(Integer)
    	at feign.FeignException.errorStatus(FeignException.java:78)
    	at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:93)
    	at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:149)
    	at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78)
    	at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)
    	at com.sun.proxy.$Proxy103.updateLoginTime(Unknown Source)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    异常原因

    权限校验失败
    即有某个地方会校验该 Feign 请求的授权信息,然后未通过,返回了 401 这个错误码
    不一定是 Feign 本身的权限校验,虽然是 Feign 报的异常
    一开始因为是 Feign 报的异常,我就开始找 Feign 请求拦截器等,但是一无所获
    后来发现,是因为 Feign 请求中缺失 Cookie,导致被网关拦截器拦截

    异常分析

    我的安全校验逻辑如下: 非登录请求进入网关后,校验 cookie,根据 cookie 值,从 Redis 中取出 JWT,并存入请求头中
    每个微服务会解析请求头中的 JWT,以此获取登录用户信息

    前端的请求进入 Controller 中后,Controller 的方法再调用 Feign
    此时的 Feign 请求是获取不到请求头的,微服务也就无法获取登录用户信息

    所以我在微服务中增加了 Feign 拦截器,用来增强请求:将前端请求中的请求头信息存入 Feign 请求中:

    /**
     * Feign 调用增强请求头,防止登录信息无法传递而被服务拦截
     *
     * @author Eugenema
     * @date 2022/7/30 12:05
     */
    @Component
    public class FeignInterceptor implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate requestTemplate) {
            //获取客户端访问的请求
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if(requestAttributes != null){
                HttpServletRequest request = requestAttributes.getRequest();
                if (request != null) {
                    String token = request.getHeader("Authorization");
    
                    //同步到 feign 请求中
                    requestTemplate.header("Authorization", token);
                    return;
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    但是
    我的 Feign 调用,配置的是网关,而不是具体的某个微服务
    /**
     * 用户 feign 调用
     *
     * @author Eugenema
     * @date 2022/7/23 17:16
     */
    @FeignClient(contextId = "userService", name = "gatewayService", path = "/userService")
    @RequestMapping("/user")
    public interface UserFeign {
    ……
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    故,Feign 请求会先经过 Feign 拦截器,将请求头加入到请求中
    然后再经过网关拦截器,结果网关拦截器会先检查 Cookie 是否存在:很明显,不存在!!!
    于是网关拦截器就拦截了,然后 Feign 调用就报错了……

    异常解决

    在 Feign 请求拦截器中,除了增强请求头外,把 Cookie 也加上

    /**
     * Feign 调用增强请求头,防止登录信息无法传递而被服务拦截
     *
     * @author Eugenema
     * @date 2022/7/30 12:05
     */
    @Component
    public class FeignInterceptor implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate requestTemplate) {
            //获取客户端访问的请求
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if(requestAttributes != null){
                HttpServletRequest request = requestAttributes.getRequest();
                if (request != null) {
                    String cookie = request.getHeader("Cookie");
                    String token = request.getHeader("Authorization");
    
                    //同步到 feign 请求中
                    requestTemplate.header("Cookie", cookie);
                    requestTemplate.header("Authorization", token);
                    return;
                }
            }
        }
    }
    
    • 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

    疑问

    因为异常是在 Feign 中报出来的,且我的网关拦截器是会对拦截下来的请求打印日志的
    但是网关当时并没有日志打印,所以一开始没有往这边想
    不太清楚是因为啥

  • 相关阅读:
    从Core Dump中提取CUDA的报错信息
    【Sping 源码解析】Spring XML方式准备创建bean
    源码编译perl5遇到的问题汇总
    Stream.toList()和Collectors.toList()的性能比较
    优化算法 - 学习率调度器
    快速全面掌握数据库系统核心知识点
    Docker export导出容器,重新运行导出的容器
    正点原子嵌入式linux驱动开发——Busybox根文件系统构建
    Python+ Flask轻松实现Mock Server
    Python实现的吃东西小游戏
  • 原文地址:https://blog.csdn.net/qq_37470815/article/details/126087667