This is the 26th day of my participation in the August Text Challenge.More challenges in August

Records on issues of mutual authentication between microservices. Since microservices often use tokens as authentication and store tokens in request headers, but no header file is passed between microservices, the authentication problems between microservices are recorded.

1 About microservice architecture certification

Take the common shopping example:

After the user logs in, the background generates the token and sends it to the foreground. The foreground directly puts the token in the request header. Or the background saves the data into Redis and writes the unique identifier into cookies. Then the gateway obtains the token according to the cookies and adds the request header.

The above describes a user accessing a single microservice application. If it involves mutual invocation between microservices, request header enhancement cannot be performed by the gateway because it does not pass through the gateway, but another microservice being invoked needs authentication. So there’s a feIGN interface call authentication problem, right?

For the above problem, we can use feign interceptor to intercept the request and add token data to the request header when the service calls other microservices. After this process, another microservice can pass the permission verification.

Use of feign interceptors

Method one: Define an interceptor and add it to the launcher class.

/** * feign interceptor will intercept */ whenever requested
@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);
                        }
                    }
                }
            }
        }
    }
}

Copy the code
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }

    @Bean
    public FeignInterceptor feignInterceptor(a){
        return newFeignInterceptor(); }}Copy the code

Method two: Define an interceptor that takes data from the background context and places it directly in.

@Slf4j
public class UserFeignInterceptor  implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        template.header("Authorization",BaseContextHandler.getToken()); }}Copy the code
// When the Feign interface calls an exception, it goes into this class
@Component
public class UserServiceFallback implements UserServiceApi {

     @Override
     public String getUserById(String userId){...// Custom logic}}Copy the code
@FeignClient(value = "ali-order", configuration = UserFeignInterceptor.class, fallback = UserServiceFallback.class)
public interface UserServiceClient extends UserServiceApi {}Copy the code

3 FeIGN interceptor description

Custom FeIGN interceptors work precisely because they implement the RequestInterceptor interface provided by Feign.

The RequestInterceptor interface defines the apply method with a RequestTemplate parameter. It has an abstract class called BaseRequestInterceptor, There are several implementation class BasicAuthRequestInterceptor, FeignAcceptGzipEncodingInterceptor, FeignContentGzipEncodingInterceptor respectively

Including BasicAuthRequestInterceptor RequestInterceptor interface is achieved, the apply method added to the RequestTemplate called Authorization header source code (see below)

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); }}Copy the code

The addHeader method defined by the baserequesttor abstract class adds a non-identical header to the requestTemplate

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(a) {
		return this.properties; }}Copy the code

Including FeignAcceptGzipEncodingInterceptor BaseRequestInterceptor inherited abstract class, apply the method to add the key to Accept – Encoding, value of gzip, deflate the 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); }}Copy the code

Including FeignContentGzipEncodingInterceptor BaseRequestInterceptor inherited abstract class, the apply method, determine whether you need the compression will, To check whether the mimeType meets the requirements and the content size exceeds the threshold, add the header named Content-Encoding, gzip, and deflate if compress is required

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; }}Copy the code

4 summarizes

With the popularity of microservices architecture, the calls between microservices become more and more frequent, so authentication between microservices is also worth paying attention to. I believe there must be a better way in the future.