JWT
啥也不做的请求
没有session、jwt
这种情况服务器接收到的用户信息不知道是否被串改或伪造,没法信任
改进-签名
下面就是加密算法
const crypto = require('crypto'); // js内置的加密模块
const sign = (data, secret) => {
// 加密算法为sha256,secret为秘钥
const hmac = crypto.createHmac('sha256', secret);
// 对信息进行签名
hmac.update(data);
return hmac.digest('hex'); // 把签名结果转换成16进制字符串
};
console.log(sign('abc', '123'));
上图生成的字符串包含两个信息,一个是我们要加密的信息(abc),另一个是密钥(123),只要加密信息和密钥是一样的,这个结果是固定的
服务器把登陆信息和签名给浏览器,以后浏览器请求的时候带上这个,服务器就可以验证了,
服务器如何验证:服务器把对过来的信息里面的info重新进行签名看和发过来的内容是否一样,一样的话就是信息没有被串改过,这就防止了串改和伪造,因为加密算法和密钥都是在服务器上的
JWT
把上面 签名 进行标准化就是我们的 JWT
JWT由三部分组成(HEADER、PAYLOAD、VERIFY SIGNATURE),每部分由点分割
第一部分:header指定算法和类型,然后base64编码,在浏览器控制台上可以验证
第二部分:信息主体,比如服务器要颁发的用户ID,用户账号,uuid,jwt过期时间等等,然后base64编码,
第三部分:对前两部分 header.payload 进行签名,以点进行连接,签名完成后进行base64编码
小结
-
JWT可以有效解决信息验证问题,由于信息可以验证,所以信息可以被信任,所以登陆状态就可以保存在客户端了,所以服务器压力就减小了(过去的做法服务器还要建立一个session表格,现在不要了)
-
JWT的出现给我们状态的保存带来一种新的方案 -- 分布式存储的方案,服务器不再去存储状态了,而是把状态交给客户端进行存储
-
session + cookie:把状态数据保存到服务端,session id 放到 cookie 里返回,这样每次请求会带上 cookie ,通过 id 来查找到对应的 session。这种方案有 CSRF、分布式 session、跨域的问题。
-
jwt:把状态保存在 json 格式的 token 里,放到 header 中,需要手动带上,没有 cookie + session 的那些问题,但是也有安全性、性能、没法手动控制失效的问题。
-
session 因为是存在服务端的,那我们就可以随时让它失效JWT 不是,因为是保存在客户端,那我们是没法手动让他失效的。比如踢人、退出登录、改完密码下线这种功能就没法实现。但也可以配合 redis 来解决,记录下每个 token 对应的生效状态,每次先去 redis 查下 jwt 是否是可用的,这样就可以让 jwt 失效。
Nestjs使用
$ nest new jwt-test -p pnpm
$ cd jwt-test
$ pnpm install -S @nestjs/jwt
在 AppModule 里引入 JwtModule:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
JwtModule.register({
secret: 'xueyou', // 密钥
signOptions: {
expiresIn: '10d' // 过期时间 10 天
}
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
在 app.controller.ts 里注入 JwtService 并写 test 接口:
import { Controller, Get, Inject, Res, Headers, UnauthorizedException } from '@nestjs/common';
import { AppService } from './app.service';
import { JwtService } from '@nestjs/jwt';
import { Response } from 'express';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Inject(JwtService)
private readonly jwtService: JwtService;
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get('test')
test(@Headers('authorization') authorization: string, @Res({ passthrough: true}) response: Response) {
if (authorization) {
try {
// 携带 jwt 需要加载 authorization 的 header 里,以 Bearer xxx 的格式
const token = authorization.split(' ')[1];
const data = this.jwtService.verify(token); // 校验并取出信息
const newToken = this.jwtService.sign({
count: data.count + 1,
});
// 返回 jwt 可以放在任何地方,header、cookie 或者 body 里都可以
response.setHeader('token', newToken);
return data.count + 1;
} catch (e) {
throw new UnauthorizedException();
}
} else {
const newToken = this.jwtService.sign({
count: 1,
});
response.setHeader('token', newToken);
return 1;
}
}
}
$ npm run start:dev
每次把返回的 token 放到请求的 Authorization 里面,然后观察返回的值,每次加1,如果内容不对就会报错,如果没有更改,返回内容不会增加
转载自:https://juejin.cn/post/7363490901345878054