Add AccessFilter layer

This commit is contained in:
renxiaoyin 2023-04-03 17:33:25 +08:00
parent 673d3b1291
commit 99815b41d8
16 changed files with 911 additions and 102 deletions

29
pom.xml
View File

@ -16,7 +16,7 @@
<properties> <properties>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<dgs.codegen.version>5.1.17</dgs.codegen.version> <dgs.codegen.version>5.1.17</dgs.codegen.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
@ -28,6 +28,13 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
<dependencies> <dependencies>
@ -35,6 +42,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId> <artifactId>spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.netflix.graphql.dgs</groupId> <groupId>com.netflix.graphql.dgs</groupId>
<artifactId>graphql-dgs-spring-boot-starter</artifactId> <artifactId>graphql-dgs-spring-boot-starter</artifactId>
@ -53,11 +64,27 @@
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -0,0 +1,48 @@
package com.longfor.bff_netflix.autoconfigure;
import com.longfor.bff_netflix.autoconfigure.ApiSignConfigLoad;
import com.longfor.bff_netflix.entity.AppSecretInfo;
import com.longfor.bff_netflix.interceptor.RequestInterceptor;
import com.longfor.bff_netflix.services.SignService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Configuration
@ConditionalOnWebApplication
@ComponentScan(value = {"com.longfor.bff_netflix.*"})
public class ApiSignAutoConfigure {
@Resource
private List<SignService> signServices;
@Resource
private ApiSignConfigLoad apiSignConfigLoad;
@Bean
public RequestInterceptor requestInterceptor(){
final Map<String, SignService> signServiceMap = new HashMap<>();
final Map<String, AppSecretInfo> appSecretInfoMap = new HashMap<>();
for (SignService signService : signServices) {
if (StringUtils.isBlank(signService.signMethod())){
throw new IllegalArgumentException("验签方式配置错误");
}
signServiceMap.put(signService.signMethod(), signService);
}
for (AppSecretInfo appSecretInfo : apiSignConfigLoad.loadAppSecretInfos()) {
}
apiSignConfigLoad.loadAppSecretInfos().forEach(a -> appSecretInfoMap.put(a.getAppKey(), a));
return new RequestInterceptor(signServiceMap, appSecretInfoMap, apiSignConfigLoad);
}
}

View File

@ -0,0 +1,41 @@
package com.longfor.bff_netflix.autoconfigure;
import com.alibaba.fastjson.JSONObject;
import com.longfor.bff_netflix.entity.AppSecretInfo;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Configuration
public class ApiSignConfigLoad {
/**
* 加载调用方应用签名信息
*/
public List<AppSecretInfo> loadAppSecretInfos() {
AppSecretInfo sample = new AppSecretInfo();
sample.setAppKey("123");
sample.setAppSecret("kkk");
return Arrays.asList(sample);
} //TODO: Load the config from Apollo Server
/**
* 是否验签默认所有接口都参与验签
*/
public boolean isSignatureValidationRequired(String path) {
return false;
}
/**
* 构建业务自定义错误响应系统提供默认异常实现
*/
public Object buildErrorResult(String message) {
JSONObject result = new JSONObject();
result.put("code", -1);
result.put("msg", message);
return result;
}
}

View File

@ -0,0 +1,130 @@
package com.longfor.bff_netflix.autoconfigure;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import javax.servlet.ServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
/**
* @author rx6
*/
public class CachedHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] cachedContent;
private static Logger log = LoggerFactory.getLogger(CachedHttpServletRequestWrapper.class);
private String encoding;
public CachedHttpServletRequestWrapper(HttpServletRequest request, String encoding) throws IOException {
super(request);
this.encoding = StringUtils.isEmpty(encoding) ? "UTF-8" : encoding;
cachedContent = writeToCachedContent(request.getInputStream());
}
/**
* 获取请求Body
*
* @return
*/
public String getBodyString() {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = new ByteArrayInputStream(cachedContent);
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName(this.encoding)));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("出错了", e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error("inputStream的io出错了", e);
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("读流关闭io出错了", e);
}
}
}
return sb.toString();
}
/**
* Description: 复制输入流</br>
*
* @param inputStream
* @return</br>
*/
public byte[] writeToCachedContent(ServletInputStream inputStream) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
} catch (IOException e) {
log.error("出错了", e);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
return bytes;
}
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new ContentCachingInputStream(this.cachedContent);
}
private class ContentCachingInputStream extends ServletInputStream {
private ByteArrayInputStream inputStream;
public ContentCachingInputStream(byte[] bytes) {
this.inputStream = new ByteArrayInputStream(bytes);
}
@Override
public boolean isFinished() {
throw new UnsupportedOperationException();
}
@Override
public boolean isReady() {
throw new UnsupportedOperationException();
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
@Override
public int read() throws IOException {
return inputStream.read();
}
}
}

