spring cloud alibaba 集成feign 自定义feign权限注解 某接口只允许feign访问 附带所有流程以及代码

本文是《从0到1 手把手搭建spring cloud alibaba 微服务大型应用框架(三) (mini-cloud) 搭建认证服务(认证/资源分离版) oauth2.0 (上)》篇的feign 集成篇

主要流程介绍

##1.本文 fegin使用场景
##2.集成fegin 以及目录结构
##3.自定义扫面注解注入fegin service
##4.设置fegin 传递token header
##5.设计某接口只允许feign访问实现原理
##6.开发只允许fegin注解以及拦截器源码

1.本文 fegin使用场景

本文主要介绍使用场景:

1.是在我们的mini-cloud框架中实现授权时feign回调我们upms服务获取用户信息

具体upms数据获取可以参考上篇《spring boot /spring cloud 集成fluent-mybatis + druid 及案例代码》

 具体流程图如下

 更详细的流程可以查看《Spring-security-oauth2 源码分析 登陆流程 /oauth/token (一)》

 2.集成fegin 以及目录结构

集成fegin 需要的pom

<!--feign 依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

 目录结构

 

 注意项

 有fegin 的项目一般都需要分成api 和biz两个module,假如A服务调用B服务的某功能,一定要B服务提供暴漏的feign接口,而不是在A服务去写一个feign 访问B服务的接口,这样设计有两个好处,一是只有B服务自己知道哪些接口可以对外暴漏,可以做些权限限定,二是如果A服务去实现了feign,那以后C服务需要feign访问B服务,C服务还得实现feign,无线增多,会很多代码冗余

 我这里是upms自己提供了自己的feign接口,认证中心引入upms-center-api 注入feign 然后访问我们的用户获取接口

 3.自定义扫面注解注入fegin service

首先看一下我们的自定义注解

package com.minicloud.authentication.annotation;

import org.springframework.cloud.openfeign.EnableFeignClients;

import java.lang.annotation.*;

/**
 * @Author alan.wang
 * @date: 2022-01-21 14:42
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableFeignClients
public @interface EnableMiniCloudFeignClients {


    String[] value() default {};

    /**
     * 主要是为了扫描我们自己框架的包
     * */
    String[] basePackages() default {"com.minicloud"};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] defaultConfiguration() default {};

    Class<?>[] clients() default {};


}

可以看见@EnableFeignClients 是我们自定义注解EnableMiniCloudFeignClients 的元注解

,几乎没有区别,只是多了一个package default指定

为什么需要指定package

因为我们是authentication-center服务调用upms-center-biz服务,feign接口并不在authentication-center服务内,如果想注入必须要让spring-boot扫面到这个feign,因为我们的package 外围是com.minicloud,为了方便我就写了这个,以后可能会更细化到子package

 4.设置fegin 传递token header

为什么要设置header

我们调用feign的时候是默认不会讲我们header信息带过去的,所以如果一个接口时有权限的接口

需要手动讲header 带过去,我这里仅仅只赋值token的认证属性,大家可以根据自己需求或者直接将所有header都带过去

package com.minicloud.authentication.config;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @Author alan.wang
 * @date: 2022-01-21 15:28
 */

public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {

        HttpServletRequest  httpServletRequest =((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
        requestTemplate.header("Authorization",httpServletRequest.getHeader("Authorization"));
    }
}

5.设计某接口只允许feign访问实现原理

为什么要某接口只允许feign调用

有时候我们系统的某个接口不希望前端或者client端可以直接调用,我们只希望是服务和服务之间调用,可以设计一个自定义注解,加上这个注解的接口只允许被feign也就是内部服务之间调用

具体方法有很多,今天主要说一种比较简便的,为了便于理解,画了下图

 大致流程如下:

1.首先我们需要把只允许feign访问的接口加上我们自定义的注解,例如:

2:我们在fegin请求时,自定加上一个专门的自定义 header属性,例如:

  

3:我们需要定义一个拦截器拦截所有被加上这个注解的接口,然后判断到访问这个接口的请求是否包含特定header以及内容是否正常,如果不匹配则抛出异常,如图:

 4:如何防止外部访问模拟出特定header 字段,如上图,我们在网关层会擦除该特定header属性

6.开发只允许fegin注解以及拦截器源码

FeignRequestInterceptor.java
package com.minicloud.authentication.config;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * @Author alan.wang
 * @date: 2022-01-21 15:28
 */

@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {

        HttpServletRequest  httpServletRequest =((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
        requestTemplate.header("Authorization",httpServletRequest.getHeader("Authorization"));
        requestTemplate.header("feign-request","feign");
    }
}

FeignSecurityAspect.java
package com.minicloud.upms.config;

import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.security.access.AccessDeniedException;

import javax.servlet.http.HttpServletRequest;

/**
 * @Author alan.wang
 * @date: 2022-01-22 15:36
 * @desc:判断header 中是否包含 feign-request
 */
@Slf4j
@Aspect
@AllArgsConstructor
public class FeignSecurityAspect {

    private final HttpServletRequest request;

    @SneakyThrows
    @Around("@annotation(miniCloudFeignInterface)")
    public Object around(ProceedingJoinPoint point, MiniCloudFeignInterface miniCloudFeignInterface) {
        String header = request.getHeader("feign-request");
        if (StrUtil.equals("feign", header)) {
            log.warn("接口 {} 没有只允许feign访问", point.getSignature().getName());
            throw new AccessDeniedException("Access is denied");
        }
        return point.proceed();
    }
}


版权声明:本文为madness1010原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。