Spring Cloud Feign 使用OAuth2
Spring Cloud 微服务架构下,服务间的调用采用的是Feign组件,为了增加服务安全性,server之间互相调用采用OAuth2的client模式。Feign使用http进行服务间的通信,同时整合了Ribbion
使得其具有负载均衡和失败重试的功能,微服务service-a调用service-b的流程 中大概流程 :

Feign调用间采用OAuth2验证的配置:
(1)采用SpringBoot自动加载机制 定义注解继承@EnableOAuth2Client
@Import({OAuth2FeignConfigure.class})
@EnableOAuth2Client
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface EnableFeignOAuth2Client {
}
(2)定义配置类OAuth2FeignConfigure
 public class OAuth2FeignConfigure {
     // feign的OAuth2ClientContext
     private OAuth2ClientContext feignOAuth2ClientContext =  new DefaultOAuth2ClientContext();
     @Resource
     private ClientCredentialsResourceDetails clientCredentialsResourceDetails;
     @Autowired
     private ObjectFactory<HttpMessageConverters> messageConverters;
     @Bean
     public OAuth2RestTemplate clientCredentialsRestTemplate(){
         return new OAuth2RestTemplate(clientCredentialsResourceDetails);
     }
     @Bean
     public RequestInterceptor oauth2FeignRequestInterceptor(){
         return new OAuth2FeignRequestInterceptor(feignOAuth2ClientContext, clientCredentialsResourceDetails);
     }
     @Bean
     public Logger.Level feignLoggerLevel() {
         return Logger.Level.FULL;
     }
     @Bean
     public Retryer retry() {
         // default Retryer will retry 5 times waiting waiting
         // 100 ms per retry with a 1.5* back off multiplier
         return new Retryer.Default(100, SECONDS.toMillis(1), 3);
     }
     @Bean
     public Decoder feignDecoder() {
         return new CustomResponseEntityDecoder(new SpringDecoder(this.messageConverters), feignOAuth2ClientContext);
     }
     /**
      *  Http响应成功 但是token失效,需要定制 ResponseEntityDecoder
      * @author maxianming
      * @date 2018/10/30 9:47
      */
     class CustomResponseEntityDecoder implements Decoder {
         private org.slf4j.Logger log = LoggerFactory.getLogger(CustomResponseEntityDecoder.class);
         private Decoder decoder;
         private OAuth2ClientContext context;
         public CustomResponseEntityDecoder(Decoder decoder, OAuth2ClientContext context) {
             this.decoder = decoder;
             this.context = context;
         }
         @Override
         public Object decode(final Response response, Type type) throws IOException, FeignException {
             if (log.isDebugEnabled()) {
                 log.debug("feign decode type:{},reponse:{}", type, response.body());
             }
             if (isParameterizeHttpEntity(type)) {
                 type = ((ParameterizedType) type).getActualTypeArguments()[0];
                 Object decodedObject = decoder.decode(response, type);
                 return createResponse(decodedObject, response);
             }
             else if (isHttpEntity(type)) {
                 return createResponse(null, response);
             }
             else {
                 // custom ResponseEntityDecoder if token is valid then go to errorDecoder
                 String body = Util.toString(response.body().asReader());
                 if (body.contains(ServerConstant.INVALID_TOKEN.getCode())) {
                     clearTokenAndRetry(response, body);
                 }
                 return decoder.decode(response, type);
             }
         }
         /**
          * token失效 则将token设置为null 然后重试
          * @author maxianming
          * @param
          * @return
          * @date 2018/10/30 10:05
          */
         private void clearTokenAndRetry(Response response, String body) throws FeignException {
             log.error("接收到Feign请求资源响应,响应内容:{}",body);
             context.setAccessToken(null);
             throw new RetryableException("access_token过期,即将进行重试", new Date());
         }
         private boolean isParameterizeHttpEntity(Type type) {
             if (type instanceof ParameterizedType) {
                 return isHttpEntity(((ParameterizedType) type).getRawType());
             }
             return false;
         }
         private boolean isHttpEntity(Type type) {
             if (type instanceof Class) {
                 Class c = (Class) type;
                 return HttpEntity.class.isAssignableFrom(c);
             }
             return false;
         }
         @SuppressWarnings("unchecked")
         private <T> ResponseEntity<T> createResponse(Object instance, Response response) {
             MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
             for (String key : response.headers().keySet()) {
                 headers.put(key, new LinkedList<>(response.headers().get(key)));
             }
             return new ResponseEntity<>((T) instance, headers, org.springframework.http.HttpStatus.valueOf(response
                     .status()));
         }
     }
     @Bean
     public ErrorDecoder errorDecoder() {
         return new RestClientErrorDecoder(feignOAuth2ClientContext);
     }
     /**
      *  Feign调用HTTP返回响应码错误时候,定制错误的解码
      * @author maxianming
      * @date 2018/10/30 9:45
      */
     class RestClientErrorDecoder implements ErrorDecoder {
         private org.slf4j.Logger logger = LoggerFactory.getLogger(RestClientErrorDecoder.class);
         private OAuth2ClientContext context;
         RestClientErrorDecoder(OAuth2ClientContext context) {
             this.context = context;
         }
         public Exception decode(String methodKey, Response response) {
             logger.error("Feign调用异常,异常methodKey:{}, token:{}, response:{}", methodKey, context.getAccessToken(), response.body());
             if (HttpStatus.SC_UNAUTHORIZED == response.status()) {
                 logger.error("接收到Feign请求资源响应401,access_token已经过期,重置access_token为null待重新获取。");
                 context.setAccessToken(null);
                 return new RetryableException("疑似access_token过期,即将进行重试", new Date());
             }
             return errorStatus(methodKey, response);
         }
     }
 }
