概述

网上的前端验证码逻辑总感觉不安全,验证码建议还是使用后端配合验证。

如果产品确定可以上网的话,就可以使用腾讯,百度等第三方验证,对接方便。但是产品可能内网部署,就必须自己写了。

本文章就是基于这一点来实现的。

前端验证码显示一个图片,后端生成图片。

部分原理

1.前端调用生端获取图片时,传入一个roomID,后端生成一个4位验征码,放入redis中。然后生成一个图片返回。

2.前端显示图片,登录时将roomID和填写的验证码,一并提交,登录接口根据roomId从redis中取出验证码判断是否正确。

这样就相当于后端验证了。

大家觉得有问题什么,可以进行评论。谢谢。

源码

前端部分代码

  1. <template>
  2. <div class="login-container">
  3. <vue-particles
  4. color="#ffffff"
  5. :particleOpacity="0.7"
  6. :particlesNumber="50"
  7. shapeType="circle"
  8. :particleSize="4"
  9. linesColor="#dedede"
  10. :linesWidth="1"
  11. :lineLinked="true"
  12. :lineOpacity="0.4"
  13. :linesDistance="150"
  14. :moveSpeed="2"
  15. :hoverEffect="true"
  16. hoverMode="grab"
  17. :clickEffect="true"
  18. clickMode="push"
  19. ></vue-particles>
  20. <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left">
  21. <div class="title-container">
  22. <h3 class="title">智能综合管理系统</h3>
  23. </div>
  24. <el-form-item prop="username">
  25. <span class="svg-container">
  26. <svg-icon icon-class="user" />
  27. </span>
  28. <el-input ref="username" v-model="loginForm.username" placeholder="用户名" name="username" type="text" tabindex="1" autocomplete="on" />
  29. </el-form-item>
  30. <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
  31. <el-form-item prop="password">
  32. <span class="svg-container">
  33. <svg-icon icon-class="password" />
  34. </span>
  35. <el-input
  36. :key="passwordType"
  37. ref="password"
  38. v-model="loginForm.password"
  39. :type="passwordType"
  40. placeholder="密码"
  41. name="password"
  42. tabindex="2"
  43. autocomplete="on"
  44. @keyup.native="checkCapslock"
  45. @blur="capsTooltip = false"
  46. @keyup.enter.native="handleLogin"
  47. />
  48. <span class="show-pwd" @click="showPwd">
  49. <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
  50. </span>
  51. </el-form-item>
  52. </el-tooltip>
  53. <el-form-item prop="yzm">
  54. <span class="svg-container">
  55. <svg-icon icon-class="password" />
  56. </span>
  57. <el-input type="text" v-model="loginForm.verifyCode" maxlength="4" placeholder="验证码" />
  58. <div class="identifyCode" @click="refreshCode">
  59. <el-image :src="verifyImageUrl"></el-image>
  60. </div>
  61. </el-form-item>
  62. <el-button :loading="loading" type="primary" style="width: 100%; margin-bottom: 30px" @click.native.prevent="handleLogin">登录</el-button>
  63. </el-form>
  64. </div>
  65. </template>
  66. <script>
  67. import { validUsername } from '@/utils/validate'
  68. import Identify from './components/Identify'
  69. import { uuid } from 'vue-uuid'; // uuid object is also exported to things
  70. // outside Vue instance.
  71. export default {
  72. name: 'Login',
  73. components: { Identify },
  74. data() {
  75. const validateUsername = (rule, value, callback) => {
  76. if (!validUsername(value)) {
  77. callback(new Error('请输入正确的用户名'))
  78. } else {
  79. callback()
  80. }
  81. }
  82. const validatePassword = (rule, value, callback) => {
  83. if (value.length < 6) {
  84. callback(new Error('密码最少6位'))
  85. } else {
  86. callback()
  87. }
  88. }
  89. return {
  90. loginForm: {
  91. username: 'admin',
  92. password: '111111',
  93. roomId: '',
  94. verifyCode: ''
  95. },
  96. loginRules: {
  97. username: [{ required: true, trigger: 'blur', validator: validateUsername }],
  98. password: [{ required: true, trigger: 'blur', validator: validatePassword }]
  99. },
  100. passwordType: 'password',
  101. capsTooltip: false,
  102. loading: false,
  103. showDialog: false,
  104. redirect: undefined,
  105. otherQuery: {},
  106. identifyCodes: '1234567890',
  107. identifyCode: '',
  108. // verifyImageRoomId: '',
  109. verifyImageUrl: '',
  110. }
  111. },
  112. watch: {
  113. $route: {
  114. handler: function (route) {
  115. const query = route.query
  116. if (query) {
  117. this.redirect = query.redirect
  118. this.otherQuery = this.getOtherQuery(query)
  119. }
  120. },
  121. immediate: true
  122. }
  123. },
  124. created() {
  125. // window.addEventListener('storage', this.afterQRScan)
  126. // this.identifyCode = ''
  127. // this.makeCode(this.identifyCodes, 4)
  128. this.refreshCode()
  129. },
  130. mounted() {
  131. if (this.loginForm.username === '') {
  132. this.$refs.username.focus()
  133. } else if (this.loginForm.password === '') {
  134. this.$refs.password.focus()
  135. }
  136. },
  137. destroyed() {
  138. // window.removeEventListener('storage', this.afterQRScan)
  139. },
  140. methods: {
  141. checkCapslock(e) {
  142. const { key } = e
  143. this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z')
  144. },
  145. showPwd() {
  146. if (this.passwordType === 'password') {
  147. this.passwordType = ''
  148. } else {
  149. this.passwordType = 'password'
  150. }
  151. this.$nextTick(() => {
  152. this.$refs.password.focus()
  153. })
  154. },
  155. createUuid() {
  156. let uuidV4 = uuid.v4().replace('-', '').replace('-', '').replace('-', '').replace('-', '')
  157. this.verifyImageRoomId = uuidV4
  158. this.verifyImageUrl = '/api/Operator/getCaptchaImage/120/40/' + this.verifyImageRoomId
  159. console.log(uuidV4)
  160. },
  161. handleLogin() {
  162. this.$refs.loginForm.validate(valid => {
  163. if (valid) {
  164. this.loading = true
  165. this.$store.dispatch('user/login', this.loginForm)
  166. .then(() => {
  167. this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
  168. this.loading = false
  169. })
  170. .catch(() => {
  171. this.loading = false
  172. })
  173. } else {
  174. console.log('error submit!!')
  175. return false
  176. }
  177. })
  178. },
  179. getOtherQuery(query) {
  180. return Object.keys(query).reduce((acc, cur) => {
  181. if (cur !== 'redirect') {
  182. acc[cur] = query[cur]
  183. }
  184. return acc
  185. }, {})
  186. },
  187. // 生成随机数
  188. randomNum(min, max) {
  189. return Math.floor(Math.random() * (max - min) + min)
  190. },
  191. // 切换验证码
  192. refreshCode() {
  193. let uuidV4 = uuid.v4().replace('-', '').replace('-', '').replace('-', '').replace('-', '')
  194. this.loginForm.roomId = uuidV4
  195. this.verifyImageUrl = '/api/Operator/getCaptchaImage/120/40/' + this.loginForm.roomId
  196. console.log(uuidV4)
  197. },
  198. // 生成四位随机验证码
  199. makeCode(o, l) {
  200. for (let i = 0; i < l; i++) {
  201. this.identifyCode += this.identifyCodes[
  202. this.randomNum(0, this.identifyCodes.length)
  203. ]
  204. }
  205. console.log(this.identifyCode)
  206. }
  207. }
  208. }
  209. </script>
  210. <style lang="scss">
  211. /* 修复input 背景不协调 和光标变色 */
  212. /* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
  213. $bg: #283443;
  214. $light_gray: #fff;
  215. $cursor: #fff;
  216. @supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
  217. .login-container .el-input input {
  218. color: $cursor;
  219. }
  220. }
  221. /* reset element-ui css */
  222. .login-container {
  223. background: url("~@/assets/background.jpg") no-repeat;
  224. min-height: 100vh;
  225. .el-input {
  226. display: inline-block;
  227. height: 47px;
  228. width: 85%;
  229. input {
  230. background: transparent;
  231. border: 0px;
  232. -webkit-appearance: none;
  233. border-radius: 0px;
  234. padding: 12px 5px 12px 15px;
  235. color: $light_gray;
  236. height: 47px;
  237. caret-color: $cursor;
  238. &:-webkit-autofill {
  239. box-shadow: 0 0 0px 1000px $bg inset !important;
  240. -webkit-text-fill-color: $cursor !important;
  241. }
  242. }
  243. }
  244. .el-form-item {
  245. border: 1px solid rgba(255, 255, 255, 0.1);
  246. background: rgba(0, 0, 0, 0.1);
  247. border-radius: 5px;
  248. color: #454545;
  249. .el-form-item__error {
  250. color: rgb(223, 223, 176);
  251. }
  252. }
  253. }
  254. </style>
  255. <style lang="scss" scoped>
  256. $bg: #2d3a4b;
  257. $dark_gray: #889aa4;
  258. $light_gray: #eee;
  259. .login-container {
  260. min-height: 100%;
  261. width: 100%;
  262. background-color: $bg;
  263. overflow: hidden;
  264. .login-form {
  265. position: absolute;
  266. left: 0;
  267. right: 0;
  268. top: 0;
  269. bottom: 0;
  270. margin: auto;
  271. width: 520px;
  272. max-width: 100%;
  273. padding: 160px 35px 0;
  274. margin: 0 auto;
  275. overflow: hidden;
  276. }
  277. .tips {
  278. font-size: 14px;
  279. color: #fff;
  280. margin-bottom: 10px;
  281. span {
  282. &:first-of-type {
  283. margin-right: 16px;
  284. }
  285. }
  286. }
  287. .svg-container {
  288. padding: 6px 5px 6px 15px;
  289. color: $dark_gray;
  290. vertical-align: middle;
  291. width: 30px;
  292. display: inline-block;
  293. }
  294. .title-container {
  295. position: relative;
  296. .title {
  297. font-size: 42px;
  298. color: $light_gray;
  299. margin: 0px auto 40px auto;
  300. text-align: center;
  301. font-weight: bold;
  302. }
  303. }
  304. .show-pwd {
  305. position: absolute;
  306. right: 10px;
  307. top: 7px;
  308. font-size: 16px;
  309. color: $dark_gray;
  310. cursor: pointer;
  311. user-select: none;
  312. }
  313. .identifyCode {
  314. position: absolute;
  315. right: 10px;
  316. top: 5px;
  317. }
  318. .thirdparty-button {
  319. position: absolute;
  320. right: 0;
  321. bottom: 6px;
  322. }
  323. @media only screen and (max-width: 470px) {
  324. .thirdparty-button {
  325. display: none;
  326. }
  327. }
  328. }
  329. </style>

