关于微服务之间认证问题
- 1 关于微服务架构认证
- 2 feign拦截器的使用
- 3 feign拦截器的说明
- 4 总结
关于微服务之间相互认证问题的记录.因微服务常使用token作为鉴权,且将token存放到请求头中,但是微服务之间没有传递头文件,所以记录一下微服务之间的认证问题.
1 关于微服务架构认证
以常用的购物为例:
用户登录后, 后台会生成token,传给前台,前台直接放到请求头中;或者后台保存数据到redis中,将唯一标识写到cookie中,由网关根据cookie获取token,增加请求头.
上面描述的是用户访问单一的微服务应用. 如果涉及到微服务之间的相互调用,由于不经过网关,所以无法由网关进行请求头增强,但是被调用的另一个微服务又需要鉴权.所以就产生了一个feign接口调用的鉴权问题?
对于上述问题,我们可以采用feign拦截器,在本服务调用其他微服务时,拦截请求,将token数据添加到请求头中.这样处理后,再调用另一个微服务时,可正常通过权限校验.
2 feign拦截器的使用
方法一: 定义一个拦截器,在启动类中添加拦截器.
/**
* feign拦截器,只要请求就会拦截
*/
@Component
public class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
if (request != null) {
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
if (headerName.equals("authorization")) {
String headerValue = request.getHeader(headerName);
template.header(headerName, headerValue);
}
}
}
}
}
}
}
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
@Bean
public FeignInterceptor feignInterceptor(){
return new FeignInterceptor();
}
}
方法二: 定义一个拦截器,从后台上下文中取出数据,直接放入.
@Slf4j
public class UserFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header("Authorization",BaseContextHandler.getToken());
}
}
// 当feign接口调用异常时会进入这个类中处理
@Component
public class UserServiceFallback implements UserServiceApi {
@Override
public String getUserById(String userId){
... // 自定义逻辑
}
}
@FeignClient(value = "ali-order", configuration = UserFeignInterceptor.class, fallback = UserServiceFallback.class)
public interface UserServiceClient extends UserServiceApi {
}
3 feign拦截器的说明
自定义feign拦截器能起到作用,正是因为实现了feign提供的RequestInterceptor接口.
RequestInterceptor接口定义了apply方法,其参数为RequestTemplate;它有一个抽象类为BaseRequestInterceptor,还有几个实现类分别为BasicAuthRequestInterceptor、FeignAcceptGzipEncodingInterceptor、FeignContentGzipEncodingInterceptor
其中BasicAuthRequestInterceptor实现了RequestInterceptor接口,其apply方法往RequestTemplate添加名为Authorization的header(见下图源码)
public class BasicAuthRequestInterceptor implements RequestInterceptor {
private final String headerValue;
/**
* Creates an interceptor that authenticates all requests with the specified username and password
* encoded using ISO-8859-1.
*
* @param username the username to use for authentication
* @param password the password to use for authentication
*/
public BasicAuthRequestInterceptor(String username, String password) {
this(username, password, ISO_8859_1);
}
/**
* Creates an interceptor that authenticates all requests with the specified username and password
* encoded using the specified charset.
*
* @param username the username to use for authentication
* @param password the password to use for authentication
* @param charset the charset to use when encoding the credentials
*/
public BasicAuthRequestInterceptor(String username, String password, Charset charset) {
checkNotNull(username, "username");
checkNotNull(password, "password");
this.headerValue = "Basic " + base64Encode((username + ":" + password).getBytes(charset));
}
/*
* This uses a Sun internal method; if we ever encounter a case where this method is not
* available, the appropriate response would be to pull the necessary portions of Guava's
* BaseEncoding class into Util.
*/
private static String base64Encode(byte[] bytes) {
return Base64.encode(bytes);
}
@Override
public void apply(RequestTemplate template) {
template.header("Authorization", headerValue);
}
}
其中抽象类BaseRequestInterceptor定义的addHeader方法,向requestTemplate添加非重名的header
public abstract class BaseRequestInterceptor implements RequestInterceptor {
/**
* The encoding properties.
*/
private final FeignClientEncodingProperties properties;
/**
* Creates new instance of {@link BaseRequestInterceptor}.
* @param properties the encoding properties
*/
protected BaseRequestInterceptor(FeignClientEncodingProperties properties) {
Assert.notNull(properties, "Properties can not be null");
this.properties = properties;
}
/**
* Adds the header if it wasn't yet specified.
* @param requestTemplate the request
* @param name the header name
* @param values the header values
*/
protected void addHeader(RequestTemplate requestTemplate, String name,
String... values) {
if (!requestTemplate.headers().containsKey(name)) {
requestTemplate.header(name, values);
}
}
protected FeignClientEncodingProperties getProperties() {
return this.properties;
}
}
其中FeignAcceptGzipEncodingInterceptor继承抽象类BaseRequestInterceptor,apply方法中添加了键为Accept-Encoding,值为gzip,deflate的header
public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
/**
* Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
* @param properties the encoding properties
*/
protected FeignAcceptGzipEncodingInterceptor(
FeignClientEncodingProperties properties) {
super(properties);
}
/**
* {@inheritDoc}
*/
@Override
public void apply(RequestTemplate template) {
addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER,
HttpEncoding.GZIP_ENCODING, HttpEncoding.DEFLATE_ENCODING);
}
}
其中FeignContentGzipEncodingInterceptor继承抽象类BaseRequestInterceptor,apply方法中,判断了是否需要compression,即mimeType是否符合要求以及content大小是否超出阈值,需要compress的话则添加名为Content-Encoding,值为gzip,deflate的header
public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor {
/**
* Creates new instance of {@link FeignContentGzipEncodingInterceptor}.
* @param properties the encoding properties
*/
protected FeignContentGzipEncodingInterceptor(
FeignClientEncodingProperties properties) {
super(properties);
}
/**
* {@inheritDoc}
*/
@Override
public void apply(RequestTemplate template) {
if (requiresCompression(template)) {
addHeader(template, HttpEncoding.CONTENT_ENCODING_HEADER,
HttpEncoding.GZIP_ENCODING, HttpEncoding.DEFLATE_ENCODING);
}
}
/**
* Returns whether the request requires GZIP compression.
* @param template the request template
* @return true if request requires compression, false otherwise
*/
private boolean requiresCompression(RequestTemplate template) {
final Map<String, Collection<String>> headers = template.headers();
return matchesMimeType(headers.get(HttpEncoding.CONTENT_TYPE))
&& contentLengthExceedThreshold(headers.get(HttpEncoding.CONTENT_LENGTH));
}
/**
* Returns whether the request content length exceed configured minimum size.
* @param contentLength the content length header value
* @return true if length is grater than minimum size, false otherwise
*/
private boolean contentLengthExceedThreshold(Collection<String> contentLength) {
try {
if (contentLength == null || contentLength.size() != 1) {
return false;
}
final String strLen = contentLength.iterator().next();
final long length = Long.parseLong(strLen);
return length > getProperties().getMinRequestSize();
}
catch (NumberFormatException ex) {
return false;
}
}
/**
* Returns whether the content mime types matches the configures mime types.
* @param contentTypes the content types
* @return true if any specified content type matches the request content types
*/
private boolean matchesMimeType(Collection<String> contentTypes) {
if (contentTypes == null || contentTypes.size() == 0) {
return false;
}
if (getProperties().getMimeTypes() == null
|| getProperties().getMimeTypes().length == 0) {
// no specific mime types has been set - matching everything
return true;
}
for (String mimeType : getProperties().getMimeTypes()) {
if (contentTypes.contains(mimeType)) {
return true;
}
}
return false;
}
}
4 总结
随着微服务架构的流行,微服务之间的调用也越来越变得频繁,所以关于微服务之间的鉴权也是值得关注的.相信将来一定会有更好的方法.