iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 7
1
Modern Web

慢慢帶你了解Flask系列 第 7

慢慢帶你了解Flask - Day7 網路相簿(3):註冊登入登出(json、session)

大家好,我是長風青雲。
今天是鐵人賽的第七天,嗯?居然已經一個禮拜了?!
(如果三十天後我還沒講完我要講的,我還是會繼續發文的 ^w^ )

上次說,我們要做註冊和登入登出~
但其實吧,我一開始看著flask-login的時候完全霧煞煞,所以一開始我就走向了另一條道路──session。
那session是什麼呢?
簡單來說session是個可以儲存資料的地方,而且因為他是建立在cookie上的,所以當使用者登入的時候,與其他電腦並沒有關係。
(遙想我一開始,居然設一個login的variable,結果我發現……我用別台電腦,他認為我還在登入,他根本不知道使用者是誰阿QwQ)→走過歪路的我

那首先我們HTML先做好吧~
(register.html)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/nav_bar.css')}}">
<title>ironman album</title>
</head>
<style>
body{
	margin: 0;
	font-family: Arial, Helvetica, sans-serif;
}

table{
	background-color:plum;
	border-radius:5px;
	padding-bottom: 10px;
	padding-top: 15px;
	padding-right: 10px;
	margin-top:20px;
	width:70%;
}

input{
	font-size: initial;
}

span {
	background-color:#FFC8B4;
	box-shadow:1px 1px 3px red;
	margin-right:8px;
}

@media screen and (max-width: 400px){
	table{
		width:90%;
	}
}

.button {
  border:0;
  background-color:darkviolet;
  color:#fff;
  border-radius:20px;
  cursor:pointer;
  width:90px;
  height:30px;
}

.button:hover{
  color:black;
  background-color: lavender;
}
</style>
<body>
<form method="post">
<ul class="computer" id="topnav">
	<li><a href="../" class="active">首頁</a></li>
	<li><a href="../album">相簿</a></li>
	<li><a href="../register">註冊</a></li>
	<li><a href="../login">登入</a></li>
	<li style="float:right"><a href="javascript:void(0);" class="icon" onclick="myFunction()"><i class="fa fa-bars"></i></a></li>
</ul>
<div style="padding-top: 100px">    
  <table align="center">
    <tr>
    <td colspan="2" align="right"><span>{{alert}}</span></td>
    </tr>
    <tr>
      <th width="15%">account</th>
      <td><input class="w3-input w3-border w3-round-large" type='text' name="userid" value={{id}}></td>
    </tr>
    <tr>
      <th>password</th>
      <td><input class="w3-input w3-border w3-round-large" type='password' name="userpw" value={{pw}}></td>
    </tr>
    <tr>
      <th>Nick Name</th>
      <td><input class="w3-input w3-border w3-round-large" type='text' name="username" value={{nick}}></td>
    </tr>
    <tr>
      <td colspan="2" align="right"><input class="button" value="送出" type='submit' name="send"></td>
    </tr>
  </table>
</div>


</form>
<script>
function myFunction() {
  var x = document.getElementById("topnav");
  if (x.className === "computer") {
    x.className = "phone";
  } else {
    x.className = "computer";
  }
}
</script>


</body>
</html>

有些前面說過的,我就不多做贅述了~
這裡我引用了w3-school的css,因為我覺得他的input做的滿好看的,所以我就link進來,然後在input加上對應的class。
然後table的部分,border-radius是讓他外框是圓弧的~多弧就看你們決定囉~
inputfont-size:initial,是為了避免使用手機時他會跟著輸入框一起放大。
spanbox-shadow,就是設定他的陰影~
button那邊的cusor:pointer是指滑鼠滑到那裡,呈現的樣子。
(login.html)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/nav_bar.css')}}">
<title>ironman album</title>
</head>
<style>
body{
	margin: 0;
	font-family: Arial, Helvetica, sans-serif;
}

table{
	background-color:plum;
	border-radius:5px;
	padding-bottom: 10px;
	padding-top: 15px;
	padding-right: 10px;
	margin-top:20px;
	width:70%;
}

input{
	font-size: initial;
}

span {
	background-color:#FFC8B4;
	box-shadow:1px 1px 3px red;
	margin-right:8px;
}

@media screen and (max-width: 400px){
	table{
		width:90%;
	}
}

.button {
  border:0;
  background-color:darkviolet;
  color:#fff;
  border-radius:20px;
  cursor:pointer;
  width:90px;
  height:30px;
}

.button:hover{
  color:black;
  background-color: lavender;
}
</style>
<body>
<ul class="computer" id="topnav">
	<li><a href="../" class="active">首頁</a></li>
	<li><a href="../album">相簿</a></li>
	<li><a href="../register">註冊</a></li>
	<li><a href="../login">登入</a></li>
	<li style="float:right"><a href="javascript:void(0);" class="icon" onclick="myFunction()"><i class="fa fa-bars"></i></a></li>