1、使用ClientCredentialsResourceDetails (即client_id、 client-secret、user-info-uri等信息配置在配置中心)初始化OAuth2RestTemplate,用户请求创建token时候验证基本信息
2、主要定义了拦截器初始化了OAuth2FeignRequestInterceptor ,使得Feign进行RestTemplate调用的请求前进行token拦截。 如果不存在token则需要auth-server中获取token
3、注意上下文对象OAuth2ClientContext建立后不放在Bean容器中,主要放在Bean容器,Spring mvc的前置处理器, 会复制token到OAuth2ClientContext中, 导致用户的token会覆盖服务间的token当不同 token间的权限不同时,验证会不通过。
4、重新定义了 Decoder 即,RestTemple http调用的响应进行解码, 由于token失效时进行了扩展,
默认情况下:token失效会返回401错误的http响应,导致进入ErrorDecoder流程,在ErrorDecoder中如果token过期,则进行除掉token,Feign重试。
扩展后:返回的是token失效的错误码,所以会走Decoder流程,所以对ResponseEntityDecoder进行了扩展,如果无效token错误码,则清空token并重试。
Spring Cloud Feign 使用OAuth2的更多相关文章
- Spring Cloud下基于OAUTH2+ZUUL认证授权的实现
		Spring Cloud下基于OAUTH2认证授权的实现 在Spring Cloud需要使用OAUTH2来实现多个微服务的统一认证授权,通过向OAUTH服务发送某个类型的grant type进行集中认 ... 
- 笔记:Spring Cloud Feign Ribbon 配置
		由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 的客户端的方式来自定义各个服务客户端调用的参 ... 
- 笔记:Spring Cloud Feign Hystrix 配置
		在 Spring Cloud Feign 中,除了引入了用户客户端负载均衡的 Spring Cloud Ribbon 之外,还引入了服务保护与容错的工具 Hystrix,默认情况下,Spring Cl ... 
- 笔记:Spring Cloud Feign 其他配置
		请求压缩 Spring Cloud Feign 支持对请求与响应进行GZIP压缩,以减少通信过程中的性能损耗,我们只需要通过下面二个参数设置,就能开启请求与响应的压缩功能,yml配置格式如下: fei ... 
- 笔记:Spring Cloud Feign 声明式服务调用
		在实际开发中,对于服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以我们通常会针对各个微服务自行封装一些客户端类来包装这些依赖服务的调用,Spring Cloud Feign 在此基础上做了进 ... 
- Spring Cloud下基于OAUTH2认证授权的实现
		GitHub(spring -boot 2.0.0):https://github.com/bigben0123/uaa-zuul 示例(spring -boot 2.0.0): https://gi ... 
- 第六章:声明式服务调用:Spring Cloud Feign
		Spring Cloud Feign 是基于 Netflix Feign 实现的,整合了 Spring Cloud Ribbon 和 Spring Cloud Hystrix,除了提供这两者的强大功能 ... 
- Spring Cloud Feign Ribbon 配置
		由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 的客户端的方式来自定义各个服务客户端调用的参 ... 
- Spring Cloud feign
		Spring Cloud feign使用 前言 环境准备 应用模块 应用程序 应用启动 feign特性 综上 1. 前言 我们在前一篇文章中讲了一些我使用过的一些http的框架 服务间通信之Http框 ... 
随机推荐
- informix遇到错误 25571:Cannot create an user thread
			今天发现informix数据库不能用dbaccess访问,报错: 25571: Cannot create an user thread. On NT check username, and IX ... 
- Atitit. 软件---多媒体区---- jmf 2.1.1 Java Media Framework 支持的格式
			Atitit. 软件---多媒体区---- jmf 2.1.1 Java Media Framework 支持的格式 JMF,全名为Java Media Framework,它可以在java appl ... 
- C++11新特性实验
			#include <iostream> #include <vector> #include <map> #include <string> #incl ... 
- 【LeetCode】Permutations II 解题报告
			[题目] Given a collection of numbers that might contain duplicates, return all possible unique permuta ... 
- 把一张图片 转成二进制流 用AFNetworking POST 上传到服务器.
			把一张图片 转成二进制流 用AFNetworking POST 上传到服务器. AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOper ... 
- 李洪强iOS开发之苹果企业开发者账号申请流程
			李洪强iOS开发之苹果企业开发者账号申请流程 一. 开发者账号类型选择 邓白氏码 DUNS number,是Data Universal Numbering System的缩写,是一个独一无二的9位数 ... 
- Bootstrap学习笔记(8)--响应式导航栏
			说明: 1. 响应式导航栏,就是右上角的三道杠,点一下下方出现隐藏的导航栏.如果屏幕够大就显示所有的导航选项,如果屏幕小比如手机,就显示部分,剩下的放到三道杠里隐藏. 2. 外面套一个大的div,其实 ... 
- 一款纯css3实现的超炫3D表单
			今天要给大家分享一款纯css3实现的超炫3D表单.该特效页面的加载的时候3d四十五度倾斜,当鼠标经过的时候表单动画回正.效果非常炫,一起看下效果图: 在线预览 源码下载 实现的代码. html代码 ... 
- [基础]sizeof和strlen
			转自网络 首先切记,sizeof不能用来求字符串长度 1.sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型.该类型保证能容纳实现所建立的最大对象的字 ... 
- 关于VS2013编辑器的问题
			如果输出报错 This function or variable may be unsafe. 解决方法 1.用VS2013打开出现错误的代码文件 2.在工程文件名处右击鼠标打开快捷菜单,找到“属性” ... 
