CSRF 토큰과 AccessToken의 차이?

SEUNGJUN·2024년 6월 30일
0

NestJS

목록 보기
7/8

CSRF토큰과 액세스 토큰 모두 사용자의 신원이나 요청의 정당성을 확인하는 데 사용된다. 하지만 이 둘의 목적과 작동 방식은 다르다.

CSRF 토큰

주 목적

  • 요청의 정당성 확인: CSRF 토큰은 특정 요청이 사용자가 의도한 것인지 확인하기 위해 사용된다. 즉, 요청이 사용자의 브라우저에서 실제로 발생했는지 확인하는 것이다.

작동 방식

  • 사용자가 폼을 로드할 때 서버가 CSRF 토큰을 생성하여 폼에 포함시킨다.

  • 사용자가 폼을 제출하면 CSRF 토큰도 함께 제출된다.

  • 서버는 제출된 CSRF 토큰을 검증하여 요청이 신뢰할 수 있는 출처에서 온 것인지 확인한다.

  • 이를 통해 CSRF 공격을 방지한다. 이는 악의적인 사이트가 사용자를 속여서 요청을 보내는 것을 방지한다.

주 사용 분야

  • 주로 웹 애플리케이션의 폼 제출이나 중요한 상태 변경 요청(POST, PUT, DELETE 등)에서 사용 된다.

AccessToken

주 목적

  • 사용자의 신원 및 권한 확인: 액세스 토큰은 사용자가 인증된 사용자임을 확인하고, 그 사용자가 특정 리소스나 서비스에 접근할 수 있는 권한이 있는지 확인하는 데 사용된다.

작동 방식

  • 사용자가 로그인하면 서버가 사용자의 자격 증명을 확인하고 액세스 토큰을 발급한다.

  • 클라이언트는 이후의 API 요청에 이 액세스 토큰을 포함하여 서버에 보낸다.

  • 서버는 액세스 토큰을 검증하여 요청이 인증된 사용자로부터 온 것인지, 그리고 해당 사용자가 요청한 리소스에 접근할 권한이 있는지를 확인한다.

  • 액세스 토큰은 특정 기간 동안 유요하며, 만료되면 갱신해야 한다.

주 사용 분야

  • 주로 API 호출 및 웹 서비스와의 통신에서 사용된다. OAuth 2.0과 같은 인증 및 권한 부여 프로토콜에서 많이 사용된다.

NestJS 동작원리

CSRF 토큰 동작 원리

1. 초기설정

NestJS 서버는 csurf 미들웨어를 사용하여 CSRF 보호를 설정한다.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as csurf from 'csurf';
import * as cookieParser from 'cookie-parser';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.use(cookieParser());
  app.use(csurf({ cookie: true }));

  await app.listen(3000);
}
bootstrap();

2. CSRF 토큰 제공

CSRF 토큰을 제공하는 엔드포인트를 설정한다ㅏ.

import { Controller, Get, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';

@Controller()
export class AppController {
  @Get('csrf-token')
  getCsrfToken(@Req() req: Request, @Res() res: Response) {
    res.cookie('XSRF-TOKEN', req.csrfToken());
    res.send({ csrfToken: req.csrfToken() });
  }
}

3. 클라이언트 측

클라이언트 측에서 CSRF 토큰을 받아서 API 호출 시 사용한다.

async function getCsrfToken() {
  const response = await fetch('/csrf-token');
  const data = await response.json();
  return data.csrfToken;
}

async function updateProfile() {
  const csrfToken = await getCsrfToken();
  const response = await fetch('/profile/update', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'CSRF-Token': csrfToken,
    },
    body: JSON.stringify({
      username: 'new_username',
    }),
  });
  const result = await response.text();
  console.log(result);
}

updateProfile();

4. 서버 측 CSRF 토큰 검증

서브 측에서는 csurf 미들웨어가 CSRF 토큰을 자동으로 검증한다.

import { Controller, Post, Req, Res } from '@nestjs/common';
import { Request, Response } from 'express';

@Controller('profile')
export class ProfileController {
  @Post('update')
  updateProfile(@Req() req: Request, @Res() res: Response) {
    // CSRF 토큰 검증은 자동으로 처리된다.
    // 프로필 업데이트 로직
    res.send('Profile updated successfully');
  }
}

Access Token 동작 원리

1. 초기 설정

NestJS 서버는 JWT(JSON Web Token)를 사용하여 액세스 토큰을 발급하고 검증한다.

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth/auth.service';
import { AuthController } from './auth/auth.controller';
import { JwtStrategy } from './auth/jwt.strategy';
import { PassportModule } from '@nestjs/passport';

@Module({
  imports: [
    PassportModule,
    JwtModule.register({
      secret: 'your_jwt_secret_key',
      signOptions: { expiresIn: '60m' },
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
})
export class AppModule {}

2. 로그인 및 액세스 토큰 발급

사용자가 로그인하면 서버는 액세스 토큰을 발급한다.

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(private readonly jwtService: JwtService) {}

  async login(user: any) {
    const payload = { username: user.username, sub: user.userId };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }

  async validateUser(username: string, pass: string): Promise<any> {
    // 사용자 검증 로직
    return { userId: 1, username: 'testuser' }; // 예시 사용자
  }
}

import { Controller, Post, Req } from '@nestjs/common';
import { AuthService } from './auth.service';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('login')
  async login(@Req() req) {
    const user = await this.authService.validateUser(req.body.username, req.body.password);
    return this.authService.login(user);
  }
}

3. 보호된 API 호출

클라이언트는 API 호출 시 액세스 토큰을 HTTP 헤더에 포함하여 서버에 요청한다.

클라이언트 측 API 호출 예시 (JavaScript)

const accessToken = 'abcdef1234567890';

fetch('https://api.example.com/user-data', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${accessToken}`
  }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

4. 액세스 토큰 검증

서버는 요청에 포함된 액세스 토큰을 검증하여 사용자의 신원과 권한을 확인한다.

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: 'your_jwt_secret_key',
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') implements CanActivate {
  canActivate(context: ExecutionContext) {
    return super.canActivate(context);
  }
}

5. 보호된 API 엔드포인트

보호된 API 엔드포인트를 작성한다.

import { Controller, Get, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './auth/jwt-auth.guard';

@Controller('user')
export class UserController {
  @Get('data')
  @UseGuards(JwtAuthGuard)
  getUserData() {
    return { username: 'testuser', email: 'testuser@example.com' };
  }
}
profile
RECORD DEVELOPER

0개의 댓글