后端接口


  1. /// <summary>
  2. /// 获取验证码
  3. /// </summary>
  4. /// <returns></returns>
  5. [HttpGet("getCaptchaImage/{width:int}/{height:int}/{roomId}")]
  6. public IActionResult GetCaptchaImage(int width, int height, string roomId)
  7. {
  8. Console.WriteLine(roomId);
  9. //int width = 100;
  10. //int height = 36;
  11. var captchaCode = Captcha.GenerateCaptchaCode();
  12. var result = Captcha.GenerateCaptchaImage(width, height, captchaCode);
  13. string redisKey = "VerifyCode_" + roomId;
  14. Startup.redisDb.StringSet(redisKey, captchaCode, new TimeSpan(0, 5, 0));
  15. Stream s = new MemoryStream(result.CaptchaByteData);
  16. return new FileStreamResult(s, "image/png");
  17. }
  18. /// <summary>
  19. /// 登录
  20. /// </summary>
  21. /// <returns></returns>
  22. [HttpPost("login")]
  23. public ApiResponseData Login(LoginDto loginInfo)
  24. {
  25. if (string.IsNullOrWhiteSpace(loginInfo.username))
  26. return ApiResponse.Error("用户名不能为空");
  27. if (string.IsNullOrWhiteSpace(loginInfo.password))
  28. return ApiResponse.Error("密码不能为空");
  29. Entity.BaseOperator operInfo = Db
  30. .Select<BaseOperator>()
  31. .Where(a => a.OperatorCode == loginInfo.username && a.Password == loginInfo.password.ToLower() && a.Status == 1 && a.IsDel == false).ToOne();
  32. if (operInfo == null)
  33. return ApiResponse.Error("用户名或者密码不正确");
  34. bool verifyResult = Captcha.ValidateCaptchaCode(loginInfo.RoomId, loginInfo.VerifyCode);
  35. if(verifyResult == false)
  36. return ApiResponse.Error("验证码不正确");
  37. //登录时重新生成token,一个用户只能在一个地方登录
  38. string token = System.Guid.NewGuid().ToString().Replace("-", "");
  39. Db.Update<BaseOperator>(operInfo.OperatorId)
  40. .Set(a => a.Token, token)
  41. .ExecuteAffrows();
  42. dynamic outJson = new ExpandoObject();//初始化一个不包含任何成员的ExpandoObject
  43. outJson.token = token;
  44. //存入redis
  45. string redisKey = "UserInfo_" + token;
  46. Startup.redisDb.StringSet(redisKey, operInfo.OperatorId, new TimeSpan(24, 0, 0));
  47. return ApiResponse.Succ(outJson);
  48. }

