Trong phần này, tất cả chúng ta sẽ tìm hiểu làm thế nào để tạo ra một hệ thống đăng nhập người dùng cho ứng dụng.

Để giúp cho bạn dễ theo dõi, sau đây là danh sách các nội dung trong loạt bài hướng dẫn này:

Bạn có thể truy cập mã nguồn cho phần này tại GitHub.

Trước đó, tất cả chúng ta đã tạo ra một form đăng nhập trong Phần 3 và tìm hiểu cách sử dụng CSDL trong Phần 4. Tiếp theo, tất cả chúng ta sẽ phối hợp các tri thức đã học trong hai phần trước để tạo ra một hệ thông đăng nhập người dùng đơn giản.

Mã băm cho mật mã của user (Password
hashing)

Trong Phần 4, tất cả chúng ta đã khái niệm trường password_hash trong mô hình dữ liệu người dùng. Trường này được dùng để chứa mã hash cho mật mã của user (Để đảm bảo tính nhất quán, từ đây tất cả chúng ta sẽ sử dụng thuật ngữ user thay vì người tiêu dùng). Mã hash này sẽ được dùng để xác thực mật mã do user nhập vào trong quá trình đăng nhập. Quá trình và những nguyên lý tạo ra hash rất phức tạp và chỉ có các Chuyên Viên về bảo mật mới hiểu rõ, vì vậy tất cả chúng ta sẽ không đi sâu vào cụ thể ở đây. Tuy nhiên, tất cả chúng ta có thể sử dụng một số thư viện Flask để tạo ra hash một cách đơn giản và nhanh chóng.

Trong các thư viện của Flask, có một gói phân phối các phương thức để làm việc với hash tên là Werkzeug. Nếu để ý quá trình thiết lập Flask, bạn có thể thấy tên của gói này nơi nào đó trong các thông điệp mà Flask in ra trong suốt quá trình thiết lập bởi vì gói này là một trong những thành phần trọng yếu nhất của Flask. Cũng vì lý do đó, Werkzeug đã được thiết lập sẵn vào trong môi trường ảo của các bạn ngay từ đầu. Ví dụ sau sẽ minh họa cách tạo hash cho mật mã bằng  thư viện Werkzeug:

1

2

3

4

>>>

from

werkzeug

.

security

import

generate_password_hash

>>>

hash

=

generate_password_hash

(

‘foobar’

)

>>>

hash

‘pbkdf2:sha256:150000$3HUtwgW8$39ae9f2f4925661c5cf9cfa04842b04b874730f87739f65316eb6e85f4678c28’

Trong ví dụ trên, chuỗi mật mã “foobar” ban đầu được chuyển hóa thành một chuỗi mã dài bằng các phương thức mã hóa không thể đảo ngược, nghĩa là bạn sẽ không thể chuyển hóa chuỗi mã này thành mật mã ban đầu được bằng bất kỳ cách nào. Quá trình này gọi là băm (hash). Và không chỉ thế, nếu bạn tiếp tục hash một mật mã nhiều lần, bạn sẽ thu được nhiều kết quả khác nhau. Vì vậy, bạn sẽ không thể nào xác nhận được là hai user có cùng mật mã hay không nếu chỉ nhìn vào giá trị hash từ mật mã của họ.

Để xác thực mật mã (verification), tất cả chúng ta sử dụng hàm thứ hai từ thư viện Werkzeug như trong ví dụ sau:

1

2

3

4

5

>>>

from

werkzeug

.

security

import

check_password_hash

>>>

check_password_hash

(

hash

,

‘foobar’

)

True

>>>

check_password_hash

(

hash

,

‘barfoo’

)

False

Quá trình xác thực sẽ nhận mật mã do user nhập vào, chuyển hóa thành mã hash và so sánh với mã hash được sinh ra từ mật mã ban đầu và đã được lưu lại trong CSDL. Nếu mã hash từ mật mã nhập vào giống với mã hash đã được lưu trữ, hàm sẽ trả về True, trái lại nó sẽ trả về False.

Quá trình tạo hash và
xác thực mật mã sẽ được thêm vào hai phương thức mới trong mô hình user như
sau:

app/models.py: Tạo mã hash và xác thực mật mã

1

2

3

4

5

6

7

8

9

10

11

12

from

werkzeug

.

security

import

generate_password_hash

,

check_password

_

hash

 

.

.

.

 

class

User

(

db

.

Model

)

:

    

.

.

.

 

    

def

set_password

(

self

,

password

)

:

        

self

.

password_hash

