概述
在使用Feign Client 调用第三方服务时,往往需要进行登录鉴权,通过token或cookie进行调用方身份鉴别,本文提供一种Feign Client通过AOP进行登录鉴权的思路。
实现思路
通过AOP在每次调用时先进行正常调用,调用失败,如果错误为未登录则调用登录接口,并将获取到的cookie或token存放在System Property,再调用接口时通过feign拦截器将cookie或token设置到request header中再进行调用:
实现方案
- 先添加一个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结构,该请求应根据实际第三方服务的登录接口而定;
- 创建一个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;
}
}
}
- 新建一个注解,用于标识哪些接口需要由以上AOP进行包围
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignPointcut {
}
- 添加一个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"));
}
};
}
}