asp.net core配合vue實(shí)現(xiàn)后端驗(yàn)證碼邏輯
概述
網(wǎng)上的前端驗(yàn)證碼邏輯總感覺(jué)不安全,驗(yàn)證碼建議還是使用后端配合驗(yàn)證。
如果產(chǎn)品確定可以上網(wǎng)的話(huà),就可以使用騰訊,百度等第三方驗(yàn)證,對(duì)接方便。但是產(chǎn)品可能內(nèi)網(wǎng)部署,就必須自己寫(xiě)了。
本文章就是基于這一點(diǎn)來(lái)實(shí)現(xiàn)的。
前端驗(yàn)證碼顯示一個(gè)圖片,后端生成圖片。
部分原理
1.前端調(diào)用生端獲取圖片時(shí),傳入一個(gè)roomID,后端生成一個(gè)4位驗(yàn)征碼,放入redis中。然后生成一個(gè)圖片返回。
2.前端顯示圖片,登錄時(shí)將roomID和填寫(xiě)的驗(yàn)證碼,一并提交,登錄接口根據(jù)roomId從redis中取出驗(yàn)證碼判斷是否正確。
這樣就相當(dāng)于后端驗(yàn)證了。
大家覺(jué)得有問(wèn)題什么,可以進(jìn)行評(píng)論。謝謝。
源碼
前端部分代碼
<template> <div class="login-container"> <vue-particles color="#ffffff" :particleOpacity="0.7" :particlesNumber="50" shapeType="circle" :particleSize="4" linesColor="#dedede" :linesWidth="1" :lineLinked="true" :lineOpacity="0.4" :linesDistance="150" :moveSpeed="2" :hoverEffect="true" hoverMode="grab" :clickEffect="true" clickMode="push" ></vue-particles> <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" autocomplete="on" label-position="left"> <div class="title-container"> <h3 class="title">智能綜合管理系統(tǒng)</h3> </div> <el-form-item prop="username"> <span class="svg-container"> <svg-icon icon-class="user" /> </span> <el-input ref="username" v-model="loginForm.username" placeholder="用戶(hù)名" name="username" type="text" tabindex="1" autocomplete="on" /> </el-form-item> <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual> <el-form-item prop="password"> <span class="svg-container"> <svg-icon icon-class="password" /> </span> <el-input :key="passwordType" ref="password" v-model="loginForm.password" :type="passwordType" placeholder="密碼" name="password" tabindex="2" autocomplete="on" @keyup.native="checkCapslock" @blur="capsTooltip = false" @keyup.enter.native="handleLogin" /> <span class="show-pwd" @click="showPwd"> <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" /> </span> </el-form-item> </el-tooltip> <el-form-item prop="yzm"> <span class="svg-container"> <svg-icon icon-class="password" /> </span> <el-input type="text" v-model="loginForm.verifyCode" maxlength="4" placeholder="驗(yàn)證碼" /> <div class="identifyCode" @click="refreshCode"> <el-image :src="verifyImageUrl"></el-image> </div> </el-form-item> <el-button :loading="loading" type="primary" style="width: 100%; margin-bottom: 30px" @click.native.prevent="handleLogin">登錄</el-button> </el-form> </div> </template> <script> import { validUsername } from '@/utils/validate' import Identify from './components/Identify' import { uuid } from 'vue-uuid'; // uuid object is also exported to things // outside Vue instance. export default { name: 'Login', components: { Identify }, data() { const validateUsername = (rule, value, callback) => { if (!validUsername(value)) { callback(new Error('請(qǐng)輸入正確的用戶(hù)名')) } else { callback() } } const validatePassword = (rule, value, callback) => { if (value.length < 6) { callback(new Error('密碼最少6位')) } else { callback() } } return { loginForm: { username: 'admin', password: '111111', roomId: '', verifyCode: '' }, loginRules: { username: [{ required: true, trigger: 'blur', validator: validateUsername }], password: [{ required: true, trigger: 'blur', validator: validatePassword }] }, passwordType: 'password', capsTooltip: false, loading: false, showDialog: false, redirect: undefined, otherQuery: {}, identifyCodes: '1234567890', identifyCode: '', // verifyImageRoomId: '', verifyImageUrl: '', } }, watch: { $route: { handler: function (route) { const query = route.query if (query) { this.redirect = query.redirect this.otherQuery = this.getOtherQuery(query) } }, immediate: true } }, created() { // window.addEventListener('storage', this.afterQRScan) // this.identifyCode = '' // this.makeCode(this.identifyCodes, 4) this.refreshCode() }, mounted() { if (this.loginForm.username === '') { this.$refs.username.focus() } else if (this.loginForm.password === '') { this.$refs.password.focus() } }, destroyed() { // window.removeEventListener('storage', this.afterQRScan) }, methods: { checkCapslock(e) { const { key } = e this.capsTooltip = key && key.length === 1 && (key >= 'A' && key <= 'Z') }, showPwd() { if (this.passwordType === 'password') { this.passwordType = '' } else { this.passwordType = 'password' } this.$nextTick(() => { this.$refs.password.focus() }) }, createUuid() { let uuidV4 = uuid.v4().replace('-', '').replace('-', '').replace('-', '').replace('-', '') this.verifyImageRoomId = uuidV4 this.verifyImageUrl = '/api/Operator/getCaptchaImage/120/40/' + this.verifyImageRoomId console.log(uuidV4) }, handleLogin() { this.$refs.loginForm.validate(valid => { if (valid) { this.loading = true this.$store.dispatch('user/login', this.loginForm) .then(() => { this.$router.push({ path: this.redirect || '/', query: this.otherQuery }) this.loading = false }) .catch(() => { this.loading = false }) } else { console.log('error submit!!') return false } }) }, getOtherQuery(query) { return Object.keys(query).reduce((acc, cur) => { if (cur !== 'redirect') { acc[cur] = query[cur] } return acc }, {}) }, // 生成隨機(jī)數(shù) randomNum(min, max) { return Math.floor(Math.random() * (max - min) + min) }, // 切換驗(yàn)證碼 refreshCode() { let uuidV4 = uuid.v4().replace('-', '').replace('-', '').replace('-', '').replace('-', '') this.loginForm.roomId = uuidV4 this.verifyImageUrl = '/api/Operator/getCaptchaImage/120/40/' + this.loginForm.roomId console.log(uuidV4) }, // 生成四位隨機(jī)驗(yàn)證碼 makeCode(o, l) { for (let i = 0; i < l; i++) { this.identifyCode += this.identifyCodes[ this.randomNum(0, this.identifyCodes.length) ] } console.log(this.identifyCode) } } } </script> <style lang="scss"> /* 修復(fù)input 背景不協(xié)調(diào) 和光標(biāo)變色 */ /* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */ $bg: #283443; $light_gray: #fff; $cursor: #fff; @supports (-webkit-mask: none) and (not (cater-color: $cursor)) { .login-container .el-input input { color: $cursor; } } /* reset element-ui css */ .login-container { background: url("~@/assets/background.jpg") no-repeat; min-height: 100vh; .el-input { display: inline-block; height: 47px; width: 85%; input { background: transparent; border: 0px; -webkit-appearance: none; border-radius: 0px; padding: 12px 5px 12px 15px; color: $light_gray; height: 47px; caret-color: $cursor; &:-webkit-autofill { box-shadow: 0 0 0px 1000px $bg inset !important; -webkit-text-fill-color: $cursor !important; } } } .el-form-item { border: 1px solid rgba(255, 255, 255, 0.1); background: rgba(0, 0, 0, 0.1); border-radius: 5px; color: #454545; .el-form-item__error { color: rgb(223, 223, 176); } } } </style> <style lang="scss" scoped> $bg: #2d3a4b; $dark_gray: #889aa4; $light_gray: #eee; .login-container { min-height: 100%; width: 100%; background-color: $bg; overflow: hidden; .login-form { position: absolute; left: 0; right: 0; top: 0; bottom: 0; margin: auto; width: 520px; max-width: 100%; padding: 160px 35px 0; margin: 0 auto; overflow: hidden; } .tips { font-size: 14px; color: #fff; margin-bottom: 10px; span { &:first-of-type { margin-right: 16px; } } } .svg-container { padding: 6px 5px 6px 15px; color: $dark_gray; vertical-align: middle; width: 30px; display: inline-block; } .title-container { position: relative; .title { font-size: 42px; color: $light_gray; margin: 0px auto 40px auto; text-align: center; font-weight: bold; } } .show-pwd { position: absolute; right: 10px; top: 7px; font-size: 16px; color: $dark_gray; cursor: pointer; user-select: none; } .identifyCode { position: absolute; right: 10px; top: 5px; } .thirdparty-button { position: absolute; right: 0; bottom: 6px; } @media only screen and (max-width: 470px) { .thirdparty-button { display: none; } } } </style>
后端接口
/// <summary> /// 獲取驗(yàn)證碼 /// </summary> /// <returns></returns> [HttpGet("getCaptchaImage/{width:int}/{height:int}/{roomId}")] public IActionResult GetCaptchaImage(int width, int height, string roomId) { Console.WriteLine(roomId); //int width = 100; //int height = 36; var captchaCode = Captcha.GenerateCaptchaCode(); var result = Captcha.GenerateCaptchaImage(width, height, captchaCode); string redisKey = "VerifyCode_" + roomId; Startup.redisDb.StringSet(redisKey, captchaCode, new TimeSpan(0, 5, 0)); Stream s = new MemoryStream(result.CaptchaByteData); return new FileStreamResult(s, "image/png"); } /// <summary> /// 登錄 /// </summary> /// <returns></returns> [HttpPost("login")] public ApiResponseData Login(LoginDto loginInfo) { if (string.IsNullOrWhiteSpace(loginInfo.username)) return ApiResponse.Error("用戶(hù)名不能為空"); if (string.IsNullOrWhiteSpace(loginInfo.password)) return ApiResponse.Error("密碼不能為空"); Entity.BaseOperator operInfo = Db .Select<BaseOperator>() .Where(a => a.OperatorCode == loginInfo.username && a.Password == loginInfo.password.ToLower() && a.Status == 1 && a.IsDel == false).ToOne(); if (operInfo == null) return ApiResponse.Error("用戶(hù)名或者密碼不正確"); bool verifyResult = Captcha.ValidateCaptchaCode(loginInfo.RoomId, loginInfo.VerifyCode); if(verifyResult == false) return ApiResponse.Error("驗(yàn)證碼不正確"); //登錄時(shí)重新生成token,一個(gè)用戶(hù)只能在一個(gè)地方登錄 string token = System.Guid.NewGuid().ToString().Replace("-", ""); Db.Update<BaseOperator>(operInfo.OperatorId) .Set(a => a.Token, token) .ExecuteAffrows(); dynamic outJson = new ExpandoObject();//初始化一個(gè)不包含任何成員的ExpandoObject outJson.token = token; //存入redis string redisKey = "UserInfo_" + token; Startup.redisDb.StringSet(redisKey, operInfo.OperatorId, new TimeSpan(24, 0, 0)); return ApiResponse.Succ(outJson); }
到此這篇關(guān)于asp.net core配合vue實(shí)現(xiàn)后端驗(yàn)證碼邏輯的文章就介紹到這了,更多相關(guān)asp.net core驗(yàn)證碼 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- ASP.NET?Core?模型驗(yàn)證消息的本地化新姿勢(shì)詳解
- ASP.NET?Core?6.0?基于模型驗(yàn)證的數(shù)據(jù)驗(yàn)證功能
- ASP.NET?Core中Cookie驗(yàn)證身份用法詳解
- asp.net core3.1cookie和jwt混合認(rèn)證授權(quán)實(shí)現(xiàn)多種身份驗(yàn)證方案
- [Asp.Net Core]用Blazor Server Side實(shí)現(xiàn)圖片驗(yàn)證碼
- ASP.NET Core實(shí)現(xiàn)自定義WebApi模型驗(yàn)證詳解
- asp.net core系列之模型綁定和驗(yàn)證方法
- ASP.NET Core WebApi中使用FluentValidation驗(yàn)證數(shù)據(jù)模型的方法
相關(guān)文章
asp.net實(shí)現(xiàn)服務(wù)器文件下載到本地的方法
這篇文章主要介紹了asp.net實(shí)現(xiàn)服務(wù)器文件下載到本地的方法,需要的朋友可以參考下2017-02-02asp.net連接數(shù)據(jù)庫(kù) 增加,修改,刪除,查詢(xún)代碼
asp.net連接數(shù)據(jù)庫(kù),實(shí)現(xiàn)增加,修改,刪除,查詢(xún)的四大功能完整代碼。2009-07-07asp.net core服務(wù)限制堆內(nèi)存大小的操作方法
asp.net core是微軟旗下支持跨平臺(tái)的開(kāi)發(fā)框架,與springboot思想類(lèi)似,支持ioc等,可以快速的開(kāi)發(fā)web api等項(xiàng)目,這篇文章主要介紹了asp.net core服務(wù)限制堆內(nèi)存大小,需要的朋友可以參考下2022-09-09MVC4制作網(wǎng)站教程第三章 瀏覽用戶(hù)組操作3.1
這篇文章主要為大家詳細(xì)介紹了MVC4制作網(wǎng)站教程,瀏覽用戶(hù)組功能的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08ASP.net?core使用Autofac實(shí)現(xiàn)泛型依賴(lài)注入
這篇文章主要介紹了ASP.net?core使用Autofac實(shí)現(xiàn)泛型依賴(lài)注入的方式學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04ASP.NET MVC錯(cuò)誤處理的對(duì)應(yīng)解決方法
這篇文章主要為大家詳細(xì)介紹了ASP.NET MVC錯(cuò)誤處理的對(duì)應(yīng)解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03asp.net?web?api2設(shè)置默認(rèn)啟動(dòng)登錄頁(yè)面的方法
這篇文章主要介紹了asp.net?web?api2設(shè)置默認(rèn)啟動(dòng)登錄頁(yè)面的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09asp.net core新特性之TagHelper標(biāo)簽助手
這篇文章主要為大家詳細(xì)介紹了asp.net core新特性之TagHelper標(biāo)簽助手的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07.net連接Mysql封裝類(lèi)代碼 可直接調(diào)用
下面是我封裝好的連接Mysql數(shù)據(jù)庫(kù)的類(lèi),直接調(diào)用即可。2013-07-07