#feign调用鉴权以及项目中该方式踩过的坑

2,117 阅读2分钟

fegin接口权限调用.jpg 分布式调用,如果使用fegin的话,可以简化从如图的上半部分。流程是用户页面发起请求,经过网关进行用户的认证,映射到对应的服务A,服务A中的c方法,需要通过fegin调用服务B中的方法。

问题:从网关认证的用户信息,如何进行在一个个服务之间进行传播,服务如何识别。 可行的解决方案:在网关进行用户认证之后,生成用户token放入缓存中,同时生成该用户对应的一个uid,该uid作为token的key,并在reqeust的请求头中,把该uid放进去,传播到下一个服务中,每个服务有个拦截器,从请求上下文中拿出该类型字段,如果是,则把uid字段取出来,去缓存中拿到token,解码成用户对象,放入项目的线程变量中,在服务A调用服务B时,在feign调用拦截器中把uid放入requestTemplate的header,也就是feign调用的http请求中的请求头中 。大概流程如下:

Untitled Diagram.jpg

##问题:本来整个流程看起来挺合理的,没啥毛病,但是,有个问题,这里的前提条件是请求有请求头,可是对于定时任务(这里用的是xx-job),他是使用反射远程调用的,并没有http请求,也就是说没有请求头。。。。 刚好有个业务处理,他是根据请求的类型,返回对应的响应内容已经异常处理(feign请求的响应体不做统一返回对象处理),此时feign请求的拦截器需要封装一个request头,设置一下类型为feign,这里是这样设置的

@Bean
public RequestInterceptor headerInterceptor() {
    return (requestTemplate) -> {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            requestTemplate.header("Client-Type", new String[]{ClientTypeEnum.FEIGN.name()});
            requestTemplate.header("sid", new String[]{request.getHeader("sid")});

        }
        
    };
}

它去请求上下文里面拿requestAttributes,如果为空,就不封装feign类型。 刚好这里有个定时任务,再方法里面进行了fegin调用,调用的服务B,此时服务B的方法发生了异常,以为没有feign类型,所以它返回了一个统一结果对象,也就是带有code,msg那些,并且没有抛出异常,那么feign这里会认为这是一个正常响应对象,会进行类型的转化,那么在A服务这里,就会报类型转化异常。

===============feign请求拦截器:可以在进行feign调用的时候,在发送请求前对请求进行一系列操作 实现: 实现RequestInterceptor 接口中的apply方法,在方法中用requestTemplate对请求进行操作

结合spring,更优雅的是在feign的配置类中,用 @RibbonClients 放类头部,@Bean放在返回值为RequestInterceptor的方法上来实现

@RibbonClients( defaultConfiguration = {FeignConfiguration.class} ) public class FeignConfiguration {

    public FeignConfiguration() {
    }

    @Bean
    public RequestInterceptor headerInterceptor() {
        return (requestTemplate) -> {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                requestTemplate.header("Client-Type", new String[]{ClientTypeEnum.FEIGN.name()});
                requestTemplate.header("sid", new String[]{request.getHeader("sid")});

            }
            
        };
    }



    @Bean
    public ErrorDecoder feignDecoder() {
        return new FeignErrorDecoder();
    }
}