View File

@ -0,0 +1,127 @@
package com.longfor.bff_netflix.autoconfigure;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
/**
* @author rxy
*/
public class CachedServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] cachedContent;
private static Logger log = LoggerFactory.getLogger(CachedServletRequestWrapper.class);
private String encoding;
public CachedServletRequestWrapper(HttpServletRequest request, String encoding) throws IOException {
super(request);
this.encoding = StringUtils.isEmpty(encoding) ? "UTF-8" : encoding;
cachedContent = writeToCachedContent(request.getInputStream());
}
/**
* 获取请求Body
*
* @return
*/
public String getBodyString() {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = new ByteArrayInputStream(cachedContent);
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName(this.encoding)));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("出错了", e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error("inputStream的io出错了", e);
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("读流关闭io出错了", e);
}
}
}
return sb.toString();
}
/**
* Description: 复制输入流</br>
*
* @param inputStream
* @return</br>
*/
public byte[] writeToCachedContent(ServletInputStream inputStream) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
} catch (IOException e) {
log.error("出错了", e);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
return bytes;
}
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new ContentCachingInputStream(this.cachedContent);
}
private class ContentCachingInputStream extends ServletInputStream {
private ByteArrayInputStream inputStream;
public ContentCachingInputStream(byte[] bytes) {
this.inputStream = new ByteArrayInputStream(bytes);
}
@Override
public boolean isFinished() {
throw new UnsupportedOperationException();
}
@Override
public boolean isReady() {
throw new UnsupportedOperationException();
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
@Override
public int read() throws IOException {
return inputStream.read();
}
}
}

View File

@ -0,0 +1,20 @@
package com.longfor.bff_netflix.autoconfigure;
import com.longfor.bff_netflix.interceptor.RequestInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private RequestInterceptor requestInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(requestInterceptor);
}
}

View File

@ -0,0 +1,12 @@
package com.longfor.bff_netflix.entity;
import lombok.Data;
@Data
public class AppSecretInfo {
private String appKey;
private String appSecret;
}

View File

@ -0,0 +1,49 @@
package com.longfor.bff_netflix.entity;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Data
public class SignRequest {
private String requestMethod;
private String path;
private Map<String, String[]> params;
private String body;
private String appKey;
private String sign;
private String signMethod;
private String nonce;
private String timestamp;
public static SignRequest build(HttpServletRequest request){
SignRequest signRequest = new SignRequest();
signRequest.requestMethod = request.getMethod();
signRequest.path = request.getServletPath();
signRequest.params = request.getParameterMap();
// signRequest.body = ""
signRequest.appKey = request.getHeader("x-appkey");
signRequest.sign = request.getHeader("x-sign");
signRequest.signMethod = getSignMethod(request);
signRequest.nonce = request.getHeader("x-nonce");
signRequest.timestamp = request.getHeader("x-timestamp");
return signRequest;
}
private static String getSignMethod(HttpServletRequest request){
final String signMethod = request.getHeader("x-sign-method");
return StringUtils.isBlank(signMethod) ? "MD5" : signMethod;
}
}

View File

@ -0,0 +1,9 @@
package com.longfor.bff_netflix.entity;
import lombok.Data;
@Data
public class ValidationResult {
private int httpStatusCode;
private String errorMessage;
}

View File

