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里还是会有安全问题

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

相关推荐
bluebonnet2713 分钟前
【Rust练习】15.match 和 if let
开发语言·后端·rust
gxhlh20 分钟前
React Native防止重复点击
javascript·react native·react.js
miao_zz22 分钟前
基于react native的锚点
android·react native·react.js
一只小白菜~36 分钟前
实现实时Web应用,使用AJAX轮询、WebSocket、还是SSE呢??
前端·javascript·websocket·sse·ajax轮询
晓翔仔1 小时前
CORS漏洞及其防御措施:保护Web应用免受攻击
前端·网络安全·渗透测试·cors·漏洞修复·应用安全
ZachOn1y1 小时前
Java 入门指南:JVM(Java虚拟机)垃圾回收机制 —— 死亡对象判断方法
java·jvm·后端·java-ee·团队开发·个人开发
ZachOn1y1 小时前
Java 入门指南:JVM(Java虚拟机)垃圾回收机制 —— 内存分配和回收规则
java·jvm·后端·个人开发
易云码2 小时前
【工作流集成】springboot+vue工作流审批系统(实际源码)
大数据·后端·低代码·系统安全·设计规范
jingling5552 小时前
后端开发刷题 | 最长上升子序列
java·开发语言·数据结构·后端·算法·动态规划
Flying_Fish_roe2 小时前
Spring Boot- 数据库相关问题
java·spring boot·后端