=

generate_password_hash

(

password

)

 

    

def

check_password

(

self

,

password

)

:

        

return

check_password_hash

(

self

.

password_hash

,

password

)

Với hai phương thức này, một đối tượng user có thể xác thực mật mã của mình. Sau đây là một ví dụ về cách sử dụng hai phương thức này:

1

2

3

4

5

6

>>>

u

=

User

(

username

=

‘thai’

,

thư điện tử

=

‘thai@example.com’

)

>>>

u

.

set_password

(

‘mypassword’

)

>>>

u

.

check_password

(

‘anotherpassword’

)

False

>>>

u

.

check_password

(

‘mypassword’

)

True

Giới thiệu về
Flask-Login

Trong phần này, tất cả chúng ta cũng sẽ tìm hiểu thêm một thư viện Flask mở rộng rất thông dụng là Flask-Login.

Thư viện mở rộng này quản lý tình trạng đăng nhập của user. Nhờ đó, hệ thống có thể ghi nhớ các thông tin về user đã đăng nhập trong một phiên làm viện và cho phép họ truy nhập các trang Website đòi hỏi user phải đăng nhập. Nó cũng phân phối tính năng “Remember me” để user vẫn giữ được tình trạng đăng nhập ngay cả khi họ đã đóng trình duyệt. Để khởi đầu phần này, tất cả chúng ta hãy thiết lập Flask-Login trong môi trường ảo của các bạn:

1

(

myenv

)

$

pip3

install

flask

login

Tương tự như các thư viện mở rộng khác, Flask-Login phải được khởi tạo ngay sau thực thể ứng dụng trong file app/__init__.py như sau:

app/__init__.py: Khởi tạo Flask-Login

1

2

3

4

5

6

7

8

.

.

.

from

flask_login

import

LoginManager

 

app

=

Flask

(

__name__

)

.

.

.

login

=

LoginManager

(

app

)

 

.

.

.

Điều chỉnh mô hình dữ liệu user
(User) cho Flask-Login

Thư viện Flask-Login sẽ
làm việc với mô hình dữ liệu user với một số các tính chất và phương thức nhất
định. Đây là một thiết kế hay bởi vì nếu các tính chất và phương thức này được
phân phối trong mô hình dữ liệu, Flask-Login sẽ làm việc đúng theo yêu cầu bất
chấp các điều kiện khác. Ví dụ như nó có thể hoạt động với các mô hình dữ liệu user
từ bất kỳ hệ CSDL nào.

Sau đây là bốn yêu cầu của Flask-Login với mô hình dữ liệu user:

  • is_authenticated: một tính chất sẽ được gán là True nếu user có tên và mật mã hợp lệ, False nếu một trong hai không đúng.
  • is_active: một tính chất được gán là True nếu tài khoản user trong cơ chế hoạt động (active) và False nếu trái lại.
  • is_anonymous: một tính chất được gán là False cho những user bình thường, và True cho những user ẩn danh (anonymous)
  • get_id(): một phương thức để trả về định danh người dùng (id) dưới dạng chuỗi

Tất cả chúng ta có thể viết mã cho bốn yêu cầu này một cách đơn giản. Nhưng bởi vì những yêu cầu này tương đối tổng quát, Flask-Login phân phối một lớp mixin gọi là UserMixin (Nếu chưa hiểu về khái niệm mixin trong Python, bạn có thể tham khảo tại đây). Lớp này có sẵn các khai báo và mã thực thi có thể sử dụng được với phần lớn mô hình dữ liệu User. Sau đây là cách thêm lớp mixin vào mô hình dữ liệu:

app/models.py: lớp mixin cho user trong Flask-Login

1

2

3

4

5

.

.

.

from

flask_login

import

UserMixin

 

class

User

(

UserMixin

,

db

.

Model

)

:

    

.

.

.

Hàm tải thông tin user

Flask-Login theo dõi tình trạng của những user đã đăng nhập bằng cách lưu các ID tương ứng trong các phiên làm việc (user session) – một vùng lưu trữ được xác lập cho mỗi user đang kết nối vào ứng dụng. Mỗi khi một user đã đăng nhập truy cập một trang mới trong ứng dụng, Flask-Login sẽ lấy ID của user đó từ phiên làm việc và tải dữ liệu về user đó vào bộ nhớ lưu trữ.

