前后端的身份認(rèn)證

2023-1-16    前端達(dá)人

1、Web 開發(fā)模式

目前主流的 Web 開發(fā)模式有兩種,分別是:

  • 基于服務(wù)端渲染的傳統(tǒng) Web 開發(fā)模式
  • 基于前后端分離的新型 Web 開發(fā)模式

1.1、服務(wù)端渲染的 Web 開發(fā)模式

        服務(wù)端渲染的概念:服務(wù)器發(fā)送給客戶端的 HTML 頁面,是在服務(wù)器通過字符串的拼接,動(dòng)態(tài)生成的。因此,客戶端不需要使用 Ajax 這樣的技術(shù)額外請求頁面的數(shù)據(jù)。代碼示例如下:

1.2、服務(wù)端渲染的優(yōu)缺點(diǎn) 

優(yōu)點(diǎn):

  •  前端耗時(shí)少。因?yàn)榉?wù)器端負(fù)責(zé)動(dòng)態(tài)生成 HTML 內(nèi)容,瀏覽器只需要直接渲染頁面即可。尤其是移動(dòng)端,更省電。
  • 有利于SEO。因?yàn)榉?wù)器端響應(yīng)的是完整的 HTML 頁面內(nèi)容,所以爬蟲更容易爬取獲得信息,更有利于 SEO。

缺點(diǎn):

  • 占用服務(wù)器端資源。即服務(wù)器端完成 HTML 頁面內(nèi)容的拼接,如果請求較多,會對服務(wù)器造成一定的訪問壓力。
  • 不利于前后端分離,開發(fā)效率低。使用服務(wù)器端渲染,則無法進(jìn)行分工合作,尤其對于前端復(fù)雜度高的項(xiàng)目,不利于項(xiàng)目高效開發(fā)。

1.3、前后端分離的 Web 開發(fā)模式

        前后端分離的概念:前后端分離的開發(fā)模式,依賴于 Ajax 技術(shù)的廣泛應(yīng)用。簡而言之,前后端分離的 Web 開發(fā)模式,就是后端只負(fù)責(zé)提供 API 接口,前端使用 Ajax 調(diào)用接口的開發(fā)模式。

1.4、前后端分離的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  • 開發(fā)體驗(yàn)好。前端專注于 UI 頁面的開發(fā),后端專注于api 的開發(fā),且前端有更多的選擇性。
  • 用戶體驗(yàn)好。Ajax 技術(shù)的廣泛應(yīng)用,極大的提高了用戶的體驗(yàn),可以輕松實(shí)現(xiàn)頁面的局部刷新。
  • 減輕了服務(wù)器端的渲染壓力。因?yàn)轫撁孀罱K是在每個(gè)用戶的瀏覽器中生成的。

缺點(diǎn):

        不利于 SEO。因?yàn)橥暾?nbsp;HTML 頁面需要在客戶端動(dòng)態(tài)拼接完成,所以爬蟲對無法爬取頁面的有效信息。(解決方案:利用 Vue、React 等前端框架的 SSR server side render)技術(shù)能夠很好的解決 SEO 問題?。?/span>

1.5、如何選擇 Web 開發(fā)模式

不談業(yè)務(wù)場景而盲目選擇使用何種開發(fā)模式都是耍流氓。

  • 比如企業(yè)級網(wǎng)站,主要功能是展示而沒有復(fù)雜的交互,并且需要良好的 SEO,則這時(shí)我們就需要使用服務(wù)器端渲染;
  • 而類似后臺管理項(xiàng)目,交互性比較強(qiáng),不需要考慮 SEO,那么就可以使用前后端分離的開發(fā)模式。

        另外,具體使用何種開發(fā)模式并不是絕對的,為了同時(shí)兼顧首頁的渲染速度前后端分離的開發(fā)效率,一些網(wǎng)站采用了首屏服務(wù)器端渲染 其他頁面前后端分離的開發(fā)模式。

2、身份認(rèn)證

