本文是《从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();
}
}