Bởi vì Flask-Login không trực tiếp làm việc với CSDL, nó cần phải có sự trợ giúp của các thành phần khác trong ứng dụng để tìm kiếm và tải dữ liệu về user. Vì vậy, thư viện này sẽ cần phải có một hàm trợ giúp để tải thông tin user. Hàm này sẽ tìm kiếm và tải các thông tin về user từ CSDL dựa trên Id của user đó. Tất cả chúng ta sẽ thêm mã cho hàm này vào module app/models.py như sau:

See also  Hướng dẫn đăng nhập Wechat trên PC và Macbook cực đơn giản

app/models.py: Hàm tải dữ liệu người dùng cho Flask-Login

1

2

3

4

5

6

from

app

import

login

.

.

.

 

@

login

.

user_loader

def

load_user

(

id

)

:

    

return

User

.

query

.

get

(

int

(

id

)

)

Tất cả chúng ta sẽ đăng ký hàm này với Flask-Login qua decorator @login.user_loader. Tham số id sẽ được truyền vào hàm này dưới dạng chuỗi (String), vì vậy nếu CSDL định dạng Id kiểu số nguyên (integer), tất cả chúng ta sẽ cần chuyển hóa Id thành số nguyên như trong ví dụ ở trên.

Đăng nhập người dùng

Sau các bước chuẩn bị ở trên, tất cả chúng ta có thể trở lại với hàm hiển thị đăng nhập mà tất cả chúng ta đã tạo ra trong Phần 3. Lưu ý là tại thời điểm này, hàm này chưa có tính năng đăng nhập thật sự mà chỉ mang ra một thông báo nhờ hàm flash() nếu user nhập vào đầy đủ tên người dùng và mật mã (với bất kỳ giá trị nào). Hiện giờ, sau khoảng thời gian đã có các CSDL và biết phương pháp để tạo ra hash cho mật mã, tất cả chúng ta có thể làm cho nó thật sự hoạt động.

app/routes.py: Hàm hiển thị đăng nhập

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

.

.

.

from

flask_login

import

current_user

,

login_user

from

app

.

models

import

User

 

.

.

.

 

@

app

.

route

(

‘/login’

,

methods

=

[

‘GET’

,

‘POST’

]

)

def

login

(

)

:

    

if

current_user

.

is_authenticated

:

        

return

redirect

(

url_for

(

‘index’

)

)

    

form

=

LoginForm

(

)

    

if

form

.

validate_on_submit

(

)

:

        

user

=

User

.

query

.

filter_by

(

username

=

form

.

username

.

data

)

.

first

(

)

        

if

user

is

None

or

not

user

.

check_password

(

form

.

password

.

data

)

:

            

flash

(

‘Invalid username or password’

)

            

return

redirect

(

url_for

(

‘login’

)

)

        

login_user

(

user

,

remember

=

form

.

remember_me

.

data

)

        

return

redirect

(

url_for

(

‘index’

)

)

    

return

render_template

(

‘login.html’

,

title

=

‘Sign In’

,

form

=

form

)

Trong hai dòng lệnh trước tiên của hàm login(), tất cả chúng ta sẽ khắc phục tình huống khi có một user đã thành công đăng nhập nhưng lại truy nhập vào trang /login vì lý do nào đó. Dù về mặt lập trình, điều này không có gì sai, nhưng đây là một điều không hay cho user (theo phép tắc thiết kế ứng dụng, để có UX – User eXperience hay trải nghiệm của người tiêu dùng – tốt thì user không nên thấy các giao diện hay tính năng mà họ không nên hoặc không cần sử dụng). Vì vậy, trong trường hợp này, tất cả chúng ta sẽ dùng phương án là chuyển hướng user của tất cả chúng ta trở lại trang chủ (với URL /index) nếu đây là một user có đăng ký và được hệ thống xác nhận (đăng nhập thành công hay theo khái niệm của thư viện Flask-Login là authenticated). Để làm được điều này, tất cả chúng ta cần sự trợ giúp của biến current_user có sẵn trong Flask-Login. Nếu user đã đăng nhập, biến này sẽ chứa các thông tin về user từ CSDL (việc tải thông tin của user từ CSDL được thực hiện qua hàm load_user() mà tất cả chúng ta đã tạo ra ở trên). Trong trường hợp user chưa đăng nhập, biến này sẽ chứa giá trị đại diện cho user ẩn danh (anonymous). Ngoài ra, biến này còn tồn tại tính chất is_authenticated rất hữu ích để giúp xác nhận một user có đăng nhập hay không. Đoạn mã của tất cả chúng ta sẽ xác minh tính chất này, và nếu nó trả về True – đồng nghĩa đây là một user đã đăng nhập thành công – thì tất cả chúng ta sẽ chuyển hướng tới và hiển thị trang chủ thay vì hiển thị trang đăng nhập.