asp.net core配合vue实现后端验证码逻辑的更多相关文章

  1. ASP.NET Core 与 Vue.js 服务端渲染

    http://mgyongyosi.com/2016/Vuejs-server-side-rendering-with-aspnet-core/ 原作者:Mihály Gyöngyösi 译者:oop ...

  2. docker部署angular和asp.net core组成的前后端分离项目

    最近使用docker对项目进行了改进,把步骤记录一下,顺便说明一下项目的结构. 项目是前后端分离的项目,后端使用asp.net core 2.2,采用ddd+cqrs架构的分层思想,前端使用的是ang ...

  3. 运行Vue在ASP.NET Core应用程序并部署在IIS上

    前言 项目一直用的ASP.NET Core,但是呢我对ASP.NET Core一些原理也还未开始研究,仅限于会用,不过园子中已有大量文章存在,借着有点空余时间,我们来讲讲如何利用ASP.NET Cor ...

  4. 用ASP.NET Core重写了一款网络考试培训的免费软件

    在IT圈混迹了近十年,今已正当而立之年却仍一事无成,心中倍感惶恐惭愧.面对竟争如此激列的环境,该如何应对?却也知不能让自已闲着,得转起来,动起来.于是,便想着利用最新技术栈将自已原来的收费产品重写一次 ...

  5. 初探ASP.NET Core 3.x (3) - Web的运作流程和ASP.NET Core的运作结构

    本文地址:https://www.cnblogs.com/oberon-zjt0806/p/12215717.html 注意:本篇大量地使用了mermaid绘制图表,加载需要较长的时间,请见谅 [TO ...

  6. 使用xunit对asp.net core webapi进行集成测试

    新项目我们采用前后端分离,后端采用asp.net core webapi, 如何对后端代码进行自动化测试呢,有以下几种方案: 1. 单元测试,目前这个方案对我们来说难度很大,抛开时间的问题,单元测试对 ...

  7. ASP.NET Core 1.0 静态文件、路由、自定义中间件、身份验证简介

    概述 ASP.NET Core 1.0是ASP.NET的一个重要的重新设计. 例如,在ASP.NET Core中,使用Middleware编写请求管道. ASP.NET Core中间件对HttpCon ...

  8. [译]ASP.NET Core 2.0 区域

    问题 如何将一个规模庞大的ASP.NET Core 2.0应用程序进行逻辑分组? 答案 新建一个ASP.NET Core 2.0空项目,修改Startup类,增加Mvc服务和中间件: public v ...

  9. 发放春节福利,ASP.NET Core断点续传

    ASP.NET Core断点续传 在ASP.NET WebAPi写过完整的断点续传文章,目前我对ASP.NET Core仅止于整体上会用,对于原理还未去深入学习,由于有园友想看断点续传在ASP.NET ...

