在前后端开发的过程中,我们一般在请求头中添加一些信息来保证验证请求身份,这里涉及到四个字段值:
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")){ 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())); logger.debug("signature: {}, encryptBASE64: {}", signature, encryptBASE64); return Arrays.equals(encryptBASE64.getBytes(), 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) }