我们一般在请求头中添加一些信息来保证验证请求身份,这里涉及到四个字段值(如nonce和timestamp不传则不参与签名,使用验签算法二):
appId: 软件Id
nonce:随机字符串
timestamp:时间戳
signature:签名
代码如下:
@Component
public class CommonApiAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String httpMethod = request.getMethodValue();
String httpUri = request.getURI().getPath();
//指定不进行验签的请求
if (httpUri.contains("/v2/api-docs") || httpUri.contains("/api/opus/callback/")){
return chain.filter(exchange.mutate().request(request.mutate().build()).build());
}
HttpHeaders headers = request.getHeaders();
String signature = headers.getFirst(Constant.SIGNATURE);
String nonce = headers.getFirst(Constant.NONCE);
String appId = headers.getFirst(Constant.APP_ID);
if (StringUtils.isAllBlank(signature, nonce, headers.getFirst(Constant.TIMESTAMP))) {
return ApiAuthUtils.checkSign(exchange, WrapMapper.illegalArgument("signature|nonce|timestamp不能为空"), HttpStatus.BAD_REQUEST);
}
long timestamp = Long.parseLong(headers.getFirst(Constant.TIMESTAMP));
// 时间戳超过1分钟直接返回false
if ((System.currentTimeMillis() - timestamp) / 1000 > 60) {
return ApiAuthUtils.checkSign(exchange, WrapMapper.wrap(403, "timestamp overtime"), HttpStatus.FORBIDDEN);
}
boolean result = CryptUtils.validateSignature(appId, timestamp, nonce, httpMethod, httpUri, signature);
if (!result) {
return ApiAuthUtils.checkSign(exchange, WrapMapper.wrap(403, "Forbidden"), HttpStatus.FORBIDDEN);
}
Builder mutate = request.mutate();
return chain.filter(exchange.mutate().request(mutate.build()).build());
};
}
}
验签算法一:
public static boolean validateSignature(String appId, long timestamp, String nonce, String httpmethod,String httpurl, String signature) {
String str = appId + "|" + nonce + "|" + timestamp + "|" + httpmethod.concat("|").concat(httpurl);
String encryptBASE64 = encryptBASE64(sha256(str.getBytes()));
log.debug("signature: {}, encryptBASE64: {}", signature, encryptBASE64);
return Arrays.equals(encryptBASE64.getBytes(), signature.getBytes());
}
验签算法二(请求头不传nonce和timestamp则不参与签名):
public static boolean validateSignature(String appId, String httpmethod, String httpurl, String signature) {
String s2 = httpmethod.concat("&").concat(httpurl).concat("&").concat("app_id=" + appId);
String encryptBASE64 = encryptbase64(SHA1(s2.getBytes()));
byte[] signature2 = encryptBASE64.getBytes();
return Arrays.equals(signature2, signature.getBytes());
}
前端请求时请求头加入对应信息:
request.interceptors.request.use(config => {
const token = storage.get(ACCESS_TOKEN)
if (token) {
config.headers['Access-Token'] = token
}
const url = config.url.split('?')[0]
const appId = '123456789'
const timestamp = new Date().getTime()
const nonce = generateNonce(timestamp)
const signature = generateSignature(appId, timestamp, nonce, config.method.toUpperCase(), url)
config.headers.appId = appId
config.headers.signature = signature
config.headers.nonce = nonce
config.headers.timestamp = timestamp
return config
}, errorHandler)
export const generateSignature = (appId, timestamp, nonce, httpMethod, httpURL) => {
const str = appId + '|' + nonce + '|' + timestamp + '|' + httpMethod + '|' + httpURL
let result = SHA256(str)
if (result) {
result = strToBase64(result)
}
return result
}
export const generateNonce = (theServerTime) => {
const randomNumber = []
for (let i = 0; i < 8; i++) {
const oneRandom = random(-128, 127)
randomNumber.push(oneRandom)
}
let timeStamp = Math.floor(new Date().getTime() / 60000)
if (theServerTime !== 0) {
timeStamp = Math.floor(theServerTime / 60000)
}
const nt = []
nt[3] = timeStamp & 0xff
nt[2] = (timeStamp >> 8) & 0xff
nt[1] = (timeStamp >> 16) & 0xff
nt[0] = (timeStamp >> 24) & 0xff
for (let x = 0; x < nt.length; x++) {
randomNumber.push(nt[x])
}
return bytesToBase64(randomNumber)
}