</ul>
<form method="post">
<div style="padding-top: 100px">    
  <table align="center">
    <tr>
    <td colspan="2" align="right"><span>{{alert}}</span></td>
    </tr>
    <tr>
      <th width="15%">account</th>
      <td><input class="w3-input w3-border w3-round-large" type='text' name="userid" value={{id}}></td>
    </tr>
    <tr>
      <th>password</th>
      <td><input class="w3-input w3-border w3-round-large" type='password' name="userpw" value={{pw}}></td>
    </tr>
    <tr>
      <td colspan="2" align="right"><input class="button" value="登入" type='submit' name="send"></td>
    </tr>
  </table>
</div>
</form>
<script>
function myFunction() {
  var x = document.getElementById("topnav");
  if (x.className === "computer") {
    x.className = "phone";
  } else {
    x.className = "computer";
  }
}
</script>


</body>
</html>

(logout.html)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="{{url_for('static',filename='css/nav_bar.css')}}">
<title>ironman album</title>
</head>
<style>
body{
	margin: 0;
	font-family: Arial, Helvetica, sans-serif;
}

table{
	background-color:plum;
	border-radius:5px;
	padding-bottom: 10px;
	padding-top: 15px;
	padding-right: 10px;
	margin-top:20px;
	width:70%;
}

input{
	font-size: initial;
}

span {
	background-color:#FFC8B4;
	box-shadow:1px 1px 3px red;
	margin-right:8px;
}

@media screen and (max-width: 400px){
	table{
		width:90%;
	}
}

.button {
  border:0;
  background-color:darkviolet;
  color:#fff;
  border-radius:20px;
  cursor:pointer;
  width:90px;
  height:30px;
}

.button:hover{
  color:black;
  background-color: lavender;
}
</style>
<body>
<form method="post">
<ul class="computer" id="topnav">
	<li><a href="../" class="active">首頁</a></li>
	<li><a href="../album">相簿</a></li>
	<li><a href="../upload">上傳</a></li>
  <li><a href="../logout">登出</a></li>
  <li style="float:right"><a href="javascript:void(0);" class="icon" onclick="myFunction()"><i class="fa fa-bars"></i></a></li>
</ul>
<div style="padding-top: 80px;padding-left:20px">
  <h2>確認登出嗎?</h2>
  <p> </p>
  <p><input class="button" value="確定" type='submit' name="send">
  <input class="button" value="取消" type='submit' name="send"></p>
</div>


</form>
<script>
function myFunction() {
  var x = document.getElementById("topnav");
  if (x.className === "computer") {
    x.className = "phone";
  } else {
    x.className = "computer";
  }
}
</script>


</body>
</html>

我看了看,login和logout沒有什麼能說的,該說的都說過了,所以就直接放上來,然後我們來進入我們的後台吧~

在此之前,不知道大家有沒有聽過json?
Json是一種儲存資料的格式,也可以是個檔名。因為方便使用,所以廣泛用在API上。
在這裡,我會直接把他當作資料庫來使用 ^w^
(如果json其實不能當資料庫的話,比如會有什麼資安問題呀,可以告訴我喔!
因為我目前就是不知道可不可以,然後又因為方便,就直接默認可以這樣做了)

我們儲存註冊者資料的地方就是使用json檔,所以請你打開一個編輯器,輸入{}
然後儲存成member.json,位置與app.py同層。

接著開始寫我們的app.py,剛才我說過,我們要使用session還有json。
所以要先做import的動作from flask import session還有import json

接下來session其實是需要一個密鑰的,雖然其實我不知道他實際用在哪裡XD
但請你還是加在app=Flask(__name__)下面,像是這樣

app=Flask(__name__)
app.secret_key= b'你的密鑰'

密鑰部分最好是亂碼,而且這部分既然都被稱為『密』鑰了,就代表他是一個secret的東西,所以要保護好他不要被別人知道喔~

@app.route('/register',methods=['POST','GET'])
def register():
	with open('./member.json','r') as file_object:
		member = json.load(file_object)
	if request.method=='POST':
		if request.values['send']=='送出':
			if request.values['userid'] in member:
				for find in member:
					if member[find]['nick']==request.values['username']:
						return render_template('register.html',alert='this account and nickname are used.')
				return render_template('register.html',alert='this account is used.',nick=request.values['username'])
			else:
				for find in member:
					if member[find]['nick']==request.values['username']:
						return render_template('register.html',alert='this nickname are used.',id=request.values['userid'],pw=request.values['userpw'])
				member[request.values['userid']]={'password':request.values['userpw'],'nick':request.values['username']}
				with open('./member.json','w') as f:
					json.dump(member, f)
				return render_template('index.html')
	return render_template('register.html')

在這裡,我設計的member長像大概是{ “帳號”:{‘password’:“密碼”,nick:”暱稱”}},帳號是我們的key,可是我腦子一抽,覺得如果同一個名稱的話不是就會搞混誰是誰誰誰了嗎?所以暱稱我也把他設計為只能有一個。
確認userid和username都沒有重複後,就會將他加入到member.json裡面。

