使用责任链模式实现登录风险控制

责任链模式

责任链模式是是设计模式中的一种行为型模式。该模式下,多个对象通过next属性进行关系关联,从而形成一个对象执行链表。当发起执行请求时,会从首个节点对象开始向后依次执行,如果一个对象不能处理该请求或者完成了请求工作(需要结合具体的业务场景),那么它会把相同的请求传给下一个接收者,依此类推。

责任链上的每个节点的处理者负责处理请求,用户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递,所以责任链将请求的发送者和请求的处理者解耦了,即使以后多加一些责任节点,也可以做到很好的扩展。

应用

 

上部分简单的介绍了责任链模式,那么现在就结合实际的业务场景来使用该模式。刚好这两天公司的产品经理就提出非常应景的需求。在用户登录时,需要判断登录账号存在的风险,比如在短时间内输入密码错误次数达到预设值,在短时间内,同一账号的登录所在地不属于同一个城市,登录ip地址不属于白名单范围内等。

当满足这些风险规则时,那么就需要根据需求对账号做进一步的处理,例如阻断登录,发送短信提醒或者禁用账号等。下面就使用责任链模式来实现这个需求功能。首先需要确定一个抽象处理类Handler,该处理类包含抽象处理方法和一个后继连接。

其次需要有若干个具体处理类XXXHandler,这个具体处理类需要继承抽象处理类Handler并且实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。最后需要有一个执行器或者客户端类来确定执行顺序,它不关心处理细节和请求的传递过程。

 

1创建抽象父类

在正式编码前,需要确认有哪些数据表和对象。

  • 风险规则类RiskRule: 用于记录不同规则信息,如触发条件,处置措施等。

  • 登录日志类LoginLog: 用于记录登录日志,其中包含登录地区,登录ip等。

  • 账户类UserAccount: 简单的账号account和密码password。

@Data
public class RiskRule {private Integer id;/*** 风险名称*/private String riskName;/*** 白名单ip*/private String acceptIp;/*** 触发次数*/private Integer triggerNumber;/*** 触发时间*/private Integer triggerTime;/*** 触发时间类型*/private Integer triggerTimeType;/*** 异常登录时间 (json)*/private String unusualLoginTime;/*** 采取的操作措施 1:提示 2:发送短信  3:阻断登录  4:封号*/private Integer operate;}
@Data
public class LoginLog {@TableId(type = IdType.AUTO)private Integer id;private String account;private Integer result;private String cityCode;private String ip;private Date time;
}

在确认完需要的对象后,现在可以编写登录风险处理抽象父类AbstractLoginHandle,该类需要包含一个nextHandle对象和filterRisk方法。filterRisk主要处理风险控制的规则并筛选出满足触发条件的规则对象,用于最后统一处理。

