当前位置: 首页 > news >正文

青岛高端模板建站wordpress什么用

青岛高端模板建站,wordpress什么用,电子政务网站建设的挑战,东营做网站seo在涉及跨系统接口调用时#xff0c;我们容易碰到以下安全问题#xff1a; 请求身份被伪造请求参数被篡改请求被抓包#xff0c;然后重放攻击 sa-token api-sign 模块将帮你轻松解决以上难题。#xff08;此插件是内嵌到 sa-token-core 核心包中的模块#xff0c;开发者无…在涉及跨系统接口调用时我们容易碰到以下安全问题 请求身份被伪造请求参数被篡改请求被抓包然后重放攻击 sa-token api-sign 模块将帮你轻松解决以上难题。此插件是内嵌到 sa-token-core 核心包中的模块开发者无需再次引入其它依赖插件直接可用 假设我们有如下业务需求 用户在 A 系统参与活动成功后活动奖励以余额的形式下发到 B 系统。 1. 初始方案直接裸奔 在不考虑安全问题的情况下我们很容易完成这个需求 1、在 B 系统开放一个接口 RestController RequestMapping(/sign) public class SignController {PostMapping(/addMoney)public String addMoney(Long userId, Long money) {// TODO 处理业务...return ADD SUCCESS;}}2、在 A 系统使用 http 工具类调用这个接口 RestController RequestMapping(/activity) public class ActivityController {PostMapping(/join)public String join() {// 参加完活动后发送余额Long userId 1L;Long money 100L;MapString, Object params new HashMap();params.put(userId, userId);params.put(money, money);String url http://localhost:8079/sign/addMoney;String result HttpUtil.post(url, params);return join;}}上述代码简单的完成了需求但是很明显它有一个安全问题 B 系统开放的接口不仅可以被 A 系统调用还可以被其它任何人调用甚至别人可以本地跑一个 for 循环调用这个接口为自己无限充值金额 2. 方案升级增加 secretKey 校验 为防止 B 系统开放的接口被陌生人任意调用我们增加一个 secretKey 参数 PostMapping(/addMoney) public String addMoney(Long userId, Long money, String secretKey) {// 校验 secretKeyif (!check(secretKey)) {throw new RuntimeException(无效 secretKey无法响应请求);}// TODO 处理业务...return ADD SUCCESS; }由于 A 系统是我们 “自己人”所以它可以拿着 secretKey 进行合法请求 PostMapping(/join) public String join() {// 参加完活动后发送余额MapString, Object params new HashMap();params.put(userId, userId);params.put(money, money);params.put(secretKey, ×××××××××××);String url http://localhost:8079/sign/addMoney;String result HttpUtil.post(url, params);return join; }现在即使 B 系统的接口被暴露了也不会被陌生人任意调用了安全性得到了一定的保证但是仍然存在一些问题 如果请求被抓包secretKey 就会泄露因为每次请求都在 url 中明文传输了 secretKey 参数。如果请求被抓包请求的其它参数就可以被任意修改例如可以将 money 参数修改为 9999999B系统无法确定参数是否被修改过。 3.方案再升级使用摘要算法生成参数签名 首先在 A 系统不要直接发起请求而是先计算一个 sign 参数 PostMapping(/join) public String join() {// 参加完活动后发送余额Long userId 1L;Long money 100L;String secretKey ×××××××××××;// 计算 signString sign md5(money money userId userId key secretKey);MapString, Object params new HashMap();params.put(userId, userId);params.put(money, money);params.put(sign, sign);String url http://localhost:8079/sign/addMoney;String result HttpUtil.post(url, params);return join; }注意此处计算签名时需要将所有参数按照字典顺序依次排列key除外挂在最后面 然后在 B 系统接收请求时使用同样的算法、同样的秘钥生成 sign 字符串与参数中 sign 值进行比较 PostMapping(/addMoney) public String addMoney(Long userId, Long money, String sign) {// 在 B 系统使用同样的算法、同样的密钥计算出 sign2与传入的 sign 进行比对String sign2 md5(money money userId userId key secretKey);if (!sign2.equals(sign)) {return 无效 sign无法响应请求;}// TODO 处理业务...return ADD SUCCESS; }因为 sign 的值是由 userId、money、secretKey 三个参数共同决定的所以只要有一个参数不一致就会造成最终生成 sign 也是不一致的所以根据比对结果 如果 sign 一致说明这是个合法请求。如果 sign 不一致说明发起请求的客户端秘钥不正确或者请求参数被篡改过是个不合法请求。 此方案优点 不在 url 中直接传递 secretKey 参数了避免了泄露风险。由于 sign 参数的限制请求中的参数也不可被篡改B 系统可放心的使用这些参数。 此方案仍然存在以下缺陷 被抓包后请求可以被无限重放B 系统无法判断请求是真正来自于 A 系统发出的还是被抓包后重放的。 PostMapping(/join) public String join() {// 参加完活动后发送余额Long userId 1L;Long money 100L;String nonce SaFoxUtil.getRandomString(32); // 随机32位字符串String secretKey ×××××××××××;// 计算 signString sign md5(money money nonce nonce userId userId key secretKey);MapString, Object params new HashMap();params.put(userId, userId);params.put(money, money);params.put(nonce, nonce);params.put(sign, sign);String url http://localhost:8079/sign/addMoney;String result HttpUtil.post(url, params);return join; }4. 方案再再升级追加 nonce 随机字符串 首先在 A 系统发起调用前追加一个 nonce 参数一起参与到签名中 public String join() {// 参加完活动后发送余额Long userId 1L;Long money 100L;String nonce SaFoxUtil.getRandomString(32); // 随机32位字符串String secretKey ×××××××××××;// 计算 signString sign md5(money money nonce nonce userId userId key secretKey);MapString, Object params new HashMap();params.put(userId, userId);params.put(money, money);params.put(nonce, nonce);params.put(sign, sign);String url http://localhost:8079/sign/addMoney;String result HttpUtil.post(url, params);return join; }然后在 B 系统接收请求时也把 nonce 参数加进去生成 sign 字符串进行比较 public String addMoney(Long userId, Long money, String nonce,String sign) {// 检查此 nonce 是否已被使用过了if (Objects.nonNull(CacheUtil.get(nonce_ nonce))) {return 此 nonce 已被使用过了请求无效;}// 在 B 系统使用同样的算法、同样的密钥计算出 sign2与传入的 sign 进行比对String sign2 md5(money money nonce nonce userId userId key secretKey);if (!sign2.equals(sign)) {return 无效 sign无法响应请求;}// 存入缓存CacheUtil.set(nonce_ nonce, 1);// TODO 处理业务...return ADD SUCCESS; }代码分析 为方便理解我们先看第 3 步此处在校验签名成功后将 nonce 随机字符串记入缓存中。再看第 1 步每次请求进来先查看一下缓存中是否已经记录了这个随机字符串如果是则立即返回无效请求。 这两步的组合保证了一个 nonce 随机字符串只能被使用一次如果请求被抓包后重放是无法通过 nonce 校验的。 至此问题似乎已被解决了 …… 吗 别急我们还有一个问题没有考虑这个 nonce 在字符串在缓存应该被保存多久呢 保存 15 分钟那抓包的人只需要等待 15 分钟你的 nonce 记录在缓存中消失请求就可以被重放了。那保存 24 小时保存一周保存半个月好像无论保存多久都无法从根本上解决这个问题。 你可能会想到那我永久保存吧。这样确实能解决问题但显然服务器承载不了这么做即使再微小的数据量在时间的累加下也总一天会超出服务器能够承载的上限。 5. 方案再再再升级追加 timestamp 时间戳 我们可以再追加一个 timestamp 时间戳参数将请求的有效性限定在一个有限时间范围内例如 15分钟。 首先在 A 系统追加 timestamp 参数 public String join() {// 参加完活动后发送余额Long userId 1L;Long money 100L;Long timestamp System.currentTimeMillis();String nonce SaFoxUtil.getRandomString(32); // 随机32位字符串String secretKey ×××××××××××;// 计算 signString sign md5(money money nonce nonce timestamp timestamp userId userId key secretKey);MapString, Object params new HashMap();params.put(userId, userId);params.put(money, money);params.put(nonce, nonce);params.put(timestamp, timestamp);params.put(sign, sign);String url http://localhost:8079/sign/addMoney;String result HttpUtil.post(url, params);return join; }在 B 系统检测这个 timestamp 是否超出了允许的范围 public String addMoney(Long userId, Long money, Long timestamp, String nonce,String sign) {// 1、检查 timestamp 是否超出允许的范围此处假定最大允许15分钟差距long timestampDisparity System.currentTimeMillis() - timestamp; // 实际的时间差if(timestampDisparity 1000 * 60 * 15) {return timestamp 时间差超出允许的范围请求无效;}// 检查此 nonce 是否已被使用过了if (Objects.nonNull(CacheUtil.get(nonce_ nonce))) {return 此 nonce 已被使用过了请求无效;}// 在 B 系统使用同样的算法、同样的密钥计算出 sign2与传入的 sign 进行比对String sign2 md5(money money nonce nonce userId userId key secretKey);if (!sign2.equals(sign)) {return 无效 sign无法响应请求;}// 将 nonce 记入缓存ttl 有效期和 allowDisparity 允许时间差一致 CacheUtil.set(nonce_ nonce, 1, 1000 * 60 * 15);// TODO 处理业务...return ADD SUCCESS; }至此抓包者 如果在 15 分钟内重放攻击nonce 参数不答应缓存中可以查出 nonce 值直接拒绝响应请求。如果在 15 分钟后重放攻击timestamp 参数不答应超出了允许的 timestamp 时间差直接拒绝响应请求。 6. 服务器的时钟差异造成安全问题 以上的代码均假设 A 系统服务器与 B 系统服务器的时钟一致才可以正常完成安全校验但在实际的开发场景中有些服务器会存在时钟不准确的问题。 假设 A 服务器与 B 服务器的时钟差异为 10 分钟即在 A 服务器为 8:00 的时候B 服务器为 7:50。 A 系统发起请求其生成的时间戳也是代表 8:00。B 系统接受到请求后完成业务处理此时 nonce 的 ttl 为 15分钟到期时间为 7:50 15分 8:05。8.05 后nonce 缓存消失抓包者重放请求攻击 timestamp 校验通过因为时间戳差距仅有 8.05 - 8.00 5分钟小于 15 分钟校验通过。-nonce 校验通过因为此时 nonce 缓存已经消失可以通过校验。sign 校验通过因为这本来就是由 A 系统构建的一个合法签名。攻击完成。 要解决上述问题有两种方案 方案一修改服务器时钟使两个服务器时钟保持一致。方案二在代码层面兼容时钟不一致的场景。 要采用方案一的同学可自行搜索一下同步时钟的方法在此暂不赘述此处详细阐述一下方案二。 我们只需简单修改一下B 系统校验参数的代码即可 public String addMoney(Long userId, Long money, Long timestamp, String nonce,String sign) {// 1、检查 timestamp 是否超出允许的范围 重点一此处需要取绝对值long timestampDisparity Math.abs(System.currentTimeMillis() - timestamp);if(timestampDisparity 1000 * 60 * 15) {return timestamp 时间差超出允许的范围请求无效;}// 检查此 nonce 是否已被使用过了if (Objects.nonNull(CacheUtil.get(nonce_ nonce))) {return 此 nonce 已被使用过了请求无效;}// 在 B 系统使用同样的算法、同样的密钥计算出 sign2与传入的 sign 进行比对String sign2 md5(money money nonce nonce userId userId key secretKey);if (!sign2.equals(sign)) {return 无效 sign无法响应请求;}// 将 nonce 记入缓存防止重复使用重点二此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 CacheUtil.set(nonce_ nonce, 1, (1000 * 60 * 15) * 2;// TODO 处理业务...return ADD SUCCESS; }8. 使用 Sa-Token 框架完成 API 参数签名 接下来步入正题使用 Sa-Token 内置的 sign 模块方便的完成 API 签名创建、校验等步骤 不限制请求的参数数量方便组织业务需求代码。自动补全 nonce、timestamp 参数省时省力。自动构建签名并序列化参数为字符串。一句代码完成 nonce、timestamp、sign 的校验防伪造请求调用、防参数篡改、防重放攻击。 8.1 引入依赖 api-sign 模块已内嵌到核心包只需要引入 sa-token 本身依赖即可请求发起端和接收端都需要引入 dependencygroupIdcn.dev33/groupIdartifactIdsa-token-spring-boot-starter/artifactIdversion1.35.0.RC/version /dependency8.2 配置密钥 请求发起端和接收端需要配置一个相同的秘钥在 application.yml 中配置 sa-token: sign:# API 接口签名秘钥 随便乱摁几个字母即可secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor8.3 请求发起端构建签名 public String join() {// 参加完活动后发送余额Long userId 1L;Long money 100L;MapString, Object params new HashMap();params.put(userId, userId);params.put(money, money);SaSignUtil.addSignParamsAndJoin(params);String url http://localhost:8079/sign/addMoney;return HttpUtil.post(url, params); }8.4 请求接受端校验签名 public String addMoney(Long userId, Long money) {// 1、校验请求中的签名SaSignUtil.checkRequest(SaHolder.getRequest());// 2、校验通过处理业务System.out.println(userId userId);System.out.println(money money);return ADD SUCCESS; }如上代码便可简单方便的完成 API 接口参数签名校验当请求端的秘钥不对或者请求参数被篡改、请求被重放时均无法通过 SaSignUtil.checkRequest 校验 8.5 原理分析 8.5.1 构建签名 SaSignUtil#addSignParamsAndJoin(params); public static String addSignParamsAndJoin(MapString, Object paramsMap) {return SaManager.getSaSignTemplate().addSignParamsAndJoin(paramsMap); }会调用 SaSignTemplate 类中的方法 SaSignTemplate#addSignParamsAndJoin() 方法 public String addSignParamsAndJoin(MapString, Object paramsMap) {// 1.添加参数timestamp、nonce、signparamsMap this.addSignParams(paramsMap);// 2.将 map 使用 转化为Stringreturn this.joinParams(paramsMap); }这个方法有两个逻辑 添加参数timestamp、nonce、sign将 map 使用 转化为String SaSignTemplate#addSignParams() 方法 public MapString, Object addSignParams(MapString, Object paramsMap) {paramsMap.put(timestamp, String.valueOf(System.currentTimeMillis()));paramsMap.put(nonce, SaFoxUtil.getRandomString(32));paramsMap.put(sign, this.createSign(paramsMap));return paramsMap; }SaSignTemplate#createSign() 方法生成签名 public String createSign(MapString, ? paramsMap) {String secretKey this.getSecretKey();SaSignException.throwByNull(secretKey, 参与参数签名的秘钥不可为空, 12201);if (((Map)paramsMap).containsKey(sign)) {paramsMap new TreeMap((Map)paramsMap);((Map)paramsMap).remove(sign);}// 按照数据字典进行排序并将 map 使用 转化为StringString paramsStr this.joinParamsDictSort((Map)paramsMap);String fullStr paramsStr key secretKey;// md5return this.abstractStr(fullStr); }public String abstractStr(String fullStr) {return SaSecureUtil.md5(fullStr); }这个方法有两个逻辑 按照数据字典进行排序并将 map 使用 转化为String使用 md5 摘要算法 8.5.2 验证签名 SaSignUtil.checkRequest(SaHolder.getRequest()); public static void checkRequest(SaRequest request) {SaManager.getSaSignTemplate().checkRequest(request); }还是会调用 SaSignTemplate 类中的方法 SaSignTemplate#checkParamMap() 方法校验请求参数 public void checkRequest(SaRequest request) {this.checkParamMap(request.getParamMap()); }public void checkParamMap(MapString, String paramMap) {String timestampValue (String)paramMap.get(timestamp);String nonceValue (String)paramMap.get(nonce);String signValue (String)paramMap.get(sign);// 1.校验时间戳this.checkTimestamp(Long.parseLong(timestampValue));// 2.校验随机数if (this.getSignConfigOrGlobal().getIsCheckNonce()) {this.checkNonce(nonceValue);}// 3.校验签名this.checkSign(paramMap, signValue); }这个方法有三个逻辑 校验时间戳判断是否在时间差范围内校验随机数判断此随机数是否已使用校验签名判断原签名和现在生成的签名是否一致 SaSignTemplate#checkNonce() 方法校验随机数 public void checkNonce(String nonce) {if (SaFoxUtil.isEmpty(nonce)) {throw new SaSignException(nonce 为空无效);} else {String key this.splicingNonceSaveKey(nonce);if (SaManager.getSaTokenDao().get(key) ! null) {throw new SaSignException(此 nonce 已被使用过不可重复使用 nonce);} else {SaManager.getSaTokenDao().set(key, nonce, this.getSignConfigOrGlobal().getSaveNonceExpire() * 2L 2L);}} }SaToken 存储 SaTokenDao 是存储接口默认实现是用的是 SaTokenDaoDefaultImpl。SaTokenDaoDefaultImpl 存储数据主要是通过 ConcurrentHashMap 存放在本地内存中。 SaManager#getSaTokenDao() 方法 public static SaTokenDao getSaTokenDao() {if (saTokenDao null) {Class var0 SaManager.class;synchronized(SaManager.class) {if (saTokenDao null) {setSaTokenDaoMethod(new SaTokenDaoDefaultImpl());}}}return saTokenDao; }SaTokenDaoDefaultImpl public class SaTokenDaoDefaultImpl implements SaTokenDao {// 数据集合 public MapString, Object dataMap new ConcurrentHashMap();// 过期时间集合 (单位: 毫秒) , 记录所有key的到期时间 [注意不是剩余存活时间] public MapString, Long expireMap new ConcurrentHashMap();public Thread refreshThread;public volatile boolean refreshFlag;public SaTokenDaoDefaultImpl() {// 定时清理过期数据this.initRefreshThread();}public String get(String key) {this.clearKeyByTimeout(key);return (String)this.dataMap.get(key);}public void set(String key, String value, long timeout) {if (timeout ! 0L timeout -2L) {this.dataMap.put(key, value);this.expireMap.put(key, timeout -1L ? -1L : System.currentTimeMillis() timeout * 1000L);}}public void initRefreshThread() {if (SaManager.getConfig().getDataRefreshPeriod() 0) {this.refreshFlag true;this.refreshThread new Thread(() - {while(true) {try {try {if (!this.refreshFlag) {return;}this.refreshDataMap();} catch (Exception var2) {var2.printStackTrace();}int dataRefreshPeriod SaManager.getConfig().getDataRefreshPeriod();if (dataRefreshPeriod 0) {dataRefreshPeriod 1;}Thread.sleep((long)dataRefreshPeriod * 1000L);} catch (Exception var3) {var3.printStackTrace();}}});this.refreshThread.start();}} }如果仅仅存放在本地内存中涉及到多个项目可能数据无法共享。 引入仓库 sa-token-dao-redis-jackson !-- Sa-Token 整合 Redis 使用 jackson 序列化方式 -- dependencygroupIdcn.dev33/groupIdartifactIdsa-token-redis-jackson/artifactIdversion1.35.0.RC/version /dependency !-- 提供Redis连接池 -- dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId /dependencySaTokenDaoRedisJackson 使用 Redis 作为存储数据的地方 SaBeanInject#setSaTokenDaoSaBeanInject 是自动配置的。当系统中存在 SaTokenDao的 Bean 实例则设置SaTokenDao 实例 public class SaBeanInject {Autowired(required false)public void setSaTokenDao(SaTokenDao saTokenDao) {SaManager.setSaTokenDao(saTokenDao);} }参考 API 接口参数签名 【开源项目】使用Sa-Token框架完成API参数签名
http://www.pierceye.com/news/618756/

