• 原理说明
  • 开发环境
  • 具体实现过程

原理说明

  • 在网站登录时经常会遇到短信验证码登录的情况,其原理如下:
  • 首先我们需要一个短信发送接口,前端发送手机号码到后端,后端随机生成一个验证码并存入redis,并且设置该key的过期时间。(这里可以用手机号做redis的key,保证数据的唯一性)
  • 然后后端将该验证码发送给当前的手机号。(第三方短信平台完成)
  • 用户拿到验证码后,将验证码发送给后端进行校验
  • 后端对传过来的验证码与redis中的进行比较,如果相同就删掉,防止可以校验多次。

开发环境

  • idea
  • springboot2.0
  • redis
  • 阿里云短信接口

具体实现过程

  1. 创建短信模板和短信签名

  1. 创建AccessKeyId

  1. 创建springboot项目,导入坐标
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions>
</dependency>
<!--阿里云短信验证码-->
<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.0.6</version> <!-- 注:如提示报错,先升级基础包版,无法解决可联系技术支持 -->
</dependency>
<dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-dysmsapi</artifactId><version>1.1.0</version>
</dependency>
<!--redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 添加jedis客户端 -->
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency>
<!--spring2.0集成redis所需common-pool2-->
<!-- 必须加上,jedis依赖此  -->
<!-- spring boot 2.0 的操作手册有标注 大家可以去看看 地址是:https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/-->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.5.0</version>
</dependency><!-- 将作为Redis对象序列化器 -->
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.47</version>
</dependency>
  1. yml文件配置redis
# redis配置
spring:redis:# Redis数据库索引(默认为0)database: 0# Redis服务器地址host: 127.0.0.1# Redis服务器连接端口port: 6379# Redis服务器连接密码(默认为空)password: #自己设置的密码jedis:pool:# 连接池最大连接数(使用负值表示没有限制)max-active: 20# 连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1# 连接池中的最大空闲连接max-idle: 10# 连接池中的最小空闲连接min-idle: 0# 连接超时时间(毫秒)timeout: 1000
  1. 进行redis配置
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {//    /**
//     * springboot1.x用这个来管理缓存
//     * 选择redis作为默认缓存工具
//     * @param redisTemplate
//     * @return
//     */
//    @Bean
//    public CacheManager cacheManager(RedisTemplate redisTemplate) {//        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
//        return rcm;
//    }/*** Logger*/private static final Logger lg = LoggerFactory.getLogger(RedisConfiguration.class);@Autowiredprivate JedisConnectionFactory jedisConnectionFactory;@Bean@Overridepublic KeyGenerator keyGenerator() {//  设置自动key的生成规则,配置spring boot的注解,进行方法级别的缓存// 使用:进行分割,可以很多显示出层级关系// 这里其实就是new了一个KeyGenerator对象,只是这是lambda表达式的写法,我感觉很好用,大家感兴趣可以去了解下return (target, method, params) -> {StringBuilder sb = new StringBuilder();sb.append(target.getClass().getName());sb.append(":");sb.append(method.getName());for (Object obj : params) {sb.append(":" + String.valueOf(obj));}String rsToUse = String.valueOf(sb);lg.info("自动生成Redis Key -> [{}]", rsToUse);return rsToUse;};}@Bean@Overridepublic CacheManager cacheManager() {// 初始化缓存管理器,在这里我们可以缓存的整体过期时间什么的,我这里默认没有配置lg.info("初始化 -> [{}]", "CacheManager RedisCacheManager Start");RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(jedisConnectionFactory);return builder.build();}@Beanpublic RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory jedisConnectionFactory ) {//设置序列化Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置redisTemplateRedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();redisTemplate.setConnectionFactory(jedisConnectionFactory);RedisSerializer stringSerializer = new StringRedisSerializer();redisTemplate.setKeySerializer(stringSerializer); // key序列化redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value序列化redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); // Hash value序列化redisTemplate.afterPropertiesSet();return redisTemplate;}@Override@Beanpublic CacheErrorHandler errorHandler() {// 异常处理,当Redis发生异常时,打印日志,但是程序正常走lg.info("初始化 -> [{}]", "Redis CacheErrorHandler");CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {@Overridepublic void handleCacheGetError(RuntimeException e, Cache cache, Object key) {lg.error("Redis occur handleCacheGetError:key -> [{}]", key, e);}@Overridepublic void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {lg.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);}@Overridepublic void handleCacheEvictError(RuntimeException e, Cache cache, Object key)    {lg.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);}@Overridepublic void handleCacheClearError(RuntimeException e, Cache cache) {lg.error("Redis occur handleCacheClearError:", e);}};return cacheErrorHandler;}/*** 此内部类就是把yml的配置数据,进行读取,创建JedisConnectionFactory和JedisPool,以供外部类初始化缓存管理器使用* 不了解的同学可以去看@ConfigurationProperties和@Value的作用**/@ConfigurationPropertiesclass DataJedisProperties{@Value("${spring.redis.host}")private  String host;@Value("${spring.redis.password}")private  String password;@Value("${spring.redis.port}")private  int port;@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;@BeanJedisConnectionFactory jedisConnectionFactory() {lg.info("Create JedisConnectionFactory successful");JedisConnectionFactory factory = new JedisConnectionFactory();factory.setHostName(host);factory.setPort(port);factory.setTimeout(timeout);factory.setPassword(password);return factory;}@Beanpublic JedisPool redisPoolFactory() {lg.info("JedisPool init successful,host -> [{}];port -> [{}]", host, port);JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();jedisPoolConfig.setMaxIdle(maxIdle);jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);return jedisPool;}}
}
  1. 创建发送短信的工具类