/**
* 登录风险处理抽象父类
*/
public abstract class AbstractLoginHandle {public AbstractLoginHandle nextHandle; // 下一个执行节点public void setNextHandle(AbstractLoginHandle nextHandle){this.nextHandle = nextHandle;}/*** 具体的执行方法,过滤出满足风控的规则* @param filter 满足风控的规则* @param ruleMap 所有规则集合* @param account 登录账户*/public abstract void filterRisk(List<RiskRule> filter, Map<Integer,RiskRule> ruleMap, UserAccount account);}

2密码错误次数

在创建完抽象父类后,下面开始实现具体的子类。首先是常见的密码错误次数,实现起来简单,需要去登录日志表中按照对应规则配置的规定时间来查询密码错误的日志即可。如果查询出的数量大于等于该规则的触发数量,那么就将该RiskRule对象添加到filter中,最后继续向下执行。

/*** 密码错误次数风险实现*/
@Component
public class PasswordErrorRiskHandle extends AbstractLoginHandle {// 配置触发时间间隔类型是秒private static final Integer SEC = 1;// 配置触发时间间隔类型是分钟private static final Integer MIN = 2;// 配置触发时间间隔类型是小时private static final Integer HOU = 3;@Resourceprivate LoginLogService loginLogService;@Overridepublic void filterRisk(List<RiskRule> filter, Map<Integer, RiskRule> ruleMap, UserAccount account) {if (MapUtil.isNotEmpty(ruleMap)) {//获取密码错误的规则信息RiskRule passwordRisk = ruleMap.get(1);if (passwordRisk != null) {//触发次数Integer triggerNumber = passwordRisk.getTriggerNumber();//触发时间Integer triggerTime = passwordRisk.getTriggerTime();//时间类型Integer triggerTimeType = passwordRisk.getTriggerTimeType();Date endTime = new Date();Date startTime;if (triggerTimeType == SEC) {startTime = DateUtil.offsetSecond(endTime, -triggerTime);} else if (triggerTimeType == MIN) {startTime = DateUtil.offsetMinute(endTime, -triggerTime);} else {startTime = DateUtil.offsetHour(endTime, -triggerTime);}// 查询范围时间内密码错误的次数Integer count = loginLogService.lambdaQuery().eq(LoginLog::getResult, 2).eq(LoginLog::getAccount, account.getAccount()).between(LoginLog::getTime, startTime, endTime).count();// 如果达到触发规则,则记录if (count != null && count.intValue() >= triggerNumber.intValue()) {filter.add(passwordRisk);}}}//是否有下一个节点 , 如果有,继续向下执行  if (this.nextHandle != null) {this.nextHandle.filterRisk(filter, ruleMap, account);}}}

3异常时间登录

到底什么时间登录才算异常时间登录,这个需要根据公司,系统来做判断。如果一家公司从来不加班,用的也都是些OA系统,正常的登录时间段都在早上8点到下午6点这样。如果有一天,一个账号突然在凌晨两三点进行了登录,那么这就可以算作异常登录。

当然,具体的时间段可以根据实际的需求进行设置。为了方便,这些时间段直接以json的方式存在的数据表中,具体格式如下。

[{"week":0,"startTime":"12:00:00","endTime":"14:00:00"},{"week":1,"startTime":"12:00:00","endTime":"14:00:00"}
]

这个需求实现也非常简单,只需要判断当前的登录时间是否在配置的异常登录时间范围内即可,如果在这个范围为内,那么就将该风险规则添加到filter中。

/*** 异常时间登录风险实现*/
@Component
public class UnusualLoginRiskHandle extends AbstractLoginHandle {@Overridepublic void filterRisk(List<RiskRule> filter, Map<Integer, RiskRule> ruleMap, UserAccount account) {if (MapUtil.isNotEmpty(ruleMap)) {RiskRule loginTimeExe = ruleMap.get(2);if (loginTimeExe != null) {// 将json转为异常时间对象List<UnusualLoginTime> unusualLoginTimes = JSONUtil.toList(loginTimeExe.getUnusualLoginTime(), UnusualLoginTime.class);Date now = new Date();// 判断当前时间是周几int dayOfWeek = DateUtil.dayOfWeek(now);for (UnusualLoginTime unusualLoginTime : unusualLoginTimes) {// 如果当前的周数与配置的周数相等,那么判断当前的具体时间if (unusualLoginTime.getWeek() == dayOfWeek) {DateTime startTime = DateUtil.parseTimeToday(unusualLoginTime.getStartTime());DateTime endTime = DateUtil.parseTimeToday(unusualLoginTime.getEndTime());// 如果当前的时间,在配置的时间范围内,那么将算作异常时间登录if (DateUtil.isIn(now, startTime, endTime)) {filter.add(loginTimeExe);break;}}}}}// 是否有下一个节点 , 如果有,继续向下执行  if (this.nextHandle != null) {this.nextHandle.filterRisk(filter, ruleMap, account);}}@Datapublic static class UnusualLoginTime {private int week;private String startTime;private String endTime;}
}

4IP白名单

在使用一些阿里云服务时,有时需要配置一些ip白名单才可以访问,非白名单内的ip将会阻断连接。这也是一种保证系统服务安全的一种方式,实现起来也比较容易。从数据库中读取ip白名单,如果是多个,可以使用英文逗号进行分割。

用户登录时,通过HttpServletRequest来获取用户的ip(这里为了方便测试,将ip作为一个字段放在了account中),如果这个ip不在白名单内,那么将这个风险规则添加到filter中。

/*** 登录ip风险实现*/
@Component
public class IPRiskHandle extends AbstractLoginHandle {@Overridepublic void filterRisk(List<RiskRule> filter, Map<Integer, RiskRule> ruleMap, UserAccount account) {if (MapUtil.isNotEmpty(ruleMap)) {RiskRule ipRisk = ruleMap.get(3);//判断是否配置登录ip白名单if (null != ipRisk && StrUtil.isNotEmpty(ipRisk.getAcceptIp())) {List<String> acceptIpList = Arrays.asList(ipRisk.getAcceptIp().split(","));//当前登录ip是否在白名单内,如果不在,则添加到filter中if (!acceptIpList.contains(account.getIp())) {filter.add(ipRisk);}}}if (this.nextHandle != null) {this.nextHandle.filterRisk(filter, ruleMap, account);}}
}

5异常地区登录

如果一个账号在短时间内在不同地区进行了登录操作,比如上一秒在北京登录,下一秒就在上海进行了登录。

那么这就可能出现了账号盗取情况,需要采取一定的处置措施,比如输入短信验证码,输入密保,封号等。

/*** 登录地区风险实现*/
@Component
public class LoginAreaRiskHandle extends AbstractLoginHandle {private static final Integer SEC = 1;private static final Integer MIN = 2;private static final Integer HOU = 3;@Resourceprivate LoginLogService loginLogService;@Overridepublic void filterRisk(List<RiskRule> filter, Map<Integer, RiskRule> ruleMap, UserAccount account) {if (MapUtil.isNotEmpty(ruleMap)) {RiskRule areaRisk = ruleMap.get(4);if (null != areaRisk) {Integer triggerTime = areaRisk.getTriggerTime();Integer triggerTimeType = areaRisk.getTriggerTimeType();Integer triggerNumber = areaRisk.getTriggerNumber();Date endTime = new Date();Date startTime;//获取查询时间范围的开始时间if (triggerTimeType == SEC) {startTime = DateUtil.offsetSecond(endTime, -triggerTime);} else if (triggerTimeType == MIN) {startTime = DateUtil.offsetMinute(endTime, -triggerTime);} else {startTime = DateUtil.offsetHour(endTime, -triggerTime);}// 指定时间范围内,登录地区是否超过指定个数List<LoginLog> loginLogList = loginLogService.lambdaQuery().select(LoginLog::getCityCode).between(LoginLog::getTime, startTime, endTime).eq(LoginLog::getResult, 1).eq(LoginLog::getAccount, account.getAccount()).list();long areaCount = CollUtil.emptyIfNull(loginLogList).stream().map(LoginLog::getCityCode).distinct().count();//如果超过指定个数,则将该风险策略添加到filterif (areaCount >= triggerNumber.longValue()) {filter.add(areaRisk);}}}if (this.nextHandle != null) {this.nextHandle.filterRisk(filter, ruleMap, account);}}
}

6组合链路节点

再将上面的各种情况实现完成后,需要有一个执行器来聚合这些handle。让这些hande节点有一定的执行顺序。

并且在所有节点执行完成后,对触发的风险规则进行处理。我自己定义的执行顺序是密码错误次数->异常时间登录->ip白名单->异常地区登录。

@Slf4j
@Component
public class LoginHandleManage {@Resourceprivate RiskRuleService riskRuleService;@Resourceprivate LoginLogService loginLogService;@Resourceprivate IPRiskHandle ipRiskHandle;@Resourceprivate LoginAreaRiskHandle loginAreaRiskHandle;@Resourceprivate PasswordErrorRiskHandle passwordErrorRiskHandle;@Resourceprivate UnusualLoginRiskHandle unusualLoginRiskHandle;/*** 构建执行顺序* passwordErrorRiskHandle -> unusualLoginRiskHandle -> ipRiskHandle -> loginAreaRiskHandle*/@PostConstructpublic void init() {passwordErrorRiskHandle.setNextHandle(unusualLoginRiskHandle);unusualLoginRiskHandle.setNextHandle(ipRiskHandle);ipRiskHandle.setNextHandle(loginAreaRiskHandle);}/*** 执行链路入口* @param account* @throws Exception*/public void execute(UserAccount account) throws Exception {//获取所有风险规则 List<RiskRule> riskRules = riskRuleService.lambdaQuery().list();Map<Integer, RiskRule> riskRuleMap = riskRules.stream().collect(Collectors.toMap(RiskRule::getId, r -> r));List<RiskRule> filterRisk = new ArrayList<>();//开始从首节点执行passwordErrorRiskHandle.filterRisk(filterRisk, riskRuleMap, account);if (CollUtil.isNotEmpty(filterRisk)) {// 获取最严重处置措施的规则Optional<RiskRule> optional = filterRisk.stream().max(Comparator.comparing(RiskRule::getOperate));if (optional.isPresent()) {RiskRule riskRule = optional.get();handleOperate(riskRule);//处置//TODO 记录日志}}}/*** 处置风险* @param riskRule* @throws Exception*/public void handleOperate(RiskRule riskRule) throws Exception {int operate = riskRule.getOperate().intValue();if (operate == OperateEnum.TIP.op) { //1log.info("========执行提示逻辑========");} else if (operate == OperateEnum.SMS.op) {//2log.info("========执行短信提醒逻辑========");} else if (operate == OperateEnum.BLOCK.op) {//3log.info("========执行登录阻断逻辑========");throw new Exception("登录存在风险!");} else if (operate == OperateEnum.DISABLE.op) {//4log.info("========执行封号逻辑========");throw new Exception("登录存在风险,账号被封!");}}
}

现在所有的逻辑已经搞定了,那么在登录的实现方法中只需要注入LoginHandleManage并调用execute即可,这样就可以与主体的登录逻辑代码实现解耦。

小结

责任链模式使用了委托的思想构建了一个链表,通过遍历链表来挨个询问链表中的每一个节点是否可以胜任某件事情,如果某个节点能够胜任,则直接处理,否则继续向下传递。责任链会造成处理的时延,但是能够很好的解耦合,提高可扩展性,可以结合具体场景,选择性使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/191505.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DefaultListableBeanFactory

DefaultListableBeanFactory 是一个完整的、功能成熟的 IoC 容器&#xff0c;如果你的需求很简单&#xff0c;甚至可以直接使用 DefaultListableBeanFactory&#xff0c;如果你的需求比较复杂&#xff0c;那么通过扩展 DefaultListableBeanFactory 的功能也可以达到&#xff0c…

金蝶云星空和管易云接口打通对接实战

金蝶云星空和管易云接口打通对接实战 对接系统&#xff1a;金蝶云星空 金蝶K/3Cloud结合当今先进管理理论和数十万家国内客户最佳应用实践&#xff0c;面向事业部制、多地点、多工厂等运营协同与管控型企业及集团公司&#xff0c;提供一个通用的ERP服务平台。K/3Cloud支持的协同…

时序数据库 TDengine + 高级分析软件 Seeq,助力企业挖掘时序数据潜力

作为一款制造业和工业互联网&#xff08;IIOT&#xff09;高级分析软件&#xff0c;Seeq 支持在工艺制造组织中使用机器学习创新的新功能。这些功能使组织能够将自己或第三方机器学习算法部署到前线流程工程师和主题专家使用的高级分析应用程序&#xff0c;从而使单个数据科学家…

sqlserver查询时去除1900-01-01

在下图示例中“chk_date”字段在数据中显示的是默认时间&#xff0c;如何将这个时间在SQL查询时设为空&#xff1f; cast(nullif(a.chk_date,) as datetime) 确认日期 以上是我的方法。 select chk_date as 日期 from Ixa_payment_req 运行结果是&#xff1a;1900-01-01 00:0…

【移远QuecPython】EC800M物联网开发板的SIM卡初始化和网络状态检测

【移远QuecPython】EC800M物联网开发板的SIM卡初始化和网络状态检测 文章目录 SIM卡初始化导入库获取SIM状态 网络检测导入库等待网络就绪 函数打包附录&#xff1a;列表的赋值类型和py打包列表赋值BUG复现代码改进优化总结 py打包 SIM卡初始化 导入库 import sim获取SIM状态…

【机器学习基础】机器学习入门(2)

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;后面的内容会越来越有意思~ &#x1f4a1;往期推荐&#xff1a;【机器学习基础】机器学习入门&#xff08;1&#xff09; &#x1f4a1;…

Java项目开发:基于Springboot+vue口腔牙科诊所管理系统

项目介绍 本选题则旨在通过标签分类管理等方式&#xff0c;实现管理员&#xff1a;首页、个人中心、会员管理、病例就诊信息管理、牙齿保健产品管理、复查提醒管理、预约挂号管理、药品信息管理、留言板管理、系统管理、订单管理&#xff0c;会员&#xff1b;首页、个人中心、…

Python logging模块打印日志

logging打印日志&#xff0c;文件名为log_config.py import logging import sysdef setup_logger(log_file, error_log_file):# 创建一个日志记录器logger logging.getLogger(__name__)logger.setLevel(logging.DEBUG) # 设置全局日志级别为 DEBUG# 创建一个文件处理器&…

arcgis--二维建筑面的三维显示设置

1、打开ArcScene软件&#xff0c;导入数据&#xff0c;如下&#xff1a; 2、 对建筑面进行拉伸。双击建筑物面图层&#xff0c;打开属性表&#xff0c;选择【拉伸】选项卡&#xff0c;参数设置如下&#xff1a; 显示结果如下&#xff1a;

C#中.NET 6.0控制台应用通过EF访问已建数据库

目录 一、新建.NET 6.0控制台应用并建立数据库连接 二、下载并安装EF程序包 三、自动生成EF模型和上下文 1.Blog类模型 2.Post类模型 3.数据库上下文 四、设计自己的应用 VS2022的.NET6.0、.NET7.0框架下默认支持EF7&#xff08;版本号7.0.13&#xff09;&#xff0c;除…

【腾讯云 HAI域探秘】探索AI绘画之路:利用腾讯云HAI服务打造智能画家

目录 前言1 使用HAI服务作画的步骤1.1 注册腾讯云账户1.2 创建算力服务器1.3 进入模型管理界面1.4 汉化界面1.5 探索AI绘画 2 模型参数的含义和调整建议2.1 模型参数的含义和示例2.2 模型参数的调整建议 3 调整参数作画的实践和效果3.1 实践说明3.2 实践效果13.3 实践效果23.4 …

No200.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

Idea 编译SpringBoot项目Kotlin报错/Idea重新编译

原因应该是一次性修改了大量的文件, SpringBoot项目启动Kotlin报错, Build Project也是同样的结果, 报错如下 Error:Kotlin: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.9.0, expected version is 1.1.13. Build-&…

消防救援大队应用“码“上监管 实现重点领域监督全覆盖

近年以来&#xff0c;一直存在消防安全风险防控不精准、问题发现不及时、监督效果不明显等难点问题&#xff0c;我们充分利用信息化手段&#xff0c;探索开通“码上监督”网络举报平台&#xff0c;实现监督途径从“线下”拓展到“线上”&#xff0c;“码上监督”马上办。 问题…

鼎捷PLM:引领国产替代,创造极致体验,探索数字化研发可行之路

目录 01 直击痛点&#xff0c;鼎捷PLM重塑研发价值链 02 从实际需求出发&#xff0c;支持创新研发 ① 正向的设计思维 ② 智能化的产品设计 ③ 支持大规模定制的设计 03 广域协同&#xff0c;全供应链快速响应研发 04 精益管理&#xff0c;研发体系化、企业低碳化 05 生…

RGMII回环:IDDR+ODDR+差分接口

目录 一、实验内容二、原理解释三、程序1、顶层文件&#xff1a;2、子模块2.1 oddr模块2.2、iddr顶层模块2.3、iddr子模块 3、仿真4、注意5、下载工程及仿真 一、实验内容 1、通过IDDR和ODDR的方式完成RGMII协议&#xff1b; 2、外部接口使用OBUFDS、IBUFDS转换成差分接口&…

CodeWhisperer 使用经验分享

今天给大家分享一下 Amazon CodeWhisperer 编程工具&#xff08;免费哦&#xff09;&#xff0c;使用这个软件后我的编码质量提升不少&#xff0c;给大家分享一下我的经验。希望大家支持哦。 Amazon CodeWhisperer 是亚⻢逊出品的一款基于机器学习的 AI 编程助手&#xff0c;可…

大数据毕业设计选题推荐-污水处理大数据平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

自适应AI chatGPT智能聊天创作官网html源码/最新AI创作系统/ChatGPT商业版网站源码

源码简介&#xff1a; 自适应AI chatGPT智能聊天创作官网html源码&#xff0c;这是最新AI创作系统&#xff0c;作为ChatGPT商业版网站源码&#xff0c;它是支持创作、编写、翻译、写代码等。是一个智能聊天系统项目源码。 注意&#xff1a;这个只是网站html源码&#xff0c;要…

一文看懂aPaaS平台是什么意思?有哪些优势?

aPaaS开发平台是什么&#xff1f; aPaaS&#xff0c;Application Platform as a Service&#xff0c;应用程序平台即服务。国际知名咨询机构Gartner对aPaaS所下的定义是&#xff1a;“这是基于PaaS&#xff08;平台即服务&#xff09;的一种解决方案&#xff0c;支持应用程序在…