Sprboot / cloud (8) Use RestTemplate to build remote invocation services

Posted by ranbla on Mon, 27 May 2019 22:57:02 +0200

Sprboot / cloud (8) Use RestTemplate to build remote invocation services

Preface

Last week, due to an emergency at home and taking a week off, the blog did not update normally.

RestTemplate introduces:

RestTemplate is the rest client tool class that comes with the spring framework. It has a rich API, and in spring cloud, Markup @LoadBalanced annotation It can realize the rest call of client load balancing.

thinking

RestTemplate provides a wealth of APIs, but these APIs are too low-level, if not slightly controlled, allowing developers to use them at will, then the subsequent code will become diverse and difficult to maintain.

At the same time, when the scale of the system is large, there will be more services, and the invocation relationship between services will be more complex. If there is no control governance, the project will also become more and more uncontrollable at the same time.

Finally, inter-service invocation also requires a clear authority authentication mechanism. It is best to configure the way to identify which services can invoke those services, so as to control the complexity of the project.

This article will provide a solution to the problem from the following points:

Realization

1. Implementing permission configuration

1. Defining Application Metadata

public class ApplicationMeta implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //Service ID
  private String serviceId;
  //private key
  private String secret;
  //Jurisdiction
  private String purview;
  //Call privileges for all services (priority decision)
  private Boolean all = false;
  //Prohibit service invocation
  private Boolean disabled = false;
  //describe
  private String description;
}

2. Defining Service Metadata

public class ServiceMeta implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //apply name
  private String owner;
  //address
  private String uri;
  //Service method
  private String method;
  //Whether HTTPS
  private Boolean isHttps = false;
  //describe
  private String description;

3. Define the RmsProperties class

@Component
@ConfigurationProperties(prefix = "org.itkk.rms.properties")
public class RmsProperties implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //Application List (Application Name: Application Address)
  private Map<String, ApplicationMeta> application;
  //Service Path (Service Number: Service Metadata)
  private Map<String, ServiceMeta> service;

4. Configuration in properties files

#Define a udf-demo (consistent with spring boot's application ID), set the private key, and call the service.
org.itkk.rms.properties.application.udf-demo.serviceId=127.0.0.1:8080
org.itkk.rms.properties.application.udf-demo.secret=ADSFHKW349546RFSGF
org.itkk.rms.properties.application.udf-demo.purview=FILE_3
org.itkk.rms.properties.application.udf-demo.all=false
org.itkk.rms.properties.application.udf-demo.disabled=false
org.itkk.rms.properties.application.udf-demo.description=sample application

#A service called FILE_3 is defined, which can be invoked later with this service number.
org.itkk.rms.properties.service.FILE_3.owner=udf-demo
org.itkk.rms.properties.service.FILE_3.uri=/service/file/download
org.itkk.rms.properties.service.FILE_3.method=POST
org.itkk.rms.properties.service.FILE_3.isHttps=false
org.itkk.rms.properties.service.FILE_3.description=File download

2. Achieving permission checking

1. Define RmsAuthHandler Interceptor Interceptor

public class RmsAuthHandlerInterceptor implements HandlerInterceptor {
  //Environmental identification
  private static final String DEV_PROFILES = "dev";
  //To configure
  @Autowired
  private RmsProperties rmsProperties;
  //environment variable
  @Autowired
  private Environment env;
  
  @Override
  public boolean preHandle(HttpServletRequest request, 
      HttpServletResponse response,
      Object handler) {
      
      .......
      
  }
}

