React+Nest实现无感登录

目录

前言

(一)介绍

(二)具体实现

1.后端登录成功发送token

2.前端接收token存储

3.前端携带access_token发请求

4.后端配置守卫

5.后端刷新token逻辑

6.前端响应拦截器实现刷新

(三)总结


前言

给自己的项目做登录功能,正好丰富一下经验,决定用一下无感登录

我大概参考了这位大佬的文章:https://www.cnblogs.com/sunyan97/p/17887134.html


(一)介绍

无感登录,就是无感刷新token

登录成功后,后端发送两个token给前端:access_tokenrefresh_token

access_token有效时间很短,一般为30min,refresh_token有效时间较长,大概是7days

  1. access_token用于带到请求头进行权限请求
  2. 当后端检测到过期时,通知前端acess_token已过期(401)
  3. 前端携带refresh_token发起刷新请求,后端根据refresh_token发送新的access_token
  4. 如果refresh_token也过期,就返回401给前端,通知前端退出登录

为什么不采用前端定时器定时发起刷新请求?

定时刷新对浏览器性能消耗过大;用到了再刷新,将刷新决定权交给后端,节省资源;

如何存储?

access_token可以存储在storage里,refresh_token尽量存储的保密一点,比如httpOnly-token等(没试过这个

(二)具体实现

前端采用react、后端采用nest,代码可能有部分删减,并不完整

1.后端登录成功发送token

使用jwt签发token

pnpm add @nestjs/jwt

在auth.controller.ts:

javascript 复制代码
// 生成 token
  async login(user: any) {
    // 临时token
    const access_token = this.jwtService.sign({
        email: user.email,
        sub: user.userId,
      },{
        expiresIn: '1m',
      },
    );
    // 刷新token
    const refresh_token = this.jwtService.sign({
        sub: user.userId,
      },{
        expiresIn: '10m',
      },
    );
    // 无感登录
    return {
      access_token,
      refresh_token,
    };
  }

2.前端接收token存储

这里用到了ahooks的useRequest发起请求

为了简单省事,access_token采用localStorage存储,refresh_token采用cookie存储

pnpm add ahooks js-cookie

javascript 复制代码
const navigate = useNavigate();
// 发起注册请求
const { run:signUpRun,loading:signUpLoading } = useRequest(
    (params)=>{
        return register(params)
    },
    {
        manual:true,
        onSuccess(res:any,params:any) {
            toast({
                title: "登录成功!",
                duration: 1000,
            })
            // 保存token
            saveToken(res.data)
    
            // 跳转到主页
            navigate(`/dashboard`, { replace: false })
        },
        onError(err:any) {} 
    }
});

const saveToken = ({ access_token, refresh_token }: { access_token: string; refresh_token: string }) => {
    localStorage.setItem('access_token', access_token);
    Cookies.set('refresh_token', refresh_token);
};

3.前端携带access_token发请求

在service配置页:

javascript 复制代码
// 请求拦截
this.instance.interceptors.request.use(
    config => {
        // 携带access_token
        const access_token =localStorage.getItem('access_token');
        if (access_token) {
            config.headers.Authorization = `Bearer ${access_token}`;
        }
        return config;
    },
    err => {
        return Promise.reject(err);
    }
);

4.后端配置守卫

配置guard自动检测传来的请求头内的access_token是否过期,过期自动返回401

在guard/login.guard.ts:

javascript 复制代码
import { CanActivate, ExecutionContext, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';

@Injectable()
export class LoginGuard implements CanActivate {
  @Inject(JwtService)
  private jwtService: JwtService;
  
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request: Request = context.switchToHttp().getRequest();

    const authorization = request.headers.authorization;
    if (!authorization) {
      throw new UnauthorizedException('用户未登录');
    }

    try {
      console.log('authorization', authorization);
      
      const token = authorization.split(' ')[1];
      const data = this.jwtService.verify(token);
      console.log('data', data);
      
      return true;
    } catch (e) {
      throw new UnauthorizedException('token失效,请重新登录');
    }
  }
}

写一个接口使用一下guard

在user.controller.ts:

javascript 复制代码
@UseGuards(LoginGuard)
  @Get('getUserByEmail')
  async getUserByEmail(@Query('email') email: string) {
    const {password,...result} =  await this.userService.findByEmail(email);
    return result
  }

当access_token有效的时候,可以直接返回数据,如果失效就返回401

下面开始实现 **"无感刷新"**的功能

5.后端刷新token逻辑

用jwtService.verify验证refresh_token是否过期,过期返回401,没过期返回新的access_token

在auth.controller.ts:

javascript 复制代码
  @Post('refresh')
  async refresh(@Body('refresh_token') refreshToken: string) {
    try {
      const data = this.jwtService.verify(refreshToken);

      const user = await this.userService.findById(data.sub);
      // 重新签发access_token
      const access_token = this.jwtService.sign(
        { sub: user.id,email: user.email, },
        { expiresIn: '30m' },
      );

      return {
        access_token,
      };
    } catch (error) {
      throw new UnauthorizedException('token已失效,请重新登录');
    }
  }

6.前端响应拦截器实现刷新

在axios的响应拦截器里实现刷新

原因如下:

请求响应后判断请求路径是否为需带权限请求路径,并判断状态码,满足要求就发起自主发起刷新请求,可以实现无感刷新;

如果不在响应拦截器里设置刷新请求,那么每次写一个带权限的请求,都需要在处理函数里增加判断权限的操作,非常复杂;而在响应器里设置,只需要配置相关路径(需要前后端规范

javascript 复制代码
// 响应拦截
this.instance.interceptors.response.use(
    (response) => response.data,
    async (err) => {
        let { data, config } = err.response;
            if (data.statusCode === 401 && config.url.includes("/user/getUserByEmail")) {
                const res:any = await refresh({ refresh_token: getRefreshToken() });             
                if (res.statusCode === 200) {
                    saveAccessToken(res.data.access_token);
                    // 重新发起请求
                    return this.instance(config);
                } else {
                    alert("登录过期,请重新登录");
                    removeToken()
                    window.location.href = '/auth/login'
                    return Promise.reject(res.data);
                }
              } else {
                return Promise.reject(err);
              }
            }
        );

测试测试:

无感登录!欧了!


(三)总结

无感登录大概就是这样,不过我没有对token进行加密,直接存在cookie里还是会有安全问题

用户权限的路由守卫还没写,后面补上,挥挥~

相关推荐
GIS程序媛—椰子5 分钟前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山29 分钟前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
睡觉谁叫~~~32 分钟前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
毕业设计制作和分享1 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
2401_865854883 小时前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
清灵xmf3 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨3 小时前
VUE+Vite之环境文件配置及使用环境变量
前端
GDAL3 小时前
npm入门教程1:npm简介
前端·npm·node.js
AskHarries3 小时前
Spring Boot集成Access DB实现数据导入和解析
java·spring boot·后端
2401_857622663 小时前
SpringBoot健身房管理:敏捷与自动化
spring boot·后端·自动化