TinyNote添加登陆功能

TinyNote添加登陆功能

tinynote添加登陆功能

登录页面

(1) 登录页面填写登录表单

用户在前端登录页面填写用户名和密码。
登录页面使用HTML和JavaScript构建,表单包含用户名和密码输入框。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login Page</title>
</head>
<body>

    <h2>Login</h2>
    
    <form id="loginForm">
        <!-- 用户名输入框 -->
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required>
        <br>
        <!-- 密码输入框 -->
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required>
        <br>
        <!-- 登录按钮 -->
        <button type="button" onclick="login()">Login</button>
    </form>

    <script>
        function login() {
            // 从输入框中获取用户名和密码
            var username = document.getElementById("username").value;
            var password = document.getElementById("password").value;

            // 使用fetch API向/login URL发送POST请求
            fetch('/login', {
                method: 'POST',
                // mode: 'no-cors',
                headers: {
                    'Content-Type': 'application/json; charset=UTF-8',
                },
                // 将用户名和密码作为JSON数据发送
                body: JSON.stringify({ username: username, password: password }),
            })
            .then(response => response.json())
            .then(data => {
                // 处理从服务器返回的JSON数据
                if (data.error) {
                    // 如果登录失败,记录错误消息到控制台
                    console.error("Login failed:", data.error);
                } else {
                    // 如果登录成功,记录成功消息和令牌到控制台
                    console.log("Login successful! Token:", data.token);

                    // 将令牌保存到localStorage
                    localStorage.setItem('jwtToken', data.token);

                    // 重定向到index.html
                    window.location.href = '/';
                }
            })
            .catch((error) => {
                // 捕捉发生的错误,并记录到控制台
                console.error('Error:', error);
            });
        }

        
    </script>

</body>
</html>

(2) 发送登录请求

用户点击登录按钮后,JavaScript发起POST请求到/loginURL。使用Fetch API发送请求,将用户名和密码以JSON格式发送给服务器。

#login.html
 fetch('/login', {
                method: 'POST',
                // mode: 'no-cors',
                headers: {
                    'Content-Type': 'application/json; charset=UTF-8',
                },
                body: JSON.stringify({ username: username, password: password }),
            })

(3) 后台进行用户名与密码验证

服务器后台通过Flask框架处理/loginURL的请求。
后台获取前端发送的用户名和密码,并与预先设定的用户名和密码进行比较验证。

# houtai.py
@app.route('/login', methods=['POST'])
def login():
    # 获取请求中的用户名和密码
    request_data = request.get_json()
    username = request_data.get('username')
    password = request_data.get('password')

    # 固定密码,以后有需求再改
    if USERNAME == username and PASSWORD == password:
        user_id = 1  # 假设'user_id' == 1
	# 如果用户名和密码匹配,则生成JWT令牌
        token = generate_token(user_id)
	# 返回生成的JWT令牌作为JSON响应
        return jsonify({"token": token})
    # 如果用户名和密码不匹配,则返回401状态码和错误消息
    return jsonify({"error": "Invalid credentials"}), 401

(4) 验证通过后,生成token

如果用户名和密码验证成功,后台生成JWT令牌。
令牌中包含用户ID和过期时间,使用jwt.encode方法生成

# houtai.py
def generate_token(user_id):
 # 设置令牌过期时间为当前时间加上24小时
    expiration_time = datetime.datetime.utcnow() + datetime.timedelta(hours=24)
 # 使用jwt库的encode函数生成JWT令牌
    token = jwt.encode({"user_id": user_id, "exp": expiration_time}, secret_key, algorithm="HS256")
# 返回生成的令牌
    return token

(5) 返回token

如果用户名和密码验证成功后台将生成的JWT令牌以JSON格式返回给前端。
如果用户名和密码验证失败,则返回错误信息。

# houtai.py
# ...

@app.route('/login', methods=['POST'])
def login():
    # 从JSON数据中获取用户名和密码
    request_data = request.get_json()
    username = request_data.get('username')
    password = request_data.get('password')

    # 固定密码,以后有需求再改
    if USERNAME == username and PASSWORD == password:
        user_id = 1  # 假设'user_id' == 1
	#通过调用generate_token函数生成JWT令牌,用于身份验证。
        token = generate_token(user_id)
	#返回包含JWT令牌的JSON响应,表示登录成功。
        return jsonify({"token": token})

    return jsonify({"error": "Invalid credentials"}), 401

(6) 前端处理登录结果

前端通过JavaScript处理后台返回的数据。
如果登录失败,记录错误消息到控制台
如果登录成功,记录成功消息和令牌到控制台,并将令牌保存到localStorage然后跳转到主页。

<!-- login.html -->
<!-- ... -->

