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:
Through spring boot's @Configuration Properties mechanism defines metadata for remote services In order to realize the configuration of authority authentication
Use Handler Interceptor to intercept and verify privileges
Define generic Rms classes to standardize the use of RestTemplate
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.