/*** 发送短信工具类*/
public class SendSMSUtil {/*** 你的accessKeyId*/private static final String accessKeyId="";/*** 你的accessKeySecret*/private static final String accessKeySecret="";/*** 签名*/private static final String signName="";/*** 短信模板*/private static final String templateCode="";/*** 验证码*/private static int code;/*** @Descirption:发送手机验证码* @param phoneNumber:需要发送的手机号码* @return OK表示成功,失败则返回失败消息*/public static String sendSmsUtil(String phoneNumber){//设置超时时间-可自行调整System.setProperty("sun.net.client.defaultConnectTimeout", "10000");System.setProperty("sun.net.client.defaultReadTimeout", "10000");// 初始化ascClient需要的几个参数// 短信API产品名称(短信产品名固定,无需修改)final String product = "Dysmsapi";// 短信API产品域名(接口地址固定,无需修改)final String domain = "dysmsapi.aliyuncs.com";// 初始化ascClient,暂时不支持多region(请勿修改)IClientProfile profile= DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);try {DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);} catch (ClientException e) {e.printStackTrace();}IAcsClient acsClient=new DefaultAcsClient(profile);// 组装请求对象SendSmsRequest request=new SendSmsRequest();//使用post提交request.setMethod(MethodType.POST);// 必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,// 验证码类型的短信推荐使用单条调用的方式;发送国际/港澳台消息时,接收号码格式为国际区号+号码,如“85200000000”request.setPhoneNumbers(phoneNumber);request.setSignName(signName);// 必填:短信模板-可在短信控制台中找到,发送国际/港澳台消息时,请使用国际/港澳台短信模版request.setTemplateCode(templateCode);//随机生成6位验证码code = (int) ((Math.random() * 9 + 1) * 100000);// 可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为// 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败request.setTemplateParam("{code:"+code+"}");// 请求失败这里会抛ClientException异常SendSmsResponse sendSmsResponse = null;try {sendSmsResponse = acsClient.getAcsResponse(request);} catch (ClientException e) {e.printStackTrace();return "请求失败";}assert sendSmsResponse.getCode() != null;// 发送不成功if (sendSmsResponse.getCode() == null || !sendSmsResponse.getCode().equals("OK")) {return sendSmsResponse.getMessage();}// 请求成功return "OK";}public static int getCode(){return code;}
}
  1. 创建控制类
@Controller
//跨域使用
@CrossOrigin
public class SmsController {@AutowiredStringRedisTemplate stringRedisTemplate;/*** 发送手机验证码* @param phoneNumber 手机号码* @return 1表示成功,0表示失败*/@RequestMapping("/sendSms")@ResponseBodypublic String SmsTest(String phoneNumber){//发送短信String result = SendSMSUtil.sendSmsUtil(phoneNumber);if (result == null || !result.equals("OK")) {// 发送不成功return "0";}// 获取验证码int code = SendSMSUtil.getCode();Map<String,Object> map=new HashMap<>();// 将数据存入redismap.put(phoneNumber,code+"");//用phoneNumber来做键,可以做到唯一性stringRedisTemplate.opsForHash().putAll(phoneNumber,map);// 设置redis过期时间,这个时间是秒为单位的,我现在设置5分钟之内有效,过了就会自动删除stringRedisTemplate.expire(phoneNumber, 60*5, TimeUnit.SECONDS);return "OK";}/*** 校验验证码* @param phoneNumber* @param checkSMSCode* @return*/@RequestMapping(value = "/checkSMSCode", method = RequestMethod.POST)@ResponseBodypublic String checkSMSCode(String phoneNumber,String checkSMSCode) {// 服务器放入的验证码//从redis中取出以电话号码为key,验证码为value的值Map<Object, Object> map=stringRedisTemplate.opsForHash().entries(phoneNumber);String serverCheckCode =(String) map.get(phoneNumber);if (serverCheckCode == null || serverCheckCode.equals("")) {return "CodeError";}// 验证码不匹配if (!checkSMSCode.equals(serverCheckCode)) {return "CodeError";}else {//如果验证成功就删除验证码stringRedisTemplate.opsForHash().delete(phoneNumber,phoneNumber);}return "OK";}}
  1. 自己写一个前端页面或者postman进行测试

这里还有点瑕疵,前端点击发送之后,要有倒计时,并且后端要有时间限制,比如1分钟之内不可以重复发送验证码,这都是小问题,大家可以自己去写一个判断

本文转载自:https://mp.weixin.qq.com/s/faDOoXnciWFQW-uo2MMp2A

短信验证码登录的实现相关推荐

