Browse Source

完成"内部鉴权+外部应用鉴权"的整合

dev
wxz 5 years ago
parent
commit
c37a6e44d7
  1. 7
      epmet-gateway/pom.xml
  2. 30
      epmet-gateway/src/main/java/com/epmet/auth/ExtAppAuthProcessor.java
  3. 88
      epmet-gateway/src/main/java/com/epmet/auth/ExtAppJwtAuthProcessor.java
  4. 74
      epmet-gateway/src/main/java/com/epmet/auth/ExtAppMD5AuthProcessor.java
  5. 60
      epmet-gateway/src/main/java/com/epmet/auth/ExternalAuthProcessor.java
  6. 22
      epmet-gateway/src/main/java/com/epmet/filter/CpAuthGatewayFilterFactory.java
  7. 12
      epmet-gateway/src/main/java/com/epmet/jwt/JwtTokenUtils.java
  8. 7
      epmet-module/epmet-common-service/common-service-client/src/main/java/com/epmet/feign/EpmetCommonServiceOpenFeignClient.java
  9. 5
      epmet-module/epmet-common-service/common-service-client/src/main/java/com/epmet/feign/fallback/EpmetCommonServiceOpenFeignClientFallback.java
  10. 18
      epmet-module/epmet-common-service/common-service-server/src/main/java/com/epmet/controller/ExternalAppController.java
  11. 2
      epmet-module/epmet-common-service/common-service-server/src/main/java/com/epmet/dao/ExternalAppSecretDao.java
  12. 1
      epmet-module/epmet-common-service/common-service-server/src/main/java/com/epmet/service/ExternalAppSecretService.java
  13. 9
      epmet-module/epmet-common-service/common-service-server/src/main/java/com/epmet/service/impl/ExternalAppSecretServiceImpl.java
  14. 7
      epmet-module/epmet-common-service/common-service-server/src/main/java/com/epmet/utils/externalapp/ExtAppJwtTokenUtils.java
  15. 8
      epmet-module/epmet-common-service/common-service-server/src/main/resources/mapper/ExternalAppSecretDao.xml

7
epmet-gateway/pom.xml

@ -58,6 +58,13 @@
<version>2.0.0</version>
<scope>compile</scope>
</dependency>
<!--用于外部应用认证的查询-->
<dependency>
<groupId>com.epmet</groupId>
<artifactId>common-service-client</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
<build>

30
epmet-gateway/src/main/java/com/epmet/auth/ExtAppAuthProcessor.java

@ -0,0 +1,30 @@
package com.epmet.auth;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 外部应用认证处理器父类
*/
public abstract class ExtAppAuthProcessor {
private Logger logger = LoggerFactory.getLogger(getClass());
private int diffMillins = 1000 * 60 * 5;
public abstract void auth(String appId, String token, Long ts);
/**
* 时间戳校验
* @param timestamp
* @return
*/
protected boolean validTimeStamp(Long timestamp) {
long now = System.currentTimeMillis();
if (Math.abs(now - timestamp) > diffMillins) {
return false;
}
return true;
}
}

88
epmet-gateway/src/main/java/com/epmet/auth/ExtAppJwtAuthProcessor.java

@ -0,0 +1,88 @@
package com.epmet.auth;
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.Result;
import com.epmet.commons.tools.utils.SpringContextUtils;
import com.epmet.feign.EpmetCommonServiceOpenFeignClient;
import com.epmet.jwt.JwtTokenUtils;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* jwt 认证处理器
*/
@Component
public class ExtAppJwtAuthProcessor extends ExtAppAuthProcessor {
private static Logger logger = LoggerFactory.getLogger(ExtAppJwtAuthProcessor.class);
@Autowired
private JwtTokenUtils jwtTokenUtils;
@Autowired
private RedisUtils redisUtils;
@Override
public void auth(String appId, String token, Long ts) {
String secret;
if (StringUtils.isBlank(secret = getTokenFromCache(appId))) {
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), String.format("根据AppId:【%s】没有找到对应的秘钥", appId));
}
Claims claim;
try {
claim = jwtTokenUtils.getClaimByToken(token, secret);
} catch (Exception e) {
String errorStackTrace = ExceptionUtils.getErrorStackTrace(e);
logger.error("解析token失败:{}", errorStackTrace);
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), "解析token失败");
}
String appIdIn = (String)claim.get("appId");
String customerId = (String)claim.get("customerId");
Long timestamp = (Long)claim.get("ts");
//校验时间戳,允许5分钟误差
if (StringUtils.isAnyBlank(appIdIn, customerId) || timestamp == null) {
logger.error("access token不完整。{},{},{}", appIdIn, customerId, timestamp);
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), "AccessToken不完整");
}
if (!validTimeStamp(timestamp)) {
logger.error("extapp token已经超时,请求被拒绝");
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), "AccessToken已经超时");
}
if (!appId.equals(appIdIn)) {
logger.error("AppId不对应,token外部的:{}, token内部解析出来的:{}", appId, appIdIn);
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), "AppId不匹配");
}
}
/**
* 通过APP ID查询对应的秘钥
* @param appId
* @return
*/
public String getTokenFromCache(String appId) {
String secret = (String)redisUtils.get(RedisKeys.getExternalAppSecretKey(appId));
if (StringUtils.isBlank(secret)) {
EpmetCommonServiceOpenFeignClient commonService = SpringContextUtils.getBean(EpmetCommonServiceOpenFeignClient.class);
Result<String> result = commonService.getSecret(appId);
if (!result.success()) {
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), result.getInternalMsg());
}
secret = result.getData();
redisUtils.set(RedisKeys.getExternalAppSecretKey(appId), secret);
}
return secret;
}
}

