<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      G
      Q
      Q
      and
      M
      E

      雙Token實現無感刷新登錄狀態

      基于access_token和refresh_token實現無感刷新登錄狀態

      雙token原理

       
      這是登錄認證的流程:

      驗證通過之后,將用戶信息放到jwt中。

       
      訪問接口的時候帶上jwt,在Guard里取出來判斷是否有效,jwt有效的話才能繼續訪問:

      這種方式有個問題:
      jwt是有有效期的,我們設置的是7天,實際上為了安全考慮會設置的很短,比如30分鐘。
      可能用戶正在訪問某個界面的時候,jwt突然失效了,必須重新登錄。
      體驗比較差。

       
      為了解決這個問題,服務端一般返回兩個token:access_tokenrefresh_token

      access_token是用來認證身份的,之前我們返回的就是這個token

      refresh_token是用來刷新token的

       
      服務端會返回新的 access_token和refresh_token,也就是這樣的流程:

      登錄成功后,返回兩個token:

      access_token用來做登錄權限:

      refresh_token用來刷新,拿到新的token:

       
       
      access_token設置為30分鐘過期,而refresh_token設置7天過期。

      這樣7天內,如果access_token過期了,那就可以用refresh_token來刷新下,拿到新的token
      只要不超過七天內未訪問系統,那就可以一直是登錄狀態,可以無限續簽,不需要登錄。
      如果超過七天內未訪問系統,那么refresh_token也就過期了,這時候需要重新登錄了。

       
      這也是一般App采用的雙token驗證。

       

      nest.js中的雙token實現

      創建一個nest項目:

      nest new access_token_and_refresh_token -p npm
      

      創建一個user模塊:

      nest g resource user --no-spec
      

      安裝tpyeOrm的依賴:

      npm install --save @nestjs/typeorm typeorm mysql2
      

      然后再mySql中建立對應的數據庫:

      CREATE DATABASE refresh_token_test DEFAULT CHARACTER SET utf8mb4;
      

      然后建立User的entity:

      import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
      
      @Entity()
      export class User {
        @PrimaryGeneratedColumn()
        id: number;
      
        @Column({ length: 50 })
        username: string;
      
        @Column({ length: 50 })
        password: string;
      }
      

      然后在appModule.ts中的entitys添加User:

       
      把服務器跑起來:

      npm run start:dev
      

      user表會在mySQL中生成;

       
       

      然后在UserController添加post類型的login接口:

        @Post('/login')
        login(@Body() LoginUser: LoginUserDto) {
          console.log(LoginUser);
      
          return 'success';
        }
      

      創建login-user.dto.ts:

      export class LoginUserDto {
        username: string;
        password: string;
      }
      

      訪問測試下:

      然后實現登錄邏輯
      在UserService里添加login方法:

        async login(loginUserDto: LoginUserDto) {
          const user = await this.entityManyager.findOne(User, {
            where: {
              username: loginUserDto.username,
            },
          });
      
          if (!user) {
            throw new HttpException('用戶不存在', HttpStatus.OK);
          }
          if (user.password !== loginUserDto.password) {
            throw new HttpException('密碼錯誤', HttpStatus.OK);
          }
      
          return user;
        }
      

      然后登陸成功之后我們要返回兩個token;

      我們引入jwt的包:

      npm install --save @nestjs/jwt
      

      然后在AppModule中引入JwtModule,設置為全局模塊,指定默認的過期時間和密鑰:

      JwtModule.register({
        global: true,
        signOptions: {
          expiresIn: '30m'
        },
        secret: 'guang'
      })
      

      然后在UserContrller中生成兩個token返回:

      @Inject(JwtService)
      private jwtService: JwtService;
      
      @Post('login')
      async login(@Body() loginUser: LoginUserDto) {
          const user = await this.userService.login(loginUser);
      
          const access_token = this.jwtService.sign({
            userId: user.id,
            username: user.username,
          }, {
            expiresIn: '30m'
          });
      
          const refresh_token = this.jwtService.sign({
            userId: user.id
          }, {
            expiresIn: '7d'
          });
      
          return {
            access_token,
            refresh_token
          }
      }
      

      access_token 里存放 userId、username,refresh_token 里只存放 userId 就好了。
      過期時間一個 30 分鐘,一個 7 天。

      訪問下user/login接口試試:

      可以看到兩個token都正確的返回了。

       

      接下來再實現LoginGuard來做登錄鑒權:

      nest g guard login --flat --no-spec
      

      登錄邏輯和之前文章寫過的一樣:

      import {
        CanActivate,
        ExecutionContext,
        Inject,
        Injectable,
        UnauthorizedException,
      } from '@nestjs/common';
      import { JwtService } from '@nestjs/jwt';
      import { Observable } from 'rxjs';
      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 {
            const token = authorization.split(' ')[1];
            const data = this.jwtService.verify(token);
            return true;
          } catch (e) {
            throw new UnauthorizedException('token失效,請重新登錄');
          }
        }
      }
      

      取出authorization header中的jwt token,這個就是access_token,對他做校驗。
      jwt有效就可以繼續訪問,否則就返回token失效,請重新登錄。

      然后再AppController添加接口并加上登錄鑒權:

      @Get('aaa')
      aaa() {
          return 'aaa';
      }
      
      @Get('bbb')
      @UseGuards(LoginGuard)
      bbb() {
          return 'bbb';
      }
      

      aaa接口可以直接訪問,bbb接口需要登錄才能訪問。

      在user表中添加一條記錄:

      INSERT INTO `refresh_token_test`.`user` ( `username`, `password`)
        VALUES ( 'guang', '123456');
      

      我們來測試下:


      鑒權邏輯生效了!

      然后我們登陸下:

      把access_token復制下來,加到header里再訪問一下bbb:

      可以成功訪問bbb;

      現在的access_token是30分鐘過期,30分鐘之后就需要重新登錄了。

       
       
      這樣顯然體驗不好,接下來實現用refresh_token來刷新的邏輯:

        @Get('refresh')
        async refresh(@Query('refresh_token') refreshToken: string) {
          try {
            const data = this.jwtService.verify(refreshToken);
      
            const user = await this.userService.findUserById(data.userId);
      
            const access_token = this.jwtService.sign(
              { userId: user.id, username: user.username },
              { expiresIn: '30m' },
            );
      
            const refresh_token = this.jwtService.sign(
              { userId: user.id },
              { expiresIn: '7d' },
            );
      
            return {
              access_token,
              refresh_token,
            };
          } catch (error) {
            throw new UnauthorizedException('token已失效,請重新登錄');
          }
        }
      

      取出refresh_token里的userId,從數據庫中把user信息查出來,然后生成新的access_token和refresh_token返回。

      如果jwt校驗失效,就返回token已失效,請重新登錄。

      在UserService中實現下這個findUserById的方法:

        async findUserById(userId: number) {
          return await this.entityManyager.findOne(User, {
            where: { id: userId },
          });
        }
      

      測試下:
      帶上有效的refresh_token,能夠拿到新的access_token和refresh_token:

      refresh_token失效或者錯誤時,會返回401的響應碼,提示需要重新登錄:

      這樣就實現了雙token的登錄鑒權機制;
      只要 7 天內帶上 refresh_token 來拿到新的 token,就可以一直保持登錄狀態。

      那前端代碼里訪問接口的時候怎么用這倆 token 呢?

      我們新建個 react 項目試一下:

       yarn create vite refresh_token_test --template vue
      

      安裝axios:

      npm install --save axios
      

      在App.tsx里訪問下/aaa、/bbb接口:

      import axios from 'axios';
      import { useEffect, useState } from 'react';
      
      function App() {
        const [aaa, setAaa] = useState();
        const [bbb, setBbb] = useState();
      
        async function query() {
          const { data: aaaData } = await axios.get('http://localhost:3000/aaa');
          const { data: bbbData } = await axios.get('http://localhost:3000/bbb');
      
          setAaa(aaaData);
          setBbb(bbbData);
        }
        useEffect(() => {
          query();
        }, [])
        
      
        return (
          <div>
            <p>{aaa}</p>
            <p>{bbb}</p>
          </div>
        );
      }
      
      export default App;
      

      在服務端開啟一下跨域支持:

      把前端項目跑起來:

      我們先的登錄一下,拿到access_token,然后再請求的時候帶上:

      import axios from "axios";
      import { useEffect, useState } from "react";
      
      function App() {
        const [aaa, setAaa] = useState();
        const [bbb, setBbb] = useState();
      
        async function login() {
          const res = await axios.post("http://localhost:3000/user/login", {
            username: "guang",
            password: "123456",
          });
      
          localStorage.setItem("access_token", res.data.access_token);
          localStorage.setItem("refresh_token", res.data.refresh_token);
        }
      
        async function query() {
          await login();
      
          const { data: aaaData } = await axios.get("http://localhost:3000/aaa");
          const { data: bbbData } = await axios.get("http://localhost:3000/bbb",{
            headers:{
              Authorization:'Bearer '+localStorage.getItem('access_token')
            }
          });
      
          setAaa(aaaData);
          setBbb(bbbData);
        }
        useEffect(() => {
          query();
        }, []);
      
        return (
          <div>
            <p>{aaa}</p>
            <p>{bbb}</p>
          </div>
        );
      }
      
      export default App;
      

      刷新下,可以看到可以請求bbb接口了:

      如果很多接口都需要添加這個header,可以放到interceptors中:

      測試下:

      也可以正常訪問;

       
      當token失效的時候,要自動刷新,這個也在interceptors里做:

      async function refreshToken() {
        const res = await axios.get("http://localhost:3000/user/refresh", {
          params: { refresh_token: localStorage.getItem("refresh_token") },
        });
      
        localStorage.setItem("access_token", res.data.access_token || "");
        localStorage.setItem("refresh_token", res.data.refresh_token || "");
      
        return res;
      }
      
      axios.interceptors.response.use(
        (response) => response,
        async (err) => {
          let { data, config } = err.response;
      
          if (data.statusCode === 401 && config.url.includes("/user/refresh")) {
            const res = await refreshToken();
      
            if (res.status === 200) {
              return axios(config);
            } else {
              alert("登錄過期,請重新登錄");
              return Promise.reject(res.data);
            }
          } else {
            return err.response;
          }
        }
      );
      

      如果返回的錯誤是 401 就刷新 token,這里要排除掉刷新的 url,刷新失敗不繼續刷新。

      如果刷新接口返回的是 200,就用新 token 調用之前的接口

      如果返回的是 401,那就返回這個錯誤。

      判斷下如果沒有 access_token 才登錄:

      然后手動修改一下access_token的值,讓他失效:

      可以看到請求bbb失敗時候,重新刷新了token,之后再次訪問bbb

      這樣,我們就實現了 access_token 的無感刷新。

      posted @ 2023-12-08 15:14  sy0313  閱讀(2599)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 加勒比无码人妻东京热| 亚洲一区二区约美女探花| 狂野欧美性猛交免费视频| 最近中文国语字幕在线播放| 国产成人AV男人的天堂| 国产精品制服丝袜无码| 欧美人与禽2o2o性论交| 国产精品白浆无码流出| 国产精品自在欧美一区| 免费无码又爽又刺激高潮虎虎视频 | 又污又黄又无遮挡的网站| 亚洲中文字幕在线观看| 国产成人a∨激情视频厨房| 欧洲精品久久久AV无码电影| 国产av日韩精品一区二区| 别揉我奶头~嗯~啊~的视频 | 国产精品黄色片| 亚洲国产欧美在线人成aaaa| 亚洲夂夂婷婷色拍WW47| 天天影视色香欲综合久久| 中文字幕人妻日韩精品| 青州市| 久久88香港三级台湾三级播放| 国产精品亚洲二区在线播放| 亚洲AV国产福利精品在现观看| 国产综合视频精品一区二区 | 狠狠色噜噜狠狠狠狠色综合久| av中文字幕国产精品| 久久国产成人精品国产成人亚洲| 国产中文字幕精品免费| 日韩中文字幕精品人妻| 久久老熟女一区二区蜜臀| 合作市| 色狠狠综合天天综合综合| 撕开奶罩揉吮奶头高潮AV| 91老肥熟女九色老女人| 又湿又紧又大又爽a视频| 亚洲天堂网中文在线资源| 欧美孕妇乳喷奶水在线观看| 亚洲欧美偷国产日韩| 一本一道av无码中文字幕麻豆|