2. Perfecting preHandle Method - Extracting Authentication Information

    String rmsApplicationName = request.getHeader(Constant.HEADER_RMS_APPLICATION_NAME_CODE);
    if (StringUtils.isBlank(rmsApplicationName)) {
      rmsApplicationName = request.getParameter(Constant.HEADER_RMS_APPLICATION_NAME_CODE);
    }
    //Obtain authentication information (sign)
    String rmsSign = request.getHeader(Constant.HEADER_RMS_SIGN_CODE);
    if (StringUtils.isBlank(rmsSign)) {
      rmsSign = request.getParameter(Constant.HEADER_RMS_SIGN_CODE);
    }
    //Get authentication information (service code)
    String rmsServiceCode = request.getHeader(Constant.HEADER_SERVICE_CODE_CODE);
    if (StringUtils.isBlank(rmsServiceCode)) {
      rmsServiceCode = request.getParameter(Constant.HEADER_SERVICE_CODE_CODE);
    }
    //Get the request address
    String url = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
    //Get the request method
    String method = request.getMethod();

3. Perfecting preHandle Method-Check

    //Judging the environment (development environment does not need validation)
    if (!DEV_PROFILES.equals(env.getProperty("spring.profiles.active"))) {
      //Determine whether authentication information is missing
      if (StringUtils.isBlank(rmsApplicationName) || StringUtils.isBlank(rmsSign)
          || StringUtils.isBlank(rmsServiceCode)) {
        throw new AuthException("missing required authentication parameters (rmsApplicationName , rmsSign)");
      }
      //Judging whether systemTag is valid
      if (!this.rmsProperties.getApplication().containsKey(rmsApplicationName)) {
        throw new AuthException("unrecognized systemTag:" + rmsApplicationName);
      }
      //Obtaining application metadata
      ApplicationMeta applicationMeta = rmsProperties.getApplication().get(rmsApplicationName);
      //Get secret
      String secret = applicationMeta.getSecret();
      //Calculate sign
      String sign = Constant.sign(rmsApplicationName, secret);
      //Compare sign
      if (!rmsSign.equals(sign)) {
        throw new AuthException("sign Validation failed");
      }
      //Determine whether there is permission to call all services
      if (!applicationMeta.getAll()) {
        //Determine whether all service permissions are forbidden to be invoked
        if (applicationMeta.getDisabled()) {
          throw new PermissionException(rmsApplicationName + " is disabled");
        }
        //Determine whether there is permission to invoke the service
        if (applicationMeta.getPurview().indexOf(rmsServiceCode) == -1) {
          throw new PermissionException("no access to this servoceCode : " + rmsServiceCode);
        }
        //Determining whether service metadata exists
        if (!rmsProperties.getService().containsKey(rmsServiceCode)) {
          throw new PermissionException("service code not exist");
        }
        //Obtaining service metadata
        ServiceMeta serviceMeta = rmsProperties.getService().get(rmsServiceCode);
        //Comparing the validity of url and method
        if (!serviceMeta.getUri().equals(url) || !serviceMeta.getMethod().equals(method)) {
          throw new PermissionException("url and method verification error");
        }
      }
    }

4. Define the RmsConfig class

@Configuration
@ConfigurationProperties(prefix = "org.itkk.rms.config")
@Validated
public class RmsConfig {

  //RMS Scanning Path
  @NotNull
  private String rmsPathPatterns;

  .........
  
}

5. Define RmsConfig class - Register bean s

  @Bean
  @LoadBalanced
  RestTemplate restTemplate(ClientHttpRequestFactory requestFactory) {
    return new RestTemplate(requestFactory);
  }
  
  @Bean
  public RmsAuthHandlerInterceptor rmsAuthHandlerInterceptor() {
    return new RmsAuthHandlerInterceptor();
  }
  
  @Bean
  public WebMvcConfigurer rmsAuthConfigurer() { //NOSONAR
    return new WebMvcConfigurerAdapter() {
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
        String[] rmsPathPatternsArray = rmsPathPatterns.split(",");
        registry.addInterceptor(rmsAuthHandlerInterceptor()).addPathPatterns(rmsPathPatternsArray);
        super.addInterceptors(registry);
      }
    };
  }

6. Configuration in properties files

