diff --git a/epmet-commons/epmet-commons-security/pom.xml b/epmet-commons/epmet-commons-security/pom.xml
new file mode 100644
index 0000000000..a4bebe4c54
--- /dev/null
+++ b/epmet-commons/epmet-commons-security/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ epmet-commons
+ com.epmet
+ 2.0.0
+
+ 4.0.0
+
+ epmet-commons-security
+
+
+
+ com.epmet
+ epmet-commons-tools
+ 2.0.0
+
+
+ io.jsonwebtoken
+ jjwt
+ 0.7.0
+
+
+
\ No newline at end of file
diff --git a/epmet-commons/epmet-commons-security/src/main/java/com/epmet/commons/security/jwt/JwtTokenUtils.java b/epmet-commons/epmet-commons-security/src/main/java/com/epmet/commons/security/jwt/JwtTokenUtils.java
new file mode 100644
index 0000000000..8cbae20749
--- /dev/null
+++ b/epmet-commons/epmet-commons-security/src/main/java/com/epmet/commons/security/jwt/JwtTokenUtils.java
@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) 2018 人人开源 All rights reserved.
+ *
+ * https://www.renren.io
+ *
+ * 版权所有,侵权必究!
+ */
+
+package com.epmet.commons.security.jwt;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.joda.time.DateTime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Jwt工具类
+ *
+ * @author Mark sunlightcs@gmail.com
+ * @since 1.0.0
+ */
+@Component
+public class JwtTokenUtils {
+ private static final Logger logger = LoggerFactory.getLogger(JwtTokenUtils.class);
+
+ public Claims getClaimByToken(String token, String secret) {
+ try {
+ return Jwts.parser()
+ .setSigningKey(secret)
+ .parseClaimsJws(token)
+ .getBody();
+ } catch (Exception e) {
+ logger.debug("validate is token error, token = " + token, e);
+ return null;
+ }
+ }
+
+ /**
+ * @return java.util.Date
+ * @param token
+ * @Author yinzuomei
+ * @Description 获取token的有效期截止时间
+ * @Date 2020/3/18 22:17
+ **/
+ public Date getExpiration(String token, String secret){
+ try {
+ return Jwts.parser()
+ .setSigningKey(secret)
+ .parseClaimsJws(token)
+ .getBody().getExpiration();
+ } catch (Exception e) {
+ logger.debug("validate is token error, token = " + token, e);
+ return null;
+ }
+ }
+
+ /**
+ * @return java.lang.String
+ * @Author yinzuomei
+ * @Description 根据app+client+userId生成token
+ * @Date 2020/3/18 22:29
+ **/
+ public String createToken(Map claims, int expire, String secret) {
+ return Jwts.builder()
+ .setHeaderParam("typ", "JWT")
+ .setClaims(claims)
+ .setIssuedAt(new Date())
+ .setExpiration(DateTime.now().plusSeconds(expire).toDate())
+ .signWith(SignatureAlgorithm.HS512, secret)
+ .compact();
+ }
+
+ /**
+ * token是否过期
+ *
+ * @return true:过期
+ */
+ public boolean isTokenExpired(Date expiration) {
+ return expiration.before(new Date());
+ }
+
+ public static void main(String[] args) {
+ Map map=new HashMap<>();
+ map.put("app","gov");
+ map.put("client","wxmp");
+ map.put("userId","100526ABC");
+ String tokenStr=Jwts.builder()
+ .setHeaderParam("typ", "JWT")
+ .setClaims(map)
+ .setIssuedAt(new Date())
+ .setExpiration(DateTime.now().plusSeconds(604800).toDate())
+ .signWith(SignatureAlgorithm.HS512, "7016867071f0ebf1c46f123eaaf4b9d6[elink.epmet]")
+ .compact();
+ System.out.println(tokenStr);
+ Claims claims= Jwts.parser()
+ .setSigningKey("7016867071f0ebf1c46f123eaaf4b9d6[elink.epmet]")
+ .parseClaimsJws(tokenStr)
+ .getBody();
+ System.out.println("app="+ claims.get("app"));
+ System.out.println("client="+ claims.get("client"));
+ System.out.println("userId="+ claims.get("userId"));
+ }
+
+}
diff --git a/epmet-commons/epmet-commons-security/src/main/java/com/epmet/commons/security/sign/openapi/OpenApiSignUtils.java b/epmet-commons/epmet-commons-security/src/main/java/com/epmet/commons/security/sign/openapi/OpenApiSignUtils.java
new file mode 100644
index 0000000000..33b82b1a23
--- /dev/null
+++ b/epmet-commons/epmet-commons-security/src/main/java/com/epmet/commons/security/sign/openapi/OpenApiSignUtils.java
@@ -0,0 +1,76 @@
+package com.epmet.commons.security.sign.openapi;
+
+import com.epmet.commons.tools.utils.Md5Util;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * OpenApi签名工具
+ */
+public class OpenApiSignUtils {
+
+ /**
+ * @Description 创建签名
+ * @return
+ * @author wxz
+ * @date 2021.03.22 16:47
+ */
+ public static String createSign(Map contentMap, String signKey) {
+ String str2beSigned = mapToSignStr(contentMap);
+ str2beSigned = str2beSigned.concat("&signKey=").concat(signKey);
+ return Md5Util.md5(str2beSigned);
+ }
+
+ /**
+ * @Description 验签
+ * @return
+ * @author wxz
+ * @date 2021.03.22 16:51
+ */
+ public static boolean checkSign(Map contentMap, String signKey) {
+ String newSign = createSign(contentMap, signKey);
+ return newSign.equals(contentMap.get("sign"));
+ }
+
+ /**
+ * @Description map转化为签名明文
+ * @return
+ * @author wxz
+ * @date 2021.03.22 16:47
+ */
+ public static String mapToSignStr(Map map) {
+ Set keySet = map.keySet();
+ String[] keyArray = (String[])keySet.toArray(new String[keySet.size()]);
+ Arrays.sort(keyArray);
+ StringBuilder sb = new StringBuilder();
+
+ for(int i = 0; i < keyArray.length; ++i) {
+ String key = keyArray[i];
+ String val = (String)map.get(key);
+ if (val != null && val.trim().length() > 0 && !"sign".equals(key)) {
+ if (!sb.toString().isEmpty()) {
+ sb.append("&");
+ }
+
+ sb.append(key).append("=").append((String)map.get(key));
+ }
+ }
+
+ return sb.toString();
+ }
+
+
+ public static void main(String[] args) {
+
+ HashMap content = new HashMap<>();
+ content.put("appId", "7d98b8af2d05752b4225709c4cfd4bd0");
+
+ String secret = "3209ee9f41704482be1a1fb5873a25376f2899191ca846119d44168316bc3e44";
+
+ String sign = createSign(content, secret);
+ System.out.println(sign);
+ }
+}
diff --git a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/exception/EpmetErrorCode.java b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/exception/EpmetErrorCode.java
index ac6d50bd9f..47546c0e81 100644
--- a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/exception/EpmetErrorCode.java
+++ b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/exception/EpmetErrorCode.java
@@ -143,7 +143,10 @@ public enum EpmetErrorCode {
TOPIC_SHIFTED_TO_ISSUE_UNDER_AUDITING(9004,"当前话题正在转议题审核"),
TOPIC_ALREADY_SHIFTED_TO_ISSUE(9005,"该话题已被转为议题,请勿重复操作"),
TOPIC_IS_HIDDEN(9006,"该话题已被屏蔽,请先解除屏蔽"),
- TOPIC_IS_CLOSED(9008,"该话题已关闭,无法转为议题");
+ TOPIC_IS_CLOSED(9008,"该话题已关闭,无法转为议题"),
+
+ // open api异常
+ OPEN_API_SIGN_ERROR(9100, "签名错误");
private int code;
private String msg;
diff --git a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/redis/RedisKeys.java b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/redis/RedisKeys.java
index 098dd1c14d..20bde9e05f 100644
--- a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/redis/RedisKeys.java
+++ b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/redis/RedisKeys.java
@@ -370,4 +370,14 @@ public class RedisKeys {
public static String getCustomerApiServiceKey(String customerId) {
return rootPrefix.concat("customer:thirdplat:apiservice:").concat(customerId);
}
+
+ /**
+ * @Description 获取openApi的accessToken的key
+ * @return
+ * @author wxz
+ * @date 2021.03.23 10:25
+ */
+ public static String getOpenApiAccessTokenKey(String appId) {
+ return rootPrefix.concat("openapi:accesstoken:").concat(appId);
+ }
}
diff --git a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/utils/ConvertUtils.java b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/utils/ConvertUtils.java
index 93c653e178..6d2e509797 100644
--- a/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/utils/ConvertUtils.java
+++ b/epmet-commons/epmet-commons-tools/src/main/java/com/epmet/commons/tools/utils/ConvertUtils.java
@@ -12,11 +12,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
/**
* 转换工具类
@@ -87,4 +90,33 @@ public class ConvertUtils {
}
return t;
}
+
+ /**
+ * @Description entity转map
+ * @return
+ * @author wxz
+ * @date 2021.03.22 16:38
+ */
+ public static Map entityToMap(Object bean) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
+ Class> type = bean.getClass();
+ Map returnMap = new HashMap(16);
+ BeanInfo beanInfo = Introspector.getBeanInfo(type);
+ PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
+
+ for(int i = 0; i < propertyDescriptors.length; ++i) {
+ PropertyDescriptor descriptor = propertyDescriptors[i];
+ String propertyName = descriptor.getName();
+ if (!"class".equals(propertyName)) {
+ Method readMethod = descriptor.getReadMethod();
+ Object result = readMethod.invoke(bean);
+ if (result != null) {
+ returnMap.put(propertyName, String.valueOf(result));
+ } else {
+ returnMap.put(propertyName, "");
+ }
+ }
+ }
+
+ return returnMap;
+ }
}
diff --git a/epmet-commons/pom.xml b/epmet-commons/pom.xml
index 636480c48f..b495dd438e 100644
--- a/epmet-commons/pom.xml
+++ b/epmet-commons/pom.xml
@@ -25,6 +25,7 @@
epmet-commons-extapp-auth
epmet-commons-thirdplat
epmet-commons-rocketmq
-
+ epmet-commons-security
+
diff --git a/epmet-module/epmet-ext/epmet-ext-client/src/main/java/com/epmet/dto/form/AccessTokenFormDTO.java b/epmet-module/epmet-ext/epmet-ext-client/src/main/java/com/epmet/dto/form/AccessTokenFormDTO.java
new file mode 100644
index 0000000000..4b315ae326
--- /dev/null
+++ b/epmet-module/epmet-ext/epmet-ext-client/src/main/java/com/epmet/dto/form/AccessTokenFormDTO.java
@@ -0,0 +1,19 @@
+package com.epmet.dto.form;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+public class AccessTokenFormDTO {
+
+ public interface GetAccessTokenGroup {}
+
+ // 签名字符串密文
+ @NotBlank(message = "签名字段不能为空", groups = { GetAccessTokenGroup.class })
+ private String sign;
+
+ // 应用id
+ @NotBlank(message = "AppId字段不能为空", groups = { GetAccessTokenGroup.class })
+ private String appId;
+}
diff --git a/epmet-module/epmet-ext/epmet-ext-server/pom.xml b/epmet-module/epmet-ext/epmet-ext-server/pom.xml
index f33a428e27..d12766c5e9 100644
--- a/epmet-module/epmet-ext/epmet-ext-server/pom.xml
+++ b/epmet-module/epmet-ext/epmet-ext-server/pom.xml
@@ -21,6 +21,16 @@
+
+ com.epmet
+ common-service-client
+ 2.0.0
+
+
+ com.epmet
+ epmet-commons-security
+ 2.0.0
+
com.epmet
epmet-ext-client
diff --git a/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/config/OpenApiConfig.java b/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/config/OpenApiConfig.java
new file mode 100644
index 0000000000..706c01af35
--- /dev/null
+++ b/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/config/OpenApiConfig.java
@@ -0,0 +1,15 @@
+package com.epmet.config;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+@Component
+@Data
+public class OpenApiConfig {
+
+ @Value("${openApi.accessToken.expire}")
+ private int accessTokenExpire;
+
+}
diff --git a/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/controller/OpenApiAccessTokenController.java b/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/controller/OpenApiAccessTokenController.java
new file mode 100644
index 0000000000..1580ca1b6a
--- /dev/null
+++ b/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/controller/OpenApiAccessTokenController.java
@@ -0,0 +1,88 @@
+package com.epmet.controller;
+
+import com.epmet.commons.security.sign.openapi.OpenApiSignUtils;
+import com.epmet.commons.tools.exception.EpmetErrorCode;
+import com.epmet.commons.tools.exception.ExceptionUtils;
+import com.epmet.commons.tools.exception.RenException;
+import com.epmet.commons.tools.redis.RedisKeys;
+import com.epmet.commons.tools.redis.RedisUtils;
+import com.epmet.commons.tools.utils.ConvertUtils;
+import com.epmet.commons.tools.utils.Result;
+import com.epmet.commons.tools.validator.ValidatorUtils;
+import com.epmet.dto.form.AccessTokenFormDTO;
+import com.epmet.feign.EpmetCommonServiceOpenFeignClient;
+import com.epmet.service.OpenApiAccessTokenService;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.beans.IntrospectionException;
+import java.lang.reflect.InvocationTargetException;
+
+@RestController
+@RequestMapping("open-api")
+public class OpenApiAccessTokenController {
+
+ @Autowired
+ private OpenApiAccessTokenService openApiAccessTokenService;
+
+ @Autowired
+ private EpmetCommonServiceOpenFeignClient commonServiceOpenFeignClient;
+
+ @Autowired
+ private RedisUtils redisUtils;
+
+ private Logger logger = LoggerFactory.getLogger(OpenApiAccessTokenController.class);
+
+ /**
+ * @Description 获取AccessToken
+ * @return
+ * @author wxz
+ * @date 2021.03.23 09:52
+ */
+ @PostMapping("get-access-token")
+ public Result getAccessToken(@RequestBody AccessTokenFormDTO input) {
+ // 1.校验参数
+ ValidatorUtils.validateEntity(input);
+ String appId = input.getAppId();
+
+ // 2.取secret
+ String secret = (String)redisUtils.get(RedisKeys.getExternalAppSecretKey(appId));
+ if (StringUtils.isBlank(secret)) {
+ Result result = commonServiceOpenFeignClient.getSecret(appId);
+ if (!result.success()) {
+ throw new RenException("调用common service查询secret失败");
+ }
+ secret = result.getData();
+ if (StringUtils.isBlank(secret)) {
+ throw new RenException(String.format("根据appId%s没有找到对应的secret", appId));
+ }
+ redisUtils.set(RedisKeys.getExternalAppSecretKey(appId), secret);
+ }
+
+ // 3.验签
+ try {
+ if (!OpenApiSignUtils.checkSign(ConvertUtils.entityToMap(input), secret)) {
+ throw new RenException(EpmetErrorCode.OPEN_API_SIGN_ERROR.getCode(), EpmetErrorCode.OPEN_API_SIGN_ERROR.getMsg());
+ }
+ } catch (RenException e) {
+ // 如果是自己抛出的异常则继续抛出
+ throw e;
+ } catch (Exception e) {
+ // 是其他意外发生的异常
+ String errorStackTrace = ExceptionUtils.getErrorStackTrace(e);
+ logger.error("验签发生未知异常:{}", errorStackTrace);
+ throw new RenException("验签发生未知异常,请查看ext服务详细日志");
+ }
+
+ //4.生成token
+ String accessToken = openApiAccessTokenService.getAccessToken(appId, secret);
+ return new Result().ok(accessToken);
+ }
+
+}
diff --git a/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/service/OpenApiAccessTokenService.java b/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/service/OpenApiAccessTokenService.java
new file mode 100644
index 0000000000..56c3bd4630
--- /dev/null
+++ b/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/service/OpenApiAccessTokenService.java
@@ -0,0 +1,15 @@
+package com.epmet.service;
+
+/**
+ * access token的service
+ */
+public interface OpenApiAccessTokenService {
+
+ /**
+ * @Description 获取AccessToken
+ * @return
+ * @author wxz
+ * @date 2021.03.22 22:57
+ */
+ String getAccessToken(String appId, String secret);
+}
diff --git a/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/service/impl/OpenApiAccessTokenServiceImpl.java b/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/service/impl/OpenApiAccessTokenServiceImpl.java
new file mode 100644
index 0000000000..8610eb2ce0
--- /dev/null
+++ b/epmet-module/epmet-ext/epmet-ext-server/src/main/java/com/epmet/service/impl/OpenApiAccessTokenServiceImpl.java
@@ -0,0 +1,44 @@
+package com.epmet.service.impl;
+
+import com.epmet.commons.security.jwt.JwtTokenUtils;
+import com.epmet.commons.tools.exception.EpmetErrorCode;
+import com.epmet.commons.tools.exception.RenException;
+import com.epmet.commons.tools.redis.RedisKeys;
+import com.epmet.commons.tools.redis.RedisUtils;
+import com.epmet.commons.tools.utils.Result;
+import com.epmet.commons.tools.utils.SpringContextUtils;
+import com.epmet.config.OpenApiConfig;
+import com.epmet.feign.EpmetCommonServiceOpenFeignClient;
+import com.epmet.service.OpenApiAccessTokenService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+
+@Service
+public class OpenApiAccessTokenServiceImpl implements OpenApiAccessTokenService {
+
+ @Autowired
+ private JwtTokenUtils jwtTokenUtils;
+
+ @Autowired
+ private OpenApiConfig openApiConfig;
+
+ @Autowired
+ private RedisUtils redisUtils;
+
+ @Override
+ public String getAccessToken(String appId, String secret) {
+ HashMap claim = new HashMap<>();
+ claim.put("appId", appId);
+
+ String token = jwtTokenUtils.createToken(claim, openApiConfig.getAccessTokenExpire(), secret);
+
+ // 缓存token
+ redisUtils.set(RedisKeys.getOpenApiAccessTokenKey(appId), token, openApiConfig.getAccessTokenExpire());
+
+ return token;
+ }
+}
diff --git a/epmet-module/epmet-ext/epmet-ext-server/src/main/resources/bootstrap.yml b/epmet-module/epmet-ext/epmet-ext-server/src/main/resources/bootstrap.yml
index 7a067d1606..9696f37468 100644
--- a/epmet-module/epmet-ext/epmet-ext-server/src/main/resources/bootstrap.yml
+++ b/epmet-module/epmet-ext/epmet-ext-server/src/main/resources/bootstrap.yml
@@ -110,3 +110,8 @@ shutdown:
graceful:
enable: true #是否开启优雅停机
waitTimeSecs: 30 # 优雅停机等待时间,超过30秒,发出告警
+
+# 对外开放接口配置
+openApi:
+ accessToken:
+ expire: 7200000