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

温州市网站建设_网站建设公司_网站建设_seo优化

越秀网站建设策划,oa办公系统网址,做数据统计的网站,欧美个人网站1故事背景 忘记密码这件事#xff0c;相信绝大多数人都遇到过#xff0c;输一次错一次#xff0c;错到几次以上#xff0c;就不允许你继续尝试了。 但当你尝试重置密码#xff0c;又发现新密码不能和原密码重复#xff1a; 图片 相信此刻心情只能用一张图形容#xf…1故事背景 忘记密码这件事相信绝大多数人都遇到过输一次错一次错到几次以上就不允许你继续尝试了。 但当你尝试重置密码又发现新密码不能和原密码重复 图片 相信此刻心情只能用一张图形容 图片 虽然但是密码还是很重要的顺便我有了一个问题三次输错密码后系统是怎么做到不让我继续尝试的 2我想了想有如下几个问题需要搞定 是只有输错密码才锁定还是账户名和密码任何一个输错就锁定 输错之后也不是完全冻结为啥隔了几分钟又可以重新输了 技术栈到底麻不麻烦 去网上搜了搜也问了下ChatGPT找到一套解决方案SpringBootRedisLua脚本。 这套方案也不算新很早就有人在用了不过难得是自己想到的问题和解法就记录一下吧。 顺便回答一下上面的三个问题 锁定的是IP不是输入的账户名或者密码也就是说任一一个输错3次就会被锁定 Redis的Lua脚本中实现了key过期策略当key消失时锁定自然也就消失了 技术栈同SpringBootRedisLua脚本 3那么自己动手实现一下 前端部分 首先写一个账密输入页面使用很简单HTML加表单提交 !DOCTYPE html html headtitle登录页面/titlestylebody {background-color: #F5F5F5;}form {width: 300px;margin: 0 auto;margin-top: 100px;padding: 20px;background-color: white;border-radius: 5px;box-shadow: 0 0 10px rgba(0,0,0,0.2);}label {display: block;margin-bottom: 10px;}input[typetext], input[typepassword] {border: none;padding: 10px;margin-bottom: 20px;border-radius: 5px;box-shadow: 0 0 5px rgba(0,0,0,0.1);width: 100%;box-sizing: border-box;font-size: 16px;}input[typesubmit] {background-color: #30B0F0;color: white;border: none;padding: 10px;border-radius: 5px;box-shadow: 0 0 5px rgba(0,0,0,0.1);width: 100%;font-size: 16px;cursor: pointer;}input[typesubmit]:hover {background-color: #1C90D6;}/style /head bodyform actionhttp://localhost:8080/login methodgetlabel forusername用户名/labelinput typetext idusername nameusername placeholder请输入用户名 requiredlabel forpassword密码/labelinput typepassword idpassword namepassword placeholder请输入密码 requiredinput typesubmit value登录/form /body /html效果如下: 图片 后端部分 技术选型分析 首先我们画一个流程图来分析一下这个登录限制流程 图片 从流程图上看首先访问次数的统计与判断不是在登录逻辑执行后而是执行前就加1了 其次登录逻辑的成功与失败并不会影响到次数的统计 最后还有一点流程图上没有体现出来这个次数的统计是有过期时间的当过期之后又可以重新登录了。 那为什么是RedisLua脚本呢 Redis的选择不难看出这个流程比较重要的是存在一个用来计数的变量这个变量既要满足分布式读写需求还要满足全局递增或递减的需求那Redis的incr方法是最优选了。 那为什么需要Lua脚本呢流程上在验证用户操作前有些操作如图 图片 这里至少有3步Redis的操作get、incr、expire如果全放到应用里面来操作有点慢且浪费资源。 Lua脚本的优点如下 减少网络开销。 可以将多个请求通过脚本的形式一次发送减少网络时延。 原子操作。 Redis会将整个脚本作为一个整体执行中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件无需使用事务。 复用。 客户端发送的脚本会永久存在redis中这样其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。 最后为了增加功能的复用性我打算使用Java注解的方式实现这个功能。 代码实现 项目结构如下 图片 配置文件 pom.xml ?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.7.11/versionrelativePath/ !-- lookup parent from repository --/parentgroupIdcom.example/groupIdartifactIdLoginLimit/artifactIdversion0.0.1-SNAPSHOT/versionnameLoginLimit/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency!-- redis --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency!-- Jedis --dependencygroupIdredis.clients/groupIdartifactIdjedis/artifactId/dependency!--切面依赖 --dependencygroupIdorg.aspectj/groupIdartifactIdaspectjweaver/artifactId/dependency!-- commons-lang3 --dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactId/dependency!-- guava --dependencygroupIdcom.google.guava/groupIdartifactIdguava/artifactIdversion23.0/version/dependency!-- lombok --dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build/projectapplication.properties ## Redis配置 spring.redis.host127.0.0.1 spring.redis.port6379 spring.redis.password spring.redis.timeout1000 ## Jedis配置 spring.redis.jedis.pool.min-idle0 spring.redis.jedis.pool.max-idle500 spring.redis.jedis.pool.max-active2000 spring.redis.jedis.pool.max-wait10000注解部分 LimitCount.java package com.example.loginlimit.annotation;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/*** 次数限制注解* 作用在接口方法上*/ Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface LimitCount {/*** 资源名称用于描述接口功能*/String name() default ;/*** 资源 key*/String key() default ;/*** key prefix** return*/String prefix() default ;/*** 时间的单位秒* 默认60s过期*/int period() default 60;/*** 限制访问次数* 默认3次*/int count() default 3; }核心处理逻辑类LimitCountAspect.java package com.example.loginlimit.aspect;import java.io.Serializable; import java.lang.reflect.Method; import java.util.Objects;import javax.servlet.http.HttpServletRequest;import com.example.loginlimit.annotation.LimitCount; import com.example.loginlimit.util.IPUtil; import com.google.common.collect.ImmutableList; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;Slf4j Aspect Component public class LimitCountAspect {private final RedisTemplateString, Serializable limitRedisTemplate;Autowiredpublic LimitCountAspect(RedisTemplateString, Serializable limitRedisTemplate) {this.limitRedisTemplate  limitRedisTemplate;}Pointcut(annotation(com.example.loginlimit.annotation.LimitCount))public void pointcut() {// do nothing}Around(pointcut())public Object around(ProceedingJoinPoint point) throws Throwable {HttpServletRequest request  ((ServletRequestAttributes)Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();MethodSignature signature  (MethodSignature)point.getSignature();Method method  signature.getMethod();LimitCount annotation  method.getAnnotation(LimitCount.class);//注解名称String name  annotation.name();//注解keyString key  annotation.key();//访问IPString ip  IPUtil.getIpAddr(request);//过期时间int limitPeriod  annotation.period();//过期次数int limitCount  annotation.count();ImmutableListString keys  ImmutableList.of(StringUtils.join(annotation.prefix()  _, key, ip));String luaScript  buildLuaScript();RedisScriptNumber redisScript  new DefaultRedisScript(luaScript, Number.class);Number count  limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);log.info(IP:{} 第 {} 次访问key为 {}描述为 [{}] 的接口, ip, count, keys, name);if (count ! null  count.intValue()  limitCount) {return point.proceed();} else {return 接口访问超出频率限制;}}/*** 限流脚本* 调用的时候不超过阈值则直接返回并执行计算器自加。** return lua脚本*/private String buildLuaScript() {return local c \nc  redis.call(get,KEYS[1]) \nif c and tonumber(c)  tonumber(ARGV[1]) then \nreturn c; \nend \nc  redis.call(incr,KEYS[1]) \nif tonumber(c)  1 then \nredis.call(expire,KEYS[1],ARGV[2]) \nend \nreturn c;;}}获取IP地址的功能我写了一个工具类IPUtil.java代码如下: package com.example.loginlimit.util;import javax.servlet.http.HttpServletRequest;public class IPUtil {private static final String UNKNOWN  unknown;protected IPUtil() {}/*** 获取 IP地址* 使用 Nginx等反向代理软件 则不能通过 request.getRemoteAddr()获取 IP地址* 如果使用了多级反向代理的话X-Forwarded-For的值并不止一个而是一串IP地址* X-Forwarded-For中第一个非 unknown的有效IP字符串则为真实IP地址*/public static String getIpAddr(HttpServletRequest request) {String ip  request.getHeader(x-forwarded-for);if (ip  null || ip.length()  0 || UNKNOWN.equalsIgnoreCase(ip)) {ip  request.getHeader(Proxy-Client-IP);}if (ip  null || ip.length()  0 || UNKNOWN.equalsIgnoreCase(ip)) {ip  request.getHeader(WL-Proxy-Client-IP);}if (ip  null || ip.length()  0 || UNKNOWN.equalsIgnoreCase(ip)) {ip  request.getRemoteAddr();}return 0:0:0:0:0:0:0:1.equals(ip) ? 127.0.0.1 : ip;}}另外就是Lua限流脚本的说明脚本代码如下 private String buildLuaScript() {return local c \nc  redis.call(get,KEYS[1]) \nif c and tonumber(c)  tonumber(ARGV[1]) then \nreturn c; \nend \nc  redis.call(incr,KEYS[1]) \nif tonumber(c)  1 then \nredis.call(expire,KEYS[1],ARGV[2]) \nend \nreturn c;;}这段脚本有一个判断 tonumber(c) tonumber(ARGV[1])这行表示如果当前key 的值大于了limitCount直接返回否则调用incr方法进行累加1且调用expire方法设置过期时间。 最后就是RedisConfig.java代码如下 package com.example.loginlimit.config;import java.io.IOException; import java.io.Serializable; import java.time.Duration; import java.util.Arrays;import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.data.redis.serializer.StringRedisSerializer; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig;Configuration public class RedisConfig extends CachingConfigurerSupport {Value(${spring.redis.host})private String host;Value(${spring.redis.port})private int port;Value(${spring.redis.password})private String password;Value(${spring.redis.timeout})private int timeout;Value(${spring.redis.jedis.pool.max-idle})private int maxIdle;Value(${spring.redis.jedis.pool.max-wait})private long maxWaitMillis;Value(${spring.redis.database:0})private int database;Beanpublic JedisPool redisPoolFactory() {JedisPoolConfig jedisPoolConfig  new JedisPoolConfig();jedisPoolConfig.setMaxIdle(maxIdle);jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);if (StringUtils.isNotBlank(password)) {return new JedisPool(jedisPoolConfig, host, port, timeout, password, database);} else {return new JedisPool(jedisPoolConfig, host, port, timeout, null, database);}}BeanJedisConnectionFactory jedisConnectionFactory() {RedisStandaloneConfiguration redisStandaloneConfiguration  new RedisStandaloneConfiguration();redisStandaloneConfiguration.setHostName(host);redisStandaloneConfiguration.setPort(port);redisStandaloneConfiguration.setPassword(RedisPassword.of(password));redisStandaloneConfiguration.setDatabase(database);JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration  JedisClientConfiguration.builder();jedisClientConfiguration.connectTimeout(Duration.ofMillis(timeout));jedisClientConfiguration.usePooling();return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration.build());}Bean(name  redisTemplate)SuppressWarnings({rawtypes})ConditionalOnMissingBean(name  redisTemplate)public RedisTemplateObject, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplateObject, Object template  new RedisTemplate();//使用 fastjson 序列化JacksonRedisSerializer jacksonRedisSerializer  new JacksonRedisSerializer(Object.class);// value 值的序列化采用 fastJsonRedisSerializertemplate.setValueSerializer(jacksonRedisSerializer);template.setHashValueSerializer(jacksonRedisSerializer);// key 的序列化采用 StringRedisSerializertemplate.setKeySerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setConnectionFactory(redisConnectionFactory);return template;}//缓存管理器Beanpublic CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {RedisCacheManager.RedisCacheManagerBuilder builder  RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory);return builder.build();}BeanConditionalOnMissingBean(StringRedisTemplate.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {StringRedisTemplate template  new StringRedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}Beanpublic KeyGenerator wiselyKeyGenerator() {return (target, method, params) - {StringBuilder sb  new StringBuilder();sb.append(target.getClass().getName());sb.append(method.getName());Arrays.stream(params).map(Object::toString).forEach(sb::append);return sb.toString();};}Beanpublic RedisTemplateString, Serializable limitRedisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplateString, Serializable template  new RedisTemplate();template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setConnectionFactory(redisConnectionFactory);return template;} }class JacksonRedisSerializerT implements RedisSerializerT {private ClassT clazz;private ObjectMapper mapper;JacksonRedisSerializer(ClassT clazz) {super();this.clazz  clazz;this.mapper  new ObjectMapper();mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);}Overridepublic byte[] serialize(T t) throws SerializationException {try {return mapper.writeValueAsBytes(t);} catch (JsonProcessingException e) {e.printStackTrace();return null;}}Overridepublic T deserialize(byte[] bytes) throws SerializationException {if (bytes.length  0) {return null;}try {return mapper.readValue(bytes, clazz);} catch (IOException e) {e.printStackTrace();return null;}} }LoginController.java package com.example.loginlimit.controller;import javax.servlet.http.HttpServletRequest;import com.example.loginlimit.annotation.LimitCount; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;Slf4j RestController public class LoginController {GetMapping(/login)LimitCount(key  login, name  登录接口, prefix  limit)public String login(RequestParam(required  true) String username,RequestParam(required  true) String password, HttpServletRequest request) throws Exception {if (StringUtils.equals(张三, username)  StringUtils.equals(123456, password)) {return 登录成功;}return 账户名或密码错误;}}LoginLimitApplication.java package com.example.loginlimit;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication public class LoginLimitApplication {public static void main(String[] args) {SpringApplication.run(LoginLimitApplication.class, args);}}4演示一下效果 图片 上面这套限流的逻辑感觉用在小型或中型的项目上应该问题不大不过目前的登录很少有直接锁定账号不能输入的一般都是弹出一个验证码框让你输入验证码再提交。我觉得用我这套逻辑改改应该不成问题核心还是接口尝试次数的限制嘛
http://www.lebaoying.cn/news/60056.html

