32 changed files with 1056 additions and 21 deletions
@ -0,0 +1,15 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<parent> |
||||
|
<artifactId>epmet-commons</artifactId> |
||||
|
<groupId>com.epmet</groupId> |
||||
|
<version>2.0.0</version> |
||||
|
</parent> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
|
||||
|
<artifactId>epmet-commons-openapi</artifactId> |
||||
|
|
||||
|
|
||||
|
</project> |
@ -0,0 +1,8 @@ |
|||||
|
package com.epmet.openapi.constant; |
||||
|
|
||||
|
/** |
||||
|
* 认证方式 |
||||
|
*/ |
||||
|
public interface AuthTypes { |
||||
|
String TAKE_TOKEN = "take_token"; |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
package com.epmet.openapi.constant; |
||||
|
|
||||
|
/** |
||||
|
* 集群内的Header key |
||||
|
*/ |
||||
|
public interface InClusterHeaderKeys { |
||||
|
|
||||
|
String APP_ID = "AppId"; |
||||
|
|
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
package com.epmet.openapi.constant; |
||||
|
|
||||
|
/** |
||||
|
* url请求参数key |
||||
|
*/ |
||||
|
public class RequestParamKeys { |
||||
|
|
||||
|
public static String APP_ID = "app_id"; |
||||
|
public static String AUTH_TYPE = "auth_type"; |
||||
|
public static String TIMESTAMP = "timestamp"; |
||||
|
public static String SIGN = "sign"; |
||||
|
public static String NONCE = "nonce"; |
||||
|
|
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<parent> |
||||
|
<artifactId>epmet-commons</artifactId> |
||||
|
<groupId>com.epmet</groupId> |
||||
|
<version>2.0.0</version> |
||||
|
</parent> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
|
||||
|
<artifactId>epmet-commons-security</artifactId> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>com.epmet</groupId> |
||||
|
<artifactId>epmet-commons-tools</artifactId> |
||||
|
<version>2.0.0</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>io.jsonwebtoken</groupId> |
||||
|
<artifactId>jjwt</artifactId> |
||||
|
<version>0.7.0</version> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
</project> |
@ -0,0 +1,131 @@ |
|||||
|
/** |
||||
|
* Copyright (c) 2018 人人开源 All rights reserved. |
||||
|
* <p> |
||||
|
* https://www.renren.io
|
||||
|
* <p> |
||||
|
* 版权所有,侵权必究! |
||||
|
*/ |
||||
|
|
||||
|
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 JwtUtils { |
||||
|
private static final Logger logger = LoggerFactory.getLogger(JwtUtils.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<String, Object> 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(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @Description 创建Token |
||||
|
* @return Token |
||||
|
* @param claims 载荷信息 |
||||
|
* @param expireTime 过期时间 |
||||
|
* @param secret 秘钥 |
||||
|
* @author wxz |
||||
|
* @date 2021.03.26 13:33 |
||||
|
*/ |
||||
|
public String createToken(Map<String, Object> claims, Date expireTime, String secret) { |
||||
|
return Jwts.builder() |
||||
|
.setHeaderParam("typ", "JWT") |
||||
|
.setClaims(claims) |
||||
|
.setIssuedAt(new Date()) |
||||
|
.setExpiration(expireTime) |
||||
|
.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<String, Object> 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")); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,109 @@ |
|||||
|
package com.epmet.commons.security.sign.openapi; |
||||
|
|
||||
|
import com.epmet.commons.tools.utils.Md5Util; |
||||
|
|
||||
|
import java.util.*; |
||||
|
|
||||
|
/** |
||||
|
* OpenApi签名工具 |
||||
|
*/ |
||||
|
public class OpenApiSignUtils { |
||||
|
|
||||
|
/** |
||||
|
* @Description 创建签名 |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.22 16:47 |
||||
|
*/ |
||||
|
public static String createSign(Map<String, String> contentMap, String signKey) { |
||||
|
String str2beSigned = mapToSignStr(contentMap); |
||||
|
str2beSigned = str2beSigned.concat("&sign_key=").concat(signKey); |
||||
|
return Md5Util.md5(str2beSigned); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @Description 验签 |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.22 16:51 |
||||
|
*/ |
||||
|
public static boolean checkSign(Map<String, String> 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<String, String> map) { |
||||
|
Set<String> 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) { |
||||
|
generateGetAccessTokenSign(); |
||||
|
System.out.println("=============="); |
||||
|
generateGetOrgDetailSign(); |
||||
|
} |
||||
|
|
||||
|
private static void generateGetAccessTokenSign() { |
||||
|
long now = System.currentTimeMillis(); |
||||
|
System.out.println(now); |
||||
|
|
||||
|
String uuid = UUID.randomUUID().toString().replace("-", ""); |
||||
|
|
||||
|
HashMap<String, String> content = new HashMap<>(); |
||||
|
content.put("app_id", "7d98b8af2d05752b4225709c4cfd4bd0"); |
||||
|
content.put("timestamp", String.valueOf(now)); |
||||
|
content.put("nonce", uuid); |
||||
|
content.put("auth_type", "take_token"); |
||||
|
|
||||
|
String secret = "3209ee9f41704482be1a1fb5873a25376f2899191ca846119d44168316bc3e44"; |
||||
|
|
||||
|
String sign = createSign(content, secret); |
||||
|
|
||||
|
System.out.println("时间戳:" + now); |
||||
|
System.out.println("随机数:" + uuid); |
||||
|
System.out.println("签名:" + sign); |
||||
|
} |
||||
|
|
||||
|
private static void generateGetOrgDetailSign() { |
||||
|
long now = System.currentTimeMillis(); |
||||
|
String uuid = UUID.randomUUID().toString().replace("-", "");; |
||||
|
System.out.println("时间戳:" + now); |
||||
|
System.out.println("随机数:" + uuid); |
||||
|
|
||||
|
HashMap<String, String> content = new HashMap<>(); |
||||
|
//content.put("orgId", "aaa");
|
||||
|
//content.put("test", null);
|
||||
|
content.put("gridId", "12128e0f614f1c00a058ea9a107033b2"); |
||||
|
content.put("app_id", "7d98b8af2d05752b4225709c4cfd4bd0"); |
||||
|
content.put("timestamp", String.valueOf(now)); |
||||
|
content.put("nonce", uuid); |
||||
|
content.put("auth_type", "take_token"); |
||||
|
|
||||
|
String secret = "3209ee9f41704482be1a1fb5873a25376f2899191ca846119d44168316bc3e44"; |
||||
|
|
||||
|
String sign = createSign(content, secret); |
||||
|
System.out.println("签名:" + sign); |
||||
|
} |
||||
|
} |
@ -0,0 +1,91 @@ |
|||||
|
package com.epmet.auth; |
||||
|
|
||||
|
import com.epmet.commons.security.jwt.JwtUtils; |
||||
|
import com.epmet.commons.security.sign.openapi.OpenApiSignUtils; |
||||
|
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.feign.EpmetCommonServiceOpenFeignClient; |
||||
|
import com.epmet.openapi.constant.InClusterHeaderKeys; |
||||
|
import com.epmet.openapi.constant.RequestParamKeys; |
||||
|
import io.jsonwebtoken.Claims; |
||||
|
import io.jsonwebtoken.Jwts; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.web.server.ServerWebExchange; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* 外部应用认证处理器:来平台token的方式 |
||||
|
*/ |
||||
|
@Component |
||||
|
public class ExtAppTakeTokenAuthProcessor extends ExtAppAuthProcessor { |
||||
|
|
||||
|
@Autowired |
||||
|
private JwtUtils jwtTokenUtils; |
||||
|
|
||||
|
@Autowired |
||||
|
private RedisUtils redisUtils; |
||||
|
|
||||
|
@Override |
||||
|
public void auth(String appId, String token, Long ts, ServerWebExchange exchange) { |
||||
|
String secret = getSecret(appId); |
||||
|
|
||||
|
// 1.过期验证
|
||||
|
String accessTokenInCache = redisUtils.getString(RedisKeys.getOpenApiAccessTokenKey(appId)); |
||||
|
Date expiration = jwtTokenUtils.getExpiration(token, secret); |
||||
|
if (StringUtils.isBlank(accessTokenInCache) || |
||||
|
expiration == null || |
||||
|
jwtTokenUtils.isTokenExpired(expiration) |
||||
|
) { |
||||
|
|
||||
|
throw new RenException(EpmetErrorCode.OPEN_API_TOKEN_EXPIRED.getCode(), |
||||
|
EpmetErrorCode.OPEN_API_TOKEN_EXPIRED.getMsg()); |
||||
|
} |
||||
|
|
||||
|
// 2.验签
|
||||
|
// 验签暂时放到具体接口中,不放在gateway
|
||||
|
//openApiSignUtils.checkSign();
|
||||
|
|
||||
|
// 2. 获取claims
|
||||
|
Claims claims = jwtTokenUtils.getClaimByToken(token, secret); |
||||
|
String appIdInAccessToken = claims.get(RequestParamKeys.APP_ID, String.class); |
||||
|
|
||||
|
if (!appId.equals(appIdInAccessToken)) { |
||||
|
// 参数列表的appId和token中封装的不一致
|
||||
|
throw new RenException(EpmetErrorCode.OPEN_API_PARAMS_APPID_DIFF.getCode(), |
||||
|
EpmetErrorCode.OPEN_API_PARAMS_APPID_DIFF.getMsg()); |
||||
|
} |
||||
|
|
||||
|
// 3.将app_id放入header中
|
||||
|
ServerHttpRequest.Builder mutate = exchange.getRequest().mutate(); |
||||
|
mutate.header(InClusterHeaderKeys.APP_ID, new String[]{appId}); |
||||
|
exchange.mutate().request(mutate.build()).build(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return |
||||
|
* @Description 获取秘钥 |
||||
|
* @author wxz |
||||
|
* @date 2021.03.23 14:12 |
||||
|
*/ |
||||
|
private String getSecret(String appId) { |
||||
|
EpmetCommonServiceOpenFeignClient commonService = SpringContextUtils.getBean(EpmetCommonServiceOpenFeignClient.class); |
||||
|
Result<String> result = commonService.getSecret(appId); |
||||
|
if (result == null || !result.success()) { |
||||
|
throw new RenException("fetchToken方式的外部应用认证,获取secret失败"); |
||||
|
} |
||||
|
String secret = result.getData(); |
||||
|
if (StringUtils.isBlank(secret)) { |
||||
|
throw new RenException("fetchToken方式的外部应用认证,获取secret失败"); |
||||
|
} |
||||
|
|
||||
|
return secret; |
||||
|
} |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
package com.epmet.utils; |
||||
|
|
||||
|
import org.springframework.http.server.reactive.ServerHttpRequest; |
||||
|
import org.springframework.util.MultiValueMap; |
||||
|
|
||||
|
public class ServerHttpRequestUtils { |
||||
|
|
||||
|
/** |
||||
|
* @Description 从url中获取appId |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.25 15:13 |
||||
|
*/ |
||||
|
public static String getRequestParam(ServerHttpRequest request, String paramName) { |
||||
|
MultiValueMap<String, String> queryParams = request.getQueryParams(); |
||||
|
return queryParams.getFirst(paramName); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
package com.epmet.dto.form.openapi; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import javax.validation.constraints.NotBlank; |
||||
|
|
||||
|
@Data |
||||
|
public class GetOrgDetailFormDTO extends OpenApiBaseFormDTO { |
||||
|
|
||||
|
@NotBlank(message = "orgId不能为空") |
||||
|
private String orgId; |
||||
|
|
||||
|
private String test; |
||||
|
|
||||
|
} |
@ -0,0 +1,31 @@ |
|||||
|
package com.epmet.dto.form.openapi; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import javax.validation.constraints.NotBlank; |
||||
|
import javax.validation.constraints.NotNull; |
||||
|
|
||||
|
/** |
||||
|
* open api基础类 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class OpenApiBaseFormDTO { |
||||
|
|
||||
|
public interface GetAccessTokenGroup {} |
||||
|
|
||||
|
@NotBlank(message = "签名不能为空", groups = { GetAccessTokenGroup.class }) |
||||
|
private String sign; |
||||
|
|
||||
|
/** |
||||
|
* 时间戳,ms |
||||
|
*/ |
||||
|
@NotNull(message = "时间戳不能为空", groups = { GetAccessTokenGroup.class }) |
||||
|
private Long timestamp; |
||||
|
|
||||
|
/** |
||||
|
* 随机数,每次请求唯一 |
||||
|
*/ |
||||
|
@NotBlank(message = "随机字段nonce不能为空", groups = { GetAccessTokenGroup.class }) |
||||
|
private String nonce; |
||||
|
|
||||
|
} |
@ -0,0 +1,12 @@ |
|||||
|
package com.epmet.dto.result.openapi; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class GetAccessTokenResultDTO { |
||||
|
|
||||
|
private String accessToken; |
||||
|
|
||||
|
private Long expireTime; |
||||
|
|
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
package com.epmet.annotation; |
||||
|
|
||||
|
import java.lang.annotation.*; |
||||
|
|
||||
|
/** |
||||
|
* OpenApi验签注解 |
||||
|
*/ |
||||
|
@Target(ElementType.METHOD) |
||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||
|
@Documented |
||||
|
public @interface OpenApiCheckSign { |
||||
|
|
||||
|
} |
@ -0,0 +1,218 @@ |
|||||
|
package com.epmet.aspect; |
||||
|
|
||||
|
import com.epmet.commons.mybatis.aspect.DataFilterAspect; |
||||
|
import com.epmet.commons.security.sign.openapi.OpenApiSignUtils; |
||||
|
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.ConvertUtils; |
||||
|
import com.epmet.commons.tools.utils.Result; |
||||
|
import com.epmet.feign.EpmetCommonServiceOpenFeignClient; |
||||
|
import com.epmet.openapi.constant.RequestParamKeys; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.aspectj.lang.JoinPoint; |
||||
|
import org.aspectj.lang.annotation.Aspect; |
||||
|
import org.aspectj.lang.annotation.Before; |
||||
|
import org.aspectj.lang.reflect.MethodSignature; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.core.annotation.Order; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.context.request.RequestAttributes; |
||||
|
import org.springframework.web.context.request.RequestContextHolder; |
||||
|
import org.springframework.web.context.request.ServletRequestAttributes; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
import java.lang.reflect.Method; |
||||
|
import java.lang.reflect.Parameter; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* OpenApi检查请求切面 |
||||
|
* 1.验签防止参数篡改 |
||||
|
* 2.timestamp+nonce防止请求重放攻击 |
||||
|
*/ |
||||
|
@Aspect |
||||
|
@Component |
||||
|
@Order(1) |
||||
|
public class OpenApiRequestCheckAspect { |
||||
|
|
||||
|
@Autowired |
||||
|
private RedisUtils redisUtils; |
||||
|
|
||||
|
@Autowired |
||||
|
private EpmetCommonServiceOpenFeignClient commonServiceOpenFeignClient; |
||||
|
|
||||
|
//请求时差单位:s
|
||||
|
long requestTimeSecDiff = 120; |
||||
|
//请求时差,单位:ms
|
||||
|
long requestTimeMillSecDiff = requestTimeSecDiff * 1000;//单位:ms
|
||||
|
|
||||
|
private static final Logger log = LoggerFactory.getLogger(DataFilterAspect.class); |
||||
|
|
||||
|
/** |
||||
|
* @Description 验签 |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.24 13:39 |
||||
|
*/ |
||||
|
@Before("execution(* com.epmet.controller.*Controller*.*(..)) && @annotation(com.epmet.annotation.OpenApiCheckSign)") |
||||
|
public void check(JoinPoint point) { |
||||
|
Object[] args = point.getArgs(); |
||||
|
MethodSignature methodSignature = (MethodSignature) point.getSignature(); |
||||
|
Method method = methodSignature.getMethod(); |
||||
|
Parameter[] parameters = method.getParameters(); |
||||
|
|
||||
|
HttpServletRequest request = getRequest(); |
||||
|
|
||||
|
Map<String, String> argMap = new HashMap<>(); |
||||
|
for (int i = 0; i < parameters.length; i++) { |
||||
|
if (parameters[i].isAnnotationPresent(RequestBody.class)) { |
||||
|
try { |
||||
|
argMap = ConvertUtils.entityToMap(args[i]); |
||||
|
} catch (Exception e) { |
||||
|
throw new RenException("验签参数转化发生异常"); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fillRequestParamsInfoArgMap(argMap, request); |
||||
|
if (!OpenApiSignUtils.checkSign(argMap, getSecret(argMap.get(RequestParamKeys.APP_ID)))) { |
||||
|
// 验签失败
|
||||
|
throw new RenException(EpmetErrorCode.OPEN_API_SIGN_ERROR.getCode()); |
||||
|
} |
||||
|
checkRepeatRequest(argMap); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @Description 填充url请求参数到map中,用来签名 |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.26 10:13 |
||||
|
*/ |
||||
|
private void fillRequestParamsInfoArgMap(Map<String, String> argMap, HttpServletRequest request) { |
||||
|
fillRequestParamsInfoArgMap(argMap, request, RequestParamKeys.APP_ID); |
||||
|
fillRequestParamsInfoArgMap(argMap, request, RequestParamKeys.AUTH_TYPE); |
||||
|
fillRequestParamsInfoArgMap(argMap, request, RequestParamKeys.NONCE); |
||||
|
fillRequestParamsInfoArgMap(argMap, request, RequestParamKeys.TIMESTAMP); |
||||
|
fillRequestParamsInfoArgMap(argMap, request, RequestParamKeys.SIGN); |
||||
|
} |
||||
|
|
||||
|
private void fillRequestParamsInfoArgMap(Map<String, String> argMap, HttpServletRequest request, String paramName) { |
||||
|
String paramValue = request.getParameter(paramName); |
||||
|
if (StringUtils.isNotBlank(paramName)) { |
||||
|
argMap.put(paramName, paramValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查请求重放 |
||||
|
* @param argMap |
||||
|
*/ |
||||
|
void checkRepeatRequest(Map<String, String> argMap) { |
||||
|
String timestampStr = argMap.get(RequestParamKeys.TIMESTAMP); |
||||
|
if (StringUtils.isBlank(timestampStr)) { |
||||
|
throw new RenException(EpmetErrorCode.OPEN_API_PARAMS_MISSING.getCode()); |
||||
|
} |
||||
|
long timestamp = Long.valueOf(timestampStr).longValue(); |
||||
|
long now = System.currentTimeMillis(); |
||||
|
|
||||
|
if (Math.abs(now - timestamp) > requestTimeMillSecDiff) { |
||||
|
// 只允许1分钟之内的请求,允许服务器之间时差为1分钟
|
||||
|
throw new RenException(EpmetErrorCode.OPEN_API_REQUEST_EXPIRED.getCode(), |
||||
|
String.format("请求已过期,允许时差为%s s", requestTimeSecDiff)); |
||||
|
} |
||||
|
String nonce = argMap.get(RequestParamKeys.NONCE); |
||||
|
String nonceInCache = redisUtils.getString(RedisKeys.getOpenApiNonceKey(nonce)); |
||||
|
if (StringUtils.isNotBlank(nonceInCache)) { |
||||
|
throw new RenException(EpmetErrorCode.OPEN_API_REQUEST_REPEAT.getCode()); |
||||
|
} |
||||
|
//将nonce缓存到redis,有效期1分钟
|
||||
|
redisUtils.set(RedisKeys.getOpenApiNonceKey(nonce), System.currentTimeMillis(), requestTimeSecDiff); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return |
||||
|
* @Description 取secret |
||||
|
* @author wxz |
||||
|
* @date 2021.03.24 12:49 |
||||
|
*/ |
||||
|
private String getSecret(String appId) { |
||||
|
String secret = (String) redisUtils.get(RedisKeys.getExternalAppSecretKey(appId)); |
||||
|
if (StringUtils.isBlank(secret)) { |
||||
|
Result<String> 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); |
||||
|
} |
||||
|
return secret; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return |
||||
|
* @Description 获取request |
||||
|
* @author wxz |
||||
|
* @date 2021.03.24 12:52 |
||||
|
*/ |
||||
|
public HttpServletRequest getRequest() { |
||||
|
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); |
||||
|
ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes; |
||||
|
return sra.getRequest(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return |
||||
|
* @Description 获取appId |
||||
|
* @author wxz |
||||
|
* @date 2021.03.24 12:53 |
||||
|
*/ |
||||
|
//public String getAppId(Parameter[] parameters, Object[] args) {
|
||||
|
// HttpServletRequest request = getRequest();
|
||||
|
// String appId = request.getHeader("AppId");
|
||||
|
// if (StringUtils.isBlank(appId)) {
|
||||
|
// for (int i = 0; i < parameters.length; i++) {
|
||||
|
// if (parameters[i].isAnnotationPresent(RequestBody.class)) {
|
||||
|
// Object arg = args[i];
|
||||
|
// try {
|
||||
|
// appId = getAppIdFromDTO(arg);
|
||||
|
// } catch (IllegalAccessException e) {
|
||||
|
// e.printStackTrace();
|
||||
|
// }
|
||||
|
// }
|
||||
|
// }
|
||||
|
// }
|
||||
|
// if (StringUtils.isBlank(appId)) {
|
||||
|
// throw new RenException("未携带AppId");
|
||||
|
// }
|
||||
|
// return appId;
|
||||
|
//}
|
||||
|
|
||||
|
//private String getAppIdFromDTO(Object dto) throws IllegalAccessException {
|
||||
|
// Field[] declaredFields = dto.getClass().getDeclaredFields();
|
||||
|
// for (int i = 0; i < declaredFields.length; i++) {
|
||||
|
// Field field = declaredFields[i];
|
||||
|
// String fieldName = field.getName();
|
||||
|
// if ("appId".equals(fieldName)) {
|
||||
|
// field.setAccessible(true);
|
||||
|
// String value = (String) field.get(dto);
|
||||
|
// return value;
|
||||
|
// }
|
||||
|
// }
|
||||
|
// return null;
|
||||
|
//}
|
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
System.out.println(System.currentTimeMillis()); |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
|
||||
|
} |
@ -0,0 +1,60 @@ |
|||||
|
package com.epmet.controller; |
||||
|
|
||||
|
import com.epmet.annotation.OpenApiCheckSign; |
||||
|
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.dto.result.openapi.GetAccessTokenResultDTO; |
||||
|
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.*; |
||||
|
|
||||
|
@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 |
||||
|
*/ |
||||
|
@OpenApiCheckSign |
||||
|
@PostMapping("get-access-token") |
||||
|
public Result<GetAccessTokenResultDTO> getAccessToken(@RequestParam("app_id") String appId) { |
||||
|
// 1.取secret
|
||||
|
String secret = (String)redisUtils.get(RedisKeys.getExternalAppSecretKey(appId)); |
||||
|
if (StringUtils.isBlank(secret)) { |
||||
|
Result<String> 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); |
||||
|
} |
||||
|
|
||||
|
//2.生成token
|
||||
|
GetAccessTokenResultDTO content = openApiAccessTokenService.getAccessToken(appId, secret); |
||||
|
return new Result<GetAccessTokenResultDTO>().ok(content); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
package com.epmet.controller; |
||||
|
|
||||
|
import com.epmet.annotation.OpenApiCheckSign; |
||||
|
import com.epmet.commons.tools.utils.Result; |
||||
|
import com.epmet.dto.form.openapi.GetOrgDetailFormDTO; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
@RestController |
||||
|
@RequestMapping("open-api") |
||||
|
public class OpenApiOrgController { |
||||
|
|
||||
|
/** |
||||
|
* @Description OpenApiCheckSign是验签注解,OpenApi的接口请加上该注解 |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.24 12:55 |
||||
|
*/ |
||||
|
@OpenApiCheckSign |
||||
|
@PostMapping("/get-org-detail") |
||||
|
public Result getOrgDetail(@RequestBody GetOrgDetailFormDTO input, |
||||
|
@RequestHeader("AppId") String appId) { |
||||
|
return new Result().ok("测试org"); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
package com.epmet.service; |
||||
|
|
||||
|
import com.epmet.dto.result.openapi.GetAccessTokenResultDTO; |
||||
|
|
||||
|
/** |
||||
|
* access token的service |
||||
|
*/ |
||||
|
public interface OpenApiAccessTokenService { |
||||
|
|
||||
|
/** |
||||
|
* @Description 获取AccessToken |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.22 22:57 |
||||
|
*/ |
||||
|
GetAccessTokenResultDTO getAccessToken(String appId, String secret); |
||||
|
} |
@ -0,0 +1,45 @@ |
|||||
|
package com.epmet.service.impl; |
||||
|
|
||||
|
import com.epmet.commons.security.jwt.JwtUtils; |
||||
|
import com.epmet.commons.tools.redis.RedisKeys; |
||||
|
import com.epmet.commons.tools.redis.RedisUtils; |
||||
|
import com.epmet.config.OpenApiConfig; |
||||
|
import com.epmet.dto.result.openapi.GetAccessTokenResultDTO; |
||||
|
import com.epmet.openapi.constant.RequestParamKeys; |
||||
|
import com.epmet.service.OpenApiAccessTokenService; |
||||
|
import org.joda.time.DateTime; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
import java.util.HashMap; |
||||
|
|
||||
|
@Service |
||||
|
public class OpenApiAccessTokenServiceImpl implements OpenApiAccessTokenService { |
||||
|
|
||||
|
@Autowired |
||||
|
private JwtUtils jwtTokenUtils; |
||||
|
|
||||
|
@Autowired |
||||
|
private OpenApiConfig openApiConfig; |
||||
|
|
||||
|
@Autowired |
||||
|
private RedisUtils redisUtils; |
||||
|
|
||||
|
@Override |
||||
|
public GetAccessTokenResultDTO getAccessToken(String appId, String secret) { |
||||
|
HashMap<String, Object> claim = new HashMap<>(); |
||||
|
claim.put(RequestParamKeys.APP_ID, appId); |
||||
|
|
||||
|
Date expireTime = DateTime.now().plusSeconds(openApiConfig.getAccessTokenExpire()).toDate(); |
||||
|
String token = jwtTokenUtils.createToken(claim, expireTime, secret); |
||||
|
// 缓存token
|
||||
|
redisUtils.set(RedisKeys.getOpenApiAccessTokenKey(appId), token, openApiConfig.getAccessTokenExpire()); |
||||
|
|
||||
|
GetAccessTokenResultDTO content = new GetAccessTokenResultDTO(); |
||||
|
content.setAccessToken(token); |
||||
|
content.setExpireTime(expireTime.getTime()); |
||||
|
|
||||
|
return content; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue