[camp]整理5週間(4.11~15)


4.11


暗号化


暗号化は、情報を秘密保持コードに変換する技術プロセスであり、伝送、受信、記憶された情報の復号化を防止する.
暗号化されていないファイルに含まれる変換されていないメールは「コメント」、暗号化された形式のメールは「パスワード」と呼ばれます.
一方向暗号化と双方向暗号化の比較

JWT


JWT(Json Web Token)は、Json形式でユーザ属性を格納するClaimベースのWeb Tokenである.主に会員認証または情報伝達に使用されるJWTは、以下のように処理される.

生成されたトークンがHTTP通信を行う場合
使用方法は{ "Authorization": "Bearer {생성된 토큰 값}", }と同じです.

Nest.js Guard


誰もがAPIの最後の防御膜Beasを実行することを阻止するのは無理です...
利用@nestjs/passport@UseGuards(AuthGuard)と一緒に使用します.
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

export class JWtAccessStrategy extends PassportStrategy(Strategy, 'aaa') {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'myAccessKey',
    }); // 검증부
  }

  // 검증이 완료되면 해당부분이 실행된다.
  validate(payload) {
    console.log(payload);
  }
}

4.12


RefreshToken


ブラウザがバックエンドサーバにログイン要求を送信すると、2つのJWTトークンが送信されます.
  • ペイロードはCookieの
  • に自動的に戻る.
  • cookie(RefreshToken)
  • コイン
  • を再発行
  • で再発行されたコインで再び招待されました.
  • もし今エクセストックが死んだら
    クッキーのRefreshTokenを使用して、対応するトークンを復元します.

    Cookie & Session (Storage)


    Cookie


    これは、サーバの代わりにWebブラウザに保存し、要求時にサーバに送信してユーザーをパーティション化できることを意味します.
    サーバはユーザのWebブラウザ(Local)に格納され,異なるブラウザは異なるCookieを格納する.
  • WebサーバがWebブラウザに送信保存され、サーバ要求時にサーバに送信文字列
  • .
  • ページにアクセスしたときにアクセスしたレコードなど...ブラウザ情報を含むテキストファイル
  • Session


    CookieはWebブラウザに露出しているため、セキュリティの問題があり、これらのセキュリティの問題を解決するために、認証情報に対応するコンテンツがセッションに格納される
    会話の動作原理
    1. Client -> Request(Server)
    2.サーバー->クライアントにCookie値をチェックし、セッションがない場合は応答値を再生成して送信
    3.Client->各リクエストのセッション値をCookieに入れてリクエストを送信
    4.サーバ->セッションによるユーザーの特定
    5.ログイン要求時にセッションをリフレッシュし、新しいセッションに応答する
    6.クライアント->終了(ブラウザ終了)時にセッションを削除し、サーバ上でもセッションを削除

    ソーシャルログインとは?


    OAuthは
    ネイバーにログイン...KaKaoでログインするなど...他の外部サービスが認証要求を提供できるようにする

    ソーシャル・ログインの順序


  • Clinetからサーバへのログイン要求の送信

  • サーバは、対応するソーシャルサービスにリクエストを送信し、応答としてトークンを受信します.

  • サーバが受信したトークン情報を使用してアクセス権を付与し、ログインとユーザー情報を保存します.

  • ログインが完了したら、別の場所に移動させます.
  • 4.13


    ソーシャル・ログインの実施(主な項目)


    カイドウ羽

    Controller

    import { User } from '../users/entities/user.entity';
    import { AuthService } from './auth.service';
    import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
    import { Request, Response } from 'express';
    import { AuthGuard } from '@nestjs/passport';
    
    interface IOAuthUser {
      user: Pick<User, 'email' | 'password' | 'name' | 'phone'>;
    }
    
    @Controller()
    export class AuthController {
      constructor(private readonly authService: AuthService) {}
    
      @Get('/login/google')
      @UseGuards(AuthGuard('google'))
      async loginGoogle(
        @Req() req: Request & IOAuthUser, //
        @Res() res: Response,
      ) {
        this.authService.socialLogin(req, res);
      }
      @Get('/login/naver')
      @UseGuards(AuthGuard('naver'))
      async loginNaver(
        @Req() req: Request & IOAuthUser, //
        @Res() res: Response,
      ) {
        this.authService.socialLogin(req, res);
      }
      @Get('/login/kakao')
      @UseGuards(AuthGuard('kakao'))
      async loginKakao(
        @Req() req: Request & IOAuthUser, //
        @Res() res: Response,
      ) {
        this.authService.socialLogin(req, res);
      }
    }
    リクエストがログインエンドポイントである場合、サービス団はリクエストを処理し、ソーシャルログインに合致するapiにリクエストを送信する.

    Service

      async socialLogin(req, res) {
        let user = await this.userService.findOne({ email: req.user.email });
        if (!user) {
          const createUser = new CreateUserInput();
          createUser.email = req.user.email;
          createUser.name = req.user.name;
          createUser.password = req.user.password;
          createUser.phone = req.user.phone;
    
          // 1:1관계의 결제테이블
          // id값을 직접생성해서 넣어준다
          const paymentId = v4();
          createUser.payment = { id: paymentId, name: 'none' };
    
          user = await this.userService.create({ createUserInput: createUser });
        }
        // 로그인
        this.setRefreshToken({ user, res });
        res.redirect(
          302,
          'http://localhost:5500/main-project/frontend/login/index.html',
        );
      }
    ソーシャルログインリクエストを受け取ると、
    1.現在そのプレイヤーが存在する場合は、リフレッシュトークンを設定し、ログイン後にリフレッシュを行う
  • ユーザが存在しない場合、ソーシャルログインapiから提供された情報に基づいて新しいユーザが生成され、上記の論理のようにリフレッシュトークンが設定され、ログインおよびリダイレクトが行われる.
  • 4.14


    支払の種類と実施

    上の図に示すように、ブランチと機能を実現する必要があるため、直接支払APIの作成は困難である
    したがって、これらの支払いプロセスをプロジェクトに直接適用することは非常に困難であり、外部APIを使用すれば簡単に実施することができる.

    IamPort API


    入力APIストリーム

  • お会計ボタンをクリックして、フロントからimport社のAPIにお会計をお願いします

  • IMPORT APIはPG(決済代理会社)に支払いを要求する

  • PGクレジットカード会社に支払要求

  • 決済が完了すると、フロントからバックグラウンドサーバに決済イベントへの応答が送信されます

  • バックエンドはDBの支払い情報に基づいてデバイスに格納される.
  • 自動ログイン


    Resolver
      @Mutation(() => Order)
      @UseGuards(GqlAuthAccessGuard)
      async createOrder(
        @Args('impUid') impUid: string,
        @Args('merchantUid') merchantUid: string,
        @Args('subscribeId')
        subscribeId: string,
        @CurrentUser() currentUser: ICurrentUser,
      ) {
        //
        return await this.orderService.create({
          impUid,
          merchantUid,
          subscribeId,
          currentUser,
        });
      }
    Service
      async create({ impUid, merchantUid, subscribeId, currentUser }) {
        // 토큰가져오기 + 결제정보가져오기도 서비스로 나눈다
    
        // 1. 아임포트 토큰을 가져온다.
        const token = await this.iamportService.createIamPortToken();
    
        // 2. 결제정보를 찾는다
        const iamPortResult = await this.iamportService.searchIamPort({ impUid });
    
        const amount = iamPortResult.data.response.amount;
    
        // 3. 프론트에서 넘겨준 제품과 검증이 완료된 유저 정보로 해당 리포지토리로 검색을한다.
        const user = await this.userRepository.findOne({
          id: currentUser.id,
        });
        const subscribe = await this.subscribeRepository.findOne({
          id: subscribeId,
        });
    
        // 4. 결제정보 (amonut)와 subscribe()의 가격이 맞는지 확인
        if (!(amount == subscribe.price)) {
          throw { status: 'forgery', message: '위조된 결제시도입니다!!!' };
        } // FIXME 결제취소 완성되면 추가해야한다
    
        // 결제정보가 맞다면 존재하는지 검증한다.
        const checkimpUid = await this.orderRepository.findOne({
          where: { impUid: impUid },
        });
    
        // 존재하면에러
        if (checkimpUid) {
          throw new ConflictException('이미 등록된 결제입니다.');
        }
    
        const userSubs = await this.userSubscribeRepository.save({
          subscribe: subscribe,
          user: user,
        });
    
        console.log(userSubs);
    
        return await this.orderRepository.save({
          impUid: impUid,
          merchantUid: merchantUid,
          userSubscribe: userSubs,
        });
      }
    これに応じて、認証および支払イベントに基づく情報がDBに格納される

    4.15


    支払の検証と取り消し
    学習決済の検証とキャンセルを基にimport APIで直接実現する.
    実施の過程で取り消しを参考にしたところ、いい文章があった.
    このような場合、IMPORTに問い合わせたところ、私たちの状況によって異なる回答が得られました.
    もし相手が故意に金額を変更して私たちに損失をもたらしたので、出荷しないし、返金もしないなら
    もちろん払い戻しのため、カスタマーサービスに連絡します.そうでなければ金額が異なる場合はコード自動返金処理を行い、
    いいと言います.
    上記の対応は、決済時に相手が偽改ざんをしたので決済は成功したが、DB(偽改ざんの場合)に保存できなければ、どのように決済しても完了するが、サーバ側がどのように対応するかによって方法が異なる.
    もし私が支払い時に上記の変更をしたら、自動返金のためにFIX
      // 허위로 결제를 만들거나 유저가 취소를 요청했을경우의 아임포트서비스 취소
      async cancelIamPort({ impUid, reason = '', currentUser }) {
        // 1. 아임포트 토큰을 가져온다.
        const token = await this.iamportService.createIamPortToken();
    
        // 2. 결제정보를 찾는다
        const iamPortResult = await this.iamportService.searchIamPort({ impUid });
        if (iamPortResult.data.response.status === 'canclled') {
          throw new UnprocessableEntityException('이미 취소가된 결제입니다..');
        }
    
        const currentOrder = await this.orderRepository.findOne({
          relations: [
            'userSubscribe',
            'userSubscribe.user',
            'userSubscribe.subscribe',
          ],
          where: { impUid: impUid },
        });
    
        const checkUser = await this.userRepository.findOne({
          where: { id: currentOrder.userSubscribe.user.id },
        });
    
        // 현재유저의 아이디와 현재주문정보의  유저 아이디를 체크한다.
        if (currentUser.id != checkUser.id) {
          throw new UnprocessableEntityException(
            '취소하려는 결제의 유저정보와 현재 유저가 맞지 않습니다.',
          );
        }
    
        const merchant_uid = currentOrder.merchantUid;
        const checksum = currentOrder.userSubscribe.subscribe.price;
    
        const cancelResult = await this.iamportService.cancelIamPort({
          reason,
          impUid,
          merchant_uid,
          checksum,
        });
        return cancelResult.data.message;
      }
    このように注文をキャンセルするかどうかを確認し、注文情報が正しいかどうかを確認した後、最終的に注文をキャンセルします.