相关文章:

  • 济南网站定制制作查品牌的软件有什么
  • 广州手机模板建站做网站需要的照片
  • 昭通市建设局网站广州购物网站开发
  • 给别人做网站打电话推销wordpress主题背景
  • 工艺品网站模版crm网站推荐
  • 外贸经常用的网站外贸网站制作要求
  • 网站建设需要的客户资料银川做网站最好的公司有哪些
  • 网站建设初学wordpress中文版本
  • 网站设计联盟人工智能logo设计
  • 做网站用jsp还是html网业制作过程
  • 怎么做赌钱网站情侣做记录网站源码
  • 延吉网站开发公司有哪些哪家app定制开发好
  • 长春建站方案开一个网站要花多少钱
  • php网站留言网站建设网上书店
  • 网站建设客户怎么找怎样做免费企业网站
  • 朋友要我帮忙做网站网站建设合同怎么交印花税
  • 乐清做手机网站做科研交流常用的网站
  • 南充网站建设多少钱嘉兴网站制作建设
  • 不会编程怎样建设网站中山软件开发项目管理
  • 中装建设集团网站市场监督管理局怎么样
  • 西安高端网站建设oppo软件商店安装
  • 备案时候网站不能打开吗百度一下你就知道官网网址
  • 婚恋网站做期货现货贵金属的人成品网站源码在线看
  • 洛阳网站建设价格低网站开发公司有资质吗
  • 福田网站建设推荐校园网站建设方案
  • 松江品划做网站公司google推广服务商
  • 水果网站建设方案书红河网站建设
  • 温州市网站建设北京网站建设推广服务
  • 龙华专业做网站wordpress浮窗音乐
  • 搭建一个企业网站需要多少钱模板建站多少钱