「大部分的註冊不是需要經過驗證嗎?」
對,你沒想錯,但這部分是我之後的實例才會說~而且到時候我會把網頁放到虛擬機上跑給大家看 ^w^
因為這是我們的第一個實例,所以一開始我幾乎什麼都沒做╮(╯∀╰)╭
喔對了,網路相簿就是我姊叫我寫的專案,所以既然是『姊姊叫你寫』的......
可想而知不會太用心d(`・∀・)b

這樣在註冊後你的member就會改變成

{"yueh860304": {"password": "不告訴你", "nick": "Charlotte"}, "yueh970304": {"password": "不告訴你", "nick": "Longwind"}}

password我自己是打我慣用的密碼啦~所以我就把它改為『不告訴你』,並不是真的會長這樣子喔!

然後你login和logout的部分就是:

@app.route('/login',methods=['GET','POST'])
def login():

	if request.method== 'POST' :
		with open('./member.json','r') as file_object:
			member = json.load(file_object)

		if request.values['userid'] in member:
			if member[request.values['userid']]['password']==request.values['userpw']:
				session['username']=request.values['userid']
				return redirect ( url_for ( 'index' ))
			else:
				return render_template('login.html',alert="Your password is wrong, please check again!")
		else:
			return render_template('login.html',alert="Your account is unregistered.")
	return render_template('login.html')


@app.route('/logout',methods=['GET','POST'])
def logout ():
	if request.method=='POST':
		if request.values['send']=='確定':
			session.pop('username',None)
		return redirect(url_for('index'))
	return render_template('logout.html')

login就是經過比對確認有這個帳號,然後帳號密碼相同的話就會把你的userid加入session
session['username']=request.values['userid']
logout則是你確認登出後,會把名為在session裡key為username的資料清空
session.pop('username',None)

現在來看個影片吧~

有沒有覺得很開心~這樣註冊和登入登出的部分做好後,我們要開始著手上傳的部分。
大家明天見唷~
https://ithelp.ithome.com.tw/upload/images/20190907/20120116XLgPXnIJ5B.jpg


上一篇
慢慢帶你了解Flask - Day6 網路相簿(2):Navigation Bar(RWD)
下一篇
慢慢帶你了解Flask - Day8 網路相簿(4):上傳檔案
系列文
慢慢帶你了解Flask30

1 則留言

0
Bbsonlin
iT邦新手 5 級 ‧ 2019-09-08 23:23:09

哈囉~
還滿開心看到有人在玩 Flask 的,Flask 是我目前滿喜歡的 Python Web Framework 之一。

底下有幾項是我個人使用了四年 Flask 的拙見,希望對你有幫助。

  1. Flask-Login 是個很棒(而且也滿成功)的 Flask Extension,它主要有幾個不錯的特性:
    • 封裝 Flask session,並提供些額外保護機制。
    • 提供簡單的 User 欄位設定(如果之後有用到 Flask-SQLAlchemy 的話,自訂的 User Model 直接繼承 UserMixin,就有基本的功能了)
    • 提供好用的 function 和 decorator(比如 @login_requiredlogin_userlogout_user 等等)
      還有滿多好用的地方,基本上簡單的登入登出機制它都幫你弄好了。
      而且如果之後想自己寫 Flask Extension,很值得研究他的原始碼 XD
  2. Flask 本身就包含了 session cookie 的安全機制,ItsDangerous 基本上就在處理這塊,所以才要設定 secret_key
  3. HTML 的表單用 request.form 或 request.get_data 來獲取請求的參數應該會簡潔明瞭一點。
  4. 直接用 JSON 檔案存帳密,單純 Demo 的話是還可以用,但我個人是非常不推薦的(這樣太危險~ XD)。
    最簡單的方式可用 Python pickle 或 shelve 序列化 JSON 檔(但其實只安全一點)。
    不然就要用市面上常用的關聯性資料庫,如 SQLite(很簡單使用,但不安全),MySQL 或 PostgreSQL(沒很簡單,但安全,也是大家常用的) 吧 ~

加油~ 撐住這三十天 /images/emoticon/emoticon58.gif

https://sdl-stickershop.line.naver.jp/stickershop/v1/sticker/207419370/android/sticker.png
謝謝你告訴我~我在使用JSON當資料庫的時候心裡的確有點慌...
可能潛意識知道資安上會出問題。(可能密碼學的老師會跳出來打我
其實我到現在還沒使用過真正的資料庫...(一直用各種方式代替
這幾天我研究看看,如果我能力夠的話我就把後面的實例寫成資料庫的樣子~

而看你這麼推Flask-login....我決定把他放進後面的實例當中
(之前因為用session規避掉這個問題,所以沒有去學習,還想著鐵人賽結束之後再去看)
而相簿就算了...我覺得我寫他寫太久,暫時不想更動他。
(而且不是因為自己想要才寫的東西,好像比較沒有愛?)

我會努力撐過30天的 d(^w^ )
我可是對我奶奶說要把連續30天的獎牌送給她呢~

我要留言

立即登入留言