@ -0,0 +1,117 @@
package com.longfor.bff_netflix.filter;
import com.longfor.bff_netflix.autoconfigure.ApiSignConfigLoad;
import com.longfor.bff_netflix.autoconfigure.CachedHttpServletRequestWrapper;
import com.longfor.bff_netflix.entity.AppSecretInfo;
import com.longfor.bff_netflix.entity.SignRequest;
import com.longfor.bff_netflix.entity.ValidationResult;
import com.longfor.bff_netflix.services.SignService;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
//@ComponentScan(value = {"com.longfor.bff_netflix.*"})
public class AccessFilter extends OncePerRequestFilter {
private final List<SignService> signServices;
private final ApiSignConfigLoad apiSignConfigLoad;
private final Map<String, SignService> signServiceMap = new HashMap<>();
private final Map<String, AppSecretInfo> appSecretInfoMap = new HashMap<>();
public AccessFilter(ApiSignConfigLoad apiSignConfigLoad, List<SignService> signServices) {
this.apiSignConfigLoad = apiSignConfigLoad;
// 1. Build SignService map
for (SignService signService : signServices) {
signServiceMap.put(signService.signMethod(), signService);
}
// 2. Build App Secret map
this.apiSignConfigLoad.loadAppSecretInfos().forEach(t -> appSecretInfoMap.put(t.getAppKey(), t));
this.signServices = signServices;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CachedHttpServletRequestWrapper wrapper = new CachedHttpServletRequestWrapper(request, "UTF-8");
String body = wrapper.getBodyString();
final SignRequest signRequest = SignRequest.build(request);
signRequest.setBody(body);
ValidationResult validationResult = validateRequest(signRequest);
if (validationResult.getHttpStatusCode() == HttpServletResponse.SC_OK) {
filterChain.doFilter(wrapper, response);
} else {
response.sendError(validationResult.getHttpStatusCode(), validationResult.getErrorMessage());
}
}
private ValidationResult validateRequest(SignRequest signRequest) {
ValidationResult result = new ValidationResult();
result.setHttpStatusCode(HttpServletResponse.SC_OK);
if (!apiSignConfigLoad.isSignatureValidationRequired(signRequest.getPath())) {
return result;
}
if (!checkRequest(signRequest)) {
result.setHttpStatusCode(HttpServletResponse.SC_BAD_REQUEST);
result.setErrorMessage("Required headers not specified in the request");
return result;
}
final AppSecretInfo appSecretInfo = appSecretInfoMap.get(signRequest.getAppKey());
if (appSecretInfo == null) {
result.setHttpStatusCode(HttpServletResponse.SC_BAD_REQUEST);
result.setErrorMessage("无效的应用ID");
return result;
}
final SignService signService = signServiceMap.get(signRequest.getSignMethod());
if (signService == null) {
result.setHttpStatusCode(HttpServletResponse.SC_BAD_REQUEST);
result.setErrorMessage("无效的签名方式");
return result;
}
final String sign = signService.sign(signRequest, appSecretInfo.getAppSecret());
if (!signRequest.getSign().equals(sign)) {
result.setHttpStatusCode(HttpServletResponse.SC_BAD_REQUEST);
result.setErrorMessage("无效的签名");
return result;
}
return result;
}
private boolean checkRequest(SignRequest signRequest) {
if (StringUtils.isAnyBlank(signRequest.getAppKey(), signRequest.getSign(), signRequest.getTimestamp(), signRequest.getNonce())) {
// errorResponse(response, "请求头中缺失用于计算签名的参数");
return false;
}
try {
final Date tm = DateUtils.parseDate(signRequest.getTimestamp(), "yyyy-MM-dd HH:mm:ss");
final Date curTime = new Date();
if (tm.before(DateUtils.addMinutes(curTime, -10)) || tm.after(curTime)) {
// errorResponse(response, "签名时间过期或超期");
return false;
}
} catch (ParseException exception) {
// errorResponse(response, "时间戳x-timestamp格式有误");
return false;
}
return true;
}
}

View File

@ -0,0 +1,116 @@
package com.longfor.bff_netflix.interceptor;
import com.alibaba.fastjson.JSON;
import com.longfor.bff_netflix.autoconfigure.ApiSignConfigLoad;
import com.longfor.bff_netflix.autoconfigure.CachedHttpServletRequestWrapper;
import com.longfor.bff_netflix.entity.AppSecretInfo;
import com.longfor.bff_netflix.entity.SignRequest;
import com.longfor.bff_netflix.services.SignService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.ContentCachingRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;
@Slf4j
public class RequestInterceptor implements HandlerInterceptor {
private final ApiSignConfigLoad apiSignConfigLoad;
private final Map<String, SignService> signServiceMap;
private final Map<String, AppSecretInfo> appSecretInfoMap;
public RequestInterceptor(Map<String, SignService> signServiceMap, Map<String, AppSecretInfo> appSecretInfoMap, ApiSignConfigLoad apiSignConfigLoad) {
this.signServiceMap = signServiceMap;
this.appSecretInfoMap = appSecretInfoMap;
this.apiSignConfigLoad = apiSignConfigLoad;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// final SignRequest signRequest = SignRequest.build(request);
// CachedHttpServletRequestWrapper wrappedRequest = new CachedHttpServletRequestWrapper(request, "UTF-8");
// BodyReaderHttpServletRequestWrapper wrappedRequest = new BodyReaderHttpServletRequestWrapper(request);
// byte[] bytes = StreamUtils.copyToByteArray(request.getInputStream());
// String body = new String(bytes);
// if (!apiSignConfigLoad.isCheckSign(signRequest.getPath())){
// return true;
// }
return true;
// if (!checkRequest(signRequest, response)){
// return false;
// }
// final AppSecretInfo appSecretInfo = appSecretInfoMap.get(signRequest.getAppKey());
// if (appSecretInfo == null){
// errorResponse(response, "无效的应用ID");
// return false;
// }
// final SignService signService = signServiceMap.get(signRequest.getSignMethod());
// if (signService == null){
// errorResponse(response, "无效的签名方式");
// return false;
// }
// final String sign = signService.sign(signRequest, appSecretInfo.getAppSecret());
// if (!signRequest.getSign().equals(sign)){
// log.error("请求中的签名值 {} 与系统计算的签名值 {} 不符", signRequest.getSign(), sign);
// errorResponse(response, "无效的签名");
// return false;
// }
// return true;
}
/**
* 输出错误响应
*/
private void errorResponse(HttpServletResponse response, String errorMsg) {
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try (PrintWriter out = response.getWriter()) {
out.print(JSON.toJSONString(apiSignConfigLoad.buildErrorResult(errorMsg)));
out.flush();
} catch (Exception e) {
log.error("writeResponse error", e);
}
}
/**
* 校验请求参数基本的合法性
*/
private boolean checkRequest(SignRequest signRequest, HttpServletResponse response) {
if (StringUtils.isAnyBlank(signRequest.getAppKey(), signRequest.getSign(), signRequest.getTimestamp(), signRequest.getNonce())) {
errorResponse(response, "请求头中缺失用于计算签名的参数");
return false;
}
try {
final Date tm = DateUtils.parseDate(signRequest.getTimestamp(), "yyyy-MM-dd HH:mm:ss");
final Date curTime = new Date();
if (tm.before(DateUtils.addMinutes(curTime, -10)) || tm.after(curTime)) {
errorResponse(response, "签名时间过期或超期");
return false;
}
} catch (ParseException exception) {
errorResponse(response, "时间戳x-timestamp格式有误");
return false;
}
return true;
}
/**
* TODO 拦截请求重放攻击后续再开发
*/
private boolean checkRepeatedNonce(SignRequest signRequest) {
return false;
}
}