2.1、什么是身份認(rèn)證

        身份認(rèn)證Authentication)又稱“身份驗(yàn)證”、“鑒權(quán)”,是指通過一定的手段,完成對用戶身份的確認(rèn)。

  • 日常生活中的身份認(rèn)證隨處可見,例如:高鐵的驗(yàn)票乘車,手機(jī)的密碼或指紋解鎖,支付寶或微信的支付密碼等。
  • 在 Web 開發(fā)中,也涉及到用戶身份的認(rèn)證,例如:各大網(wǎng)站的手機(jī)驗(yàn)證碼登錄、郵箱密碼登錄、二維碼登錄等。

2.2、為什么需要身份認(rèn)證

        身份認(rèn)證的目的,是為了確認(rèn)當(dāng)前所聲稱為某種身份的用戶,確實(shí)是所聲稱的用戶。例如,你去找快遞員取快遞,你要怎么證明這份快遞是你的。

        在互聯(lián)網(wǎng)項(xiàng)目開發(fā)中,如何對用戶的身份進(jìn)行認(rèn)證,是一個(gè)值得深入探討的問題。例如,如何才能保證網(wǎng)站不會錯(cuò)誤的將“馬云的存款數(shù)額”顯示到“馬化騰的賬戶”上。

2.3、不同開發(fā)模式下的身份認(rèn)證

對于服務(wù)端渲染前后端分離這兩種開發(fā)模式來說,分別有著不同的身份認(rèn)證方案:

  • 服務(wù)端渲染推薦使用 Session 認(rèn)證機(jī)制
  • 前后端分離推薦使用 JWT 認(rèn)證機(jī)制

3、Session 認(rèn)證機(jī)制

3.1、HTTP 協(xié)議的無狀態(tài)性

  • 了解 HTTP 協(xié)議的無狀態(tài)性是進(jìn)一步學(xué)習(xí) Session 認(rèn)證機(jī)制的必要前提。
  • HTTP 協(xié)議的無狀態(tài)性,指的是客戶端的每次 HTTP 請求都是獨(dú)立的,連續(xù)多個(gè)請求之間沒有直接的關(guān)系,服務(wù)器不會主動(dòng)保留每次 HTTP 請求的狀態(tài)。

3.2、如何突破 HTTP 無狀態(tài)的限制 

        對于超市來說,為了方便收銀員在進(jìn)行結(jié)算時(shí)給 VIP 用戶打折,超市可以為每個(gè) VIP 用戶發(fā)放會員卡。

注意:現(xiàn)實(shí)生活中的會員卡身份認(rèn)證方式,在 Web 開發(fā)中的專業(yè)術(shù)語叫做 Cookie 

3.3、什么是 Cookie

  • Cookie 存儲在用戶瀏覽器中的一段不超過 4 KB 的字符串。它由一個(gè)名稱Name)、一個(gè)Value)和其它幾個(gè)用于控制 Cookie 有效期、安全性、使用范圍可選屬性組成。
  • 不同域名下的 Cookie 各自獨(dú)立,每當(dāng)客戶端發(fā)起請求時(shí),會自動(dòng)當(dāng)前域名下所有未過期的 Cookie 一同發(fā)送到服務(wù)器。

Cookie的幾大特性:

  1. 自動(dòng)發(fā)送
  2. 域名獨(dú)立
  3. 過期時(shí)限
  4. 4KB 限制

3.4、Cookie 在身份認(rèn)證中的作用

        客戶端第一次請求服務(wù)器的時(shí)候,服務(wù)器通過響應(yīng)頭的形式,向客戶端發(fā)送一個(gè)身份認(rèn)證的 Cookie,客戶端會自動(dòng)將 Cookie 保存在瀏覽器中。

        隨后,當(dāng)客戶端瀏覽器每次請求服務(wù)器的時(shí)候,瀏覽器會自動(dòng)將身份認(rèn)證相關(guān)的 Cookie,通過請求頭的形式發(fā)送給服務(wù)器,服務(wù)器即可驗(yàn)明客戶端的身份。

3.5、Cookie 不具有安全性 

        由于 Cookie 是存儲在瀏覽器中的,而且瀏覽器也提供了讀寫 Cookie 的 API,因此 Cookie 很容易被偽造,不具有安全性。因此不建議服務(wù)器將重要的隱私數(shù)據(jù),通過 Cookie 的形式發(fā)送給瀏覽器。