Trước đó, tất cả chúng ta cũng dùng hàm flash() để hiển thị một thông báo là người tiêu dùng đăng nhập thành công mà không thực sự tiến hành quá trình đăng nhập. Nhưng lần này thì tất cả chúng ta có thể thay thế mã giả này bằng mã thật cho quá trình đăng nhập. Để làm điều này, trước hết, tất cả chúng ta cần tải dữ liệu về user từ CSDL. Thông qua form đăng nhập, tất cả chúng ta sẽ thu được username. Giá trị này sẽ được dùng để tìm Id tương ứng của user này trong CSDL bằng hàm filter_by() của đối tượng query (truy vấn) trong thư viện SQLAlchemy. Hàm filter_by sẽ trả về các đối tượng có giá trị username khớp với username mà tất cả chúng ta đã sử dụng. Bởi vì tất cả chúng ta biết rằng các username mang tính duy nhất nên chỉ có tối đa một đối tượng user trong CSDL có username trùng với username mà tất cả chúng ta đang tìm. Do đó, tất cả chúng ta có thể sử dụng hàm first(), hàm này sẽ trả về một kết quả nếu có một user như vậy hoặc None nếu user không tồn tại trong CSDL. Trong Phần 4, tất cả chúng ta đã thấy kết quả khi gọi hàm all() trong một truy vấn: nó sẽ trả về toàn bộ kết quả trùng phù hợp với truy vấn đó trong CSDL. Hàm first() thường được sử dụng trong trường hợp tất cả chúng ta biết trước chỉ có thể có tối đa một kết quả.

Nếu tất cả chúng ta tìm được một user từ CSDL có cùng username như trong form đăng nhập, tất cả chúng ta sẽ tiến hành bước tiếp theo là xác minh mật mã. Quá trình này được thực hiện bằng cách sử dụng hàm check_password() mà tất cả chúng ta đã nói ở phần đầu của bài này. Hàm này sẽ tạo hash từ mật mã do user nhập vào và so sánh với giá trị password_hash từ bảng User. Nếu hai giá trị này giống nhau thì mật mã do user nhập vào là đúng (valid), trái lại thì mật mã này là sai (invalid). Như vậy, cuối cùng tất cả chúng ta có hai khả năng người dùng không được cho phép đăng nhập: username hoặc password nhập vào là không đúng. Trong cả hai trường hợp, tất cả chúng ta sẽ dùng flash() để hiển thị một thông báo lỗi và chuyển hướng về trang đăng nhập để user có thể nhập các thông tin lần nữa.

Nếu username và password tương ứng đều đúng, tất cả chúng ta sẽ gọi một hàm trong thư viện Flask-Login là login_user() để ghi nhận tình trạng của user này là đã đăng nhập thành công. Điều này cũng đồng nghĩa với việc ứng dụng sẽ tải và lưu giữ các thông tin về user từ database vào bộ nhớ lưu trữ và giữ lại các thông tin này trong suốt phiên làm việc của user (cho đến khi user đăng xuất – logout – ra khỏi ứng dụng). Nhờ đó, khi user truy cập bất kỳ trang nào, ứng dụng cũng có thể đơn giản tìm được thông tin về họ qua biến current_user.

Sau thời điểm user đăng nhập thành công, tất cả chúng ta sẽ chuyển (redirect) user đến trang chủ.

Đăng xuất (Log Out)

Để hoàn tất quá trình xuất nhập, tất cả chúng ta cũng phải phân phối tính năng đăng xuất (log out) ra khỏi ứng dụng. Tất cả chúng ta sẽ sử dụng một hàm khác của thư viện Flask-Login gọi là logout_user() cho mục đích này:

See also  Cách tạo tin nhắn độc đáo trên màn hình đăng nhập Windows 10

app/routes.py: Hàm hiển thị Logout

1

2

3

4

5

6

7

8

9

.

.

.

from

flask_login

import

logout

_

user

 

.

.

.

 

@

app

.

route

(

‘/logout’

)

def

logout

(

)

:

    

logout_user

(

)

    

return

redirect

(

url_for

(

‘index’

)

)

Tất cả chúng ta cũng nên thay link Login trong thay định hướng bằng link Logout sau khoảng thời gian user đăng nhập thành công. Tất cả chúng ta sẽ làm điều này bằng cách update file base.html:

app/templates/base.html: Hiển thị link login và logout trên thanh định hướng