74
epmet-gateway/src/main/java/com/epmet/auth/ExtAppMD5AuthProcessor.java

@ -0,0 +1,74 @@
package com.epmet.auth;
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.Md5Util;
import com.epmet.commons.tools.utils.Result;
import com.epmet.commons.tools.utils.SpringContextUtils;
import com.epmet.feign.EpmetCommonServiceOpenFeignClient;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* md5 认证处理器
*/
@Component
public class ExtAppMD5AuthProcessor extends ExtAppAuthProcessor {
private static Logger logger = LoggerFactory.getLogger(ExtAppMD5AuthProcessor.class);
//@Autowired
//private EpmetCommonServiceOpenFeignClient commonServiceOpenFeignClient;
@Autowired
private RedisUtils redisUtils;
@Override
public void auth(String appId, String token, Long ts) {
if (ts == null) {
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), "需要传入时间戳参数");
}
String secret;
if (StringUtils.isBlank(secret = getTokenFromCache(appId))) {
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), String.format("根据AppId:%s没有找到对应的秘钥", appId));
}
String localDigest = Md5Util.md5(secret.concat(":") + ts);
if (!localDigest.equals(token)) {
// 调用方生成的摘要跟本地生成的摘要不匹配
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), "签名不匹配,认证失败");
}
if (!validTimeStamp(ts)) {
logger.error("AccessToken已经超时,请求被拒绝");
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), "AccessToken已经超时,请求被拒绝");
}
}
/**
* 通过APP ID查询对应的秘钥
*
* @param appId
* @return
*/
public String getTokenFromCache(String appId) {
String secret = (String) redisUtils.get(RedisKeys.getExternalAppSecretKey(appId));
if (StringUtils.isBlank(secret)) {
EpmetCommonServiceOpenFeignClient commonService = SpringContextUtils.getBean(EpmetCommonServiceOpenFeignClient.class);
Result<String> result = commonService.getSecret(appId);
if (!result.success()) {
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), result.getInternalMsg());
}
secret = result.getData();
redisUtils.set(RedisKeys.getExternalAppSecretKey(appId), secret);
}
return secret;
}
}

60
epmet-gateway/src/main/java/com/epmet/auth/ExternalAuthProcessor.java