#Interception path
org.itkk.rms.config.rmsPathPatterns=/service/**

3. Implementing Rms class

1. Define rms classes

@Component
public class Rms {
  //apply name
  @Value("${spring.application.name}")
  private String springApplicationName;
  //restTemplate
  @Autowired
  private RestTemplate restTemplate;
  //To configure
  @Autowired
  private RmsProperties rmsProperties;

2. Define rms class-call method

  public <I, O> ResponseEntity<O> call(String serviceCode, I input, String uriParam,
      ParameterizedTypeReference<O> responseType, Map<String, ?> uriVariables) {
    //Client Privilege Verification
    verification(serviceCode);
    //Building Request Path
    String path = getRmsUrl(serviceCode);
    //Getting the Request Method
    String method = getRmsMethod(serviceCode);
    //Assembly path parameters
    if (StringUtils.isNotBlank(uriParam)) {
      path += uriParam;
    }
    //Build the request header
    HttpHeaders httpHeaders = buildSystemTagHeaders(serviceCode);
    //Build the request message body
    HttpEntity<I> requestEntity = new HttpEntity<>(input, httpHeaders);
    //Request and return
    return restTemplate.exchange(path, HttpMethod.resolve(method), requestEntity, responseType,
        uriVariables != null ? uriVariables : new HashMap<String, String>());
  }

3. Define rms classes - other methods

  //Build the request header
  private HttpHeaders buildSystemTagHeaders(String serviceCode) {
    String secret = rmsProperties.getApplication().get(springApplicationName).getSecret();
    HttpHeaders headers = new HttpHeaders();
    headers.add(Constant.HEADER_RMS_APPLICATION_NAME_CODE, springApplicationName);
    headers.add(Constant.HEADER_RMS_SIGN_CODE, Constant.sign(springApplicationName, secret));
    headers.add(Constant.HEADER_SERVICE_CODE_CODE, serviceCode);
    return headers;
  }
  //Client Validation
  private void verification(String serviceCode) {
    ApplicationMeta applicationMeta = rmsProperties.getApplication().get(springApplicationName);
    if (!applicationMeta.getAll()) {
      if (applicationMeta.getDisabled()) {
        throw new PermissionException(springApplicationName + " is disabled");
      }
      if (applicationMeta.getPurview().indexOf(serviceCode) == -1) {
        throw new PermissionException("no access to this servoceCode : " + serviceCode);
      }
    }
  }
  //Getting the Request Method
  private String getRmsMethod(String serviceCode) {
    return rmsProperties.getService().get(serviceCode).getMethod();
  }
  //Construction of url
  private String getRmsUrl(String serviceCode) {
    //Obtaining service metadata
    ServiceMeta serviceMeta = rmsProperties.getService().get(serviceCode);
    //Building Request Path
    StringBuilder url =
        new StringBuilder(serviceMeta.getIsHttps() ? Constant.HTTPS : Constant.HTTP);
    url.append(rmsProperties.getApplication().get(serviceMeta.getOwner()).getServiceId());
    url.append(serviceMeta.getUri());
    return url.toString();
  }
  //Calculate sign
  public static String sign(String rmsApplicationName, String secret) {
    final String split = "_";
    StringBuilder sb = new StringBuilder();
    sb.append(rmsApplicationName).append(split).append(secret).append(split)
        .append(new SimpleDateFormat(DATA_FORMAT).format(new Date()));
    return DigestUtils.md5Hex(sb.toString());
  }

3. Client Call

//Obtain file information
ResponseEntity<RestResponse<FileInfo>> fileInfo = rms.call("FILE_4", fileParam, null,
              new ParameterizedTypeReference<RestResponse<FileInfo>>() {
              }, null);

Code Warehouse (Blog Matching Code)

End

In this way, the remote service invocation is standardized, only concerned with the interface number and the access and exit parameters of the interface, which can increase communication efficiency, and also has a lightweight service governance mechanism. The invocation between services is more controllable. Finally, the configuration file is clear.

For the fastest update, please pay attention to the public number.

Topics: Java Spring REST