随机推荐

  1. 一种用于 API 的查询语言-GraphQL

    GitHub地址 官网地址 中文网址

  2. 【技巧】使用xshell和xftp连接centos连接配置

    说明:xshell用来执行指令,xftp用来上传和下载文件. ① 这是xshell连接属性: ②.这是xftp连接属性 附件:这里给个xshelll和xftp的免安装的破解版本地址.侵删. 度娘链接: ...

  3. 【插件篇】前段bootstrap-table-treegrid试手,解决无法显示树形列表或者图标不显示问题。

    说明:具体代码操作我就不贴了.官方有正规的例子!bootstrap-table-examples传送 使用注意事项: 传入的id和pid可以是string类型的(我后台返回的是Long类型转换成str ...

  4. 全套visio版本安装教程及下载地址

    1:visio 2003 安装教程及下载地址 https://mp.weixin.qq.com/s/vhJUagKBz3vM-Dru0cwYow 2:visio 2007 安装教程及下载地址 http ...

  5. Asp.NetCore Web开发之模型验证

    在开发中,验证表单数据是很重要的一环,如果对用户输入的数据不加限制,那么当错误的数据提交到后台后,轻则破坏数据的有效性,重则会导致服务器瘫痪,这是很致命的. 所以进行数据有效性验证是必要的,我们一般通 ...

  6. C++基础——文件逐行读取与字符匹配

    技术背景 用惯了python,对其他语言就比较的生疏.但是python很多时候在性能上比较受局限,这里尝试通过C++来实现一个文件IO的功能,看看是否能够比python的表现更好一些.关于python ...

  7. 009.kubernets的调度系统之污点和容忍

    Taints和Tolerations(污点和容忍) Taint需要与Toleration配合使用,让pod避开那些不合适的node.在node上设置一个或多个Taint后,除非pod明确声明能够容忍这 ...

  8. 分布式存储ceph---openstack对接ceph存储后端(4)

    ceph对接openstack环境 一.使用RBD方式提供存储,如下数据: 1.image:保存glance中的image 2.volume存储:保存cinder的volume:保存创建虚拟机时选择创 ...

  9. 03-用三种方法设置CentOS7使用代理服务器上网

    一.永久设置 编辑配置文件 vi /etc/profile 在文件后添加以下内容: export http_proxy='http://代理服务器IP:端口号' export https_proxy= ...

  10. PID基础

    经常有人会问到PID的用法,今天小编在这里例举温度控制中的PID部分,希望能够把PID的具体应用说明白. 先说几个名词: 1.直接计算法和增量算法:这里的所谓增量算法就是相对于标准算法的相邻两次运算之 ...