注意:千萬不要使用 Cookie 存儲重要且隱私的數(shù)據(jù)!比如用戶的身份信息、密碼等。 

3.6、提高身份認(rèn)證的安全性

        為了防止客戶偽造會員卡,收銀員在拿到客戶出示的會員卡之后,可以在收銀機(jī)上進(jìn)行刷卡認(rèn)證。只有收銀機(jī)確認(rèn)存在的會員卡,才能被正常使用。

這種“會員卡 + 刷卡認(rèn)證”的設(shè)計(jì)理念,就是 Session 認(rèn)證機(jī)制的精髓。 

3.7、Session 工作原理

 

4、在 Express 中使用 Session 認(rèn)證

4.1、安裝 express-session 中間件

在 Express 項(xiàng)目中,只需要安裝 express-session 中間件,即可在項(xiàng)目中使用 Session 認(rèn)證:

npm install express-session

4.2、配置 express-session 中間件

        express-session 中間件安裝成功后,需要通過 app.use() 注冊 session 中間件,示例代碼如下:

 
  1. // 導(dǎo)入 session 中間件
  2. const session = require('express-session')
  3. // 配置 session 中間件
  4. app.use(
  5. session({
  6. secret: 'itheima', // secret 屬性的值可以為任意字符串
  7. resave: false, // 固定寫法
  8. saveUninitialized: true, // 固定寫法
  9. })
  10. )

4.3、向 session 存數(shù)據(jù)

        當(dāng) express-session 中間件配置成功后,即可通過 req.session 來訪問和使用 session 對象,從而存儲用戶的關(guān)鍵信息:

 
  1. // 登錄的 API 接口
  2. app.post('/api/login', (req, res) => {
  3. // 判斷用戶提交的登錄信息是否正確
  4. if (req.body.username !== 'admin' || req.body.password !== '000000') {
  5. return res.send({ status: 1, msg: '登錄失敗' })
  6. }
  7. // TODO_02:請將登錄成功后的用戶信息,保存到 Session 中
  8. // 注意:只有成功配置了 express-session 這個(gè)中間件之后,才能夠通過 req 點(diǎn)出來 session 這個(gè)屬性
  9. req.session.user = req.body // 用戶的信息
  10. console.log(req.body)
  11. req.session.islogin = true // 用戶的登錄狀態(tài)
  12. res.send({ status: 0, msg: '登錄成功' })
  13. })

4.4、從 session 取數(shù)據(jù)

可以直接從 req.session 對象上獲取之前存儲的數(shù)據(jù),示例代碼如下:

 
  1. // 獲取用戶姓名的接口
  2. app.get('/api/username', (req, res) => {
  3. // TODO_03:請從 Session 中獲取用戶的名稱,響應(yīng)給客戶端
  4. if (!req.session.islogin) {
  5. return res.send({ status: 1, msg: 'fail' })
  6. }
  7. res.send({
  8. status: 0,
  9. msg: 'success',
  10. username: req.session.user.username,
  11. })
  12. })

4.5、清空 session

調(diào)用 req.session.destroy() 函數(shù),即可清空服務(wù)器保存的 session 信息。

 
  1. // 退出登錄的接口
  2. app.post('/api/logout', (req, res) => {
  3. // TODO_04:清空 Session 信息
  4. req.session.destroy()
  5. res.send({
  6. status: 0,
  7. msg: '退出登錄成功',
  8. })
  9. })

4.6、完整示例