@ -1,6 +1,15 @@
package com.epmet.auth;
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.utils.Result;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@ -11,11 +20,60 @@ import reactor.core.publisher.Mono;
@Component
public class ExternalAuthProcessor extends AuthProcessor {
private Logger logger = LoggerFactory.getLogger(getClass());
// 头s
public static final String AUTHORIZATION_TOKEN_HEADER_KEY = "Authorization";
public static final String ACCESS_TOKEN_HEADER_KEY = "AccessToken";
public static final String APP_ID_HEADER_KEY = "appId";
public static final String APP_ID_TIMESTAMP_KEY = "ts";
public static final String APP_ID_CUSTOMER_ID_KEY = "CustomerId";
public static final String APP_ID_AUTY_TYPE_KEY = "AuthType";
// 认证方式
public static final String APP_AUTH_TYPE_JWT = "jwt";
public static final String APP_AUTH_TYPE_MD5 = "md5";
@Autowired
private ExtAppJwtAuthProcessor jwtAuthProcessor;
@Autowired
private ExtAppMD5AuthProcessor md5AuthProcessor;
@Override
public Mono<Void> auth(ServerWebExchange exchange, GatewayFilterChain chain) {
HttpHeaders headers = exchange.getRequest().getHeaders();
String token = headers.getFirst(ACCESS_TOKEN_HEADER_KEY);
String appId = headers.getFirst(APP_ID_HEADER_KEY);
String ts = headers.getFirst(APP_ID_TIMESTAMP_KEY);
String customerId = headers.getFirst(APP_ID_CUSTOMER_ID_KEY);
String authType = headers.getFirst(APP_ID_AUTY_TYPE_KEY);
if (StringUtils.isAnyBlank(token, appId)) {
throw new RenException("请求头中的AccessToken和AppId不能为空");
}
logger.info("外部应用请求认证拦截Aspect执行,appId:{}, token:{}, ts:{}, customerId:{}, authType:{}",
appId, token, ts, customerId, authType);
// 没传authType或者传的jwt都用jwtprocessor处理
try {
if (StringUtils.isBlank(authType) || APP_AUTH_TYPE_JWT.equals(authType)) {
jwtAuthProcessor.auth(appId, token, StringUtils.isNotBlank(ts) ? new Long(ts) : null);
} else if (APP_AUTH_TYPE_MD5.equals(authType)) {
md5AuthProcessor.auth(appId, token, StringUtils.isNotBlank(ts) ? new Long(ts) : null);
} else {
throw new RenException(EpmetErrorCode.OPER_EXTERNAL_APP_AUTH_ERROR.getCode(), "未知的认证类型");
}
} catch (RenException e) {
return response(exchange, new Result<>().error(e.getCode(), e.getMsg()));
} catch (Exception e) {
logger.error("外部应用请求认证发生未知错误:" + ExceptionUtils.getErrorStackTrace(e));
return response(exchange, new Result<>().error("外部应用请求认证发生未知错误"));
}
return null;
return chain.filter(exchange);
}
}

22
epmet-gateway/src/main/java/com/epmet/filter/CpAuthGatewayFilterFactory.java

@ -1,24 +1,31 @@
package com.epmet.filter;
import com.alibaba.fastjson.JSON;
import com.epmet.auth.ExternalAuthProcessor;
import com.epmet.auth.InternalAuthProcessor;
import com.epmet.commons.tools.constant.AppClientConstant;
import com.epmet.commons.tools.constant.Constant;
import com.epmet.commons.tools.exception.EpmetErrorCode;
import com.epmet.commons.tools.exception.RenException;
import com.epmet.commons.tools.utils.Result;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
@ -77,10 +84,11 @@ public class CpAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<CpA
return externalAuthProcessor.auth(exchange, chain);
case AUTH_TYPE_INTERNAL:
return internalAuthProcessor.auth(exchange, chain);
case AUTH_TYPE_UNKNOW:
throw new RenException(EpmetErrorCode.ERR401.getCode(), "无法确定认证方式");
case AUTH_TYPE_NO_NEED:
break;
default:
return response(exchange, new Result<>().error(EpmetErrorCode.ERR401.getCode(),
EpmetErrorCode.ERR401.getMsg()));
}
return chain.filter(exchange);
@ -174,4 +182,12 @@ public class CpAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<CpA
}
}
protected Mono<Void> response(ServerWebExchange exchange, Object object) {
String json = JSON.toJSONString(object);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8));
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
exchange.getResponse().setStatusCode(HttpStatus.OK);
return exchange.getResponse().writeWith(Flux.just(buffer));
}
}

12
epmet-gateway/src/main/java/com/epmet/jwt/JwtTokenUtils.java

@ -62,6 +62,18 @@ public class JwtTokenUtils {
}
}
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

7
epmet-module/epmet-common-service/common-service-client/src/main/java/com/epmet/feign/EpmetCommonServiceOpenFeignClient.java

@ -46,4 +46,11 @@ public interface EpmetCommonServiceOpenFeignClient {
*/
@PostMapping("/commonservice/externalapp/getcustomerids")
Result<List<String>> getExternalCustomerIds();
/**
* 查询秘钥(仅限内部使用)
* @return
*/
@PostMapping("/commonservice/externalapp/get-secret")
Result getSecret(@RequestBody String appId);
}

5
epmet-module/epmet-common-service/common-service-client/src/main/java/com/epmet/feign/fallback/EpmetCommonServiceOpenFeignClientFallback.java

@ -34,4 +34,9 @@ public class EpmetCommonServiceOpenFeignClientFallback implements EpmetCommonSer
public Result<List<String>> getExternalCustomerIds() {
return ModuleUtils.feignConError(ServiceConstant.EPMET_COMMON_SERVICE, "getExternalCustomerIds", null);
}
@Override
public Result getSecret(String appId) {
return ModuleUtils.feignConError(ServiceConstant.EPMET_COMMON_SERVICE, "getSecret", appId);
}
}

18
epmet-module/epmet-common-service/common-service-server/src/main/java/com/epmet/controller/ExternalAppController.java

