08。如何保证API接口的安全性问题01
1.互联网Api接口到底如何保证安全性问题?
2.代码落地实战防御XSS、CSRF攻击
3.代码落地如何防御接口数据被黑客抓包篡改?
4.接口数据加密对称还是非对称加密好安全架构设计方案如何防御xss攻击
XSS攻击通常指的是通过利用 网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括 Java、 VBScript、 ActiveX、 Flash 或者甚至是普通的HTML。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容。 [1]
脚本攻击:利用JavaScript 注入 到后台数据库中,在通过展示数据加载该脚本 该脚本中(
1.使用js获取cookie信息(jwt)
2.将该jwt数据 上传黑客服务器(ajax)
)
获取jwt---用户会话信息 让后模拟请求形式使用该jwt登录。
xss攻击典型网站:论坛、评论区
http://127.0.0.1:8080/getUserInfo?userName=
http://127.0.0.1:8080/getUserInfo?userName=模拟xss攻击
前端传递 js 脚本到服务器端{ "channel": "", "equipment": "", "password": "123456", "phoneNumber": "15921009758" }
后端接口将该脚本存放数据库中
package com.mayikt.main.api.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.mayikt.common.core.api.BaseApiService; import com.mayikt.common.core.api.BaseResponse; import com.mayikt.main.api.UserLoginLogService; import com.mayikt.main.api.dto.res.UserLoginLogResDto; import com.mayikt.main.api.impl.entity.SysUserLoginLog; import com.mayikt.main.api.impl.mapper.SysUserLoginLogMapper; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; /** * @ClassName UserLoginLogImpl */ @RestController @CrossOrigin public class UserLoginLogImpl extends BaseApiService> implements UserLoginLogService { @Autowired private SysUserLoginLogMapper sysUserLoginLogMapper; @Override public BaseResponse> getUserLoginLog() { // 模拟查询登录的日志记录 List sysUserLoginLogs = sysUserLoginLogMapper.selectList(new QueryWrapper<>()); List userLoginLogResDtos = new ArrayList<>(); for (int i = 0; i < sysUserLoginLogs.size(); i++) { UserLoginLogResDto userLoginLogResDto = new UserLoginLogResDto(); BeanUtils.copyProperties(sysUserLoginLogs.get(i), userLoginLogResDto); userLoginLogResDtos.add(userLoginLogResDto); } return setResultSuccessData(userLoginLogResDtos); } } package com.mayikt.main.api; import com.mayikt.common.core.api.BaseResponse; import com.mayikt.main.api.dto.res.UserLoginLogResDto; import org.springframework.web.bind.annotation.GetMapping; import java.util.List; /** * @ClassName UserLoginLogService */ public interface UserLoginLogService { @GetMapping("/getUserLoginLog") BaseResponse> getUserLoginLog(); }
前端html xss模拟攻击
防御xss方式1
将用户前端所提交的参数进行过滤。
html 大于> 小于号 <
<>
String equipment = loginUserReqDto.getEquipment(); loginUserReqDto.setEquipment(StringUtils.isEmpty(equipment) ? null : StringEscapeUtils.escapeHtml(equipment)); SysUserLoginLog sysUserLoginLog = new SysUserLoginLog(sysUser.getId(), IPUtils.getIpAddr(request), new Date(), token, loginUserReqDto.getChannel(), loginUserReqDto.getEquipment());
该方式的缺陷:每个参数都需要像这样写 代码非常冗余
String str = ""; String s = StringEscapeUtils.escapeHtml(str); System.out.println(s);
方式2
接口接受参数 ?传递参数形式---
传递参数都是json数据形式
spring mvc 接受 json数据提供 api回调
package com.mayikt.main.security; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.google.common.net.MediaType; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; /** * 解决post 请求传递json数据 防御xss攻击 */ @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("swagger-ui.html") .addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); } @Override protected void extendMessageConverters(List> messageConverters) { /** * 替换默认的MappingJackson2HttpMessageConverter,过滤(json请求参数)xss */ ListIterator> listIterator = messageConverters.listIterator(); while (listIterator.hasNext()) { HttpMessageConverter<?> next = listIterator.next(); if (next instanceof MappingJackson2HttpMessageConverter) { listIterator.remove(); break; } } messageConverters.add(getMappingJackson2HttpMessageConverter()); } public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() { // 创建自定义ObjectMapper SimpleModule module = new SimpleModule(); module.addDeserializer(String.class, new JsonHtmlXssDeserializer(String.class)); ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.getApplicationContext()).build(); objectMapper.registerModule(module); // 创建自定义消息转换器 MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper); return mappingJackson2HttpMessageConverter; } } /** * 对入参的json进行转义 */ class JsonHtmlXssDeserializer extends JsonDeserializer { public JsonHtmlXssDeserializer(Class string) { super(); } @Override public Class handledType() { return String.class; } @Override public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException { String value = jsonParser.getValueAsString(); if (!StringUtils.isEmpty(value)) { return StringEscapeUtils.escapeHtml(value); } return value; } } 抓包如何防止篡改数据Fiddler抓包工具的使用
1.可以使用第三方抓包工具,对请求前后实现代理,可以修改参数请求内容和参数响应内容,抓包工具http调试工具
2.Fiddler4下载地址:https://pc.qq.com/detail/10/detail_3330.html
使用Fiddler4篡改请求之前:
防御篡改数据
使用MD5可以直接验证签名参数 MD5 属于单向加密,只能够暴力破解。
MD5应用场景 在nacos分布式配置中心中,使用MD5 比对文件内容是否发生改变
HasherPro比对文件内容是否发生改变。
MD5在线暴力破解地址:https://www.cmd5.com/
String userName= "123456" ;
System. out .println( DigestUtils. md5Hex (userName));
黑客如何破解?自己需要根据参数内容 生成签名
如果只是改了参数内容---没有用的 所以我们需要该签名
{"password":"123456","phoneNumber":"phoneNumber","channel":"安卓","equipment":""}
{sign=325ab041d4889825a46d1e1e802ab5de, timestamp=1652537015771}
package com.mayikt.main.security; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONPObject; import org.apache.commons.codec.digest.DigestUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.*; /** * 参数验证签名 */ public class SignUtil { private static Logger logger = LoggerFactory.getLogger(SignUtil.class); /** * 加密密钥 */ private final static String APP_KEY = "mykey123456"; public final static String SECRET_KEY = "mysecret123456"; /** * 字符编码 */ private final static String INPUT_CHARSET = "UTF-8"; /** * 超时时间 */ private final static int TIME_OUT = 30 * 60 * 1000; /** * 请求参数Map转换验证Map * * @param requestParams 请求参数Map * @param charset 是否要转utf8编码 * @return * @throws UnsupportedEncodingException */ public static Map toVerifyMap(Map requestParams, boolean charset) { Map params = new HashMap<>(); for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) { String name = (String) iter.next(); String[] values = requestParams.get(name); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } // 乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化 if (charset) valueStr = getContentString(valueStr, INPUT_CHARSET); params.put(name, valueStr); } return params; } /** * 除去数组中的空值和签名参数 * * @param sArray 签名参数组 * @return 去掉空值与签名参数后的新签名参数组 */ public static Map paraFilter(Map sArray) { Map result = new HashMap<>(); if (sArray == null || sArray.size() <= 0) { return result; } for (String key : sArray.keySet()) { String value = sArray.get(key); if (value == null || value.equals("") || key.equalsIgnoreCase("sign")) { continue; } result.put(key, value); } return result; } /** * 把数组所有元素排序,并按照"参数=参数值"的模式用"&"字符拼接成字符串 * * @param params 需要排序并参与字符拼接的参数组 * @return 拼接后字符串 */ public static String createLinkString(Map params) { return createLinkString(params, false); } /** * 把数组所有元素排序,并按照"参数=参数值"的模式用"&"字符拼接成字符串 * * @param params 需要排序并参与字符拼接的参数组 * @param encode 是否需要UrlEncode * @return 拼接后字符串 */ public static String createLinkString(Map params, boolean encode) { List keys = new ArrayList<>(params.keySet()); Collections.sort(keys); String prestr = ""; for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = params.get(key); if (encode) value = urlEncode(value, INPUT_CHARSET); if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符 prestr = prestr + key + "=" + value; } else { prestr = prestr + key + "=" + value + "&"; } } return prestr; } /** * 编码转换 * * @param content * @param charset * @return * @throws UnsupportedEncodingException */ private static byte[] getContentBytes(String content, String charset) { if (charset == null || "".equals(charset)) { return content.getBytes(); } try { return content.getBytes(charset); } catch (UnsupportedEncodingException e) { throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); } } /** * 编码转换 * * @param content * @param charset * @return */ private static String getContentString(String content, String charset) { if (charset == null || "".equals(charset)) { return new String(content.getBytes()); } try { return new String(content.getBytes("ISO-8859-1"), charset); } catch (UnsupportedEncodingException e) { throw new RuntimeException("指定的编码集不对,您目前指定的编码集是:" + charset); } } /** * URL转码 * * @param content * @param charset * @return */ private static String urlEncode(String content, String charset) { try { return URLEncoder.encode(content, charset); } catch (UnsupportedEncodingException e) { throw new RuntimeException("指定的编码集不对,您目前指定的编码集是:" + charset); } } /** * 生成要请求的签名参数数组 * * @return 要请求的签名参数数组 */ public static Map signJson(String json) { // 时间戳加入签名参数组中 HashMap sParaTemp = new HashMap<>(); sParaTemp.put("timestamp", String.valueOf(System.currentTimeMillis())); // 生成签名结果 String mysign = DigestUtils.md5Hex(getContentBytes(json, INPUT_CHARSET)); // 签名结果加入请求提交参数组中 sParaTemp.put("sign", mysign); return sParaTemp; } public static boolean verifyJson(String json, String sign, String timestamp) { // 将json数据做一个格式化 JSONObject data = JSONObject.parseObject(json); // 获得签名验证结果 String mysign = DigestUtils.md5Hex(getContentBytes(json, INPUT_CHARSET)); if (mysign.equals(sign)) { // 是否超时 long curr = System.currentTimeMillis(); if ((curr - Long.valueOf(timestamp)) > TIME_OUT) { logger.info("api is time out"); return false; } return true; } else { return false; } } public static void main(String[] args) { JSONObject jsonObject = new JSONObject(); jsonObject.put("channel", "ios"); jsonObject.put("equipment", "