<script>
    function login() {
        // 从输入框中获取用户名和密码
        var username = document.getElementById("username").value;
        var password = document.getElementById("password").value;

        // 使用fetch API向/login端点发送POST请求
        fetch('/login', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json; charset=UTF-8',
            },
            body: JSON.stringify({ username: username, password: password }),
        })
        .then(response => response.json())
        .then(data => {
            // 处理从服务器返回的JSON数据
            if (data.error) {
                // 如果登录失败,记录错误消息到控制台
                console.error("Login failed:", data.error);
            } else {
                // 如果登录成功,记录成功消息和令牌到控制台
                console.log("Login successful! Token:", data.token);

                // 将令牌保存到localStorage
                localStorage.setItem('jwtToken', data.token);

                // 重定向到index.html
                window.location.href = '/';
            }
        })
        .catch((error) => {
            // 捕捉发生的错误,并记录到控制台
            console.error('Error:', error);
        });
    }       
</script>
<!-- ... -->

受保护的请求 (添加token验证)

(1) 获取日记列表

index.html包含了日记内容的显示区域、表单用于提交新的日记,以及获取日记列表的JavaScript代码。

#index.html
<body>
    <div class="container">     
        <!-- 日记表单 -->
        <form id="diary-form">
            <!-- 日记内容输入框 -->
            <textarea id="diary-content"></textarea>
            <br>
            <!-- 保存按钮 -->
            <button type="submit">Save</button>
        </form>

        <!-- 用于显示日记列表的div -->
        <div id="diary-list"></div>

        <!-- 引入index.js脚本 -->
        <script src="index.js"></script>
    </div>
</body>

(2) 获取JWT令牌发送GET请求

获取存储在localStorage中的JWT令牌,将JWT令牌包含在请求头中,向服务器的/get_diaries发送GET请求。

#index.js
// 获取存储在localStorage中的JWT令牌
token = localStorage.getItem('jwtToken');

// 向服务器发起GET请求获取日记列表
fetch('/get_diaries', {
    headers: {
        'Authorization': token,
    }
})
// 处理响应
.then(response => {
    if (response.status === 401) {
        // 如果返回状态码为401,表示未认证,将页面重定向到登录页面
        window.location.href = '/login.html';
    }
    return response.json();
})
.then(diaries => {
    // 在页面上显示日记列表
    const diaryList = document.getElementById('diary-list');
    diaries.forEach((diary, index) => {
        // 创建日记内容显示区域
        const pre = document.createElement('pre');
        pre.textContent = diary.content;

        // 创建删除按钮
        const delButton = document.createElement('button');
        delButton.textContent = '删除';
        delButton.onclick = () => {
            // 发送删除请求到后台
            fetch(`/delete_diary/${index}`, { method: 'DELETE' })
                .then(() => {
                    // 从页面上删除该日记
                    diaryList.removeChild(pre);
                    alert('删除成功!');
                    location.reload();
                })
                .catch(error => console.error('删除失败:', error));
        };
        pre.appendChild(delButton);

        // 创建行号
        const lineNumber = document.createElement('span');
        lineNumber.textContent = `(${index + 1}) `;
        lineNumber.style.color = '#999';
        lineNumber.style.fontSize = '0.8em';
        pre.insertBefore(lineNumber, pre.firstChild);

        diaryList.appendChild(pre);
    });
})
.catch(error => console.error('获取日记列表失败:', error));

(3) 认证处理

在后台,/get_diaries通过验证请求头中的token实现认证。如果认证失败,返回401状态码。

# houtai.py
# ...

@app.route('/get_diaries', methods=['GET'])
def get_diaries():
    token = request.headers.get('Authorization')

    if not token:
        return jsonify({"error": "Token is missing"}), 401

    decoded_data = verify_token(token)

    if not decoded_data:
        return jsonify({"error": "Token is invalid or expired"}), 401
    
    # 从文件中读取所有日记
    with open(CONF.DIARY_CSV_DIR, 'r+', newline='',encoding='utf-8') as file:
        reader = csv.reader(file, delimiter=',')  
        diaries = list(reader)
        diaries = [{'content': line[0], 'lineNumber': index+1}
                   for index, line in enumerate(diaries[1:])]

    # 返回日记列表
    return jsonify(diaries)

(4) 处理401响应

在前端,当收到401状态码时,跳转到登录页面。

.then(response => {
        if (response.status === 401) {
            console.error(response)
            // Redirect to the login page or handle unauthorized access as needed
            window.location.href = '/login.html'; 
        }
        return response.json();
    })

(5) github地址

https://github.com/99fukong/web/commit/2ba7ce5fc97cd6b30bea4bceec7d785231337cf4

LICENSED UNDER CC BY-NC-SA 4.0
Comment