  1. 5.Spring Security 短信验证码登录

    Spring Security 短信验证码登录 在 Spring Security 添加图形验证码一节中,我们已经实现了基于 Spring Boot + Spring Security 的账号密码登录 ...

  2. 【每日随笔】电子签名 ( 下载 “e 签保“ 应用 | 使用 手机号 + 短信验证码 登录 | 发起签署 | 签名 | 获取签名后的 PDF 文件及出证信息 )

    文章目录 一.下载 "e 签保" 应用 二.使用 手机号 + 短信验证码 登录 三.发起签署 四.签名 五.获取签名后的 PDF 文件及出证信息 一.下载 "e 签保&q ...

  3. java antd实现登录,基于 antd pro 的短信验证码登录

    概要 整体流程 前端 页面代码 请求验证码和登录的 service (src/services/login.js) 处理登录的 model (src/models/login.js) 后端 短信验证码 ...

  4. Abp Core 添加短信验证码登录(动态密码登录)

    交流QQ群:555913397 有什么问题可以加群大家一起交流 Abp Core 添加短信验证码登录(动态密码登录) 现目前我国网站的已经很少使用电子邮箱了,基本上都是手机号作为账号,有时候粗心的用户 ...

  5. OAuth2.0 - 自定义模式授权 - 短信验证码登录

    一.OAuth2.0 - 自定义模式授权 上篇文章我们分析了目前的情况,演示了微服务的大环境下在保证安全的情况下通过SpringGateWay实现统一的鉴权处理,但是前面的演示中,我们都是基于用户名密 ...

  6. 手机发送短信验证码登录完整实例

    项目需求 后台生成随机6位数作为验证码,发送给手机,同时将验证码存入缓存,用户登录时验证输入的验证码是否过期或者是否正确. 一.发送短信 1.了解短信发送 通过发送短信的API,建立一个URL类的对象 ...

  7. 短信验证码登录流程思路及详细步骤

    点击蓝色"java大数据修炼之道"关注我哟加个"星标",每晚21:00,一起学技术 来源: blog.csdn.net/classabcd/article/de ...

  8. 短信验证码登录,以及第三方登录

    短信验证码登录 首先去阿里云服务器开通短信服务功能,进入短信服务界面 点击国内消息,申请一个签名和模板 申请一个AccessKey,并且将短信服务的权限加入其中 加入相关的依赖 <depende ...

  9. 微信短信验证码登录教程

    这个会登录的就不用看了 这是给新手写的教程 ...... 第一步 打开微信 点击登录以后出现 点击 用短信验证码登录 以后出现 点击获取验证码 之后出来一个对话框 点击确定 然后过一会手机会收到一条验 ...

  10. 手机短信验证码登录功能的开发实录(机器识别码、短信限流、错误提示、发送验证码倒计时60秒)

    短信验证码登录功能 项目分析 核心代码 1.外部js库调用 2.HTML容器构建 3.javaScript业务逻辑验证 4.后端验证逻辑 总结 短信验证码是通过发送验证码到手机的一种有效的验证码系统, ...

最新文章

  1. BFS:走出迷宫并输出最小步数
  2. Python设计模式-中介者模式
  3. HM16.0之帧间预测——xCheckRDCostInter()函数
  4. oracle11 登陆慢,oracle11g安装后电脑启动很慢怎么解决
  5. Activator.CreateInstance 方法 (Type) 的用法
  6. Javascript之in操作符的用法
  7. curl 同时发送多个请求
  8. php背景图片居中对齐命令,css怎么设置背景图片自适应居中
  9. 【教程分享】大数据视频教程
  10. springcloud springboot 集成cxf webservice框架,配置cxf拦截器
  11. matlab 最舒适的背景配色
  12. 强大数定律与弱大数定律(民科解释)
  13. cad移动时捕捉不到基点,为什么CAD对象捕捉打开了却捕捉不了?
  14. android 百度地图 根据地址 查到其经纬度,使用百度地图api实现根据地址查询经纬度...
  15. Ubuntu16.04下使用VLC media player播放器实现倍速播放
  16. 为Windows 7的winsxs文件夹瘦身,慎重。
  17. 服务器虚拟机移动,从物理服务器迁移到虚拟机的两大方案
  18. mycat启动报错:but failed to start(个例)
  19. 大容量充电宝什么牌子最好?市面上最大容量充电宝推荐
  20. java 实习生刚入职都会做些什么工作呢?

热门文章

  1. php 屏蔽deprecated,解决php deprecated 的问题
  2. react 树形结构递归方法
  3. 《当我谈跑步时我谈些什么》:痛苦难以避免,而磨难可以选择
  4. mysql将公历农历转换_SQL农历转换函数(显示中文格式,加入润月的显示)
  5. Audio解析strategy配置文件
  6. 美国计算机硕士要读多久,去美国读研究生需要多久 各专业时长一览
  7. 饱和度,对比度,锐度
  8. 六、流行框架介绍(SpringBoot框架详解(含底层原理介绍,适用于springBoot1.x和springBoot2.x,属于通用版本))
  9. 关于cad生成dwf文件和插入dwf快
  10. Device ID的用处和读取方法