@ -2,6 +2,7 @@ package com.epmet.controller;
import com.epmet.commons.tools.exception.EpmetErrorCode;
import com.epmet.commons.tools.exception.RenException;
import com.epmet.commons.tools.exception.ValidateException;
import com.epmet.commons.tools.page.PageData;
import com.epmet.commons.tools.utils.Result;
import com.epmet.commons.tools.validator.ValidatorUtils;
@ -10,6 +11,7 @@ import com.epmet.dto.form.ExternalAppFormDTO;
import com.epmet.dto.result.ExternalAppAuthResultDTO;
import com.epmet.dto.result.ExternalAppResultDTO;
import com.epmet.service.ExternalAppAuthService;
import com.epmet.service.ExternalAppSecretService;
import com.epmet.service.ExternalAppService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@ -34,6 +36,9 @@ public class ExternalAppController {
@Autowired
private ExternalAppService externalAppService;
@Autowired
private ExternalAppSecretService externalAppSecretService;
/**
* 外部请求认证
* @param formDTO
@ -128,4 +133,17 @@ public class ExternalAppController {
return new Result().ok(newSecret);
}
/**
* 查询秘钥(仅限内部使用)
* @return
*/
@PostMapping("/get-secret")
public Result getSecret(@RequestBody String appId) {
if (StringUtils.isBlank(appId)) {
throw new ValidateException(EpmetErrorCode.CUSTOMER_VALIDATE_ERROR.getCode(), "缺少应用ID参数");
}
String secret = externalAppSecretService.getSecretByAppId(appId);
return new Result().ok(secret);
}
}

2
epmet-module/epmet-common-service/common-service-server/src/main/java/com/epmet/dao/ExternalAppSecretDao.java

@ -41,4 +41,6 @@ public interface ExternalAppSecretDao extends BaseDao<ExternalAppSecretEntity> {
ExternalAppSecretEntity getSecretsByAppId(@Param("appId") String appId);
int updateSecret(@Param("appId") String appId, @Param("secret") String secret);
String getSecretByAppId(String appId);
}

1
epmet-module/epmet-common-service/common-service-server/src/main/java/com/epmet/service/ExternalAppSecretService.java

@ -25,4 +25,5 @@ package com.epmet.service;
* @since v1.0.0 2020-08-18
*/
public interface ExternalAppSecretService {
String getSecretByAppId(String appId);
}

9
epmet-module/epmet-common-service/common-service-server/src/main/java/com/epmet/service/impl/ExternalAppSecretServiceImpl.java

@ -17,7 +17,9 @@
package com.epmet.service.impl;
import com.epmet.dao.ExternalAppSecretDao;
import com.epmet.service.ExternalAppSecretService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 外部应用秘钥列表
@ -28,4 +30,11 @@ import org.springframework.stereotype.Service;
@Service
public class ExternalAppSecretServiceImpl implements ExternalAppSecretService {
@Autowired
private ExternalAppSecretDao externalAppSecretDao;
@Override
public String getSecretByAppId(String appId) {
return externalAppSecretDao.getSecretByAppId(appId);
}
}

7
epmet-module/epmet-common-service/common-service-server/src/main/java/com/epmet/utils/externalapp/ExtAppJwtTokenUtils.java

@ -80,14 +80,13 @@ public class ExtAppJwtTokenUtils {
//String appId = "acc4ad66c82a7b46e741364b4c62dce2";
// String customrId = "b09527201c4409e19d1dbc5e3c3429a1";
//孔村
String secret = "657cd46d385a4c2ba6d9355aee24654ac3951deab7e6436e91201561b94969b5";
String appId = "5efcfb775125d656f39583b8110a3d7d";
String secret = "c4096eb0497943c78327c5192621b209c38f20592f6a49cc8c79e8b77f3bd5c8";
String appId = "f358d63a89f3670c197c62ca4c3a0366";
String customrId = "2fe0065f70ca0e23ce4c26fca5f1d933";
claim.put("customerId", customrId);
claim.put("appId", appId);
claim.put("customerId", customrId);
long ts = System.currentTimeMillis() - 1000 * 60 * 4;
long ts = System.currentTimeMillis() + 1000 * 60 * 1;
System.out.println("时间戳:" + ts);
claim.put("ts", ts);

8
epmet-module/epmet-common-service/common-service-server/src/main/resources/mapper/ExternalAppSecretDao.xml

@ -40,5 +40,13 @@
AND DEL_FLAG = 0
</select>
<!--查询秘钥-->
<select id="getSecretByAppId" resultType="java.lang.String">
select SECRET
from external_app_secret s
where s.DEL_FLAG = 0
and s.APP_ID = #{appId}
</select>
</mapper>
Loading…
Cancel
Save