一、加密签名api验证背景
最近一直在研究iuap平台的开发和使用,发现平台自带加签组件来支持创建api签名验证,但是demo不够完善,摸索了一下,写入博客和大家分享一下,在以后遇到使用iuap平台时,自己的应用需要用到发布api以后能做加密签名验证。此能力需要依赖iuap平台的组件包:iuap-security。
二、加密签名api验证实现
1.由于iuap平台的权限验证框架使用的是shiro框架,本次功能实现也基于shiro框架的过滤器进行实现。
2.创建shiro签名过滤器
public class SignAuthFilter extends AccessControlFilter {
private static final Logger logger = LoggerFactory.getLogger(SignAuthFilter.class);
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse rp = (HttpServletResponse) response;
rp.setCharacterEncoding("UTF-8");
rp.setContentType("application/json;charset=UTF-8");
if ((request instanceof HttpServletRequest)) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
//从http头取签名信息,如果http头不存在,则从请求参数里取
String sign = httpRequest.getHeader("sign");
if (StringUtils.isEmpty(sign)) {
sign = httpRequest.getParameter("sign");
}
//从http头取appId信息,如果http头不存在,则从请求参数里取
String appId = httpRequest.getHeader("appid");
if (StringUtils.isEmpty(appId)) {
appId = httpRequest.getParameter("appid");
}
//判断签名信息和appId是否为空
if ((null == sign) || (null == appId)) {
rp.setStatus(400);
rp.addHeader("Send event message validate error", "请求方法不允许,请检查参数!");
rp.getWriter().write("sign请求方法不允许,请检查参数!");
return false;
}
//验证签名时间
String ts = httpRequest.getHeader("ts");
if (StringUtils.isEmpty(ts)) {
ts = httpRequest.getParameter("ts");
}
if (null == ts) {
rp.setStatus(400);
rp.addHeader("Send event message validate error", "请求方法不允许,请检查参数!");
rp.getWriter().write("ts请求方法不允许,请检查参数!");
return false;
}
if (StringUtils.isNumeric(ts)) {
long sendTs = Long.parseLong(ts);
//如果签名有效时间在有效期外,则提示签名过期(当前时间-签名有效时间大于5分钟,则认为签名有效时间过期)
if (System.currentTimeMillis() - sendTs > 300000L) {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setStatus(400);
resp.addHeader("send message validate error", " 400 ,签名已经过期!");
resp.getWriter().write("签名已经过期!");
return false;
}
}
//验证签名信息
if (!ValidatorSigna(sign, appId, httpRequest)) {
rp.setStatus(400);
rp.addHeader("Send event message validate error", "请求方法不允许,请检查参数!");
rp.getWriter().write("key请求方法不允许,请检查参数!");
return false;
}
}
return true;
}
/**
* 验证签名有效性
* @param httpRequset
* @return
*/
private boolean ValidatorSigna(String sign, String appId, HttpServletRequest httpRequset) {
if ((StringUtils.isNotEmpty(sign)) && (StringUtils.isNotEmpty(appId))) {
try {
//获取url全路径
String url = httpRequset.getRequestURL().toString();
//获取查询参数
if (StringUtils.isNotBlank(httpRequset.getQueryString())) {
url = url + "?" + httpRequset.getQueryString();
}
//生成验签支撑对象
SignProp prop = SignPropGenerator.genSignProp(url);
return new VirifyFactory().getVerifier(appId).verify(sign, prop);
} catch (UAPSecurityException e) {
logger.error(e.getMessage(), e);
} catch (MalformedURLException e) {
logger.error(e.getMessage(), e);
}
}
return false;
}
class VirifyFactory extends ServerVerifyFactory {
@Override
protected Credential genCredential(String appId) {
try {
return ClientCredentialGenerator.loadCredential(VirifyFactory.class.getResource("/").getFile().toString() + "/security/authfile.txt");
} catch (UAPSecurityException e) {
e.printStackTrace();
}
return null;
}
}
3.修改applicationContext-shiro.xml配置文件,对api进行权限过滤
3.1增加过滤器
>
>
3.2加入过滤器集合
3.3增加过滤URL
到此整个扩展就完成了,下面我们进行测试代码的编写,为了让应用中使用更加的边接,做了一个工具类进行调用。
4.api工具类
public class ApiUtils {
/**
* api调用
* @param apiUrl api地址
* @param apiParams api参数
*/
public static void ApiCall(String apiUrl, ApiParams apiParams, String apiType) {
Map<String, String> headers = new LinkedHashMap<String, String>();
//传出http请求参数,http请求参数一定要在生成sign之前传入,否则在生成sign时会和服务器的不一样。
headers.put("ts", apiParams.getExpiredTs());
headers.put("sign", geneClientSign(apiUrl, apiParams));
headers.put("appid", apiParams.getAppId());
//如果是post请求,则用post
if ("post".equals(apiType)) {
//如果body参数为空,则使用http参数发起请求,否则使用body参数
if ("".equals(apiParams.getBodyParams())) {
HttpUtils.postUrl(apiUrl, apiParams.getHttpParams(), headers, "UTF-8");
} else {
HttpUtils.postUrl(apiUrl, apiParams.getBodyParams(), headers, "UTF-8");
}
} else {
HttpUtils.doGet(apiUrl, apiParams.getHttpParams(), headers, "UTF-8");
}
}
/**
* 生成客户端签名
* @param apiUrl api地址
* @param apiParams api参数
* @return 加密签名字符串
*/
private static String geneClientSign(String apiUrl, ApiParams apiParams) {
//解析http参数重新生成url地址
try {
Map<String, String> params = apiParams.getHttpParams();
if ((params != null) && (!params.isEmpty())) {
List<NameValuePair> pairs = new ArrayList<NameValuePair>(params.size());
for (Map.Entry<String, String> entry : params.entrySet()) {
String value = entry.getValue();
if (value != null) {
pairs.add(new BasicNameValuePair(entry.getKey(), value));
}
}
apiUrl = apiUrl + "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs, "UTF-8"));
}
//根据新的url地址生成验签支撑对象
SignProp prop = SignPropGenerator.genSignProp(apiUrl);
prop.setIp(apiParams.getServerIp());
Credential credential = new Credential();
credential.setAppId(apiParams.getAppId());
credential.setKey(apiParams.getSignKey());
credential.setExpiredTs(apiParams.getExpiredTs());
DigestSigner digestSigner = new DigestSigner(credential);
return digestSigner.sign(prop);
} catch (UAPSecurityException | ParseException | IOException e) {
e.printStackTrace();
}
return null;
}
}
5.junit测试
public class MaCrUserApiTest extends AbstractJUnit4SpringContextTests {
/**
* 创建加签文件
*
*/
@Test
public void apiCreateSigleFile() {
byte[] appidByte = new String("OA移动端").getBytes();
String appid = IUAPESAPI.encoder().encodeForBase64(appidByte);
try {
String key = IUAPESAPI.encryptor().encrypt(appid);
SimpleDateFormat smdf = new SimpleDateFormat("yyyy-MM-dd");
Long overdueDateTime = smdf.parse("2019-12-06").getTime();
File file = new File("D:\\oamobile.txt");
FileWriter fw = new FileWriter(file);
BufferedWriter write = new BufferedWriter(fw);
write.write("appid=" + appid);
write.newLine();//换行
write.write("key=" + key);
write.newLine();//换行
write.write("expiredTs=" + overdueDateTime);
write.flush();
write.close();
} catch (EncryptException | ParseException | IOException e) {
e.printStackTrace();
}
}
@Test
public void apiGetCall() throws ParseException {
ApiParams apiParams = new ApiParams();
apiParams.setAppId("5YWo5Z+f5Lit5Y+w");
Long overdueDateTime = System.currentTimeMillis();
apiParams.setExpiredTs(overdueDateTime.toString());
apiParams
.setSignKey("ETMsDgAAAXB8GP61ABRBRVMvQ0JDL1BLQ1M1UGFkZGluZwCAABAAEHGgER1sydlUQsEWeOfkHMQAAAAgvJqoL8hcvxYm3J/h7VXPR+FYE95qmsUDHm1gvbLx+TsAFEtMmFVE7aKNyxlu37VlrxI9sTHl");
Map<String, String> httpParams = new LinkedHashMap<String, String>();
httpParams.put("indocno", "40281181662e4ecb01662e4ecdab0000");
apiParams.setHttpParams(httpParams);
ApiUtils.ApiCall("http://localhost:8081/appcloud-main/api/macruserweb/findbyid", apiParams, null);
}
@Test
public void apiPostCall() throws ParseException {
Properties properties = PropertiesUtils.getProperties("/security/authfile.txt");
ApiParams apiParams = new ApiParams();
apiParams.setAppId(properties.getProperty("appid"));
Long overdueDateTime = System.currentTimeMillis();
apiParams.setExpiredTs(overdueDateTime.toString());
apiParams.setSignKey(properties.getProperty("key"));
List<MaLgBusLog> maLgBusLogList = new ArrayList<MaLgBusLog>();
MaLgBusLog maLgBusLog = new MaLgBusLog();
maLgBusLog.setIpAddress("asdfasdfas");
maLgBusLog.setRequestUrl("asdfasdfas");
maLgBusLog.setRequestMethod("asdfasdfas");
maLgBusLog.setRequestParam("你好安抚大使的");
maLgBusLog.setRecTime(new Date());
maLgBusLogList.add(maLgBusLog);
ObjectMapper objectMapper = new ObjectMapper();
try {
String json = objectMapper.writeValueAsString(maLgBusLogList);
apiParams.setBodyParams(json.toString());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//JSONArray json = JSONArray.fromObject(maLgBusLogList);
ApiUtils.ApiCall("http://localhost:8181/appcloud-log/api/malgbuslogweb/savedata", apiParams, "post");
}
}
至此基于iuap平台组件创建加密签名api验证就已经全部完成!