为什么需要登录凭证?
web开发中,我们使用的协议http是无状态协议,http每次请求都是一个单独的请求,和之前的请求没有关系,服务器就不知道上一步你做了什么操作,我们需要一个办法证明我没登录过
制作登录凭证
制作登录凭证的过程主要涉及到用户身份验证和信息的存储与验证。以下是制作登录凭证的基本步骤:
- 设计登录界面:首先,你需要设计一个用户登录的界面,这个界面通常包含输入用户名和密码的字段,以及一个提交按钮。
- 用户输入信息:用户在登录界面输入用户名和密码,然后点击提交按钮。
- 后台验证:服务器或后台系统接收到用户提交的用户名和密码后,会进行验证。验证过程通常包括检查用户名和密码是否匹配数据库中存储的信息。
- 生成登录凭证:如果用户名和密码验证通过,服务器会生成一个登录凭证。这个凭证通常是一个包含用户信息和认证信息的令牌(Token),它可以是服务器生成的Session ID,也可以是JWT(JSON Web Token)等形式的令牌。
- 返回登录凭证:服务器将生成的登录凭证返回给客户端,客户端通常会将其存储在本地,如浏览器的Cookie或LocalStorage中。
- 后续请求验证:在用户后续的请求中,客户端会附带这个登录凭证,服务器会根据这个凭证来验证用户的身份和权限。
在这个过程中,确保数据传输的安全性是非常重要的。通常,用户名和密码的传输会使用HTTPS等加密协议进行保护,以防止信息泄露。同时,对于登录凭证的存储和使用,也需要采取适当的安全措施,以防止被恶意利用。
请注意,具体的实现方式会根据你使用的技术栈和框架有所不同。例如,如果你使用的是Spring Security等安全框架,它通常会提供一套完整的用户认证和授权机制,包括登录凭证的生成和管理。在实际开发中,建议参考你所使用的技术栈和框架的官方文档或相关教程,以获取更具体和详细的指导。
1.cookie(一般服务器来设置,现在使用不多)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>客户端的网站</h2>
<button>设置cookie</button>
</body>
<script>
/**
* 在浏览器通过js设置cookie(很少用)
* 没设置max-age(过期时间,/毫秒),是内存cookie
* 设置max-age是硬盘cookie
*/
const btnE1=document.querySelector('button')
btnE1.onclick=function(){
document.cookie='age=12'
document.cookie='name=why;max-age=30'
}
</script>
</html>
服务端设置node
cookie
const Koa = require('koa');
const Router = require('@koa/router');
const app = new Koa();
const router = new Router({ prefix: '/users' });
router.get('/login', (ctx, next) => {
ctx.cookies.set('username', 'zs', {
maxAge: 24 * 60 * 60 * 1000,
})
ctx.body = 'users page';
});
router.get('/list', (ctx, next) => {
const value = ctx.cookies.get('username');
console.log(value)
if (value === 'zs') {
ctx.body = `userslist--${value}`;
} else {
ctx.body = '没有权限,请先登录';
}
});
app.use(router.routes())
app.use(router.allowedMethods());
app.listen(3004, () => {
console.log('服务器启动成功')
}
)
cookie-sessionID
相当于session的升级,加密,双重加密
const Koa = require('koa');
const Router = require('@koa/router');
const koasession = require('koa-session');
const app = new Koa();
const router = new Router({ prefix: '/users' });
// ---------
const session = koasession({
key: 'SESSION_ID',
maxAge: 60 * 60 * 1000,
signed: true, //加密
}, app)
app.keys = ['some secret hurr']; //加盐操作,随便写点值,相当于双重认证
app.use(session);
// ---------
router.get('/login', (ctx, next) => {
ctx.session.yan = 'ikub'
ctx.body = '登录成功';
});
router.get('/list', (ctx, next) => {
const value = ctx.session.yan
console.log(value)
});
app.use(router.routes())
app.use(router.allowedMethods());
app.listen(3004, () => {
console.log('服务器启动成功')
}
)
为什么cookie | cookie-sessionID不常用了?
Cookie和Session都是在Web开发中常用的技术,用于在用户访问网站时跟踪和存储用户信息。它们虽然都用于此目的,但它们在实现方式、存储位置、安全性等方面存在显著差异。
- 实现方式和存储位置:
- Cookie是服务器在本地机器上存储的小段文本,并随每一个请求发送至同一服务器。它主要保存在客户端,即用户的浏览器上。
- Session则是保存在服务器端的,是一种用来存放用户数据的类HashTable结构。当浏览器首次发送请求时,服务器会生成一个HashTable和一个Session ID,并通过响应发送给浏览器。之后的请求中,浏览器会将这个Session ID包含在请求中,以便服务器识别并恢复对应的用户会话。
- 存储内容:
- Cookie只能存储ASCII字符串,需要通过编码方式才能存储为Unicode字符或者二进制数据。
- Session则能够存储任何类型的数据,包括string、integer、list、map等。
- 安全性:
- Cookie对客户端是可见的,因此存在被分析、窃取或进行cookie欺骗的风险,这使得它在安全性方面相对较弱。
- Session存储在服务器上,对客户端是透明的,因此不存在敏感信息泄漏的风险,安全性相对较高。
- 有效期:
- 通过设置cookie的属性,可以实现cookie的长期有效。
- 它的过期时间通常是由服务器设置的,并且可以是一个较长的固定时间段,比如几小时或几天,这取决于服务器端的配置。
- 服务器压力:
- 由于cookie保存在客户端,不占用服务器资源,因此对服务器的压力较小。
- Session保存在服务器端,每个用户的会话信息都需要在服务器上维护,因此当用户量较大时,可能会对服务器造成较大压力。
总的来说,Cookie和Session在Web开发中各有其用途。Cookie主要用于在客户端保存一些用户信息,如登录状态等,而Session则主要用于在服务器端保存用户的会话信息。它们之间的选择通常取决于具体的应用场景和需求。
--
当结合使用Cookie和Session时,关闭浏览器页面并不一定会销毁Session。实际上,Session的销毁与浏览器的关闭行为并没有直接的联系,而是取决于Session的过期时间设置以及服务器的Session管理策略。
以下是关于Session销毁的一些关键点:
-
过期时间:Session通常会有一个过期时间,这是服务器设置的。当Session达到这个过期时间后,即使浏览器仍然打开,Session也会被自动销毁。过期时间的设置可以在服务器端进行配置,常见的设置方式包括基于固定时间间隔的过期(如30分钟)或基于用户活动的过期(如用户最后一次操作后的某个时间)。
-
服务器管理:服务器负责管理和跟踪所有的Session。当服务器检测到某个Session已经过期或者由于其他原因(如资源限制、服务器重启等)需要销毁时,它会主动销毁这个Session。
-
浏览器关闭:关闭浏览器只是断开了客户端与服务器之间的连接,它并不会直接销毁服务器端的Session。也就是说,即使你关闭了浏览器,服务器仍然会保留对应的Session信息,直到Session过期或被服务器主动销毁。
-
手动销毁:在某些情况下,你可能需要在服务器端手动销毁某个Session,比如用户明确退出登录或者管理员强制结束某个会话。这通常可以通过调用服务器端的Session管理API来实现。
-
Cookie与Session的关系:Cookie在这里扮演的是传递Session ID的角色。即使你关闭了浏览器,只要Cookie中的Session ID没有过期并且没有被清除,下次你打开浏览器并访问同一网站时,浏览器仍然会发送包含这个Session ID的Cookie给服务器,从而恢复之前的会话。
因此,结合使用Cookie和Session时,关闭浏览器页面并不会直接导致Session的销毁。要销毁Session,需要依赖于服务器端的Session管理策略和过期时间的设置。
jwt生成token
token
const Koa = require('koa');
const Router = require('@koa/router');
const jwt = require('jsonwebtoken');
const app = new Koa();
const router = new Router({ prefix: '/users' });
const secret = 'secretxxx';
router.get('/login', (ctx, next) => {
//颁发token
/**
* payload: string | object | Buffer,
* secretOrPrivateKey: jwt.Secret,
* options?: jwt.SignOptions | undefined): string (+4 overloads)
*/
const token = jwt.sign(
{ id: 1 },
secret,
{ expiresIn: '1h' });
ctx.body = {
token,
message: '成功'
};
});
router.get('/list', (ctx, next) => {
const authorization = ctx.headers.authorization;
const token = authorization.replace('Bearer ', '');
console.log(token)
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNzEwMTI1MTE4LCJleHAiOjE3MTAxMjg3MTh9.s3nYx08tvwTk086cNkZK-0_KQ-ytBBfothOMlOOuclw
//验证token
try {
const result = jwt.verify(token, secret);
console.log(result)
//{ id: 1, iat: 1710125118, exp: 1710128718 }
//验证成功去数据库查数据返回数据
ctx.body = {
message: '成功',
code: 0,
list: [
{ name: 'zs', age: 18 },
{ name: 'ls', age: 20 }
]
}
} catch (error) {
ctx.body = {
message: 'token过期||无效的token',
code: 1,
}
}
});
app.use(router.routes())
app.use(router.allowedMethods());
app.listen(3004, () => {
console.log('服务器启动成功')
}
)
算法:
HS256 =>针对对称加密
const Koa = require('koa');
const Router = require('@koa/router');
const jwt = require('jsonwebtoken');
const app = new Koa();
const router = new Router({ prefix: '/users' });
const secret = 'secretxxx';
router.get('/login', (ctx, next) => {
//颁发token
/**
* payload: string | object | Buffer,
* secretOrPrivateKey: jwt.Secret,
* options?: jwt.SignOptions | undefined): string (+4 overloads)
*/
const token = jwt.sign(
{ id: 1 },
secret,
{ expiresIn: '1h' });
ctx.body = {
token,
message: '成功'
};
});
router.get('/list', (ctx, next) => {
const authorization = ctx.headers.authorization;
const token = authorization.replace('Bearer ', '');
console.log(token)
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNzEwMTI1MTE4LCJleHAiOjE3MTAxMjg3MTh9.s3nYx08tvwTk086cNkZK-0_KQ-ytBBfothOMlOOuclw
//验证token
try {
const result = jwt.verify(token, secret);
console.log(result)
//{ id: 1, iat: 1710125118, exp: 1710128718 }
//验证成功去数据库查数据返回数据
ctx.body = {
message: '成功',
code: 0,
list: [
{ name: 'zs', age: 18 },
{ name: 'ls', age: 20 }
]
}
} catch (error) {
ctx.body = {
message: 'token过期||无效的token',
code: 1,
}
}
});
app.use(router.routes())
app.use(router.allowedMethods());
app.listen(3004, () => {
console.log('服务器启动成功')
}
)
RS256=>针对非对称加密
使用git bash 终端
要生成RSA私钥和公钥,你可以使用OpenSSL这个强大的工具。以下是如何使用OpenSSL生成一个私钥和对应的公钥的步骤:
- 生成私钥
首先,你需要生成一个私钥。在命令行中,使用以下命令:
bash复制代码
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 |
这个命令会生成一个2048位的RSA私钥,并将其保存在private_key.pem
文件中。
- 从私钥中提取公钥
有了私钥之后,你可以从中提取出对应的公钥。使用以下命令:
bash复制代码
openssl rsa -pubout -in private_key.pem -out public_key.pem |
这个命令会从private_key.pem
文件中读取私钥,并提取出公钥,然后将公钥保存到public_key.pem
文件中。
现在,你拥有了一个私钥文件private_key.pem
和一个公钥文件public_key.pem
。
注意:
- 私钥是保密的,只有你自己或你信任的系统应该能够访问它。不要将私钥泄露给任何人。
- 公钥可以公开分享,用于验证由私钥签名的数据。
- 在生产环境中,你应该使用安全的方式来存储和管理私钥,例如使用硬件安全模块(HSM)或密钥管理服务(KMS)。
如果你正在开发一个需要用到这些密钥的应用程序,你需要在你的代码中安全地加载这些密钥文件。确保你的应用程序不会将这些文件的内容暴露给未经授权的用户或系统
const Koa = require('koa');
const Router = require('@koa/router');
const jwt = require('jsonwebtoken');
const fs = require('fs');
const app = new Koa();
const router = new Router({ prefix: '/users' });
// 颁发私钥,验证私钥
const privateKey = fs.readFileSync('./keys/private_key.pem');
const publicKey = fs.readFileSync('./keys/public_key.pem');
router.get('/login', (ctx, next) => {
//颁发token
/**
* payload: string | object | Buffer,
* secretOrPrivateKey: jwt.Secret,
* options?: jwt.SignOptions | undefined): string (+4 overloads)
*/
const token = jwt.sign({ id: 1 }, privateKey,
{
expiresIn: '1h',
algorithm: 'RS256' //签名算法,默认为HS256
});
ctx.body = {
token,
message: '成功'
};
});
router.get('/list', (ctx, next) => {
const authorization = ctx.headers.authorization;
const token = authorization.replace('Bearer ', '');
console.log(token)
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNzEwMTI1MTE4LCJleHAiOjE3MTAxMjg3MTh9.s3nYx08tvwTk086cNkZK-0_KQ-ytBBfothOMlOOuclw
//验证token
try {
const result = jwt.verify(token, publicKey, {
algorithms: ['RS256'] //签名算法,默认为HS256
});
console.log(result)
//{ id: 1, iat: 1710125118, exp: 1710128718 }
//验证成功去数据库查数据返回数据
ctx.body = {
message: '成功',
code: 0,
list: [
{ name: 'zs', age: 18 },
{ name: 'ls', age: 20 }
]
}
} catch (error) {
ctx.body = {
message: 'token过期||无效的token',
code: 1,
}
}
});
app.use(router.routes())
app.use(router.allowedMethods());
app.listen(3004, () => {
console.log('服务器启动成功')
}
)
前端获取token
后端将 token 返回给前端通常是通过 HTTP 响应来完成的。在用户成功登录或其他需要身份验证的操作后,后端服务器会生成一个 token,并将其包含在 HTTP 响应的头部或正文中返回给前端。下面是一个基本的流程说明:
1. 发送请求到后端
前端应用(例如,一个使用 JavaScript、React、Vue.js 或 Angular 构建的单页应用)会向后端发送一个请求。这个请求通常是用户登录请求,其中包含了用户的凭据(如用户名和密码)。
2. 后端验证凭据并生成 Token
后端服务器接收到请求后,会验证提供的凭据。如果凭据有效,服务器会生成一个 token。这个 token 通常是一个加密的字符串,包含了用户的身份信息以及有效期等信息。常见的 token 格式是 JWT(JSON Web Token)。
3. 后端将 Token 返回给前端
后端有多种方式将生成的 token 返回给前端:
- HTTP 响应头:将 token 设置在 HTTP 响应的某个自定义头部中,例如
Authorization
或X-Auth-Token
。 - HTTP 响应体:将 token 作为 JSON 对象的一部分返回在响应的正文中。这通常用于 RESTful API 的响应。
- Cookie:在某些情况下,后端可能将 token 设置为 Cookie 并返回给前端。这种方法在使用传统的服务器端渲染或需要跨域共享 token 时可能更为合适。
示例:使用 HTTP 响应头返回 Token
假设后端使用 Express.js(一个 Node.js 框架)来构建 API,以下是一个简单的示例,展示如何在登录成功后将 token 返回给前端:
const express = require('express');
const jwt = require('jsonwebtoken'); // 假设使用 jwt-simple 或 jsonwebtoken 库来生成 token
const app = express();
// 模拟用户验证函数
function authenticateUser(username, password) {
// 在这里进行实际的用户验证逻辑
// 如果验证成功,返回用户信息;否则返回 null 或抛出错误
return { id: 1, username }; // 示例用户信息
}
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = authenticateUser(username, password);
if (user) {
// 生成 token,假设 secret 是你的密钥
const token = jwt.sign(user, 'your_secret_key', { expiresIn: '1h' });
// 将 token 设置在响应头中返回给前端
res.setHeader('Authorization', `Bearer ${token}`);
res.send({ message: 'Login successful' });
} else {
res.status(401).send({ message: 'Invalid credentials' });
}
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
在这个例子中,当用户通过 POST 请求 /login
路由并提供有效的用户名和密码时,服务器会生成一个 JWT token,并将其作为 Authorization
响应头的一部分返回给前端。前端可以解析这个响应头,提取 token,并将其存储在本地(如 localStorage),以便在后续的请求中使用。
前端处理响应并存储 Token
前端接收到响应后,需要解析响应头或响应体,提取出 token,并将其存储在适当的位置。以下是一个使用 JavaScript Fetch API 和 async/await 的简单示例:
async function login(username, password) {
try {
const response = await fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password }),
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
// 从响应头中提取 token
const token = response.headers.get('Authorization');
if (token) {
// 存储 token,这里假设使用 localStorage
localStorage.setItem('token', token);
console.log('Token stored successfully');
} else {
throw new Error('Token not found in response');
}
} catch (error) {
console.error('Error during login:', error);
}
}
在这个示例中,login
函数发送一个登录请求到后端,并从响应头中提取 token。然后,它将 token 存储在 localStorage
中,以便后续使用。请注意,这只是一个基本示例,实际应用中可能需要处理更多的边缘情况和安全性问题。
后续使用token
在前端获取到后端返回的 token 之后,后续的使用通常涉及以下几个关键步骤:
1. 存储 Token
将 token 存储在前端的安全位置,以便在后续的请求中使用。常见的存储位置包括:
- localStorage:用于长期存储,即使在浏览器关闭和重新打开后也能保留数据。
- sessionStorage:仅在当前浏览器会话中有效,关闭浏览器窗口后数据会丢失。
- Cookies:存储在浏览器中,并随每个请求发送到服务器。但请注意,Cookies 可能受到跨域请求的限制,且大小有限。
示例:使用 localStorage 存储 Token
javascript复制代码
// 假设你已经从响应头中获取了 token | |
const token = response.headers.get('Authorization'); | |
// 将 token 存储在 localStorage 中 | |
localStorage.setItem('user-token', token); |
2. 在请求中附带 Token
在发送需要身份验证的请求时,前端需要在请求的头部附带 token。这通常是通过设置 HTTP 请求头的 Authorization
字段来实现的。
示例:使用 Fetch API 附带 Token
javascript复制代码
async function fetchProtectedResource() { | |
const token = localStorage.getItem('user-token'); // 从 localStorage 获取 token | |
if (!token) { | |
throw new Error('No token found'); | |
} | |
const response = await fetch('/protected-resource', { | |
method: 'GET', | |
headers: { | |
'Authorization': `Bearer ${token}`, // 在请求头中附带 token | |
'Content-Type': 'application/json', | |
}, | |
}); | |
if (!response.ok) { | |
throw new Error('Network response was not ok'); | |
} | |
const data = await response.json(); | |
return data; | |
} |
在这个示例中,fetchProtectedResource
函数从 localStorage
中获取 token,并将其作为 Authorization
头部的一部分附加到 GET 请求中。
3. 处理 Token 过期或无效的情况
后端可能会在 token 过期或无效时返回一个错误响应。前端需要能够捕获这些错误,并相应地处理,例如提示用户重新登录或自动刷新 token(如果后端支持)。
示例:处理 Token 过期错误
javascript复制代码
async function fetchProtectedResource() { | |
try { | |
// ... 省略之前的代码 ... | |
if (!response.ok) { | |
if (response.status === 401 || response.status === 403) { | |
// Token 可能过期或无效,处理这些情况 | |
handleTokenExpirationOrInvalidation(); | |
} else { | |
throw new Error('Network response was not ok'); | |
} | |
} | |
// ... 省略处理响应的代码 ... | |
} catch (error) { | |
console.error('Error fetching protected resource:', error); | |
} | |
} | |
function handleTokenExpirationOrInvalidation() { | |
// 这里可以清除存储的 token,重定向到登录页面,或尝试刷新 token | |
localStorage.removeItem('user-token'); | |
window.location.href = '/login'; // 重定向到登录页面 | |
} |
在这个示例中,如果后端返回 401 或 403 状态码,前端会调用 handleTokenExpirationOrInvalidation
函数来处理 token 过期或无效的情况。
4. 在路由守卫中检查 Token
如果你使用的是前端路由(例如 Vue Router、React Router 等),可以在路由守卫中检查用户是否已登录(即是否存在有效的 token)。如果 token 不存在或无效,可以将用户重定向到登录页面。
示例:在 Vue Router 中使用路由守卫检查 Token
javascript复制代码
import router from './router'; // 引入路由配置 | |
import store from './store'; // 引入 Vuex 存储(如果有使用) | |
router.beforeEach((to, from, next) => { | |
const token = localStorage.getItem('user-token'); // 从 localStorage 获取 token | |
if (to.path !== '/login' && !token) { | |
// 如果要访问的路由不是登录页面且不存在 token,则重定向到登录页面 | |
next('/login'); | |
} else { | |
// 否则继续导航到目标路由 | |
next(); | |
} | |
}); |
在这个示例中,router.beforeEach
是一个全局的路由守卫,它会在路由变化之前被调用。它检查目标路由(to
)是否是登录页面,以及是否存在有效的 token。如果不满足条件,就将用户重定向到登录页面。
请注意,这些步骤是基本的指导,实际应用中可能需要根据具体需求和框架进行调整和扩展。此外,还需要注意处理安全性问题,如使用 HTTPS 保护数据传输、