作者:微信小助手
发布时间:2023-10-02T12:14:59
用户登录之后,会返回一个用户的标识,之后带上这个标识请求别的接口,就能识别出该用户。 标识登录状态的方案有两种: session 和 jwt。 session 是通过 cookie 返回一个 id,关联服务端内存里保存的 session 对象,请求时服务端取出 cookie 里 id 对应的 session 对象,就可以拿到用户信息。 jwt 不在服务端存储,会直接把用户信息放到 token 里返回,每次请求带上这个 token,服务端就能从中取出用户信息。 这个 token 一般是放在一个叫 authorization 的 header 里。 这两种方案一个服务端存储,通过 cookie 携带标识,一个在客户端存储,通过 header 携带标识。 session 的方案默认不支持分布式,因为是保存在一台服务器的内存的,另一台服务器没有。 jwt 的方案天然支持分布式,因为信息保存在 token 里,只要从中取出来就行。 所以 jwt 的方案用的还是很多的。 服务端把用户信息放入 token 里,设置一个过期时间,客户端请求的时候通过 authorization 的 header 携带 token,服务端验证通过,就可以从中取到用户信息。 但是这样有个问题: token 是有过期时间的,比如 3 天,那过期后再访问就需要重新登录了。 这样体验并不好。 想想你在用某个 app 的时候,用着用着突然跳到登录页了,告诉你需要重新登录了。 是不是体验很差? 所以要加上续签机制,也就是延长 token 过期时间。 主流的方案是通过双 token,一个 access_token、一个 refresh_token。 登录成功之后,返回这两个 token: 访问接口时带上 access_token 访问: 当 access_token 过期时,通过 refresh_token 来刷新,拿到新的 access_token 和 refresh_token 这里的 access_token 就是我们之前的 token。 为什么多了个 refresh_token 就能简化呢? 因为如果你重新登录,是不是需要再填一遍用户名密码?而有了 refresh_token 之后,只要带上这个 token 就能标识用户,不需要传用户名密码就能拿到新 token。 而 access_token 一般过期时间设置的比较短,比如 30 分钟,refresh_token 设置的过期时间比较长,比如 7 天。 这样,只要你 7 天内访问一次,就能刷新 token,再续 7 天,一直不需要登录。 但如果你超过 7 天没访问,那 refresh_token 也过期了,就需要重新登录了。 想想你常用的 APP,是不是没再重新登录过? 而不常用的 APP,再次打开是不是就又要重新登录了? 这种一般都是双 token 做的。 知道了什么是双 token,以及它解决的问题,我们来实现一下。 新建个 nest 项目: 进入项目,把它跑起来: 访问 http://localhost:3000 可以看到 hello world,代表服务跑成功了: 在 AppController 添加一个 login 的 post 接口: 这里通过 @Body 取出请求体的内容,设置到 dto 中。 dto 是 data transfer object,数据传输对象,用来保存参数的。 我们创建 src/user.dto.ts 在 postman 里访问下这个接口: 返回了 success,服务端也打印了收到的参数: 然后我们实现下登录逻辑: 这里我们就不连接数据库了,就是内置几个用户,匹配下信息。 如果没找到,就返回用户不存在。 找到了但是密码不对,就返回密码错误。 否则返回用户信息和 token。 测试下: 当 username 不存在时: 当 password 不对时: 登录成功时: 然后我们引入 jwt 模块来生成 token: 在 AppModule 里注册下这个模块:
npx nest new token-test
npm run start:dev
@Post('login')
login(@Body() userDto: UserDto) {
console.log(userDto);
return 'success';
}export class UserDto {
username: string;
password: string;
}
const users = [
{ username: 'guang', password: '111111', email: 'xxx@xxx.com'},
{ username: 'dong', password: '222222', email: 'yyy@yyy.com'},
]
@Post('login')
login(@Body() userDto: UserDto) {
const user = users.find(item => item.username === userDto.username);
if(!user) {
throw new BadRequestException('用户不存在');
}
if(user.password !== userDto.password) {
throw new BadRequestException("密码错误");
}
return {
userInfo: {
username: user.username,
email: user.email
},
accessToken: 'xxx',
refreshToken: 'yyy'
};
}
npm install @nestjs/jwt