Bài viết này tập trung vào việc triển khai xác thực với express-session, connect-flash, redis và ejs. Dưới đây là các tính năng tôi xây dựng trong ví dụ của mình
- Đăng nhập người dùng
- Người dùng có thể truy cập trang hồ sơ của anh ấy, trang này hiển thị hồ sơ thời gian đăng nhập trước đó của anh ấy
- Nếu người dùng đã đăng nhập trước đó, hãy bỏ qua trang đăng nhập và chuyển hướng đến trang hồ sơ của anh ấy
- Bên trong trang hồ sơ, người dùng có thể chọn đăng xuất. Sau đó, chúng tôi xóa hồ sơ thời gian đăng nhập trước đó của anh ấy. Chuyển hướng anh ta đến trang đăng nhập
Trước khi bắt đầu, hãy thiết lập môi trường
Các tập tinconst users = [
0. cho tất cả mã máy chủ
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]const users = [
1. tất cả các trang html
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]const users = [
2. để lưu trữ bí mật phiên cấp tốc
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]const users = [
3 [tùy chọn]. Cài đặt
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]const users = [
4 để định dạng mã của chúng tôi
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
Trong thực tế này, chúng ta phải hiển thị hai trang
const users = [
5. trang đăng nhập
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]const users = [
6. trang hồ sơ
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
7 là ngôn ngữ mẫu JavaScript để tạo tệp HTML. Bạn có thể viết JavaScript bên trong tệp const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
7Thiết lập dự ánrequire['dotenv'].config[]
const express = require['express']
const flash = require['connect-flash']
const app = express[]
const port = 3000
app.set['view engine', 'ejs']const redis = require['redis']
var session = require['express-session']let RedisStore = require['connect-redis'][session]
let redisClient = redis.createClient[]redisClient.on['error', [err] =>
console.log[`Fail to connect to redis. ${err}`]
]
redisClient.on['connect', [] => console.log['Successfully connect to redis']]const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]app.use[flash[]]
Lưu ý quan trọng. chúng ta không nên để lộ
const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
9 trong phiên. Đây là chìa khóa để ký ID phiên được lưu trữ trong trình duyệt của người dùng. Thực tiễn tốt nhất được đề cập trong tài liệu phiên nhanhapp.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
Để tạo một biến môi trường en, hãy cài đặt
app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
0 và tạo một tệp có tên const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
2Dữ liệu người dùng giả để trình diễn
const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
Xin lưu ý rằng ở cấp độ sản xuất, những thông tin này phải được lưu trữ trong cơ sở dữ liệu và mật khẩu phải được băm. Chúng tôi khuyên bạn không nên lưu trữ mật khẩu ở dạng văn bản thuần túy, tôi. e.
app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
2, vì lý do bảo mậtVì phương pháp này tập trung vào express và session, nên tôi chỉ cần tạo một vài dữ liệu giả với đối tượng JavaScript thay vì lưu trữ chúng trong cơ sở dữ liệu
Hãy bắt đầu dự án của chúng ta
Trang đăng nhập người dùng [trang chủ]Trang chủ là trang đăng nhập
app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
Xác thực dựa trên isAuthĐầu tiên, chúng tôi phải quyết định xem người dùng này đã đăng nhập trước đó chưa, bằng cách kiểm tra xem phiên có khóa và giá trị
app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
3 khôngTrước khi chúng tôi tiếp tục, bạn có thể hỏi tại sao chúng tôi có thể truy cập phiên của anh ấy nếu anh ấy chưa đăng nhập?
Bất kể người dùng đã đăng nhập hay chưa, chúng tôi có thể truy cập phiên của anh ấy vì chúng tôi đã đặt phần mềm trung gian phiên toàn cầu trước khi vào mọi tuyến đường
// session middleware, for creating a session
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
Như đã đề cập trong bài viết trước của tôi, phần mềm trung gian này tạo phiên mới hoặc lấy dữ liệu phiên hiện có từ cửa hàng Redis nếu cookie của khách hàng có ID phiên. Do đó, mọi người dùng đều có phiên trước khi nhập bất kỳ tuyến đường nào trong ứng dụng của chúng tôi
Sau đó, chúng tôi quyết định xem người dùng đã đăng nhập hay chưa trước đó, bằng cách kiểm tra khóa và giá trị
app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
3 trong đối tượng phiên của anh ấyif [req.session.isAuth] {
return res.redirect['/profile']
}
Làm thế nào hoặc khi nào chúng ta nên thêm
app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
5 vào phiên? . Ngay bây giờ, chúng ta chỉ cần hiểu rằng app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
5 là thuộc tính mà chúng ta đã sử dụng để xác thựcCuối cùng, nếu người dùng có
app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
3 trong phiên của mình, điều đó có nghĩa là anh ta đã đăng nhập rồi. Anh ta có thể bỏ qua trang đăng nhập và được chuyển hướng đến app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
9. Mặt khác, chúng tôi hiển thị trang chủ để yêu cầu người dùng đăng nhậpapp.get['/', [req, res] => {
// ...
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
Xác thực đầu vàoSau khi người dùng nhấp vào nút 'Đăng nhập', biểu mẫu HTML sẽ gửi yêu cầu
// session middleware, for creating a session0 đến máy chủ của chúng tôi. Chúng tôi xác thực thông tin nhập của người dùng bằng cách so sánh tên người dùng và mật khẩu trong đối tượng yêu cầu do khách hàng gửi với dữ liệu giả trong máy chủ của chúng tôi
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
Dữ liệu giả
const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
// session middleware, for creating a session0 yêu cầu đăng nhập
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
app.post['/', [req, res] => {
const { username, password } = req.body
// Prevent empty input
if [username.trim[] === '' || password.trim[] === ''] {
req.flash['showInputError', true]
return res.redirect['/']
}
// Validate input
const targetUser = users.find[
[user] => user.username === username && user.password === password
]
// Wrong username or password
if [!targetUser] {
req.flash['showInputError', true]
return res.redirect['/']
}
req.session.isAuth = true
req.session.username = targetUser.username
req.session.timestamps = []
res.redirect['/profile']
}]
Nếu tên người dùng và mật khẩu của người dùng khớp với một trong những dữ liệu giả mạo của chúng tôi, anh ta là người dùng hợp lệ và đăng nhập thành công. Như đã đề cập ở đầu bài viết này, chúng tôi kiểm tra trên
app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
3 để xác thực. Sau khi chúng tôi xác thực người dùng, chúng tôi thêm app.get['/', [req, res] => {
if [req.session.isAuth] {
return res.redirect['/profile']
}
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
3 vào phiên của người dùng để cho biết rằng người dùng này đã đăng nhập thành côngTrong khi đó, chúng tôi thêm
// session middleware, for creating a session4 và
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
// session middleware, for creating a session5 vào phiên của người dùng, để sau này được hiển thị trong trang
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
// session middleware, for creating a session6 của người dùng
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
Hãy thử đăng nhập với tên người dùng và mật khẩu chính xác
Kết quả
Hiển thị thông báo lỗi với connect-flash
Tuy nhiên, nếu người dùng nhập sai thông tin, chúng ta phải
- Chuyển hướng anh ta trở lại
// session middleware, for creating a session
7 [i. trang đăng nhập điện tử]
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}] - Kết xuất trang đăng nhập với thông báo lỗi
Để hiển thị thông báo lỗi trên trang chủ, chúng tôi phải quyết định xem chúng tôi có hiển thị lỗi hay không bằng cách sử dụng
// session middleware, for creating a session8đèn flash kết nối
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
// session middleware, for creating a session8 là một thư viện để truyền dữ liệu khi chuyển hướng người dùng đến một trang. Dữ liệu được truyền qua flash sẽ bị xóa sau khi dữ liệu được hiển thị trên trang
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
Ghi chú quan trọng
- Dữ liệu tin nhắn flash được lưu trữ trong phiên. Bạn phải thêm phần mềm trung gian phiên trước khi thêm phần mềm trung gian flash
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]app.use[flash[]]
- Nếu bạn gọi một chức năng kết xuất mà không bao gồm dữ liệu tin nhắn flash, dữ liệu sẽ vẫn được giữ và sẽ không bị xóa
Quay lại ví dụ của chúng tôi, nếu chúng tôi nhận được đầu vào trống hoặc đầu vào không chính xác, chúng tôi sẽ thêm một thông báo flash vào đối tượng yêu cầu của người dùng
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
0Sau khi người dùng được chuyển hướng đến trang chủ, chúng tôi kiểm tra
if [req.session.isAuth] {
return res.redirect['/profile']
}
0 để quyết định xem chúng tôi có phải hiển thị thông báo lỗi trên trang hay khôngapp.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
1Hãy thử để có đầu vào trống
Kết quả
Chúng ta có thể có cách tiếp cận khác?
Vui lòng bỏ qua phần này nếu bạn không muốn biết các vấn đề của các phương pháp khác
Trước khi sử dụng tin nhắn flash, tôi đã nghĩ đến hai cách khả thi để chuyển
if [req.session.isAuth] {
return res.redirect['/profile']
}
0 sau if [req.session.isAuth] {
return res.redirect['/profile']
}
2- Cách tiếp cận 1. Lưu trữ trực tiếp giá trị của
if [req.session.isAuth] {
3 trong phiên mà không cần sử dụng đèn flash
return res.redirect['/profile']
} - Cách tiếp cận 2. Lưu trữ giá trị của
if [req.session.isAuth] {
3 trong truy vấn URL. Thay vì chuyển hướng người dùng đến
return res.redirect['/profile']
}if [req.session.isAuth] {
5, hãy chuyển hướng anh ta đến
return res.redirect['/profile']
}if [req.session.isAuth] {
6
return res.redirect['/profile']
}
Trước khi làm việc với tin nhắn flash, tôi đã thử một cách tiếp cận khác, chỉ lưu trữ thông báo lỗi trong phiên. Như thế này
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
2Sau đó, hiển thị thông báo lỗi sẽ được hiển thị dựa trên phiên lưu trữ bên trong
if [req.session.isAuth] {
return res.redirect['/profile']
}
0app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
3Từ đoạn mã trên, chúng ta có thể phát hiện ra một số vấn đề
- Tôi phải cố tình hủy dữ liệu phiên. Mặt khác,
if [req.session.isAuth] {
0 sẽ luôn đúng và được lưu trữ trong phiên của người dùng. Khi người dùng làm mới trang sau khi đăng nhập không thành công lần đầu tiên, trang sẽ tiếp tục hiển thị
return res.redirect['/profile']
}if [req.session.isAuth] {
9, thay vì xóa thông báo
return res.redirect['/profile']
} - Thông thường, chúng tôi sử dụng phiên để lưu trữ dữ liệu sử dụng, không phải dữ liệu để quyết định nội dung của trang [i. e hiển thị thông báo lỗi]. Do đó, cách tiếp cận này có thể gây nhầm lẫn cho các nhà phát triển khác
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
4Theo cách tiếp cận này, nếu đầu vào của người dùng không chính xác, chúng tôi sẽ chuyển hướng anh ta đến một URL có truy vấn
app.get['/', [req, res] => {
// ...
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
0. Dựa trên truy vấn, chúng tôi kiểm tra xem có nên hiển thị thông báo lỗi khôngGiải pháp này có vấn đề vì nếu người dùng ở lại
app.get['/', [req, res] => {
// ...
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
1 và làm mới trang, anh ta sẽ vẫn ở lại app.get['/', [req, res] => {
// ...
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
1 và trang sẽ tiếp tục hiển thị thông báo lỗitrang hồ sơQuay lại ví dụ của chúng tôi, người dùng hiện được chuyển hướng đến trang
// session middleware, for creating a session6 sau khi thực thi đoạn mã sau
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
5Trước khi chúng tôi kết xuất
// session middleware, for creating a session6, chúng tôi cần thực thi chức năng phần mềm trung gian để xác thực. Đó là bởi vì người dùng có thể truy cập trực tiếp vào
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
// session middleware, for creating a session6, thay vì đến từ trang chủ
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]// login page
app.get['/', [req, res] => {
...
}]
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
6Kết xuất trang hồ sơ với các biến EJSSau khi đi qua phần mềm trung gian, chúng tôi có thể kết xuất trang hồ sơ. Nó hiển thị tên người dùng của người dùng và các bản ghi đăng nhập của anh ấy, được lưu trữ trong đối tượng phiên của anh ấy trước đó
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
5Chúng tôi chuyển các biến vào mẫu EJS
app.get['/', [req, res] => {
// ...
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
6 và hiển thị chúngHồ sơ. ejs
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
8Nhận dữ liệu phiên từ RedisHãy tưởng tượng bây giờ chúng ta truy cập lại vào
app.get['/', [req, res] => {
// ...
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
7. Chúng tôi có thể vào trang '/profile' mà không bị yêu cầu đăng nhập. Đó là bởi vì chúng tôi đã lưu trữ ID phiên trong trình duyệt của người dùng. Đối tượng phiên của nó có khóa và giá trị app.get['/', [req, res] => {
// ...
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
8Quá trình hoạt động như được đề cập dưới đây
Trong thiết bị đầu cuối, chúng tôi có thể sử dụng
app.get['/', [req, res] => {
// ...
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
9 để kiểm tra dữ liệu hiện có trong Redis. Ví dụ: sau khi tôi đăng nhập thành công lần đầu tiên, Redis sẽ lưu trữ dữ liệu phiênHiện tại, ID phiên cũng có thể được tìm thấy trong cookie của trình duyệt của tôi
Nếu tôi truy cập lại vào
app.get['/', [req, res] => {
// ...
const showInputError = req.flash['showInputError'][0] || false
res.render['index', { showInputError }]
}]
7, bản ghi thời gian đăng nhập sẽ tích lũy và tất cả sẽ được hiển thị trên trangĐăng xuất
Chúng ta đang gần hoàn tất
Tính năng cuối cùng là đăng xuất khi người dùng nhấp vào nút ‘Đăng xuất’. Mã ở đây khá đơn giản
app.use[
session[{
store: new RedisStore[{ client: redisClient }],
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
}]
]
9const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
1 là một phương thức trong phiên cấp tốc để hủy dữ liệu phiên và const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
2 là một chức năng để xóa một cookie cụ thể trong trình duyệt của khách hàngToàn bộ logic của ví dụ nàyTóm lược
Đây là một ví dụ cơ bản cho thấy cách triển khai xác thực dựa trên phiên với
const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
3, const users = [
{ id: 111, username: 'tom', password: '123' },
{ id: 222, username: 'chris', password: '123' },
{ id: 333, username: 'mary', password: '123' },
]
4 và ejs. Trong bài viết tiếp theo, tôi sẽ chia sẻ kiến thức của mình về cách kết nối với cơ sở dữ liệu mySQL để lưu trữ tên người dùng và mật khẩu