zoukankan      html  css  js  c++  java
  • 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

      1 public class OAuth2FeignConfigure {
      2     // feign的OAuth2ClientContext
      3     private OAuth2ClientContext feignOAuth2ClientContext =  new DefaultOAuth2ClientContext();
      4 
      5     @Resource
      6     private ClientCredentialsResourceDetails clientCredentialsResourceDetails;
      7 
      8     @Autowired
      9     private ObjectFactory<HttpMessageConverters> messageConverters;
     10 
     11     @Bean
     12     public OAuth2RestTemplate clientCredentialsRestTemplate(){
     13         return new OAuth2RestTemplate(clientCredentialsResourceDetails);
     14     }
     15 
     16     @Bean
     17     public RequestInterceptor oauth2FeignRequestInterceptor(){
     18         return new OAuth2FeignRequestInterceptor(feignOAuth2ClientContext, clientCredentialsResourceDetails);
     19     }
     20 
     21     @Bean
     22     public Logger.Level feignLoggerLevel() {
     23         return Logger.Level.FULL;
     24     }
     25 
     26 
     27     @Bean
     28     public Retryer retry() {
     29         // default Retryer will retry 5 times waiting waiting
     30         // 100 ms per retry with a 1.5* back off multiplier
     31         return new Retryer.Default(100, SECONDS.toMillis(1), 3);
     32     }
     33 
     34 
     35     @Bean
     36     public Decoder feignDecoder() {
     37         return new CustomResponseEntityDecoder(new SpringDecoder(this.messageConverters), feignOAuth2ClientContext);
     38     }
     39 
     40 
     41     /**
     42      *  Http响应成功 但是token失效,需要定制 ResponseEntityDecoder
     43      * @author maxianming
     44      * @date 2018/10/30 9:47
     45      */
     46     class CustomResponseEntityDecoder implements Decoder {
     47         private org.slf4j.Logger log = LoggerFactory.getLogger(CustomResponseEntityDecoder.class);
     48 
     49         private Decoder decoder;
     50 
     51         private OAuth2ClientContext context;
     52 
     53         public CustomResponseEntityDecoder(Decoder decoder, OAuth2ClientContext context) {
     54             this.decoder = decoder;
     55             this.context = context;
     56         }
     57 
     58         @Override
     59         public Object decode(final Response response, Type type) throws IOException, FeignException {
     60             if (log.isDebugEnabled()) {
     61                 log.debug("feign decode type:{},reponse:{}", type, response.body());
     62             }
     63             if (isParameterizeHttpEntity(type)) {
     64                 type = ((ParameterizedType) type).getActualTypeArguments()[0];
     65                 Object decodedObject = decoder.decode(response, type);
     66                 return createResponse(decodedObject, response);
     67             }
     68             else if (isHttpEntity(type)) {
     69                 return createResponse(null, response);
     70             }
     71             else {
     72                 // custom ResponseEntityDecoder if token is valid then go to errorDecoder
     73                 String body = Util.toString(response.body().asReader());
     74                 if (body.contains(ServerConstant.INVALID_TOKEN.getCode())) {
     75                     clearTokenAndRetry(response, body);
     76                 }
     77                 return decoder.decode(response, type);
     78             }
     79         }
     80         
     81         /**
     82          * token失效 则将token设置为null 然后重试
     83          * @author maxianming
     84          * @param
     85          * @return 
     86          * @date 2018/10/30 10:05
     87          */
     88         private void clearTokenAndRetry(Response response, String body) throws FeignException {
     89             log.error("接收到Feign请求资源响应,响应内容:{}",body);
     90             context.setAccessToken(null);
     91             throw new RetryableException("access_token过期,即将进行重试", new Date());
     92         }
     93 
     94         private boolean isParameterizeHttpEntity(Type type) {
     95             if (type instanceof ParameterizedType) {
     96                 return isHttpEntity(((ParameterizedType) type).getRawType());
     97             }
     98             return false;
     99         }
    100 
    101         private boolean isHttpEntity(Type type) {
    102             if (type instanceof Class) {
    103                 Class c = (Class) type;
    104                 return HttpEntity.class.isAssignableFrom(c);
    105             }
    106             return false;
    107         }
    108 
    109         @SuppressWarnings("unchecked")
    110         private <T> ResponseEntity<T> createResponse(Object instance, Response response) {
    111 
    112             MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
    113             for (String key : response.headers().keySet()) {
    114                 headers.put(key, new LinkedList<>(response.headers().get(key)));
    115             }
    116             return new ResponseEntity<>((T) instance, headers, org.springframework.http.HttpStatus.valueOf(response
    117                     .status()));
    118         }
    119     }
    120 
    121 
    122 
    123     @Bean
    124     public ErrorDecoder errorDecoder() {
    125         return new RestClientErrorDecoder(feignOAuth2ClientContext);
    126     }
    127 
    128     /**
    129      *  Feign调用HTTP返回响应码错误时候,定制错误的解码
    130      * @author maxianming
    131      * @date 2018/10/30 9:45
    132      */
    133     class RestClientErrorDecoder implements ErrorDecoder {
    134         private org.slf4j.Logger logger = LoggerFactory.getLogger(RestClientErrorDecoder.class);
    135 
    136         private OAuth2ClientContext context;
    137 
    138         RestClientErrorDecoder(OAuth2ClientContext context) {
    139             this.context = context;
    140         }
    141 
    142         public Exception decode(String methodKey, Response response) {
    143             logger.error("Feign调用异常,异常methodKey:{}, token:{}, response:{}", methodKey, context.getAccessToken(), response.body());
    144             if (HttpStatus.SC_UNAUTHORIZED == response.status()) {
    145                 logger.error("接收到Feign请求资源响应401,access_token已经过期,重置access_token为null待重新获取。");
    146                 context.setAccessToken(null);
    147                 return new RetryableException("疑似access_token过期,即将进行重试", new Date());
    148             }
    149             return errorStatus(methodKey, response);
    150         }
    151     }
    152 
    153 
    154 }

    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并重试。

    
    

         

      

  • 相关阅读:
    (Vue中)cehart在同一个dom上画图图切换时饼图有折线图的坐标系
    linux(centos7)修改服务器时间
    centos6 yum源不能使用
    Linux 使用 history 来减少重复命令的几个实用技巧。
    7个Shell 拿来就用脚本实例!
    keepalived的配置解析&安装与爬坑
    linux最全命令使用手册
    linux各种误删文件恢复方法(经典强推)
    位运算符
    SQL 书写、执行顺序
  • 原文地址:https://www.cnblogs.com/mxmbk/p/9883629.html
Copyright © 2011-2022 走看看