相关文章:

  • 需要做网站的公司有哪些免费网页模板之家
  • 淘客网站怎么备案合肥在线官网
  • 马上飞做的一些网站免费自助建站系统有哪些
  • 建网站投放广告赚钱wordpress全屏弹窗插件
  • 电子商务公司网站模版通辽网站建设公司
  • 国外社交网站建设苏州seo门户网
  • 小程序建站公司唐山网页搜索排名提升
  • 网站后台模板北京网络营销方案
  • 网站如何不被百度搜到浙江网站怎么做推广
  • 网站建设主机类型怎么选diy电子商城网站
  • 中文域名 怎么做网站门户网站建站系统
  • 网站上的个人词条怎么做的做网站推广有用吗
  • 定兴县住房和城乡建设局网站河南省新闻奖
  • 江西省建设工程协会网站查询郑州网站建设一汉狮网络
  • 网站是否含有seo收录功能素材下载平台网站源码
  • 西宁个人网站建设不错的网站建设
  • 海南综合网站两学一做电视夜校做网店网站
  • wordpress分类页面空白网站建设优化哪家好
  • 宁波模板建站哪家服务专业wordpress 神箭手
  • 一张图片网站代码视频生成链接在线工具
  • 网站品牌推广浙江手机版建站系统开发
  • 网站后台密码在哪个文件建站报价表
  • 昌乐营销型网站建设个人管理系统
  • 手机网站开发位置定位天津和平做网站公司
  • 搜搜提交网站入口国外wordpress空间
  • python 做网站 数据库做企业官网还有必要吗
  • 数据录入网站开发安阳县实验中学
  • 网站 风格镜子厂家东莞网站建设
  • 做网站策划需要用什么软件网站建设 好发信息网
  • wordpress网站优化pc建站 手机网站