微服务之间传递token解决方案
在springcloud 微服务中大部分是通过token来验证用户的,那么有个问题,假设现在有A服务,B服务,外部使用REST Api请求调用A服务,在请求头上有token字段,A服务根据token解决了认证和授权,应用内部也有了用户信息,但是A服务逻辑处理完后,调用B服务,B也有自己的认证和授权逻辑,他也需要token,(比如利用token去获取用户信息,获取授权信息等),如何才能把信息也转发到B服务呢,最差的解决办法就是吧需要的信息放到请求参数中,比如调用的时候,带上user,但是这样的话,增加了网络开销,暴露了用户信息。还比较麻烦,每次调用其他服务都要加个参数。
解决方案
解决方案1:可以是webconfig安全配置 不拦截以/f开头的方法,那么服务A是可以调用服务B了,但是因为没有用户信息
解决方案2:就是调用的时候带上token。服务B自己去认证服务,验证token。
我们想想就知道,其实服务B也是一个服务,如果不走内部调用,外部调用也可以调用,只是需要请求头里带上Authorization bearer 75120819-a26e-459f-ba38-c305ed258024 这样的token。那么我们内部调用也可以这样处理,带上我们的token。
其实我们feign其实也就是内部配置了一个客户端,调用服务而已,其实也是外部调用。
只是feign利用注解,eureka的注册信息等自己创建客户端,拼接对象和地址。
困难点就是,fegin如何配置加上token。
fegin有一个RequestInterceptor,每次构造request的时候,都会走这里,那么我们直接重写RequestInterceptor,把request里的请求参数包括请求头全部复制到feign的request里,然后配置进去就好。
但是直接使用一般情况下HttpServletRequest上下文对象是为空的,原因也很简单,hystrix的隔离级别(线程池隔离和信号量隔离),默认的线程池隔离好像是拿不到request的。
其实加个配置就可以解决。
步骤1(调整hystrix的隔离级别)
服务A中 application.yml 加入如下配置
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE #加上这个就可以获取到HttpServletRequest
thread:
timeoutInMilliseconds: 10000
据说是因为fegin的调用的话隔离级别配置在hystrix里,而获取httpRequest是通过threadlocal里的,不这么配置获取不到
步骤2(复制请求头)
实现RequestInterceptor接口(全复制,可能有问题,我遇到bug了)
//注意,因为fegin调用的时候,因为如果全部请求头复制赋值,会导致后续请求都沿用这个请求头,但是
//如果这个最初的请求头的配置有问题,比如content-length不一致,就会调用失败,如服务A的content-length是30,服务B的content-length为15,服务B会报调用失败,所以只复制认证信息
@Configuration
public class FeginInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
try {
Map<String,String> headers = getHeaders();
for(String headerName : headers.keySet()){
requestTemplate.header(headerName, headers.get(headerName));
}
}catch (Exception e){
e.printStackTrace();
}
}
private Map<String, String> getHeaders(){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
}
单纯复制请求头中认证信息
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
requestTemplate.header(HttpHeaders.AUTHORIZATION, request.getHeader(HttpHeaders.AUTHORIZATION));
}
}
步骤3(加入配置)
在@FeignClient接口里添加configuration = {FeignConfig.class}
@FeignClient(value="被调用的服务名",configuration={FeignConfig.class})
或者
在application.yml
中增加feign客户端的拦截器:
feign:
client:
config:
rpc-user: # feign name
requestInterceptors:
- com.xx.user.rolnipclouduser.interceptor.FeignTokenInterceptor
主要解决:
有些服务之间存在相互调用,比如服务A调用服务B。但是这里存在授权的概念,外部请求服务A有权限,但是这个外部用户可能没有服务B的权限,但是这个属于内部服务调用,对权限校验应该不用这么严格,应该要能调用到,所有这边的处理是对于/f/请求的请求,都放行不进行权限拦截。而内部服务调用,本质就是服务内部加了个httpclientt进行请求,那么不进行配置的话,对服务器来说就是一个无状态的会话,那么这个请求链路上的服务都是没有用户状态的。但是我又不想每个内部请求都带上用户信息,那么就内部的请求调用都带上外部请求的认证信息token。