1

2

3

4

5

6

7

8

9

    

<divvàgt;

        Myblog:

        

<a

href

=

“{{ url_for(‘index’) }}”

>

Home

</avàgt;

        {% if current_user.is_anonymous %}

        

<a

href

=

“{{ url_for(‘login’) }}”

>

Login

</avàgt;

        {% else %}

        

<a

href

=

“{{ url_for(‘logout’) }}”

>

Logout

</avàgt;

        {% endif %}

    

</divvàgt;

Như đã nói ở trên, tính chất is_anonymous được thư viện Flask-Login phân phối qua lớp UserMixin. Biểu thức current_user.is_anonymous sẽ có giá trị True khi user chưa đăng nhập.

Yêu cầu User đăng nhập

Flask-Login có một chức
năng rất hữu ích để yêu cầu người tiêu dùng đăng nhập nếu họ muốn truy nhập vào một
số trang nhất định trong ứng dụng. Nếu một user không đăng nhập tìm cách truy cập
các trang này, Flask-Login sẽ tự động chuyển họ đến trang đăng nhập và chuyển
trở lại trang được yêu cầu truy cập sau khoảng thời gian user đã đăng nhập thành công.

Để sử dụng tính năng này, Flask-Login cần biết hàm hiển thị cho quá trình đăng nhập. Tất cả chúng ta sẽ thay đổi file app/__init__.py để làm điều này:

1

2

3

.

.

.

login

=

LoginManager

(

app

)

login

.

login_view

=

‘login’

Giá trị ‘login‘ ở trên là tên của hàm hiển thị đăng nhập. Hay nói cách khác, này là tên bạn sẽ dùng khi gọi hành url_for() để lấy giá trị của URL.

Tất cả chúng ta sẽ dùng decorator @login_required cho những hàm hiển thị nào chỉ cho phép user có đăng nhập truy cập. Khi bạn thêm decorator này vào một hàm hiển thị và dưới decorator @app.route, hàm này sẽ được bảo vệ và không cho phép những user không đăng nhập truy cập. Sau đây là ví dụ với hàm hiển thị cho trang chủ của ứng dụng:

app/routes.py: decorator @login_required

1

2

3

4

5

6

7

from

flask_login

import

login

_

required

 

@

app

.

route

(

‘/’

)

@

app

.

route

(

‘/index’

)

@

login_required

def

index

(

)

:

    

.

.

.

Tất cả chúng ta cần làm một việc nữa là thêm tính năng trở về trang mà user yêu cầu truy cập sau khoảng thời gian đã đăng nhập thành công. Khi một user chưa đăng nhập truy cập vào một trang được hiển thị bởi một hàm hiển thị đã được bảo vệ với decorator @login_required, decorator này sẽ chuyển hướng user đó đến trang đăng nhập, nhưng nó sẽ thêm vào một vài thông số kỹ thuật để ứng dụng biết rằng cần phải trở lại trang này sau khoảng thời gian user đăng nhập thành công. Ví dụ như khi user muốn truy cập trang /index khi chưa đăng nhập, decorator @login_required sẽ xác minh yêu cầu này và chuyển hướng user đến /login, nhưng nó sẽ thêm một tham số vào URL của trang /login như sau: /login?next=/index. Tham số next trong URL này sẽ được gán giá trị của URL được yêu cầu ban đầu, nhờ đó ứng dụng biết sẽ cần phải trở lại trang /index sau khoảng thời gian user đăng nhập thành công.

Sau đây là đoạn mã để đọc và xử lý tham số next:

app/routes.py: Chuyển hướng tới trang tại “next”

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

from

flask

import

request

from

werkzeug

.

urls

import

url

_

parse

 

@

app

.

route

(

‘/login’

,

methods

=

[

‘GET’

,

‘POST’

]

)

def

login

(

)

:

    

.

.

.

    

if

form

.

validate_on_submit

(

)

:

        

user

=

User

.

query

.

filter_by

(

username

=

form

.

username

.

data

)

.

first

(

)

        

if

user

is

None

or

not

user

.

check_password

(

form

.

password

.

data

)

:

            

flash

(

‘Invalid username or password’

)

            

return

redirect

(

url_for

(

‘login’

)

)

        

login_user

(

user

,

remember

=

form

.

remember_me

.

data

)

        

next_page

=

request

.

args

.

get

(

‘next’

)

        

if

not

next_page

or

url_parse

(

next_page

)

.

netloc

!=

:

            

next_page

=

url_for

(

‘index’

)

        

