Feign Client 调用接口前先登录的方案

Feign Client 调用接口前先登录的方案

竺沛 203 2022-05-03

概述

在使用Feign Client 调用第三方服务时,往往需要进行登录鉴权,通过token或cookie进行调用方身份鉴别,本文提供一种Feign Client通过AOP进行登录鉴权的思路。

实现思路

通过AOP在每次调用时先进行正常调用,调用失败,如果错误为未登录则调用登录接口,并将获取到的cookie或token存放在System Property,再调用接口时通过feign拦截器将cookie或token设置到request header中再进行调用:
image.png

实现方案

  1. 先添加一个Feigen Client 登录第三方服务的接口
@Component
@FeignClient
public interface Auth {
    /**
     * 调用接口进行登录,
     *
     * @param url : 第三方服务的地址,可以直接写在@FeignClient注解中
     * @param req :第三方服务登录需要传递的参数
     * @return 返回Response用于获取set-cookie的sessionID
     */
    @PostMapping("/Login")
    feign.Response Auth(URI url, @RequestBody AuthReq req);


}

因为采用Cookie作为用户鉴别机制所以接口返回Response,从中获取SessionID如果采用Token 可以直接返回相应的JSON结构,该请求应根据实际第三方服务的登录接口而定;

  1. 创建一个Feign调用前AOP切面,该AOP主要功能为先进行正常调用,如果接口返回无权限则进行登录获取token或session后在进行调用
@Aspect
@Component
public class FeignAop {
    @Autowired
    Auth auth;
    //表示在执行所有被@FeignPointcut标识过的feign远程调用都会进入该注解
    @Around(value = "execution(* (@FeignPointcut *).*(..)) || execution(@FeignPointcut * *(..))")
    public Object toCenterLogAopExceptionHandle(ProceedingJoinPoint pjp) throws Throwable {
        try {
            //尝试进行调用 
            return pjp.proceed();
        } catch (Throwable e) {
            if (e instanceof FeignException) {
		//调用失败且错误为没有登录或登录过期
                if (((FeignException) e).status() == HttpStatus.HTTP_UNAUTHORIZED) {
                    try {
                        var req = new AuthReq();
                        req.setUserName("admin");
			req.setPwd("123456");
			//调用登录接口,可以在@FeignClient注解处直接指定URI地址
                        var res = auth.Auth((URI) pjp.getArgs()[0], req);
			//从Responce中获取cookie并保存到System Property,不同的服务可以采用不同的property name
                        System.setProperty("feign.auth.cookie", res.headers().get("Set-Cookie").iterator().next());
                        res.close();
			//再次进行调用
                        return pjp.proceed();
                    } catch (Throwable ex) {
                        ......
                    }
                }
            }
            log.error("调用失败 :", e);
            throw e;
        }
    }
}
  1. 新建一个注解,用于标识哪些接口需要由以上AOP进行包围
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignPointcut {
}
  1. 添加一个feign RequestInterceptor 用于在request中注入cookie
@Configuration
public class FeignConfiguration {

    /**
     * 创建 Feign 请求拦截器, 在发送请求前设置认证的 Token, 各个微服务将 Token 设置 到环境变量中来达到通用的目的
     *
     * @return
     */
    @Bean
    public RequestInterceptor basicAuthRequestInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
		//从System Property获取登录接口设置的cookie或token
                requestTemplate.header("Cookie", System.getProperty("feign.auth.cookie"));
            }
        };
    }
}