项目中访问方式大致分为以下三种
- 外部从网关访问,需要token鉴权。如后台的增删改查是最常用的,只要用户登录就可以了(有些接口可能需要加权限)
- 外部从网关访问,不需要token鉴权,直接对外暴露无token访问。配置security.oauth2.client.release-urls就可以了
- 服务间Feign访问,不需要token鉴权,但又不对外暴露,外部不能访问,只能服务之间访问。实现方式如下
鉴于第三种情况,我们配置ignore-url使接口直接对处暴露,此时该接口不需要鉴权,服务内部通过Feign访问,服务外部通过url也可以访问,并不是我们想要的,所以我们通过在header中加入了一个from(SecurityConstants.FROM)参数来辨别是否内部调用。只有请求Header带有from参数时,才允许通过访问,否则抛出异常。那这样其实在外网通过Gateway访问时,也可以手动带上这个from参数来访问。所以需要在Gateway中设置了一个GlobalFilter过滤器,这个过滤器会删除请求头里的from参数。这样外部访问由于缺少头部信息,被拒绝访问;而服务间通过Feign访问,不经过Gateway,则可以正常访问。
使用场景:以上第三种情况,如A服务要调用B服务的方法
-
在B服务相应接口上使用@Inside注解,使得url无需鉴权(如:SysLogController.save方法)
@Inside的作用其实就是在项目加载时,获取有@Inside注解方法的uri,然后将这些uri自动加入到ignore-url中
/** * <p> * 日志表 前端控制器 * </p> * * @author */ @RestController @AllArgsConstructor @RequestMapping("/log") @Api(value = "log", tags = "日志管理模块") public class SysLogController { private final SysLogService sysLogService; /** * 插入日志 * * @param sysLog 日志实体 * @return */ @ApiOperation(value = "插入日志") @Inside @PostMapping("/save") public R save(@Valid @RequestBody SysLog sysLog) { return R.ok(sysLogService.save(sysLog)); } }
-
在A服务编写Feign接口(如:FeignLogService)
//ServiceNameConstants.UMPS_ADMIN_SERVICE为相应的服务名 @FeignClient(contextId = "feignLogService", value = ServiceNameConstants.UMPS_ADMIN_SERVICE) public interface FeignLogService { /** * 保存日志 * * @param sysLog 日志实体 * @param from 是否内部调用 * @return succes、false */ @PostMapping("/log/save") R<Boolean> saveLog(@RequestBody SysLog sysLog, @RequestHeader(SecurityConstants.FROM) String from); }
-
在A服务调用接口(如:SysLogListener),带上SecurityConstants.FROM=SecurityConstants.FROM_IN参数指定为内部识别
@Async @Order @EventListener(SysLogEvent.class) public void saveSysLog(SysLogEvent event) { SysLog sysLog = event.getSysLog(); reignLogService.saveLog(sysLog, SecurityConstants.FROM_IN); }
-
以上就完成了A服务调用B服务的过程
服务间的token传递
- 没有标识内部调用的,Feign拦截器会将上游服务的token传递给下游服务
public class BaseFeignClientInterceptor extends OAuth2FeignRequestInterceptor {
/**
* fein拦截器将本服务的token,通过copyToken的形式传递给下游服务
* @param requestTemplate
*/
@Override
public void apply(RequestTemplate requestTemplate) {
Collection<String> fromHeader = requestTemplate.headers().get(SecurityConstants.FROM);
if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) {
return;
}
accessTokenContextRelay.copyToken();
if (oAuth2ClientContext != null
&& oAuth2ClientContext.getAccessToken() != null) {
super.apply(requestTemplate);
}
}
}
fallback统一处理
- SpringCloud使用feign的时候,需要指定fallback策略, 但大多数情况的fallback策略为了保证幂等都会很简单,输出错误日志即可,实际开发中一个feignService就要定义一个fallback非常不方便
- 通过重写com.alibaba.cloud.sentinel.feign.SentinelFeign、com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler实现fallback统一处理
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
if (EQUALS.equals(method.getName())) {
try {
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
}
catch (IllegalArgumentException e) {
return false;
}
}
else if (HASH_CODE.equals(method.getName())) {
return hashCode();
}
else if (TO_STRING.equals(method.getName())) {
return toString();
}
Object result;
InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
// only handle by HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
.get(hardCodedTarget.type().getName() + Feign.configKey(hardCodedTarget.type(), method));
// resource default is HttpMethod:protocol://url
if (methodMetadata == null) {
result = methodHandler.invoke(args);
}
else {
String resourceName = methodMetadata.template().method().toUpperCase() + ":" + hardCodedTarget.url()
+ methodMetadata.template().path();
Entry entry = null;
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
}
catch (Throwable ex) {
// fallback handle
if (!BlockException.isBlockException(ex)) {
Tracer.trace(ex);
}
if (fallbackFactory != null) {
try {
Object fallbackResult = fallbackMethodMap.get(method).invoke(fallbackFactory.create(ex),
args);
return fallbackResult;
}
catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an
// interface
throw new AssertionError(e);
}
catch (InvocationTargetException e) {
throw new AssertionError(e.getCause());
}
}
else {
// 若是R类型 执行自动降级返回R
if (R.class == method.getReturnType()) {
log.error("feign 服务间调用异常", ex);
return R.failed(ex.getLocalizedMessage());
}
else {
throw ex;
}
}
}
finally {
if (entry != null) {
entry.exit(1, args);
}
ContextUtil.exit();
}
}
}
else {
// other target type using default strategy
result = methodHandler.invoke(args);
}
return result;
}