From b95a25327e86c71d2789a5f7960977b5f4b02850 Mon Sep 17 00:00:00 2001 From: wangkang Date: Tue, 13 Jun 2017 10:59:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 8.md | 399 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 399 insertions(+) create mode 100644 8.md diff --git a/8.md b/8.md new file mode 100644 index 0000000..ba6104d --- /dev/null +++ b/8.md @@ -0,0 +1,399 @@ +# spring boot / cloud (八) 使用RestTemplate来构建远程调用服务 + +##前言 + +上周因家里突发急事,请假一周,故博客没有正常更新 + +###RestTemplate介绍: + + +>RestTemplate是spring框架中自带的rest客户端工具类,具有丰富的API,并且在spring cloud中,标记@LoadBalanced注解,可以实现客户端负载均衡的rest调用. + + +##思路 + + +RestTemplate虽然提供了丰富的API,但是这些API过于底层,如果不稍加控制,让开发人员随意使用,那后续的代码也将会变的五花八门,难以维护. + +同时,当系统规模大了之后,将会有更多的服务,并且服务之间的调用关系也将更加复杂,如果不进行管控治理的话,同样,项目同期也将越来越不可控, + +最后,服务间调用也需要有明确的权限认证机制,最好是能通过配置的方式来明确,哪些服务可以调用那些服务.从而来把控项目的复杂度. + +本文将从以下几点来提供一个解决问题的思路: + +- 通过spring boot的@ConfigurationProperties机制来定义远程服务的元数据,从而实现权限认证的配置化 + + +- 使用HandlerInterceptor来进行拦截,实现权限的验证 + + +- 定义通用Rms类,来规范RestTemplate的使用 + + +##实现 + + +###1.实现权限配置 + + +####1.定义Application元数据 + + +``` java +public class ApplicationMeta implements Serializable { + //ID + private static final long serialVersionUID = 1L; + //服务ID + private String serviceId; + //私钥 + private String secret; + //权限 + private String purview; + //所有服务的调用权限(优先判定) + private Boolean all = false; + //禁止服务调用 + private Boolean disabled = false; + //描述 + private String description; +} +``` + + +####2.定义Service元数据 + + +``` java +public class ServiceMeta implements Serializable { + //ID + private static final long serialVersionUID = 1L; + //应用名称 + private String owner; + //地址 + private String uri; + //服务方法 + private String method; + //是否HTTPS + private Boolean isHttps = false; + //描述 + private String description; +``` + + +####3.定义RmsProperties类 + + +``` java +@Component +@ConfigurationProperties(prefix = "com.egridcloud.rms.properties") +public class RmsProperties implements Serializable { + //ID + private static final long serialVersionUID = 1L; + //应用清单(应用名称 : 应用地址) + private Map application; + //服务路径(服务编号 : 服务元数据) + private Map service; +``` + + +####4.在properties文件中进行配置 + + +``` properties +#定义了一个叫udf-demo(跟spring boot的应用ID一致),设置了私钥,以及可调用的服务 +com.egridcloud.rms.properties.application.udf-demo.serviceId=127.0.0.1:8080 +com.egridcloud.rms.properties.application.udf-demo.secret=ADSFHKW349546RFSGF +com.egridcloud.rms.properties.application.udf-demo.purview=FILE_3 +com.egridcloud.rms.properties.application.udf-demo.all=false +com.egridcloud.rms.properties.application.udf-demo.disabled=false +com.egridcloud.rms.properties.application.udf-demo.description=sample application + +#定义了一个叫FILE_3的服务,后续使用这个服务编号进行调用即可 +com.egridcloud.rms.properties.service.FILE_3.owner=udf-demo +com.egridcloud.rms.properties.service.FILE_3.uri=/service/file/download +com.egridcloud.rms.properties.service.FILE_3.method=POST +com.egridcloud.rms.properties.service.FILE_3.isHttps=false +com.egridcloud.rms.properties.service.FILE_3.description=文件下载 +``` + + +###2.实现权限校验 + + +####1.定义RmsAuthHandlerInterceptor拦截器 + + +``` java +public class RmsAuthHandlerInterceptor implements HandlerInterceptor { + //环境标识 + private static final String DEV_PROFILES = "dev"; + //配置 + @Autowired + private RmsProperties rmsProperties; + //环境变量 + @Autowired + private Environment env; + + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler) { + + ....... + + } +} +``` + + +####2.完善preHandle方法-取出认证信息 + + +``` java + String rmsApplicationName = request.getHeader(Constant.HEADER_RMS_APPLICATION_NAME_CODE); + if (StringUtils.isBlank(rmsApplicationName)) { + rmsApplicationName = request.getParameter(Constant.HEADER_RMS_APPLICATION_NAME_CODE); + } + //获取认证信息(sign) + String rmsSign = request.getHeader(Constant.HEADER_RMS_SIGN_CODE); + if (StringUtils.isBlank(rmsSign)) { + rmsSign = request.getParameter(Constant.HEADER_RMS_SIGN_CODE); + } + //获取认证信息(服务代码) + String rmsServiceCode = request.getHeader(Constant.HEADER_SERVICE_CODE_CODE); + if (StringUtils.isBlank(rmsServiceCode)) { + rmsServiceCode = request.getParameter(Constant.HEADER_SERVICE_CODE_CODE); + } + //获取请求地址 + String url = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString(); + //获取请求方法 + String method = request.getMethod(); +``` + + +####3.完善preHandle方法-校验 + + +``` java + //判断环境(开发环境无需校验) + if (!DEV_PROFILES.equals(env.getProperty("spring.profiles.active"))) { + //判断是否缺少认证信息 + if (StringUtils.isBlank(rmsApplicationName) || StringUtils.isBlank(rmsSign) + || StringUtils.isBlank(rmsServiceCode)) { + throw new AuthException("missing required authentication parameters (rmsApplicationName , rmsSign)"); + } + //判断systemTag是否有效 + if (!this.rmsProperties.getApplication().containsKey(rmsApplicationName)) { + throw new AuthException("unrecognized systemTag:" + rmsApplicationName); + } + //获得应用元数据 + ApplicationMeta applicationMeta = rmsProperties.getApplication().get(rmsApplicationName); + //获得secret + String secret = applicationMeta.getSecret(); + //计算sign + String sign = Constant.sign(rmsApplicationName, secret); + //比较sign + if (!rmsSign.equals(sign)) { + throw new AuthException("sign Validation failed"); + } + //判断是否有调用所有服务的权限 + if (!applicationMeta.getAll()) { + //判断是否禁止调用所有服务权限 + if (applicationMeta.getDisabled()) { + throw new PermissionException(rmsApplicationName + " is disabled"); + } + //判断是否有调用该服务的权限 + if (applicationMeta.getPurview().indexOf(rmsServiceCode) == -1) { + throw new PermissionException("no access to this servoceCode : " + rmsServiceCode); + } + //判断服务元数据是否存在 + if (!rmsProperties.getService().containsKey(rmsServiceCode)) { + throw new PermissionException("service code not exist"); + } + //获得服务元数据 + ServiceMeta serviceMeta = rmsProperties.getService().get(rmsServiceCode); + //比较url和method的有效性 + if (!serviceMeta.getUri().equals(url) || !serviceMeta.getMethod().equals(method)) { + throw new PermissionException("url and method verification error"); + } + } + } +``` + + +####4.定义RmsConfig类 + + +``` java +@Configuration +@ConfigurationProperties(prefix = "com.egridcloud.rms.config") +@Validated +public class RmsConfig { + + //RMS扫描路径 + @NotNull + private String rmsPathPatterns; + + ......... + +} +``` + + +####5.定义RmsConfig类-注册bean + + +``` java + @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.在properties文件中进行配置 + + +``` properties +#拦截路径 +com.egridcloud.rms.config.rmsPathPatterns=/service/** +``` + + +###3.实现Rms类 + + +####1.定义rms类 + + +``` java +@Component +public class Rms { + //应用名称 + @Value("${spring.application.name}") + private String springApplicationName; + //restTemplate + @Autowired + private RestTemplate restTemplate; + //配置 + @Autowired + private RmsProperties rmsProperties; +``` + + +####2.定义rms类-call方法 + + +``` java + public ResponseEntity call(String serviceCode, I input, String uriParam, + ParameterizedTypeReference responseType, Map uriVariables) { + //客户端权限验证 + verification(serviceCode); + //构建请求路径 + String path = getRmsUrl(serviceCode); + //获得请求方法 + String method = getRmsMethod(serviceCode); + //拼装路径参数 + if (StringUtils.isNotBlank(uriParam)) { + path += uriParam; + } + //构建请求头 + HttpHeaders httpHeaders = buildSystemTagHeaders(serviceCode); + //构建请求消息体 + HttpEntity requestEntity = new HttpEntity<>(input, httpHeaders); + //请求并且返回 + LOGGER.info("rms url : {} , method : {} ", path, method); + return restTemplate.exchange(path, HttpMethod.resolve(method), requestEntity, responseType, + uriVariables != null ? uriVariables : new HashMap()); + } +``` + +####3.定义rms类-其他方法 + + +``` java + //构建请求头 + 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; + } + //客户端验证 + 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); + } + } + } + //获得请求方法 + private String getRmsMethod(String serviceCode) { + return rmsProperties.getService().get(serviceCode).getMethod(); + } + //构造url + private String getRmsUrl(String serviceCode) { + //获取服务元数据 + ServiceMeta serviceMeta = rmsProperties.getService().get(serviceCode); + //构建请求路径 + 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(); + } + //计算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.客户端调用 + + +``` java +//获得文件信息 +ResponseEntity> fileInfo = rms.call("FILE_4", fileParam, null, + new ParameterizedTypeReference>() { + }, null); +``` + + +##结束 + +这样,规范了远程服务的调用,只关心接口编号和接口的入参和出参,能够增加沟通效率,并且也有了轻量级的服务治理机制,服务间的调用更可控,到最后,配置文件一拉出来一清二楚. + +--------- + +想获得最快更新,请关注公众号 + +![想获得最快更新,请关注公众号](https://mmbiz.qlogo.cn/mmbiz_jpg/gjOvoY7GOt5a4dicfGbqze591YAEiaRONE0nOsiaur4nlsmKtUpRuONue28wJ9JfOXfBl99OoVmYncohMnEY4LMdg/0?wx_fmt=jpeg "想获得最快更新,请关注公众号") +