index.html 

 
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>后臺主頁</title>
  7. <script src="./jquery.js"></script>
  8. </head>
  9. <body>
  10. <h1>首頁</h1>
  11. <button id="btnLogout">退出登錄</button>
  12. <script>
  13. $(function () {
  14. // 頁面加載完成后,自動(dòng)發(fā)起請求,獲取用戶姓名
  15. $.get('/api/username', function (res) {
  16. // status 為 0 表示獲取用戶名稱成功;否則表示獲取用戶名稱失敗!
  17. if (res.status !== 0) {
  18. alert('您尚未登錄,請登錄后再執(zhí)行此操作!')
  19. location.href = './login.html'
  20. } else {
  21. alert('歡迎您:' + res.username)
  22. }
  23. })
  24. // 點(diǎn)擊按鈕退出登錄
  25. $('#btnLogout').on('click', function () {
  26. // 發(fā)起 POST 請求,退出登錄
  27. $.post('/api/logout', function (res) {
  28. if (res.status === 0) {
  29. // 如果 status 為 0,則表示退出成功,重新跳轉(zhuǎn)到登錄頁面
  30. location.href = './login.html'
  31. }
  32. })
  33. })
  34. })
  35. </script>
  36. </body>
  37. </html>

login.html

 
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>登錄頁面</title>
  7. <script src="./jquery.js"></script>
  8. </head>
  9. <body>
  10. <!-- 登錄表單 -->
  11. <form id="form1">
  12. <div>賬號:<input type="text" name="username" autocomplete="off" /></div>
  13. <div>密碼:<input type="password" name="password" /></div>
  14. <button>登錄</button>
  15. </form>
  16. <script>
  17. $(function () {
  18. // 監(jiān)聽表單的提交事件
  19. $('#form1').on('submit', function (e) {
  20. // 阻止默認(rèn)提交行為
  21. e.preventDefault()
  22. // 發(fā)起 POST 登錄請求
  23. $.post('/api/login', $(this).serialize(), function (res) {
  24. // status 為 0 表示登錄成功;否則表示登錄失??!
  25. if (res.status === 0) {
  26. location.href = './index.html'
  27. } else {
  28. alert('登錄失敗!')
  29. }
  30. })
  31. })
  32. })
  33. </script>
  34. </body>
  35. </html>

app.js

 
  1. // 導(dǎo)入 express 模塊
  2. const express = require('express')
  3. // 創(chuàng)建 express 的服務(wù)器實(shí)例
  4. const app = express()
  5. // TODO_01:請配置 Session 中間件
  6. const session = require('express-session')
  7. app.use(
  8. session({
  9. secret: 'itheima',
  10. resave: false,
  11. saveUninitialized: true,
  12. })
  13. )
  14. // 托管靜態(tài)頁面
  15. app.use(express.static('./pages'))
  16. // 解析 POST 提交過來的表單數(shù)據(jù)
  17. app.use(express.urlencoded({ extended: false }))
  18. // 登錄的 API 接口
  19. app.post('/api/login', (req, res) => {
  20. // 判斷用戶提交的登錄信息是否正確
  21. if (req.body.username !== 'admin' || req.body.password !== '000000') {
  22. return res.send({ status: 1, msg: '登錄失敗' })
  23. }
  24. // TODO_02:請將登錄成功后的用戶信息,保存到 Session 中
  25. // 注意:只有成功配置了 express-session 這個(gè)中間件之后,才能夠通過 req 點(diǎn)出來 session 這個(gè)屬性
  26. req.session.user = req.body // 用戶的信息
  27. console.log(req.body)
  28. req.session.islogin = true // 用戶的登錄狀態(tài)
  29. res.send({ status: 0, msg: '登錄成功' })
  30. })
  31. // 獲取用戶姓名的接口
  32. app.get('/api/username', (req, res) => {
  33. // TODO_03:請從 Session 中獲取用戶的名稱,響應(yīng)給客戶端
  34. if (!req.session.islogin) {
  35. return res.send({ status: 1, msg: 'fail' })
  36. }
  37. res.send({
  38. status: 0,
  39. msg: 'success',
  40. username: req.session.user.username,
  41. })
  42. })
  43. // 退出登錄的接口
  44. app.post('/api/logout', (req, res) => {
  45. // TODO_04:清空 Session 信息
  46. req.session.destroy()
  47. res.send({
  48. status: 0,
  49. msg: '退出登錄成功',
  50. })
  51. })
  52. // 調(diào)用 app.listen 方法,指定端口號并啟動(dòng)web服務(wù)器
  53. app.listen(80, function () {
  54. console.log('Express server running at http://127.0.0.1:80')
  55. })

