Cookie与Session #
一、Cookie基础 #
1.1 什么是Cookie? #
Cookie是存储在客户端的小型数据片段,用于在HTTP请求之间保持状态。
1.2 读取Cookie #
javascript
server.route({
method: 'GET',
path: '/cookies',
handler: (request, h) => {
return {
sessionId: request.state.sessionId,
preferences: request.state.preferences,
allCookies: request.state
};
}
});
1.3 设置Cookie #
javascript
server.route({
method: 'POST',
path: '/set-cookie',
handler: (request, h) => {
return h.response({ message: 'Cookie set' })
.state('sessionId', 'abc123');
}
});
二、Cookie配置 #
2.1 Cookie选项 #
javascript
server.route({
method: 'POST',
path: '/login',
handler: (request, h) => {
return h.response({ message: 'Logged in' })
.state('sessionId', 'abc123', {
ttl: 24 * 60 * 60 * 1000,
isSecure: true,
isHttpOnly: true,
path: '/',
domain: '.example.com',
sameSite: 'Strict'
});
}
});
2.2 Cookie选项说明 #
| 选项 | 说明 |
|---|---|
| ttl | 有效期(毫秒) |
| isSecure | 仅HTTPS传输 |
| isHttpOnly | 禁止JavaScript访问 |
| path | Cookie路径 |
| domain | Cookie域名 |
| sameSite | 同站策略 |
| encoding | 编码方式 |
2.3 全局Cookie配置 #
javascript
const server = Hapi.server({
port: 3000,
state: {
cookies: {
strictHeader: false,
ignoreErrors: true
}
}
});
server.state('sessionId', {
ttl: 24 * 60 * 60 * 1000,
isSecure: true,
isHttpOnly: true,
path: '/',
encoding: 'base64json'
});
三、Cookie操作 #
3.1 设置多个Cookie #
javascript
server.route({
method: 'GET',
path: '/multi-cookies',
handler: (request, h) => {
return h.response({ message: 'Cookies set' })
.state('user', 'john')
.state('theme', 'dark')
.state('language', 'zh-CN');
}
});
3.2 清除Cookie #
javascript
server.route({
method: 'POST',
path: '/logout',
handler: (request, h) => {
return h.response({ message: 'Logged out' })
.unstate('sessionId')
.unstate('user');
}
});
3.3 Cookie编码 #
javascript
server.state('data', {
encoding: 'base64json'
});
server.route({
method: 'GET',
path: '/encoded',
handler: (request, h) => {
const data = { userId: 123, role: 'admin' };
return h.response({ message: 'OK' })
.state('data', data);
}
});
四、Cookie认证 #
4.1 安装cookie插件 #
bash
npm install @hapi/cookie
4.2 配置Cookie认证 #
javascript
const Cookie = require('@hapi/cookie');
await server.register(Cookie);
server.auth.strategy('session', 'cookie', {
cookie: {
name: 'session',
password: 'password-must-be-at-least-32-characters',
isSecure: false,
ttl: 24 * 60 * 60 * 1000
},
redirectTo: '/login',
validateFunc: async (request, session) => {
const user = await User.findById(session.id);
if (!user) {
return { valid: false };
}
return {
valid: true,
credentials: user
};
}
});
server.auth.default('session');
4.3 登录路由 #
javascript
server.route({
method: 'POST',
path: '/login',
options: {
auth: false
},
handler: async (request, h) => {
const { username, password } = request.payload;
const user = await User.authenticate(username, password);
if (!user) {
throw Boom.unauthorized('Invalid credentials');
}
request.cookieAuth.set({ id: user.id });
return { message: 'Logged in', user };
}
});
4.4 登出路由 #
javascript
server.route({
method: 'POST',
path: '/logout',
handler: (request, h) => {
request.cookieAuth.clear();
return { message: 'Logged out' };
}
});
五、Session管理 #
5.1 服务端Session #
javascript
const sessions = new Map();
server.state('sessionId', {
ttl: 24 * 60 * 60 * 1000,
isSecure: true,
isHttpOnly: true
});
server.route({
method: 'POST',
path: '/login',
handler: async (request, h) => {
const { username, password } = request.payload;
const user = await authenticate(username, password);
if (!user) {
throw Boom.unauthorized('Invalid credentials');
}
const sessionId = generateSessionId();
sessions.set(sessionId, {
userId: user.id,
createdAt: Date.now()
});
return h.response({ message: 'Logged in' })
.state('sessionId', sessionId);
}
});
server.route({
method: 'GET',
path: '/profile',
handler: (request, h) => {
const sessionId = request.state.sessionId;
const session = sessions.get(sessionId);
if (!session) {
throw Boom.unauthorized('Session expired');
}
return { userId: session.userId };
}
});
5.2 Redis Session #
javascript
const Redis = require('ioredis');
const redis = new Redis();
server.route({
method: 'POST',
path: '/login',
handler: async (request, h) => {
const { username, password } = request.payload;
const user = await authenticate(username, password);
if (!user) {
throw Boom.unauthorized('Invalid credentials');
}
const sessionId = generateSessionId();
await redis.setex(
`session:${sessionId}`,
86400,
JSON.stringify({ userId: user.id })
);
return h.response({ message: 'Logged in' })
.state('sessionId', sessionId);
}
});
server.route({
method: 'GET',
path: '/profile',
handler: async (request, h) => {
const sessionId = request.state.sessionId;
const data = await redis.get(`session:${sessionId}`);
if (!data) {
throw Boom.unauthorized('Session expired');
}
const session = JSON.parse(data);
return { userId: session.userId };
}
});
六、JWT认证 #
6.1 安装JWT插件 #
bash
npm install @hapi/jwt
6.2 配置JWT认证 #
javascript
const Jwt = require('@hapi/jwt');
await server.register(Jwt);
server.auth.strategy('jwt', 'jwt', {
keys: 'your-secret-key-at-least-32-characters',
verify: {
aud: 'urn:audience:myapp',
iss: 'urn:issuer:myapp',
sub: false,
nbf: true,
exp: true,
maxAgeSec: 86400
},
validate: (artifacts, request, h) => {
return {
isValid: true,
credentials: artifacts.decoded.payload
};
}
});
6.3 登录获取Token #
javascript
const Jwt = require('@hapi/jwt');
server.route({
method: 'POST',
path: '/login',
options: {
auth: false
},
handler: async (request, h) => {
const { username, password } = request.payload;
const user = await authenticate(username, password);
if (!user) {
throw Boom.unauthorized('Invalid credentials');
}
const token = Jwt.token.generate(
{
aud: 'urn:audience:myapp',
iss: 'urn:issuer:myapp',
id: user.id,
username: user.username,
role: user.role
},
{
key: 'your-secret-key',
algorithm: 'HS256'
},
{
ttlSec: 86400
}
);
return { token, user };
}
});
6.4 使用Token访问 #
javascript
server.route({
method: 'GET',
path: '/profile',
options: {
auth: 'jwt'
},
handler: (request, h) => {
return {
user: request.auth.credentials
};
}
});
七、OAuth认证 #
7.1 安装Bell插件 #
bash
npm install @hapi/bell
7.2 配置OAuth #
javascript
const Bell = require('@hapi/bell');
await server.register(Bell);
server.auth.strategy('github', 'bell', {
provider: 'github',
password: 'cookie-encryption-password',
clientId: 'your-github-client-id',
clientSecret: 'your-github-client-secret',
isSecure: false
});
server.route({
method: ['GET', 'POST'],
path: '/login/github',
options: {
auth: 'github',
handler: (request, h) => {
if (!request.auth.isAuthenticated) {
return 'Authentication failed';
}
const profile = request.auth.credentials.profile;
return h.response({
message: 'Logged in',
user: {
id: profile.id,
username: profile.username,
email: profile.email
}
});
}
}
});
八、权限控制 #
8.1 角色检查 #
javascript
const checkRole = (role) => {
return (request, h) => {
if (request.auth.credentials.role !== role) {
throw Boom.forbidden('Insufficient permissions');
}
return h.continue;
};
};
server.route({
method: 'GET',
path: '/admin',
options: {
auth: 'jwt',
pre: [
{ method: checkRole('admin') }
]
},
handler: (request, h) => {
return { message: 'Admin area' };
}
});
8.2 权限中间件 #
javascript
server.ext('onPostAuth', (request, h) => {
const route = request.route;
if (route.settings.auth && route.settings.plugins.rbac) {
const requiredRole = route.settings.plugins.rbac.role;
const userRole = request.auth.credentials.role;
if (userRole !== requiredRole) {
throw Boom.forbidden('Insufficient permissions');
}
}
return h.continue;
});
server.route({
method: 'DELETE',
path: '/users/{id}',
options: {
auth: 'jwt',
plugins: {
rbac: {
role: 'admin'
}
}
},
handler: deleteUser
});
九、安全最佳实践 #
9.1 Cookie安全 #
javascript
server.state('sessionId', {
ttl: 24 * 60 * 60 * 1000,
isSecure: true,
isHttpOnly: true,
path: '/',
sameSite: 'Strict'
});
9.2 Token安全 #
javascript
server.route({
method: 'POST',
path: '/login',
handler: async (request, h) => {
const token = generateToken(user);
return h.response({ token })
.header('X-Content-Type-Options', 'nosniff')
.header('X-Frame-Options', 'DENY')
.header('X-XSS-Protection', '1; mode=block');
}
});
9.3 CSRF保护 #
bash
npm install @hapi/crumb
javascript
const Crumb = require('@hapi/crumb');
await server.register({
plugin: Crumb,
options: {
cookieOptions: {
isSecure: true
}
}
});
十、完整示例 #
10.1 完整认证系统 #
javascript
const Hapi = require('@hapi/hapi');
const Cookie = require('@hapi/cookie');
const Jwt = require('@hapi/jwt');
const Boom = require('@hapi/boom');
const init = async () => {
const server = Hapi.server({ port: 3000 });
await server.register([Cookie, Jwt]);
server.auth.strategy('session', 'cookie', {
cookie: {
name: 'session',
password: 'password-must-be-at-least-32-characters',
isSecure: false
},
validateFunc: async (request, session) => {
const user = await User.findById(session.id);
return { valid: !!user, credentials: user };
}
});
server.auth.strategy('jwt', 'jwt', {
keys: 'your-secret-key',
verify: { aud: false, iss: false },
validate: (artifacts) => ({
isValid: true,
credentials: artifacts.decoded.payload
})
});
server.route([
{
method: 'POST',
path: '/login',
options: { auth: false },
handler: async (request, h) => {
const { username, password } = request.payload;
const user = await authenticate(username, password);
if (!user) {
throw Boom.unauthorized('Invalid credentials');
}
request.cookieAuth.set({ id: user.id });
return { message: 'Logged in', user };
}
},
{
method: 'POST',
path: '/logout',
handler: (request, h) => {
request.cookieAuth.clear();
return { message: 'Logged out' };
}
},
{
method: 'GET',
path: '/profile',
options: { auth: 'session' },
handler: (request, h) => {
return request.auth.credentials;
}
}
]);
await server.start();
};
init();
十一、总结 #
Cookie与Session要点:
| 功能 | 方法 |
|---|---|
| 读取Cookie | request.state |
| 设置Cookie | h.response().state() |
| 清除Cookie | h.response().unstate() |
| Cookie认证 | @hapi/cookie |
| JWT认证 | @hapi/jwt |
| OAuth认证 | @hapi/bell |
下一步,让我们学习Hapi的高级特性!
最后更新:2026-03-28