Day26:26 - 优化 - 後端 & 前端 - 忘记密码

γεια σας,我是Charlie!

在Day25当中我们完成了Email订单通知,而今天我们将完成忘记密码的部分。

================================◉‿◉=================================

我们的忘记密码是要做成Email验证的机制,所以必须要在使用者按下忘记密码後後端发送Email,让使用者点选Email中的连结後可以重设密码。

所以我们先建立一个新的APP:resetPWD,并且加上URL连结:

keyboardmarket\urls.py

url('reset',include('resetPWD.urls')),

resetPWD\urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
	url(r'^$',views.resetPWD)
]

接着是views的部分,这里的话会分成三种:

  1. 用户按下忘记密码时,必须产生token并且寄回去
  2. 用户到忘记密码页面时,必须验证token
  3. 用户重设之後,必须重设资料库的密码

第一种是POST请求,而第二种是GET请求,第三种是PUT请求。

而我们这边的TOKEN产生使用PyJWT,因为需要有期限、加密,故这边使用。

首先先建立产生token跟解译token的方法:

def makeResetToken(user):
	key = "Your random generate key"
	now = datetime.datetime.now()
	expiretime = now + datetime.timedelta(hours = 1)
	username = user.name
	payload = {
		"username":username,
		"exp":expiretime.timestamp()
	}
	return jwt.encode(payload,key,algorithm = 'HS256')


def decodeResetToken(token):
	key = "Your random generate key"
	try:
		res = jwt.decode(token,key,algorithms = ['HS256'])
	except jwt.ExpiredSignatureError:
		return R.badRequest("验证超时,请重新操作!")
	except Exception as e:
		return R.internalServerError(str(e))
	else:
		return res["username"]

接着我们先建立post里面的基本程序码:

if request.method == "POST":
	req = request.body
	data = json.loads(req)
	if "username" not in data:
		return R.badRequest("username does not exist!")
	user = User.objects.filter(name = data["username"])
	if not user:
		return R.badRequest("user not found!")
	token = makeResetToken(user[0])
	return R.ok("reset password request success")

能产生token之後,接着我们要发送token到使用者的email,在templates当中建立reset资料夹,建立resetpassword.html:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>重设密码</title>
</head>
<body>
	<h1>{{ username }}您好</h1>
	<p>您在keyboardmarket发起了重置密码的请求</p>
	<p>密码重置连结为:</p>
	<p>{{ token }}</p>
	<p>请在一小时内完成重置密码动作,否则失效</p>
	<hr>
	<p>本信件为自动发送,请勿回覆</p>
</body>
</html>

接着在emailClient中新建send_reset_message方法:

def send_reset_message(self,
						username,
						token,
						user_email):
	email_template = render_to_string(
		'reset/resetpassword.html',
		{
			"username":username,
			"token":"http://localhost:8080/#/reset?token=" + token
		}
	)

	email = EmailMessage(
		"键盘贸易 - 重设密码通知信",
		email_template,
		self.EMAIL_HOST_USER,
		[user_email]
	)
	email.content_subtype = 'html'
	email.fail_silently = False
	email.send()

然後到resetPWD views中新增寄送方法:

token = makeResetToken(user[0])
username = user[0].name
user_email = user[0].email
client.send_reset_message(username,token,user_email)
return R.ok("reset password request success")

再来是GET方法,GET方法是验证token是否合法,所以在GET方法中建立检查:

if request.method == "GET":
	req = request.GET
	if "token" not in req:
		return R.badRequest("token not found")
	token = req["token"]
	username = decodeResetToken(token)
	if type(username) != str:
		return username
	user = User.objects.filter(name = username)
	if not user:
		return R.badRequest("user not found")
	return R.ok("request is valid")

再来是PUT方法,修改密码的部分:

if request.method == "PUT":
	req = request.body
	data = json.loads(req)
	if "password" not in data or "token" not in data:
		return R.badRequest("required parameter not found")
	username = decodeResetToken(data["token"])
	if type(username) != str:
		return username
	user = User.objects.filter(name = username)
	if not user:
		return R.badRequest("User not found")
	user = user[0]
	md5 = hashlib.md5()
	passwordString = data["password"] + username
	md5.update(passwordString.encode())
	pwd = md5.hexdigest()
	user.password = pwd
	user.save()
	return R.ok("update success")

再来是前端的部分,先在loginPage里面加上超连结:

<a href="/#/createreset">忘记密码?</a>

接着在components当中建立createreset.vue,让使用者可以输入用户名称以获取email讯息:

<template>
	<html lang="zh-Hant-TW">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<link rel="icon" type="image/x-con" href="@/assets/favicon.ico">
		<div id="app">
			<headerComponent></headerComponent>
			<div id="resetHeader" style="width:100%;height:100px;background-image: linear-gradient(to right,#C9C9C9,#F2FFFF,#00E6E6);padding-top: 40px;">
				<h3>重置密码</h3>
			</div>
			<div id="resetForm">
				<b-form @submit="onSubmit" style="width: 100%;">
					<b-form-group
					id="username+group"
					label="用户名:"
					label-for="username"
					style="margin:10px;"
					>
						<b-form-input
						id="username"
						placeholder="请输入用户名"
						v-model="username"
						required
						>
						</b-form-input>
					</b-form-group>
					<b-button type="submit" variant="info" style="width: 80px;margin: 10px;">发送重置连结</b-button>
					<div id="result">
						<p>{{ resetText }}</p>
					</div>
				</b-form>
			</div>
		</div>
	</html>