5、JWT 認(rèn)證機(jī)制

5.1、了解 Session 認(rèn)證的局限性

        Session 認(rèn)證機(jī)制需要配合 Cookie 才能實(shí)現(xiàn)。由于 Cookie 默認(rèn)不支持跨域訪問,所以,當(dāng)涉及到前端跨域請求后端接口的時(shí)候,需要做很多額外的配置,才能實(shí)現(xiàn)跨域 Session 認(rèn)證。

注意:

  • 當(dāng)前端請求后端接口不存在跨域問題的時(shí)候,推薦使用 Session 身份認(rèn)證機(jī)制。
  • 當(dāng)前端需要跨域請求后端接口的時(shí)候,不推薦使用 Session 身份認(rèn)證機(jī)制,推薦使用 JWT 認(rèn)證機(jī)制。

5.2、什么是 JWT

JWT(英文全稱:JSON Web Token)是目前最流行跨域認(rèn)證解決方案

5.3、JWT 工作原理

總結(jié):用戶的信息通過 Token 字符串的形式,保存在客戶端瀏覽器中。服務(wù)器通過還原 Token 字符串的形式來認(rèn)證用戶的身份。 

5.4、JWT 組成部分

JWT 通常由三部分組成,分別是 Header(頭部)、Payload(有效荷載)、Signature(簽名)。

三者之間使用英文的“.”分隔,格式如下:

下面是 JWT 字符串的示例: 

5.5、JWT 三個(gè)部分各自代表的含義 

JWT 的三個(gè)組成部分,從前到后分別是 Header、PayloadSignature

其中:

  • Payload 部分才是真正的用戶信息,它是用戶信息經(jīng)過加密之后生成的字符串。
  • Header 和 Signature 安全性相關(guān)的部分,只是為了保證 Token 的安全性。

5.6、JWT 使用方式 

客戶端收到服務(wù)器返回的 JWT 之后,通常會將它儲存在 localStorage 或 sessionStorage 中。

此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè) JWT 的字符串,從而進(jìn)行身份認(rèn)證。推薦的做法是把 JWT 放在 HTTP 請求頭的 Authorization 字段中,格式如下:

 

6、在 Express 中使用 JWT

6.1、安裝 JWT 相關(guān)的包

運(yùn)行如下命令,安裝如下兩個(gè) JWT 相關(guān)的包:

npm install jsonwebtoken express-jwt

其中:

  • jsonwebtoken 用于生成 JWT 字符串
  • express-jwt 用于將 JWT 字符串解析還原成 JSON 對象

6.2、導(dǎo)入 JWT 相關(guān)的包

使用 require() 函數(shù),分別導(dǎo)入 JWT 相關(guān)的兩個(gè)包:

 
  1. // 安裝并導(dǎo)入 JWT 相關(guān)的兩個(gè)包,分別是 jsonwebtoken 和 express-jwt
  2. const jwt = require('jsonwebtoken')
  3. const expressJWT = require('express-jwt')

6.3、定義 secret 密鑰

        為了保證 JWT 字符串的安全性,防止 JWT 字符串在網(wǎng)絡(luò)傳輸過程中被別人破解,我們需要專門定義一個(gè)用于加密解密的 secret 密鑰:

  • 當(dāng)生成 JWT 字符串的時(shí)候,需要使用 secret 密鑰對用戶的信息進(jìn)行加密,最終得到加密好的 JWT 字符串
  • 當(dāng)把 JWT 字符串解析還原成 JSON 對象的時(shí)候,需要使用 secret 密鑰進(jìn)行解密
 
  1. // 定義 secret 密鑰,建議將密鑰命名為 secretKey,本質(zhì)上就是一個(gè)字符串
  2. const secretKey = 'itheima No1 ^_^'

6.4、在登錄成功后生成 JWT 字符串

