最近接触了一下nestJS框架,记录一个典型的登录逻辑应该如何做。
Auth.module.ts
整体的module设计是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Module({ imports: [ UsersModule, ConfigModule, JwtModule.registerAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => { const secret = configService.get<string>('JWT_SECRET', 'defaultSecretKey');
return { secret, signOptions: { expiresIn: '7d' }, }; }, }), ], providers: [AuthService, JwtStrategy, CaptchaService], controllers: [AuthController, CaptchaController], exports: [AuthService, CaptchaService], }) export class AuthModule { }
|
- AuthController是用来接收login和register请求的。而AuthService用来执行登录或者注册的实际操作。
- 其中的captchaModule负责提供验证码。captchaController提供路由可以获取验证码图片,captureService负责生成验证码图片对应答案
- JwtService负责生成JWT验证用户身份。
Auth.controller.ts
关于认证的流程应该包括两种情况:
- 会话续期 —— 用户已经登录过了,浏览器带着有效的 jwt Cookie 来访问。
- 首次登录 —— 用户没有 Cookie,或 Cookie 已失效,必须提交用户名和密码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| const token = req.cookies?.jwt; console.log('toekn', token);
if (token) { try { const payload = this.jwtService.verify(token); const user = await this.authService.validateUserById(payload.sub); if (user) { return { success: true, message: 'Already logged in', user }; }
res.clearCookie('jwt', { httpOnly: true, sameSite: 'lax', }); return { success: false, message: 'Invalid token', user }; } catch (e) { res.clearCookie('jwt', { httpOnly: true, sameSite: 'lax', }); return { success: false, message: 'Invalid token', user: null }; } }
if (!loginDto) { res.clearCookie('jwt', { httpOnly: true, sameSite: 'lax', }); return { success: false, message: 'Invalid token and no login data provided', user: null }; }
const captchaOk = this.captchaService.validateCaptcha(loginDto.captchaId, loginDto.captchaText); if (!captchaOk) { return { error: 'Invalid captcha' }; }
const user = await this.authService.validateUser(loginDto);
if (!user) { return { error: 'Invalid credentials' }; } const { accessToken } = await this.authService.login(user);
res.cookie('jwt', accessToken, { httpOnly: true, sameSite: 'lax', maxAge: 7 * 24 * 60 * 60 * 1000 });
return { success: true, message: 'Login successful', user };
|
关于jwt的登录状态保存
首先http是无状态的,因此需要保存用户的信息,以实现登录状态。常见两种方案:session;jwt
- session-cookie 浏览器为每个请求打上一个tag即Session ID,服务器根据cookie中的sessionID id去查找对应的session。
这种方案有很多风险
CSRF(cross-site request forgery)
不同于SSRF,利用服务器通过发送url攻击用户,获取内网文件或者资料。CSRF利用用户的cookie伪造请求,从而实现注入控制服务器。这种方式可以通过生成随机值来解决
分布式session
这种情况主要是指对于同一个cookie请求,session分布在不同的服务器上,而要完整的完成一次请求就需要多台服务器协同工作。
对应的解决方案为session复制,并通过框架同步操作(spring-session);把 session 保存在 redis
3. 跨域
cookie是为了安全,是做了domain的限制的,设置cookie的时候会指定一个 domain,只有这个 domain的请求才会带上cookie。而且还可以设置过期时间,路径等等:如果是二级域名不同,仅需要将domain设计成顶级域名即可。如果是顶级域名不同,就需要服务端做中转。
- 另外一种方式是将JWT使用json方式保存(json web token),然后将其放在request header中发送。
- 第一个问题毫无疑问就是安全性,把数据放在header绝对是一个危险的操作。所以要放在https中才能够使用。