</template>

接着在apis当中建立reset.js,新增发送重置密码讯息的方法:

import { host,port } from '@/apis/constant.js'
import axios from 'axios'

export function createReset(username){
  return axios.post(`http://${host()}:${port()/reset`,{
    "username":username
  })
}

然後在createResetPage当中引入,并且新增onSubmit方法:

methods:{
  onSubmit(e){
    e.preventDefault()
    createReset(this.username).then((response) => {
      if(response.data.code == STATUS_OK){
        this.resetText = "重置信发送成功,请至当初登记信箱查收"
      }else{
        this.resetText = response.data.data
      }
    })
  }
}

可以测试看看能不能收的到信:
https://ithelp.ithome.com.tw/upload/images/20211010/20141666G8tMdjUa2A.png

https://ithelp.ithome.com.tw/upload/images/20211010/20141666dPX3MFtK9E.png

再来是reset的部分,先建立reset.vue,并加上路径:

<template>
	<html lang="zh-Hant-TW">
		<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
		<link rel="icon" type="image/x-con" href="@/assets/favicon.ico">
		<div id="app">
			<headerComponent></headerComponent>
			<div id="resetHeader" style="width:100%;height:100px;background-image: linear-gradient(to right,#C9C9C9,#F2FFFF,#00E6E6);padding-top: 40px;">
				<h3>重置密码</h3>
			</div>
			<div id="resetForm" style="width: 50%;height: 500px;">
				<b-form @submit="onSubmit" style="width:100%;">
					<b-form-group
					id="password_group"
					label="密码:"
					label-for="password"
					style="margin:10px;"
					>
						<b-form-input
						id="password"
						placeholder="请输入密码"
						v-model="password"
						required
						>
						</b-form-input>
					</b-form-group>
					<b-form-group
					id="password1_group"
					label="密码2:"
					label-for="password1"
					style="margin:10px;"
					>
						<b-form-input
						id="password1"
						placeholder="请再次输入密码"
						v-model="password1"
						required
						>
						</b-form-input>
					</b-form-group>
					<b-button type="submit" variant="info" style="width: 80px;margin: 10px;">重设</b-button>
				</b-form>
			</div>
		</div>
	</html>
</template>

接着先建立验证token的API程序码:

export function resetValidate(token){
  return axios.get(`http://${host()}:${port()}/reset`,{
    params:{
      "token":token
    }
  })
}

然後在vue创立时验证:

created(){
  var token = this.$route.query.token
  resetValidate(token).then((response) => {
    if(request.data.code == STATUS_OK){
      this.$fire({type:"success",text:"token验证成功,请进行重置"})
    }else{
      this.$fire({type:"error",text:response.data.data}).then(() => {
        location.href = "/#/index"
      })
    }
  })
}

接着创立重置密码的API方法,在reset.js中新增resetPassword方法:

export function resetPassword(password,token){
  return axios.put(`http://${host()}:${port()}/reset`,{
    "password":password,
    "token":token
  })
}

并修改onSubmit方法,成功的话则返回login Page:

onSubmit(e){
e.preventDefault()
if(this.password != this.password1){
  this.$fire({type:"error",text:"两次密码不一致"}).then(() => {
    return
  })
}
var token = this.$route.query.token
resetPassword(this.password,token).then((response) => {
  if(response.data.code == STATUS_OK){
    this.$fire({type:"success",text:"修改密码成功,将导回登入页"}).then(() => {
      location.href = "/#/login"
    })
  }else{
    this.$fire({type:"error",text:response.data.data}).then(() => {
      location.href = "/#/index"
    })
  }
})
}

就可以正常修改密码了。

================================◉‿◉=================================

Day26结束了!在今天我们完成了修改密码的前端跟後端的部分,而明天我们将开始实作google recaptcha的部分。


<<:  DAY25 - 网站分析工具介绍 - 质化分析工具Hotjar

>>:  【Day 25】NumPy (2)

D4 - 彭彭的课程#Python 简介、安装、与快速开始

今天开始来看一下彭彭的课程 Python 简介、安装、与快速开始 参考连结如下: https://w...

[Day4]Fibonaccimal Base

今天一样来讲解一星的Fibonaccimal Base 附上程序码 import static ja...

[Day06] CH04:我已读你的已读——认识 Scanner

今天我们要来实作一道题目,是不是很期待呢? Question:输入两个数字,印出两数字的和 看到「和...

110/18 - Android 10以上图片剪裁

Android 10以上就很简单,直接使用MediaStore抓到图片路径,然後送给图片剪裁就好 i...

铁人赛 Day15 -- RWD响应式网页 -- 用手机、电脑、平板的拢来啦

什麽是RWD? 响应式网页设计(Responsive Web Design),可以让不同的设备都可以...