調(diào)用 jsonwebtoken 包提供的 sign() 方法,將用戶的信息加密成 JWT 字符串,響應(yīng)給客戶端:

 
  1. // 登錄接口
  2. app.post('/api/login', function (req, res) {
  3. // 將 req.body 請求體中的數(shù)據(jù),轉(zhuǎn)存為 userinfo 常量
  4. const userinfo = req.body
  5. // 登錄失敗
  6. if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
  7. return res.send({
  8. status: 400,
  9. message: '登錄失??!',
  10. })
  11. }
  12. // 登錄成功
  13. // TODO_03:在登錄成功之后,調(diào)用 jwt.sign() 方法生成 JWT 字符串。并通過 token 屬性發(fā)送給客戶端
  14. // 參數(shù)1:用戶的信息對象
  15. // 參數(shù)2:加密的秘鑰
  16. // 參數(shù)3:配置對象,可以配置當(dāng)前 token 的有效期
  17. // 記?。呵f不要把密碼加密到 token 字符中
  18. const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
  19. res.send({
  20. status: 200,
  21. message: '登錄成功!',
  22. token: tokenStr, // 要發(fā)送給客戶端的 token 字符串
  23. })
  24. })

6.5、將 JWT 字符串還原為 JSON 對象

  • 客戶端每次在訪問那些有權(quán)限接口的時(shí)候,都需要主動(dòng)通過請求頭中的 Authorization 字段,將 Token 字符串發(fā)送到服務(wù)器進(jìn)行身份認(rèn)證。
  • 此時(shí),服務(wù)器可以通過 express-jwt 這個(gè)中間件,自動(dòng)將客戶端發(fā)送過來的 Token 解析還原成 JSON 對象:
 
  1. // 使用 app.use() 來注冊中間件
  2. // expressJWT({ secret: secretKey }) 就是用來解析 Token 的中間件
  3. // .unless({ path: [/^\/api\//] }) 用來指定哪些接口不需要訪問權(quán)限
  4. app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))

6.6、使用 req.user 獲取用戶信息

當(dāng) express-jwt 這個(gè)中間件配置成功之后,即可在那些有權(quán)限的接口中,使用 req.user 對象,來訪問從 JWT 字符串中解析出來的用戶信息了,示例代碼如下: 

 
  1. // 這是一個(gè)有權(quán)限的 API 接口
  2. app.get('/admin/getinfo', function (req, res) {
  3. // TODO_05:使用 req.user 獲取用戶信息,并使用 data 屬性將用戶信息發(fā)送給客戶端
  4. console.log(req.user)
  5. res.send({
  6. status: 200,
  7. message: '獲取用戶信息成功!',
  8. data: req.user, // 要發(fā)送給客戶端的用戶信息
  9. })
  10. })

6.7、捕獲解析 JWT 失敗后產(chǎn)生的錯(cuò)誤 

藍(lán)藍(lán)設(shè)計(jì)建立了UI設(shè)計(jì)分享群,每天會分享國內(nèi)外的一些優(yōu)秀設(shè)計(jì),如果有興趣的話,可以進(jìn)入一起成長學(xué)習(xí),請加藍(lán)小助,微信號:ben_lanlan,報(bào)下信息,藍(lán)小助會請您入群。歡迎您加入噢~~希望得到建議咨詢、商務(wù)合作,也請與我們聯(lián)系01063334945。


分享此文一切功德,皆悉回向給文章原作者及眾讀者.
免責(zé)聲明:藍(lán)藍(lán)設(shè)計(jì)尊重原作者,文章的版權(quán)歸原作者。如涉及版權(quán)問題,請及時(shí)與我們?nèi)〉寐?lián)系,我們立即更正或刪除。


藍(lán)藍(lán)設(shè)計(jì)www.bouu.cn )是一家專注而深入的界面設(shè)計(jì)公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)UI設(shè)計(jì)公司、界面設(shè)計(jì)公司、UI設(shè)計(jì)服務(wù)公司、數(shù)據(jù)可視化設(shè)計(jì)公司、UI交互設(shè)計(jì)公司、高端網(wǎng)站設(shè)計(jì)公司、UI咨詢、用戶體驗(yàn)公司、軟件界面設(shè)計(jì)公司


分享本文至:

日歷

鏈接

個(gè)人資料

存檔