方案介绍
有两种方案:
- 服务端存储的 session + cookie 的方案
- 客户端存储的 jwt token 的方案
服务端存储的 session + cookie
session + cookie 的给 http 添加状态的方案是服务端保存 session 数据,然后把 id 放入 cookie 返回,cookie 是自动携带的,每个请求可以通过 cookie 里的 id 查找到对应的 session,从而实现请求的标识。
这种方案有 CSRF、分布式 session、跨域等问题,不过都是有解决方案的。
缺点:
- CSRF(跨站请求伪造):
- 一般会用随机值来解决,每次随机生成一个值返回,后面再发起的请求需要包含这个值才行,否则就认为是非法的。这个随机值叫做 token,可以放在参数中,也可以放在 header 中,因为钓鱼网站拿不到这个随机值,就算带了 cookie 也没发通过服务端的验证。
- 分布式 session:登录之后 session 是保存在某一台服务器的,之后可能会访问到别的服务器,这时候那台服务器是没有对应的 session 的,就没法完成对应的功能。
- 解决方法是,把 session 保存在 redis,这样每台服务器都去那里查,只要一台服务器登录了,其他的服务器也就能查到 session,
- 跨域
- 解决方法是,要求后端代码设置了对应的 header:Access-Control-Allow-Origin: "当前域名"; Access-Control-Allow-Credentials: true
客户端存储的 token
JWT 的方案是把状态数据保存在 header 里,每次请求需要以authorization: Bearer xxxxx.xxxxx.xxxx形式手动携带。服务端就可以解析出对应的 header、payload、verify signature 这三部分,然后根据 header 里的算法也对 header、payload 加上 salt 做一次加密,如果得出的结果和 verify signature 一样,就接受这个 token。
没有 session + cookie 方案的 CSRF、分布式、跨域的问题,但是也有安全性、性能、没法控制等问题。
缺点:
- 安全性:JWT 把数据直接 Base64 之后就放在了 header 里,那别人就可以轻易从中拿到状态数据,比如用户名等敏感信息,也能根据这个 JWT 去伪造请求。
- 解决方法是,要搭配 https 来用
- 性能:JWT 把状态数据都保存在了 header 里,每次请求都会带上,比起只保存个 id 的 cookie 来说,请求的内容变多了,性能也会差一些。
- 解决方法是,JWT 里也不要保存太多数据。
- 没法让 JWT 失效:因为是保存在客户端,那我们是没法手动让他失效的。
- 可以配合 redis 来解决,记录下每个 token 对应的生效状态,每次先去 redis 查下 jwt 是否是可用的,这样就可以让 jwt 失效。
Nest里两种方案的使用
session + cookie
npm install express-session @types/express-session
在入口模块里启用:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(session({
secret: 'guang',
resave: false,
saveUninitialized: false
}));
await app.listen(3000);
}
bootstrap();
- 使用 express-session 中间件,指定加密的密钥 secret。
- resave 为 true 是每次访问都会更新 session,不管有没有修改 session 的内容,而 false 是只有 session 内容变了才会去更新 session。
- saveUninitalized 设置为 true 是不管是否设置 session,都会初始化一个空的 session 对象。比如你没有登录的时候,也会初始化一个 session 对象,这个设置为 false 就好。
在 controller 里就可以注入 session 对象:
@Get('getSession')
sss(@Session() session) {
console.log(session)
session.count = session.count ? session.count + 1 : 1;
return session.count;
}
jwt
npm install @nestjs/jwt
在 AppModule 里引入 JwtModule:
方法一:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
JwtModule.register({
secret: 'hazymoon',
signOptions: {
expiresIn: '7d'
}
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
方法二:
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
JwtModule.registerAsync({
async useFactory() {
await 1
return {
secret: 'hazymoon',
signOptions: {
expiresIn: '7d'
}
}
}
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
在 controller 里注入 JwtModule 里的 JwtService:
@Inject(JwtService)
private jwtService: JwtService;
@Get('login')
ttt(@Res({ passthrough: true}) response: Response) {
const newToken = this.jwtService.sign({
count: 1
});
response.setHeader('token', newToken);
return 'hello';
}
- 这里使用 jwtService.sign 来生成一个 jwt token,放到 response header 里。
之后的请求需要带上这个 token,在服务端取出来
@Get('getToken')
ttt(@Headers('authorization') authorization: string, @Res({ passthrough: true}) response: Response) {
if(authorization) {
try {
const token = authorization.split(' ')[1];
const data = this.jwtService.verify(token);
const newToken = this.jwtService.sign({
count: data.count + 1
});
response.setHeader('token', newToken);
return data.count + 1
} catch(e) {
console.log(e);
throw new UnauthorizedException();
}
} else {
const newToken = this.jwtService.sign({
count: 1
});
response.setHeader('token', newToken);
return 1;
}
}