大家好!今天咱们来聊聊 Vue SSR 应用中 Cookie、Session 和用户身份验证那些事儿,保证让大家听完之后,感觉这东西也没那么神秘。咱们争取用最通俗易懂的语言,加上实实在在的代码,把这些概念掰开了、揉碎了,彻底搞明白。
开场白:SSR 的世界,水有点深
SSR(Server-Side Rendering,服务端渲染)是个好东西,能提升 SEO,改善首屏加载速度。但是,一旦涉及到 Cookie、Session 和用户身份验证,就开始有点头疼了。为啥呢?因为 SSR 意味着你的代码要在服务器和客户端两个地方跑,状态同步就成了个麻烦事。
第一幕:Cookie,是谁偷走了我的身份?
Cookie 这玩意儿,大家应该都不陌生,它就像个小纸条,浏览器会帮你记住一些信息,下次再访问的时候,直接带上这个小纸条,服务器就能认出你来了。
-
客户端设置 Cookie:
// 在 Vue 组件中 document.cookie = "username=John Doe; expires=Thu, 18 Dec 2024 12:00:00 UTC; path=/";
这段代码会在用户的浏览器里设置一个名为
username
的 Cookie,值为John Doe
,过期时间是 2024 年 12 月 18 日,路径是根目录/
。 -
服务器端读取 Cookie:
在 Node.js (Express) 中,我们可以使用
cookie-parser
中间件来方便地读取 Cookie:const express = require('express'); const cookieParser = require('cookie-parser'); const app = express(); app.use(cookieParser()); app.get('/', (req, res) => { const username = req.cookies.username; res.send(`Hello ${username || 'Guest'}!`); }); app.listen(3000, () => console.log('Server listening on port 3000'));
这样,服务器就能从
req.cookies
对象里拿到客户端发来的 Cookie 了。
SSR 中 Cookie 的陷阱
在 SSR 应用中,直接使用 document.cookie
往往行不通。因为在服务器端,压根就没有 document
这个东西。所以,我们需要换个思路。
-
服务器端设置 Cookie:
在服务器端,我们需要使用
res.setHeader
方法来设置 Cookie:// 在 Vue SSR 的服务器端代码中 const Vue = require('vue'); const renderer = require('vue-server-renderer').createRenderer(); const express = require('express'); const app = express(); app.get('*', (req, res) => { const app = new Vue({ data: { url: req.url }, template: `<div>访问的 URL 是: {{ url }}</div>` }) renderer.renderToString(app, (err, html) => { if (err) { res.status(500).end('Internal Server Error') return } // 设置 Cookie res.setHeader('Set-Cookie', 'serverCookie=SSRvalue; Path=/'); res.end(` <!DOCTYPE html> <html lang="en"> <head><title>Hello</title></head> <body>${html}</body> </html> `) }) }) app.listen(8080, () => { console.log('server started at localhost:8080') })
这样,服务器就会在响应头里加上
Set-Cookie
字段,告诉浏览器要设置 Cookie。 -
客户端读取服务器端设置的 Cookie:
服务器端设置的 Cookie 会自动被浏览器保存,下次客户端发起请求时,会自动带上这些 Cookie。所以,在客户端,我们仍然可以使用
document.cookie
来读取 Cookie。
第二幕:Session,你是谁?从哪儿来?
Session 比 Cookie 更高级一点,它把用户的身份信息保存在服务器端,然后在 Cookie 里存一个 Session ID,下次用户来的时候,服务器只需要根据 Session ID 就能找到用户的身份信息了。
-
Session 的工作流程:
- 用户第一次访问服务器,服务器创建一个 Session,生成一个 Session ID,并把 Session ID 放在 Cookie 里发送给浏览器。
- 浏览器保存 Session ID。
- 用户再次访问服务器,浏览器会自动带上 Session ID。
- 服务器根据 Session ID 找到对应的 Session,从而识别用户身份。
-
使用
express-session
创建 Session:const express = require('express'); const session = require('express-session'); const app = express(); app.use(session({ secret: 'your secret here', // 用于加密 Session ID 的密钥,务必修改 resave: false, saveUninitialized: true, cookie: { secure: false } // 在 HTTPS 环境下设置为 true })); app.get('/', (req, res) => { if (req.session.views) { req.session.views++; res.send(`You visited this page ${req.session.views} times`); } else { req.session.views = 1; res.send('Welcome to this page for the first time!'); } }); app.listen(3000, () => console.log('Server listening on port 3000'));
这段代码会创建一个 Session,并把访问次数保存在 Session 里。
SSR 中 Session 的挑战
在 SSR 应用中,Session 的问题在于,服务器端渲染的时候,我们可能需要访问 Session 中的数据,比如判断用户是否登录,然后根据登录状态渲染不同的页面。
-
在服务器端访问 Session:
在 Vue SSR 的服务器端代码中,我们可以通过
req.session
对象来访问 Session:// 在 Vue SSR 的服务器端代码中 app.get('*', (req, res) => { const user = req.session.user; // 从 Session 中获取用户信息 const app = new Vue({ data: { isLoggedIn: !!user // 根据用户信息判断是否登录 }, template: `<div> <p v-if="isLoggedIn">Welcome, user!</p> <p v-else>Please log in.</p> </div>` }); renderer.renderToString(app, (err, html) => { if (err) { res.status(500).end('Internal Server Error'); return; } res.end(` <!DOCTYPE html> <html lang="en"> <head><title>Hello</title></head> <body>${html}</body> </html> `); }); });
这样,服务器端就可以根据 Session 中的用户信息来渲染页面了。
-
客户端同步 Session 状态:
虽然服务器端已经根据 Session 渲染了页面,但是客户端的 Vue 应用并不知道 Session 的状态。所以,我们需要在客户端同步 Session 的状态。
-
方案一:在服务器端把 Session 数据注入到 Vuex 中。
// 在 Vue SSR 的服务器端代码中 app.get('*', (req, res) => { const user = req.session.user; const vuexState = { user: user || null // 将用户信息注入到 Vuex 的 state 中 }; const app = new Vue({ store, // 假设你已经创建了一个 Vuex store data: { isLoggedIn: !!user }, template: `<div> <p v-if="isLoggedIn">Welcome, user!</p> <p v-else>Please log in.</p> </div>` }); // 在渲染之前,设置 Vuex 的 state store.replaceState(vuexState); renderer.renderToString(app, (err, html) => { if (err) { res.status(500).end('Internal Server Error'); return; } res.end(` <!DOCTYPE html> <html lang="en"> <head><title>Hello</title> <script> window.__INITIAL_STATE__ = ${JSON.stringify(vuexState)} </script> </head> <body>${html}</body> </html> `); }); }); // 在客户端,在 Vuex store 创建之后,从 window.__INITIAL_STATE__ 中恢复 state const store = new Vuex.Store({ state: {}, mutations: {} }); if (typeof window !== 'undefined' && window.__INITIAL_STATE__) { store.replaceState(window.__INITIAL_STATE__); }
这样,客户端在创建 Vue 应用的时候,就可以从
window.__INITIAL_STATE__
中拿到服务器端注入的 Session 数据,并将其恢复到 Vuex 中。 -
方案二:在客户端发起一个请求,获取 Session 数据。
// 在客户端 Vue.prototype.$getSession = () => { return axios.get('/api/session') .then(response => { return response.data; }); }; // 在组件中使用 mounted() { this.$getSession().then(session => { this.user = session.user; }); }
这种方法比较简单,但是会增加一次 HTTP 请求。
-
第三幕:用户身份验证,你是谁?凭什么访问我的资源?
用户身份验证是保护应用安全的重要手段。一般来说,我们会使用用户名和密码来验证用户身份。
-
用户身份验证的流程:
- 用户提交用户名和密码。
- 服务器验证用户名和密码是否正确。
- 如果验证通过,服务器创建一个 Session,并把用户信息保存在 Session 里。
- 服务器在 Cookie 里设置 Session ID。
- 用户下次访问服务器,浏览器会自动带上 Session ID。
- 服务器根据 Session ID 找到对应的 Session,从而识别用户身份。
-
使用 Passport.js 进行用户身份验证:
Passport.js 是一个流行的 Node.js 身份验证中间件,它支持多种身份验证策略,比如本地用户名密码验证、OAuth 验证等等。
const express = require('express'); const session = require('express-session'); const passport = require('passport'); const LocalStrategy = require('passport-local').Strategy; const app = express(); // 配置 Session app.use(session({ secret: 'your secret here', resave: false, saveUninitialized: false })); // 初始化 Passport app.use(passport.initialize()); app.use(passport.session()); // 配置本地策略 passport.use(new LocalStrategy( (username, password, done) => { // 在这里验证用户名和密码 if (username === 'admin' && password === 'password') { const user = { id: 1, username: 'admin' }; return done(null, user); } else { return done(null, false, { message: 'Incorrect username or password.' }); } } )); // 序列化用户 passport.serializeUser((user, done) => { done(null, user.id); }); // 反序列化用户 passport.deserializeUser((id, done) => { // 在这里根据 ID 从数据库中查找用户 const user = { id: 1, username: 'admin' }; done(null, user); }); // 定义登录路由 app.post('/login', passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login', failureFlash: true }) ); // 定义登出路由 app.get('/logout', (req, res) => { req.logout(); res.redirect('/'); }); // 保护路由 function ensureAuthenticated(req, res, next) { if (req.isAuthenticated()) { return next(); } res.redirect('/login'); } app.get('/', ensureAuthenticated, (req, res) => { res.send('Welcome to the protected area!'); }); app.listen(3000, () => console.log('Server listening on port 3000'));
这段代码使用 Passport.js 实现了本地用户名密码验证。
SSR 中用户身份验证的策略
在 SSR 应用中,用户身份验证的策略和 Session 的策略类似,我们需要在服务器端验证用户身份,并在客户端同步用户身份状态。
-
服务器端验证用户身份:
在 Vue SSR 的服务器端代码中,我们可以使用 Passport.js 来验证用户身份:
// 在 Vue SSR 的服务器端代码中 app.get('*', (req, res) => { // 使用 ensureAuthenticated 中间件来保护路由 ensureAuthenticated(req, res, () => { const user = req.user; // 从 req.user 中获取用户信息 const app = new Vue({ data: { isLoggedIn: !!user }, template: `<div> <p v-if="isLoggedIn">Welcome, user!</p> <p v-else>Please log in.</p> </div>` }); renderer.renderToString(app, (err, html) => { if (err) { res.status(500).end('Internal Server Error'); return; } res.end(` <!DOCTYPE html> <html lang="en"> <head><title>Hello</title></head> <body>${html}</body> </html> `); }); }); });
-
客户端同步用户身份状态:
和 Session 一样,我们需要在客户端同步用户身份状态。可以使用之前提到的两种方法:注入 Vuex 或者发起 HTTP 请求。
总结:SSR 中 Cookie、Session 和用户身份验证的注意事项
问题 | 解决方案 | 注意事项 |
---|---|---|
服务器端设置 Cookie | 使用 res.setHeader('Set-Cookie', ...) |
Cookie 的 Path 属性要设置正确,否则客户端可能无法读取 Cookie。 |
服务器端读取 Cookie | 使用 cookie-parser 中间件,通过 req.cookies 对象读取 Cookie。 |
|
服务器端访问 Session | 使用 express-session 中间件,通过 req.session 对象访问 Session。 |
secret 密钥务必修改,cookie.secure 属性在 HTTPS 环境下设置为 true 。 |
客户端同步 Session 状态 | 1. 在服务器端把 Session 数据注入到 Vuex 中。 2. 在客户端发起一个请求,获取 Session 数据。 | 第一种方法性能更好,但是实现起来稍微复杂一点。 |
用户身份验证 | 使用 Passport.js 中间件,配置不同的身份验证策略。 | 要注意保护路由,只有登录用户才能访问。 |
CSRF 攻击 | 使用 CSRF 保护中间件,比如 csurf 。 |
CSRF 攻击是一种常见的 Web 安全漏洞,可以伪造用户请求,造成安全问题。 |
XSS 攻击 | 对用户输入进行转义,避免 XSS 攻击。 | XSS 攻击是一种常见的 Web 安全漏洞,可以注入恶意脚本到页面中,窃取用户信息或者进行其他恶意操作。 |
安全性 | 务必使用 HTTPS,保护 Cookie 和 Session ID 不被窃取。 | |
性能 | 尽量减少 Cookie 的大小,避免影响性能。 | |
跨域问题 | 如果你的 API 和前端应用不在同一个域名下,需要处理跨域问题。可以使用 CORS 中间件,或者使用 JSONP。 |
结语:路漫漫其修远兮,吾将上下而求索
Cookie、Session 和用户身份验证是 Web 开发中非常重要的概念,在 SSR 应用中,我们需要特别注意服务器端和客户端的状态同步问题。希望今天的讲座能帮助大家更好地理解这些概念,并在实际项目中灵活应用。
当然,这只是一个入门级别的介绍,还有很多高级的用法和技巧,比如使用 JWT(JSON Web Token)进行身份验证,使用 Redis 存储 Session 数据等等,需要大家在实践中不断学习和探索。
记住,编程之路没有终点,只有不断学习和进步,才能成为真正的编程专家!加油!