return

redirect

(

next_page

)

    

.

.

.

Ngay sau khoảng thời gian hàm login_user() trả về kết quả là user đăng nhập thành công, tất cả chúng ta sẽ lấy giá trị của tham số next trong URL. Flask phân phối biến request với toàn bộ thông tin được user nhập vào và gởi qua trình duyệt. Rõ ràng và cụ thể là tính chất request.args trả về toàn bộ các tham số được thêm vào URL trong một dictionary. Có ba khả năng cần được xem xét để quyết định sẽ chuyển hướng tới vị trí nào sau khoảng thời gian user đăng nhập thành công:

  • Nếu URL đăng nhập không có tham số next, user sẽ được chuyển hướng tới trang chủ (/index) theo mặc định.
  • Nếu URL đăng nhập có tham số next được gán với giá trị là một đường dẫn tương đối (hay nói cách khác là một URL không có phần tên miền -tên miền), user sẽ được chuyển hướng tới URL đó.
  • Nếu URL đăng nhập có tham số next được gán với giá trị là một URL đầy đủ với cả tên miền, user sẽ được chuyển hướng tới trang chủ của ứng dụng.

Trường hợp đầu và trường hợp thứ hai tương đối dễ hiểu. Trường hợp thứ ba nhằm đảm bảo cho ứng dụng được bảo mật hơn. Một người có ác ý có thể đặt một URL dẫn đến một trang Website nguy hiểm vào trong tham số next, vì vậy, ứng dụng chỉ chuyển hướng khi gặp đường dẫn tương đối, nhờ đó đảm bảo là mọi trang Website được chuyển đến đều nằm trong cùng site với ứng dụng. Để phân biệt các URL tương đối và đầy đủ, tất cả chúng ta sử dụng hàm url_parse() từ thư viện Werkzeug và xác minh xem thành phần netloc có được gán hay không.

Hiển thị user đã đăng nhập trong các
template

Bạn còn nhớ trong Phần 2 tất cả chúng ta đã tạo ra một user giả để mô phỏng hệ thống đăng nhập user khi thiết kế trang chủ khi tất cả chúng ta còn chưa có hệ thống này hay không? Lúc này tất cả chúng ta đã có một hệ thống đăng nhập hoàn chỉnh với các user thật, vì vậy, tất cả chúng ta có thể bỏ user giả đó và làm việc với các user thật. Thay vì user giả, tất cả chúng ta có thể dùng biến current_user trong các template:

app/templates/index.html: Truyền thông tin về user đang đăng nhập vào template

1

2

3

4

5

6

7

8

{

%

extends

“base.html”

%

}

 

{

%

block

content

%

}

    

<h1vàgt;

Hi, {{ current_user.username }}!

</h1vàgt;

    {% for post in posts %}

    

<divvàgt;

<pvàgt;

{{ post.author.username }} :

<bvàgt;

{{ post.body }}

</bvàgt;

</pvàgt;

</divvàgt;

    

{

%

endfor

%

}

{

%

endblock

%

}

Tất cả chúng ta cũng có thể loại bỏ tham số user trong các hàm hiển thị:

app/routes.py: Không truyền tham số user đến các template nữa

1

2

3

4

5

@

app

.

route

(

‘/’

)

@

app

.

route

(

‘/index’

)

def

index

(

)

:

    

.

.

.

    

return

render_template

(

“index.html”

,

title

=

‘Home Page’

,

posts

=

posts

)

Đây là lúc để xác minh xem tính năng đăng nhập và đăng xuất làm việc thế nào. Bởi vì tất cả chúng ta còn chưa có tính năng đăng ký user, cách duy nhất để thêm user vào CSDL là dùng cơ chế dòng lệnh của Python. Tất cả chúng ta hãy khởi đầu bằng cách gọi flask shell và dùng các lệnh sau đây để đăng ký một user với hệ thống:

1

2

3

4

>>>

u

=

User

See also  Cách Đăng Ký, Đăng Nhập Shopee Cho Người Mới

(

username

=

‘thai’

,

thư điện tử

=

‘thai@example.com’

)

>>>

u

.

set_password

(

‘thaipham’

)

>>>

db

.

session

.

add

(

u

)

>>>

db

.

session

.

commit

(

)

Lúc này, nếu bạn chạy ứng dụng và truy cập địa chỉ http://localhost:5000/ or http://localhost:5000/index, ứng dụng sẽ lập tức chuyển hướng bạn đến trang đăng nhập. Và sau khoảng thời gian đã nhập vào các thông tin về user mà bạn vừa thêm vào trong CSDL, bạn sẽ được chuyển hướng tới trang mà bạn yêu cầu và một lời chào mừng sẽ được hiển thị.

