• vue接口token认证登录(加手机验证)


    做一样东西就尽可能把他做好,因为人有【保存体力的天性】,一旦成型,很不想花时间去改

    ------从自己身上总结


    需求:用户没有登录不能跳转任何页面,只能登录

    实现思路:用户登录,后端验证,返回token,前端拿token,存储到cookie中,请求后端,其他接口,传递token。

    条件:我是使用vue2-cli的脚手架搭建的,所以主要配置是在main.js中设置

    思路拆卸:

    1.登录页面

    1-2补:前端输入验证(电话号码格式、不为空、点击发送验证码后60秒计时,刷新也无效)

    2.后端验证接口

    3.后端生成token方法

    4.获取token存储到cookie

    5.发送请求时,携带token

    你需要了解的基本知识:

    vue-router,后端(springBoot),vue基础

    登录页面

    需求设计图

    没有图,脑子真是一片空,俺先设计一个

    我前端画完之后

    附上,这段的vue代码,我导入了bootstrap

    bootstrap(官网直接下载)

    main.js

    1. import Vue from 'vue'
    2. import App from './App.vue'
    3. import SignIn from './SignIn.vue'
    4. import "./bootstrap-3.4.1-dist/css/bootstrap.css"
    5. Vue.config.productionTip = false
    6. // new Vue({
    7. // render: h => h(App),
    8. // }).$mount('#app')
    9. new Vue({
    10. render: h => h(SignIn),
    11. }).$mount('#app')

    signIn.vue

    1. <template>
    2. <!-- 手机号 -->
    3. <div id="app">
    4. <div style="width: 300px;height: 200px;border: 2px solid blue;align-content: center;">
    5. <div class="row"><span>一键登录及注册</span></div>
    6. <div style="display: flex;">
    7. <input type="text" style="margin: 5px;" placeholder="请输入手机号"/>
    8. </div>
    9. <div style="border: 2px solid red;display: flex;margin: 5px;">
    10. <input type="text" placeholder="请输入短信验证码"/>
    11. <button type="submit" >发送验证码</button>
    12. </div>
    13. <div class="login_btn">
    14. <button class="btn btn-success">登录|注册</button>
    15. </div>
    16. </div>
    17. </div>
    18. </template>
    19. <script>
    20. export default {
    21. name:"app",
    22. data() {
    23. return {
    24. phone:"", //手机号
    25. verifyCode:"", //验证码
    26. btnTitle:"获取验证码",
    27. disabled:false, //是否可点击
    28. errors:{}, //验证提示信息
    29. } }
    30. }
    31. </script>
    32. <style>
    33. </style>

    Js交互

    首先做一个60秒的按钮验证,点击发送后禁用,然后计时60秒,然后加到cookie里,刷新页面也继续重新计时

    1.首先给按钮加一个id,并且加一个点击的方法

     2.加这3个,按钮内容,初始化总数,定时器标志

     3.加入js代码

    1. SMSLimit:function(){
    2. if (!this.timer) {
    3. this.timer = setInterval(() => {
    4. submitButton.disabled=true
    5. if (this.time_count > 0) {
    6. this.time_count--
    7. this.btnTitle = '重新发送' + this.time_count + 's'
    8. } else {
    9. submitButton.disabled=false
    10. this.btnTitle = '获取验证码'
    11. clearInterval(this.timer)
    12. this.timer = null
    13. this.time_count = 10
    14. }
    15. }, 1000)
    16. }
    17. }

    为了大家能看清楚,我还专门用ev录屏,录了视频,然后在网上在线转换为gif图,用心良苦啊

     你能很明显的看到,我刷新后,照样可以发送,这样,有用户为了求快,没啥事,就刷新,那样就很费钱,烧你短信钱,让原本不富裕的家庭,雪上加霜。所以我们要避免这些正常用户的误操,从而降低成本。

    “对于有一定IT基础的人员,我们要后端使用ip验证,避免他们用代码恶意刷我们的短信,这种情况,检测到就直接加入黑名单,封号。我们文章后面在说。如何做。”

    Js交互2

    这时候,我们使用cookie实现,刷新也不能中断,思路是这样:

    【读取时间,从cookie中读取,时间到了,清除cookie,用户刷新,重新计时,让他们不敢在刷新】

    更新以上思路:现在是什么时代,HTML5的时代,HTML5有什么牛逼的,对,他自带localStorage存储,所以我们可以不用cookie,而去恰好以前没有玩过localStorage,刚好可以一探究竟。

    思路:

    1.第一次点击时,判断localStorage是否有值

    2.没有值,则调用请求短信接口,判断完毕后,添加值

    3.倒计时结束后,清除localStorage的值

    4.当用户想【快点发验证】码,点击刷新时,刷新完会调用上面的代码,此时localStorage有值,则不会进行短信请求接口,并且还会重新开始计时。

    1.判断是否有值

    2.无值则调用注册接口,添加值

     3.倒计时结束,清除值

     4.刷新完毕后,如果有值,则调用此方法,进行重新计时

    (注意:mount是钩子函数,注意他的位置,小白不懂,请去走一遍官方实例的代码)

     效果图

     前端第一阶段代码整体:

    又做了少量优化

    1. <template>
    2. <!-- 手机号 -->
    3. <div id="app">
    4. <div style="width: 300px;height: 200px;border: 2px solid blue;align-content: center;">
    5. <div class="row"><span>一键登录及注册</span></div>
    6. <div style="display: flex;">
    7. <input v-model="phone" type="text" style="margin: 5px;" placeholder="请输入手机号"/>
    8. </div>
    9. <div style="border: 2px solid red;display: flex;margin: 5px;">
    10. <input type="text" placeholder="请输入短信验证码"/>
    11. <button id="submitButton" type="submit" @click="SMSLimit(phone)">{{btnTitle}}</button>
    12. </div>
    13. <div class="login_btn">
    14. <button class="btn btn-success">登录|注册</button>
    15. </div>
    16. </div>
    17. </div>
    18. </template>
    19. <script>
    20. export default {
    21. name:"app",
    22. data() {
    23. return {
    24. phone:"", //手机号
    25. verifyCode:"", //验证码
    26. disabled:false, //是否可点击
    27. errors:{} ,//验证提示信息
    28. btnTitle:"获取验证码",
    29. time_count: 10,
    30. timer: null
    31. } },
    32. methods:{
    33. sendSMS:function(phone){
    34. console.log(this.phone)
    35. var patt=/^1[0-9]{10}$/
    36. if(patt.test(this.phone)){
    37. alert("手机号验证正确")
    38. return true
    39. }else{
    40. alert("手机号不正确"+this.phone)
    41. return false;
    42. }
    43. },
    44. SMSLimit:function(phone){
    45. if (!this.timer) {
    46. alert(this.phone)
    47. //手机号码格式验证
    48. if(this.sendSMS(phone)==false){
    49. return
    50. }
    51. submitButton.disabled=true
    52. if(localStorage.getItem("flagTime")!=1){
    53. //如果有值,则不发送请求
    54. alert("调用注册接口")
    55. }
    56. localStorage.setItem("flagTime",1)
    57. this.jishi()
    58. }
    59. },jishi:function(){
    60. submitButton.disabled=true
    61. this.timer = setInterval(() => {
    62. if (this.time_count > 0) {
    63. this.time_count--
    64. this.btnTitle = '重新发送' + this.time_count + 's'
    65. } else {
    66. localStorage.clear()
    67. submitButton.disabled=false
    68. this.btnTitle = '获取验证码'
    69. clearInterval(this.timer)
    70. this.timer = null
    71. this.time_count = 10
    72. }
    73. }, 1000)
    74. }
    75. },
    76. mounted() {
    77. if(localStorage.getItem("flagTime")==1){
    78. this.jishi()
    79. }
    80. }
    81. }
    82. </script>
    83. <style>
    84. </style>

    后端验证部分

    首先你要会springBoot,然后会java,还有mybaits。因为我是拿这个写的这个案例,你要是不会,可以先去网上找找demo和教程去试一试。

    建立的文件夹和项目的过程,我就不写了,与我以前的写的项目一样,可以参照我的这篇文章

    springBoot+mybaties后端多层架构(demo)_我要用代码向我喜欢的女孩表白的博客-CSDN博客

    后端主要处理2个事情

    1.账号密码的验证  ,那你得有个数据库存储用户的信息

    2.生成token(登录凭证,有了这个前端每次请求,后端会进行认证,没有凭证的话,只能访问登录注册接口),登录成功后,返回给前端

    3.恶意请求短信的防范,这里要建ip库,对特定时间经常频繁,请求的人,进行ip封禁。因为正常人不会用1秒请求10次,而且还能绕过,前端的验证,那肯定是程序员对决了。。

    总思路:

    思路:用户发送请求,后端给此ip,进行记录,并判断是否在黑名单内,不在则生成code,然后存下(手机号和code,还有创建时间),写个mysql定时器,删除(过期5分钟)的数据,发送短信给用户,用户填写验证码后,与后端库中比对,成功则插入。

    所以总共是【2个】请求,获取短信   、  登录 或注册

    建库

    建用户库(我使用的是mysql):

    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for test_user
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `test_user`;
    7. CREATE TABLE `test_user` (
    8. `user_id` int(0) NOT NULL AUTO_INCREMENT COMMENT '用户id',
    9. `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户名称',
    10. `create_time` timestamp(0) NULL DEFAULT NULL COMMENT '注册时间',
    11. `phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '用户手机号',
    12. PRIMARY KEY (`user_id`) USING BTREE
    13. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
    14. SET FOREIGN_KEY_CHECKS = 1;

    建立ip封禁库

    判断https://ipinfo.io/json

    ip主表:

    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for test_ip
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `test_ip`;
    7. CREATE TABLE `test_ip` (
    8. `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
    9. `ip_num` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT 'ip号',
    10. `status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '1为正常,2为封ip',
    11. `create_time` timestamp(0) NULL DEFAULT NULL COMMENT '最早一次创建时间',
    12. `update_time` timestamp(0) NULL DEFAULT NULL COMMENT '更新时间',
    13. PRIMARY KEY (`id`) USING BTREE
    14. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
    15. SET FOREIGN_KEY_CHECKS = 1;

    ip详情表

    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for test_ip_info
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `test_ip_info`;
    7. CREATE TABLE `test_ip_info` (
    8. `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
    9. `ip_num` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT 'ip号码',
    10. `create_time` timestamp(0) NULL DEFAULT NULL COMMENT '创建时间',
    11. `phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '登录/注册 用的手机号',
    12. `country` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '登录/注册的 ip区域 ',
    13. `region` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '省',
    14. `city` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '市',
    15. PRIMARY KEY (`id`) USING BTREE
    16. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
    17. SET FOREIGN_KEY_CHECKS = 1;

    ip封禁(短信发送接口)

    1.记录请求的ip,以及输入过来的手机号,分析区域,存入库中

    2.如果1分钟内,请求超过3次,则被封禁,修改此ip的状态(程序员可以通过js代码请求,跳过前端检测),并且返回,【由于你多次发短信,导致ip已被封禁】

    3.如果没有封禁问题,则调用短信验证发送代码

    ip封禁编写(注:ip封禁只是在发短信中的一个验证模块)

    注意:本机部署,本机测试,是找不到外网地址的,只有一个ip,请求另外一个公网ip才能找到地址,所以我们在本机写完后,会丢到我的阿里云上进行测试。

    另外,本机测试访问地址不能写localhost,否则会默认变成Host中对应的ip,也就是0.0.0.0.0.0.1

    定义一个数据库与前端接收的类,方便入库

    1. package com.example.etf.story.paramer;
    2. import lombok.Data;
    3. import java.util.Date;
    4. @Data
    5. public class NetInfoParam {//浏览器的相关信息,用户发送短信的时候用到
    6. private String ipNum;//用户ip
    7. private String phone;//他发送的手机号
    8. private Date createTime;
    9. private String country;
    10. private String region;//区域
    11. private String city;//城市
    12. private String updateTime;
    13. }

    controller层代码

    1. @Autowired
    2. private HttpServletRequest request;
    3. @PostMapping("/sendSMS")
    4. @ResponseBody
    5. public R sendSMS(@RequestBody NetInfoParam netInfoParam,HttpServletRequest request){
    6. String ip = clientService.getIp(request);
    7. return ok(ip,"ip");
    8. }

    service层代码

    (这段代码是我抄的,因为我的后端代码不可能写的这么丑)

    1. public String getIp(HttpServletRequest request) {
    2. //直接获取真实的ip
    3. String ip = request.getHeader("X-Real-IP");
    4. if (ip != null && !"".equals(ip) && !"unknown".equalsIgnoreCase(ip)) {
    5. return ip;
    6. }
    7. ip = request.getHeader("X-Forwarded-For");
    8. //从多次路由器的请求转发中,找到最终的办网ip
    9. if (ip != null && !"".equals(ip) && !"unknown".equalsIgnoreCase(ip)) {
    10. int index = ip.indexOf(',');
    11. if (index != -1) {
    12. //只获取第一个值
    13. return ip.substring(0, index);
    14. } else {
    15. return ip;
    16. }
    17. } else {
    18. //取不到真实ip则返回空,返回内网地址。
    19. return request.getRemoteAddr();
    20. }
    21. }

    封号逻辑:

    当前用户插入,60秒内,是否超过3条,此用户的插入数据,说明他不是正常用短信发送,他肯定是强行请求接口,所以这种情况对他进行封禁。

    如果是封号用户,则返回true,此代码也会对每次请求进行保存

    1. public boolean recordIp(NetInfoParam netInfoParam){
    2. TestClientMapper mapper = new SqlSessionF().getSqlSession().getMapper(TestClientMapper.class);
    3. //1.查询是否有数据
    4. TestIp testIpEntity = mapper.selectIp(netInfoParam.getIpNum());
    5. Date date = new Date();
    6. netInfoParam.setCreateTime(date);
    7. netInfoParam.setUpdateTime(date);
    8. //2.没数据则添加
    9. if(testIpEntity==null){
    10. netInfoParam.setStatus("1");
    11. mapper.insertIp(netInfoParam);
    12. }
    13. //3.添加此ip详情的记录
    14. mapper.insertIpInfo(netInfoParam);
    15. //封禁条件,进行是否封禁,判断最新这条插入60秒内,是否有多条此ip的数据,如果大于3条则封禁
    16. int rs = mapper.updateIpStatus(netInfoParam);//直接封禁了
    17. if(rs>0){
    18. return true;
    19. }
    20. //4.判断是否为封禁
    21. if(testIpEntity!=null&&testIpEntity.getStatus()=="2"){//封禁
    22. return true;
    23. }else{
    24. return false;
    25. }
    26. }

    mapper接口

    1. //查询是否存在此ip,以前有没有记录过
    2. TestIp selectIp(String ipNum);
    3. //给主表插入ip
    4. int insertIp(NetInfoParam netInfoParam);
    5. //给ip的每条数据表插入记录
    6. int insertIpInfo(NetInfoParam netInfoParam);
    7. //更新封号状态
    8. int updateIpStatus(NetInfoParam netInfoParam);

    mybaits.xml(sql)

    1. <select id="selectIp" resultType="com.example.etf.story.dao.TestIp">
    2. select * from test_ip where ip_num=#{ipNum}
    3. </select>
    4. <insert id="insertIp">
    5. insert into test_ip(ip_num,status,create_time,update_time)
    6. values (#{ipNum},#{status},#{createTime},#{updateTime})
    7. </insert>
    8. <insert id="insertIpInfo">
    9. insert into test_ip_info(ip_num,create_time,phone,country,region,city)
    10. values (#{ipNum},#{createTime},#{phone},#{country},#{region},#{city})
    11. </insert>
    12. <update id="updateIpStatus">
    13. update test_ip_info,test_ip
    14. set status="2"
    15. where
    16. (SELECT count(1) FROM `test_ip_info` where ip_num=#{ipNum}
    17. and create_time >date_add(#{createTime},interval -60 second)
    18. and create_time &lt;= #{createTime})>3
    19. </update>

    controller层新增代码,调用刚刚的记录ip方法,如果返回是封禁,则不发送短信

     录了个屏,给大家看看效果

    ip封禁做完了,接下来该对正常的用户发送短信了,用户输入手机号正常,另外不是ip封禁的用户,则可以进行短信发送。

    没有思路,不写代码,在地铁中经常听到【成都TOD,不规划,不设计,不设计,不实施的原则】

    工作中也能用到,如果你不思考,很爽的写完代码,可能会有让你蛋疼的疏忽。所以每次我写代码,都要先写思路流程,然后跟着流程写代码,就如鱼得水。

    思路:先网上找个短信平台,看看他的接口文档,搜搜用户评价,然后买个短信服务,然后写个方法,随机生成验证码,发送短信的时候,发验证码出去,并且将手机号与验证码绑定,存储在session中,那样就可以统一设定过期时间。

    短信验证

    找个平台,买短信,我用的阿里,因为我的服务器也是阿里。,花了4大洋,买了200条。

    然后申请,签名,申请模板,大概2小时左右通过

    签名和模板都要有,【名称很重要,记下】

    创建一个AcessKey()

     创建后,把AcecessKey和AccessKey Secret给记录下来,妥善保管

     打开Api界面,找到你对应的api【编程语言】,把你的

    1.签名

    2.模板

    3.AccessKeyId和AccessKey Secret

    全部填写到左边,然后右边就会自动填入参数到代码中。

    别忘记先导入 pom依赖(java)

    1. <dependency>
    2. <groupId>com.aliyun</groupId>
    3. <artifactId>dysmsapi20170525</artifactId>
    4. <version>2.0.12</version>
    5. </dependency>

    复制代码

    地址

    阿里云 OpenAPI 开发者门户https://next.api.aliyun.com/api/Dysmsapi/2017-05-25/SendSms?spm=5176.25163407.quickstart-index-d6f48_f4003_0.d_65e2c_8da05_2.6769bb6e3BrrTT¶ms=%7B%22SignName%22:%22%E9%98%BF%E9%87%8C%E4%BA%91%E7%9F%AD%E4%BF%A1%E6%B5%8B%E8%AF%95%22,%22TemplateCode%22:%22SMS_154950909%22,%22PhoneNumbers%22:%22132471313123%22,%22TemplateParam%22:%22%7B%22code%22%3A%221234%22%7D%22%7D&lang=JAVA

    文档

    阿里云 OpenAPI 开发者门户

    一定要在网站上测试好后,确定能发送短信了,在把代码复制到你的项目中。然后添加你的accessKeyId和Secret

     
    

    由于文章太长,csdn保存有问题,上次写的,一大段没有进行自动保存。所以这里,以另一篇写,关于登录注册模块。

    参考资料:

    java获取不到真实ip;获取不到ip地址;_wx60b5e2d0590be的技术博客_51CTO博客

  • 相关阅读:
    C++ 算法学习 之 vector assign
    第一次汇报yandex广告数据时,应该展示哪些数据
    Shiro安全(五):Shiro权限绕过之Shiro-682&CVE-2020-13933
    04 使用Keil模拟器和Debug (printf) Viewer窗口实现scanf输入,并进行串口收发回环,无需fgetc重定向
    一文带你上手自动化测试中的PO模式!
    java培训技术SpringMVC视图解析器
    APS生产计划排产在注塑行业的应用
    搞定“项目八怪”,你就是管理高手!
    解决Python爬虫中selenium模块中的find_element_by_id方法无法使用
    【猿创征文|Unity开发实战】—— 2D项目1 - Ruby‘s Adventure 游戏地图绘制(2-1)
  • 原文地址:https://blog.csdn.net/qq_38403590/article/details/125343176