View File

@ -0,0 +1,23 @@
package com.longfor.bff_netflix.services;
import cn.hutool.crypto.digest.MD5;
import com.longfor.bff_netflix.entity.SignRequest;
import com.longfor.bff_netflix.util.ParamToStrUtil;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Service
public class Md5SignServiceImpl implements SignService {
@Override
public String signMethod() {
return "MD5";
}
@Override
public String sign(SignRequest signRequest, String secretKey) {
final String bodyStr = ParamToStrUtil.paramToStr(signRequest.getBody(), signRequest.getParams());
return MD5.create().digestHex(bodyStr + secretKey + signRequest.getTimestamp() + signRequest.getNonce());
}
}

View File

@ -0,0 +1,24 @@
package com.longfor.bff_netflix.services;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import com.longfor.bff_netflix.entity.SignRequest;
import com.longfor.bff_netflix.util.ParamToStrUtil;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Service
public class Sha256SignServiceImpl implements SignService {
@Override
public String signMethod() {
return "SHA256";
}
@Override
public String sign(SignRequest signRequest, String secretKey) {
final String bodyStr = ParamToStrUtil.paramToStr(signRequest.getBody(), signRequest.getParams());
return new HMac(HmacAlgorithm.HmacSHA256).digestHex(bodyStr + secretKey + signRequest.getTimestamp() + signRequest.getNonce());
}
}

View File

@ -0,0 +1,17 @@
package com.longfor.bff_netflix.services;
import com.longfor.bff_netflix.entity.SignRequest;
public interface SignService {
/**
* 签名方式
*/
String signMethod();
/**
* 计算签名
*/
String sign(SignRequest signRequest, String secretKey);
}

View File

@ -0,0 +1,49 @@
package com.longfor.bff_netflix.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@Slf4j
public class ParamToStrUtil {
private ParamToStrUtil(){}
public static String paramToStr(String body, Map<String, String[]> urlParams) {
Map<String, Object> data = new HashMap<>();
if (StringUtils.isNotBlank(body)){
data.putAll(JSON.parseObject(body, new TypeReference<Map<String, Object>>(){}));
}
if (!CollectionUtils.isEmpty(urlParams)){
urlParams.forEach((key, value) -> data.put(key, value[0]));
}
return dataToStr(data);
}
private static String dataToStr(Map<String, Object> data){
if (CollectionUtils.isEmpty(data)){
return "";
}
StringBuilder sb = new StringBuilder();
data.entrySet().stream()
.filter(entry -> Objects.nonNull(entry.getValue()))
.sorted(Map.Entry.comparingByKey())
.forEach(paramEntry -> sb.append(paramEntry.getKey()).append("=").append(paramEntry.getValue()).append("&"));
sb.deleteCharAt(sb.length()-1);
String str;
try {
str = URLEncoder.encode(sb.toString(), "utf-8");
}catch (Exception e){
throw new IllegalArgumentException("参数转码异常");
}
return str.toLowerCase();
}
}