Đăng ký user

Tính năng cuối mà tất cả chúng ta sẽ xây dựng trong bài ngày hôm nay là form đăng ký để user mới có thể đăng ký và đăng nhập vào ứng dụng. Tất cả chúng ta sẽ cần tạo ra một lớp website form mới trong app/forms.py:

app/forms.py: form đăng ký user

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

from

flask_wtf

import

FlaskForm

from

wtforms

import

StringField

,

PasswordField

,

BooleanField

,

SubmitField

from

wtforms

.

validators

import

ValidationError

,

DataRequired

,

Thư điện tử

,

EqualTo

from

app

.

models

import

User

 

.

.

.

 

class

RegistrationForm

(

FlaskForm

)

:

    

username

=

StringField

(

‘Username’

,

validators

=

[

DataRequired

(

)

]

)

    

thư điện tử

=

StringField

(

‘Thư điện tử’

,

validators

=

[

DataRequired

(

)

,

Email

(

)

]

)

    

password

=

PasswordField

(

‘Password’

,

validators

=

[

DataRequired

(

)

]

)

    

password2

=

PasswordField

(

        

‘Repeat Password’

,

validators

=

[

DataRequired

(

)

,

EqualTo

(

‘password’

)

]

)

    

submit

=

SubmitField

(

‘Register’

)

 

    

def

validate_username

(

self

,

username

)

:

        

user

=

User

.

query

.

filter_by

(

username

=

username

.

data

)

.

first

(

)

        

if

user

is

not

None

:

            

raise

ValidationError

(

‘Please use a different username.’

)

 

    

def

validate_email

(

self

,

thư điện tử

)

:

        

user

=

User

.

query

.

filter_by

(

thư điện tử

=

thư điện tử

.

data

)

.

first

(

)

        

if

user

is

not

None

:

            

raise

ValidationError

(

‘Please use a different thư điện tử address.’

)

Có vài điểm đáng lưu ý liên quan tới việc xác minh dữ liệu nhập trong form mới này. Trước hết, tất cả chúng ta thêm một biến xác minh dữ liệu (validator) thứ hai ngoài DataRequired vào trường thư điện tử. BIến này gọi là Thư điện tử. Đây là một biến xác minh dữ liệu có sẵn trong WTForms để đảm bảo rằng địa chỉ thư điện tử do user nhập vào phù phù hợp với định dạng của địa chỉ thư điện tử.

Tiếp theo, tất cả chúng ta cũng tuân theo quy ước chung của các form đăng ký là yêu cầu user nhập mật mã hai lần để tránh tình trạng nhập sai mật mã ngoài ý muốn. Vì vậy, tất cả chúng ta dùng hai trường passwordpassword2. Trường password2 cũng sử dụng một biến xác minh có sẵn gọi là EqualTo để đảm bảo rằng giá trị nhập vào trường này phải giống như giá trị của trường password đã nhập trước đó.

Và cuối cùng, tất cả chúng ta cũng thêm hai hàm mới gọi là validate_username()validate_email(). Khi tất cả chúng ta thêm bất kỳ hàm nào với tên gọi theo mẫu validate_, WTForms sẽ hiểu những hàm này như là những biến xác minh dữ liệu tùy biến (custom validator) và sẽ sử dụng chúng cho các trường tương ứng sau khoảng thời gian đã dùng các biến xác minh dữ liệu sẵn có. Trong trường hợp này, tất cả chúng ta dùng các hàm xác minh dữ liệu này để chắc nịch rằng các giá trị username và thư điện tử đã được user nhập vào không tồn tại trong CSDL, vì vậy các truy vấn dữ liệu phải không tìm được dữ liệu trong bảng User căn cứ trên các giá trị này. Nếu có kết quả trả về, các hàm này sẽ mang ra ngoại lệ ValidationError. Tham số cho các ngoại lệ Validation là các thông báo lỗi tương ứng và sẽ được hiển thị bên cạnh các trường này khi các dữ liệu nhập vào không đúng.

Để hiển thị form này, tất cả chúng ta cần xây dựng một template HTML. Tất cả chúng ta sẽ mang template này vào file app/templates/register.html. Tất cả chúng ta cũng viết mã cho template này tương tự như cho form đăng nhập:

app/templates/register.html: Template cho form đăng ký user

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

{

%

extends

“base.html”

%

}

 

{

%

block

content

%

}

    

<h1vàgt;

Register

</h1vàgt;

    

<form

action

=

“”

method

=

“post”

>

        {{ form.hidden_tag() }}

        

<pvàgt;

            {{ form.username.label }}

<brvàgt;

            {{ form.username(size=32) }}

<brvàgt;

            {% for error in form.username.errors %}

            

<span

style

=

“color: red;”

>

[{{ error }}]

</spanvàgt;

            {% endfor %}

        

</pvàgt;

        

<pvàgt;

            {{ form.thư điện tử.label }}

<brvàgt;

            {{ form.thư điện tử(size=64) }}

<brvàgt;

            {% for error in form.thư điện tử.errors %}

            

<span

style

=

“color: red;”

>

[{{ error }}]

</spanvàgt;

            {% endfor %}

        

</pvàgt;

        

<pvàgt;

            {{ form.password.label }}

<brvàgt;

            {{ form.password(size=32) }}

<brvàgt;

            {% for error in form.password.errors %}

            

<span

style

=

“color: red;”

>

[{{ error }}]

</spanvàgt;

            {% endfor %}

        

</pvàgt;

        

<pvàgt;

            {{ form.password2.label }}

<brvàgt;

            {{ form.password2(size=32) }}

<brvàgt;

            {% for error in form.password2.errors %}

            

<span

style

=

“color: red;”

>

[{{ error }}]

</spanvàgt;

            {% endfor %}

        

</pvàgt;

        

<pvàgt;

{{ form.submit() }}

</pvàgt;

    

</formvàgt;

{

%

endblock

%

}

Tất cả chúng ta cũng nên thêm
link từ trang login đến trang đăng ký để các user mới có thể đăng ký với hệ
thống như sau:

app/templates/login.html: Link đến trang đăng ký

1

    

<pvàgt;

New User?

<a

href

=

“{{ url_for(‘register’) }}”

>

Click to Register!

</avàgt;

</pvàgt;

Và cuối cùng, tất cả chúng ta cần viết hàm hiển thị cho trang đăng ký trong app/routes.py:

app/routes.py: Hàm hiển thị cho form đăng ký user

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

from

app

import

db

from

app

.

forms

import

RegistrationForm

 

.

.

.

 

@

app

.

route

(

‘/register’

,

methods

=

[

‘GET’

,

‘POST’

]

)

def

register

(

)

:

    

if

current_user

.

is_authenticated

:

        

return

redirect

(

url_for

(

‘index’

)

)

    

form

=

RegistrationForm

(

)

    

if

form

.

validate_on_submit

(

)

:

        

user

=

User

(

username

=

form

.

username

.

data

,

thư điện tử

=

form

.

thư điện tử

.

data

)

        

user

.

set_password

(

form

.

password

.

data

)

        

db

.

session

.

add

(

user

)

        

db

.

session

.

commit

(

)

        

flash

(

‘Congratulations, you are now a registered user!’

)

        

return

redirect

(

url_for

(

‘login’

)

)

    

return

render_template

(

‘register.html’

,

title

=

‘Register’

,

form

=

form

)

Trong hàm này, tất cả chúng ta làm một số việc theo tuần tự như sau: đảm bảo rằng các user đã đăng ký không thể truy cập trang này (cách xử lý tương tự như khi khi một user đã đăng nhập tìm cách truy cập trang đăng nhập). Tiếp theo, tất cả chúng ta sẽ xác minh các dữ liệu đăng ký có hợp lệ hay không, nếu không có lỗi (được xác nhận qua điều kiện if_validate_on_submit()), tất cả chúng ta sẽ tạo một user mới với các giá trị của username, thư điện tử và password do user đã nhập vào, lưu vào CSDL và chuyển hướng tới trang đăng nhập để user có thể đăng nhập.

Với các thay đổi này, user sẽ có thể tạo tài khoản người tiêu dùng, đăng nhập và đăng xuất. Hãy thử toàn bộ các tình huống có lỗi để hiểu cách làm việc của mã xác minh lỗi mà tất cả chúng ta đã thêm vào chương trình. Tất cả chúng ta sẽ trở lại với hệ thống xác nhận người dùng và thêm tính năng để user có thể reset lại mật mã trong trường hợp họ quên trong một nội dung sau. Nhưng hiện tại, tất cả chúng ta hãy chuyển sang phần khác của ứng dụng.

Tất cả chúng ta sẽ kết thúc phần này ở đây. Hẹn gặp bạn trong phần tiếp theo.