728x90
반응형
Req:RTU Equip[(EHT-RC)] Send 8 bytes[01 03 00 01 00 02 95 CB]
Res:RTU Equip[(EHT-RC)] Recv(1.3) 9 bytes[01 03 04 00 FD 01 D5 AB CC]

 

Req에서 01은 디바이스 ID (또는 Slave ID) , 03은 명령(읽기), 00 01은 시작 레지스터 주소(1:온도, 2:습도) , 00 02 개수

Res에서 01은 디바이스 ID, 03은 명령, 04는 뒤에 가져온 바이트 수(4바이트), 00FD 온도, 01 D5 습도 (나머진 CRC)

 

00 FD 01 D5는 총 4바이트로 봅니다. 응답 메시지에서 04는 뒤에 오는 데이터의 바이트 수를 의미합니다.

 

한 개의 16진수 값(예: 00)은 4비트입니다.

따라서 두 자리의 16진수 값(예: 00 또는 FF)은 8비트이며, 이는 1바이트(0~255)에 해당합니다.

따라서 00과 같은 값은 1바이트에 해당하며, 00 FD 01 D5는 총 4바이트가 됩니다.

1바이트는 8비트이고, 32비트는 4바이트입니다.

728x90
반응형

'일기 > 개발일기' 카테고리의 다른 글

[241022] Modbus RTU vs Modbus TCP/IP  (1) 2024.10.22
[241021] FMS와 스위치  (1) 2024.10.21
[241015] 모드버스 RTU와 TCP  (0) 2024.10.15
[241014] RS-485 Modbus RTU 통신  (1) 2024.10.14
[241008] Next.js와 nextAuth 자동화 기능  (1) 2024.10.08
728x90
반응형

RS486 Modbus RTU

  • RS485: RS485는 Recommended Standard 485의 약자로, 산업용 통신 표준입니다. 이 표준은 다중 드롭 네트워크에서 최대 32개의 장치가 하나의 버스에서 통신할 수 있도록 합니다. (N:N 통신)
  • Modbus: Modbus는 산업용 전자 장치 간의 통신을 위한 프로토콜입니다. Modbus는 마스터-슬레이브 구조를 사용하며, 데이터 전송을 위해 다양한 물리적 매체(RS485, RS232, TCP/IP 등)를 지원합니다
  • RTU (Remote Terminal Unit): RTU는 Modbus 프로토콜의 전송 모드 중 하나로, Remote Terminal Unit의 약자입니다. RTU 모드는 이진수 형식으로 데이터를 전송하며, 주로 RS485와 같은 직렬 통신에서 사용.
  • TCP/IP (Transmission Control Protocol/Internet Protocol): TCP/IP는 인터넷과 같은 네트워크에서 데이터를 전송하기 위한 프로토콜 스택입니다. Modbus TCP/IP는 이 프로토콜 스택을 사용하여 이더넷 네트워크를 통해 데이터를 전송합니다.

RTU와 TCP/IP의 차이점

  • 물리적 계층
    • RTU는 RS485 또는 RS232와 같은 직렬 통신
    • TCP/IP는 이더넷 네트워크
  • 데이터 전송 형식
    • RTU는 데이터를 이진 형식으로 전송
    • TCP/IP는 데이터를 ASCII 형식으로 인코딩하여 전송
  • 속도와 유연성
    • RTU는 구현이 더 간단하고 비용이 적음
    • TCP/IP는 기존의 이더넷 네트워크를 사용하기 때문에 더 빠르고 유연
  • 주소 지정
    • RTU는 각 장치에 노드 번호를 할당
    • TCP/IP는 각 장치에 IP 주소를 할당

TCP/IP


패킷 전송 방식: 데이터는 패킷이라는 작은 조각으로 나뉘어 전송되며, 네트워크 경로를 효율적으로 이용합니다. 이 때문에 데이터 충돌이 최소화되고, 각 패킷이 최적의 경로를 선택해 전달될 수 있습니다.

에러 제어: TCP/IP는 데이터 전송 중 발생할 수 있는 오류를 감지하고 수정하는 기능을 내장하고 있어, 데이터 손실이 거의 없습니다.

라우팅 기능: 네트워크 내 여러 경로를 통해 데이터를 전송할 수 있어, 특정 경로에 문제가 생겨도 대체 경로로 데이터를 전송할 수 있습니다. 이는 네트워크 안정성을 높이는 데 크게 기여합니다.

멀티태스킹: 여러 장치가 동시에 통신할 수 있는 능력이 뛰어나며, 대규모 데이터 전송 환경에 최적화되어 있습니다. 예를 들어, 수백 대의 센서가 데이터를 동시에 전송하더라도 네트워크가 효율적으로 처리할 수 있습니다.

확장성: 새로운 장치를 네트워크에 쉽게 추가할 수 있어, 네트워크의 크기를 유연하게 확장할 수 있습니다.

 

RTU

직렬 통신 방식: RTU는 직렬 통신 방식으로 데이터를 전송하는데, 이는 한 번에 하나의 데이터 스트림만 전송할 수 있어, 대규모 데이터 전송에 비효율적입니다.

낮은 처리 능력: 직렬 통신은 TCP/IP에 비해 데이터 전송 속도가 느립니다. 이는 많은 양의 데이터를 빠르게 전송해야 하는 환경에서는 큰 단점이 됩니다.

오류 처리의 한계: RTU는 기본적으로 오류 감지 및 수정 기능이 부족하며, 수동으로 오류를 처리해야 하는 경우가 많아 데이터 신뢰성이 떨어질 수 있습니다.

확장성 부족: 네트워크에 새로운 장치를 추가하는 과정이 복잡하고, 물리적인 배선 변경이 필요할 수 있습니다. 이는 대규모 네트워크에서는 큰 문제가 될 수 있습니다.

이러한 이유들 때문에 TCP/IP가 대규모 데이터 관리와 네트워크 확장성에서 더 유리하며,

RTU는 소규모 단순 통신 환경에 더 적합합니다.

728x90
반응형
728x90
반응형

RS-485 Modbus RTU 통신

  1. RS-485: 차동 신호를 사용하는 직렬 통신 표준으로, 노이즈에 강하고 장거리 통신이 가능합니다.
  2. Modbus: 산업용 통신 프로토콜로, 마스터-슬레이브 구조를 가집니다.
  3. RTU: Remote Terminal Unit의 약자로, 바이너리 데이터 전송 방식을 사용합니다.
  4. 프레임 구조: [슬레이브 주소] [기능 코드] [데이터] [CRC 체크섬]

주어진 데이터 설명: [TX]는 전송(Transmit), [RX]는 수신(Receive)을 나타냅니다.

예를 들어, 첫 번째 전송 프레임을 분석해보면: [TX] 01 03 00 01 00 70 15 EE

  • 01: 슬레이브 주소 (1번 장치)
  • 03: 기능 코드 (레지스터 읽기)
  • 00 01: 시작 레지스터 주소 (온도)
  • 00 70: 읽을 레지스터 개수 (112개)
  • 15 EE: CRC 체크섬

수신 프레임: [RX] 01 03 02 00 01 79 84

  • 01: 슬레이브 주소
  • 03: 기능 코드
  • 02: 바이트 수
  • 00 01: 데이터 (값: 1)
  • 79 84: CRC 체크섬

이 통신에서는 마스터가 슬레이브(온습도계)에게 데이터를 요청하고, 슬레이브가 응답하는 과정을 보여줍니다.

RS-485 Modbus RTU 통신에서 추가로 알아야 할 내용들:

  1. 통신 속도: 일반적으로 9600, 19200, 38400, 57600, 115200 bps 등이 사용됩니다. 마스터와 슬레이브 간 속도가 일치해야 합니다.
  2. 데이터 형식: 8비트 데이터, 노 패리티, 1 스톱 비트가 주로 사용됩니다.
  3. 기능 코드: 다양한 기능 코드가 있습니다. 예를 들어:
    • 01: 코일 상태 읽기
    • 02: 입력 상태 읽기
    • 03: 보유 레지스터 읽기
    • 04: 입력 레지스터 읽기
    • 05: 단일 코일 쓰기
    • 06: 단일 레지스터 쓰기
    • 15: 다중 코일 쓰기
    • 16: 다중 레지스터 쓰기
  4. 에러 처리: 슬레이브가 요청을 처리할 수 없는 경우, 에러 응답을 보냅니다. 이때 기능 코드에 0x80을 더한 값을 사용합니다.
  5. 타이밍: 프레임 간 간격, 문자 간 간격 등의 타이밍 규칙이 있습니다. 이를 준수하지 않으면 통신 오류가 발생할 수 있습니다.
  6. 종단 저항: RS-485 라인의 양 끝에 120Ω 저항을 연결하여 신호 반사를 방지합니다.
  7. 주소 범위: Modbus에서는 1부터 247까지의 주소를 사용할 수 있습니다. 0은 브로드캐스트 주소로 사용됩니다.
  8. 데이터 모델: Modbus는 4가지 기본 데이터 타입을 사용합니다 - 코일, 이산 입력, 입력 레지스터, 보유 레지스터.

 

 

[TX] 02 03 00 02 00 01 25 F9 [RX] 02 03 02 01 F7 BC 52

02 : 기계에 설정한 번호

03 : 3번의 기능(Read) 수행

00 02 : 주소2번 (습도)

00 01 : 읽을 레지스터(메모리 위치) 수

25 F9 : CRC

02 : 기계 번호

03 : 3번 기능

02 : 바이트수

01 F3 : 데이터 (01F7 → 503 → 50.3% 습도)

BC 52 : CRC


[TX] 02 06 00 0D 00 01
[RX] 02 06 00 0D 00 01

02(장치번호2) 06(쓰기기능) 00 0D(13주소, Slave address) 00 01 (값1)

  • 현재 주소가 2인 장치에
  • 06 단일 레지스터 쓰기 명령을 사용하여
  • 레지스터 주소 13(000D) - 주소(1~247)에
  • 값 1을 써라.
728x90
반응형
728x90
반응형

그냥 node.js로 처음부터 구성할때는 요청해야 할 api설정, 미들웨어 등 직접 작성하고, 사용할 곳을 정했어야 했는데

Next.js 프레임워크와 nextAuth 라이브러리를 사용하니 자동으로 처리하는 것이 많아 혼란이 생겼다.

 

middleware.ts라는 파일을 만들기만 했는데 왜 자동으로 사용되는거지??

인증관련 코드에서 api/auth/session 같은 코드는 짠적이 없는데 왜 계속 신호가 가는거지?? 등등

 

편하긴 하지만 내가 알지 못하는 동작들이 있어서 머리가 아팠다.

그래서 검색을 통해 알아낸 정보를 적었다.

 


 

Next.js 자동화 기능

  1. 라우팅 자동화
    • 파일 시스템 기반 라우팅
    • pages/ 또는 app/ 디렉토리의 파일 구조에 따라 자동으로 라우트 생성
    • 동적 라우트 ([id].js) 자동 처리
  2. 코드 최적화
    • 자동 코드 분할 (Code Splitting)
    • 이미지 최적화 (next/image)
    • 자동 폰트 최적화
    • 자동 정적 최적화 (Automatic Static Optimization)
  3. 빌드 프로세스
    • TypeScript 설정 자동화
    • 환경 변수 자동 로딩 (.env 파일)
    • Hot Module Replacement (HMR)

NextAuth.js 자동화 기능

  1. API 라우트 자동 생성
    • /api/auth/[...nextauth] 경로가 자동으로 처리
    • 다음 엔드포인트 자동 생성:
      • /api/auth/signin
      • /api/auth/signout
      • /api/auth/session
      • /api/auth/providers
      • /api/auth/csrf
  2. 세션 관리
    • 세션 생성 및 갱신 자동화
    • JWT 또는 데이터베이스 세션 자동 처리
  3. 미들웨어 자동화
    • middleware.ts 파일 생성 시 자동으로 인증 체크
    • 설정된 경로에 대한 보호 자동화
  4. 타입 안정성
    • Session 타입 자동 생성
    • API 응답 타입 자동 처리

자주 놓치는 자동화 기능들

  1. 자동 리다이렉션
    • 인증되지 않은 사용자가 보호된 페이지 접근 시 자동 리다이렉트
    • 로그인 후 원래 페이지로 자동 리다이렉트
  2. CSRF 보호
    • CSRF 토큰 자동 생성 및 검증
  3. 애셋 최적화
    • 폰트, 이미지 등의 자동 최적화
    • 필요한 JS/CSS만 로드
  4. 개발 환경 최적화
    • Fast Refresh
    • 에러 오버레이
    • TypeScript 오류 자동 보고

주의사항

  1. 자동화된 기능을 오버라이드할 때는 주의가 필요
  2. 커스텀 설정이 필요한 경우 공식 문서 참조
  3. 자동화된 기능이 항상 최적은 아닐 수 있음

 

728x90
반응형

'일기 > 개발일기' 카테고리의 다른 글

[241015] 모드버스 RTU와 TCP  (0) 2024.10.15
[241014] RS-485 Modbus RTU 통신  (1) 2024.10.14
[241002-3] Next.js signIn의 redirect  (0) 2024.10.02
[241002] Next.js 미들웨어  (0) 2024.10.02
[240930] Next.js 인증  (2) 2024.09.30
728x90
반응형

redirect 옵션

  • redirect: true: 인증이 성공하면 자동으로 지정된 URL로 리다이렉트합니다. 이 경우 **callbackUrl**을 지정해야 합니다. 인증이 성공하면 사용자가 callbackUrl로 이동합니다.
  • redirect: false: 인증이 성공해도 자동으로 리다이렉트하지 않습니다. 대신, 응답 객체를 통해 인증 결과를 처리할 수 있습니다. 이 경우 callbackUrl을 지정할 필요가 없습니다.

callbackUrl 옵션

  • callbackUrl: redirect: true일 때 사용됩니다. 인증이 성공하면 사용자가 이동할 URL을 지정합니다. 예를 들어, /dashboard로 설정하면 인증 성공 시 사용자가 대시보드 페이지로 이동합니다.
const response = await signIn('AuthCheck', {
    id,
    password,
    redirect: true,
    callbackUrl: '/dashboard',
});
// 인증 성공 시 자동으로 '/dashboard'로 리다이렉트
// 중요한건 뒤에 뭐가 있든 실행을 안한다!!!!
const response = await signIn('AuthCheck', {
    id,
    password,
    redirect: false,
});

if (response.ok) {
    alert('로그인 성공');
    router.push('/dashboard'); // 수동으로 '/dashboard'로 이동
} else {
    alert('로그인 실패');
    console.error(response.error);
}

redirect: false를 사용하면 인증 성공 후 원하는 로직을 추가로 실행할 수 있습니다. 예를 들어, 성공 메시지를 표시한 후 특정 페이지로 이동하는 등의 작업을 할 수 있습니다.

 

결론

redirect: true를 하면 callbackUrl로 이동하고 뒤에 뭐가 있든 실행을 안해버린다는 것!

나는 그것도 모르고 alert을 하고 있었다....

728x90
반응형

'일기 > 개발일기' 카테고리의 다른 글

[241015] 모드버스 RTU와 TCP  (0) 2024.10.15
[241014] RS-485 Modbus RTU 통신  (1) 2024.10.14
[241008] Next.js와 nextAuth 자동화 기능  (1) 2024.10.08
[241002] Next.js 미들웨어  (0) 2024.10.02
[240930] Next.js 인증  (2) 2024.09.30
728x90
반응형
  1. public 폴더에 동영상 파일을 넣는다. (mp4)
  2. types폴더에 video.d.ts 파일 생성
clare module '*.mp4' {
    const src: string;
    export default src;
}

    3. components 폴더에 VideoComponent.tsx 파일 생성

'use client';

import { Box } from '@mui/material';
import BkVideo from '../public/LoginPage_video.mp4';

const VideoComponent = () => {
    return (
        <Box
            component={'div'}
            position={'fixed'}
            zIndex={-1}
            width={'100vw'}
            height={'100vh'}
            top={0}
            left={0}
        >
            <video
                autoPlay
                playsInline
                loop
                muted
                width={'100%'}
                height={'100%'}
                preload="none"
                src={BkVideo}
                style={{
                    objectFit: 'cover',
                }}
            />
        </Box>
    );
};

export default VideoComponent;

    4. 로그인 페이지에 적용

'use client';
import React from 'react';
import { Container, Box, Typography, Paper } from '@mui/material';
import VideoComponent from '@/components/VideoComponent';
import LoginForm from '@/components/LoginForm';

export default function LoginPage() {
    return (
        <Container
            component="main"
            maxWidth="xs"
            sx={{
                height: '100vh',
                display: 'flex',
                alignItems: 'center',
            }}
        >
            <Paper elevation={3} sx={{ padding: 4, width: '100%' }}>
                <Box
                    sx={{
                        display: 'flex',
                        flexDirection: 'column',
                        alignItems: 'center',
                    }}
                >
                    <Typography component="h1" variant="h5" sx={{ mb: 2 }}>
                        FMS 5.6
                    </Typography>
                    <LoginForm />
                </Box>
            </Paper>
            <VideoComponent />
        </Container>
    );
}

 


public폴더에 이미지를 넣고 다음과 같이 작성

body {
    background-image: url('../public/bkground.jpg');
    /* background-size: cover;
    background-position: center; */
}

 

728x90
반응형
728x90
반응형

Next.js에서 middleware란?

 

Next.js에서 middleware.ts라는 파일을 프로젝트 루트 디렉토리에 생성하면 자동으로 미들웨어로 인식됩니다. 이 파일은 요청이 완료되기 전에 실행되며, 요청에 따라 응답을 수정하거나 리디렉션, 헤더 수정 등을 할 수 있습니다

 

문제발생

현재 그냥 api/auth를 사용하면서 middleware를 적용하면 에러발생

    if (!token) {
        console.log('No token found, redirecting to login');
        // if (
        //     request.nextUrl.pathname !== '/login' &&
        //     request.nextUrl.pathname !== '/'
        // ) {
        //     return NextResponse.redirect(new URL('/login', request.url));
        // }
    } else {
        console.log('Token found:', token);
    }

 

에러발생이유

미들웨어가 전역으로 진행중인데, 로그인화면에서 아이디와 비밀번호를 제출할땐, token이 생성되기 전이라서 if문의 기능을 하지 못함

 

문제해결

config에서 로그인과 관련된 /login /api 폴더를 제외함

export const config = {
    matcher: ['/((?!api|_next/static|_next/image|favicon.ico|.*\\\\.png$|login).*)'],
};

토큰이 없을때 리다이렉션하는 로직을 좀 더 세밀하게 조정

if (!token) {
    if (
        !request.nextUrl.pathname.startsWith('/api/auth') &&
        !request.nextUrl.pathname.startsWith('/login') &&
        request.nextUrl.pathname !== '/'
    ) {
        return NextResponse.redirect(new URL('/login', request.url));
    }
    console.log('No token found, but on an allowed path');
} else {
    console.log('Token found:', token);
}

 

전체코드

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { getToken } from 'next-auth/jwt';

export async function middleware(request: NextRequest) {
    console.log('Middleware is running');
    const token = await getToken({
        req: request,
        secret: process.env.NEXTAUTH_SECRET,
    });

    if (!token) {
        if (
            !request.nextUrl.pathname.startsWith('/api/auth') &&
            !request.nextUrl.pathname.startsWith('/login') &&
            request.nextUrl.pathname !== '/'
        ) {
            return NextResponse.redirect(new URL('/login', request.url));
        }
        console.log('No token found, but on an allowed path');
    } else {
        console.log('Token found:', token);
    }

    return NextResponse.next();
}

export const config = {
    matcher: [
        '/((?!api|_next/static|_next/image|favicon.ico|.*\\.png$|login).*)',
    ],
};
728x90
반응형

'일기 > 개발일기' 카테고리의 다른 글

[241015] 모드버스 RTU와 TCP  (0) 2024.10.15
[241014] RS-485 Modbus RTU 통신  (1) 2024.10.14
[241008] Next.js와 nextAuth 자동화 기능  (1) 2024.10.08
[241002-3] Next.js signIn의 redirect  (0) 2024.10.02
[240930] Next.js 인증  (2) 2024.09.30
728x90
반응형

https://nextjs.org/docs/app/building-your-application/authentication#database-sessions

 

Building Your Application: Authentication | Next.js

Learn how to implement authentication in your Next.js application.

nextjs.org

 

 

인증에 대한 프로세스를 세 가지 개념으로 분류

  1. 인증: 사용자가 본인이 맞는지 확인합니다. 사용자 이름과 비밀번호 등 사용자가 가지고 있는 정보로 신원을 증명해야 합니다.
  2. 세션 관리: 요청 전반에서 사용자의 인증 상태를 추적합니다.
  3. 인증: 사용자가 액세스할 수 있는 경로와 데이터를 결정합니다

인증 (가입 및 로그인)

  1. 사용자 자격 증명 (프론트에서 이름, 이메일 등 받는 방식)
  2. 서버에서 형식 검증
import { z } from 'zod'
 
export const SignupFormSchema = z.object({
  name: z
    .string()
    .min(2, { message: 'Name must be at least 2 characters long.' })
    .trim(),
  email: z.string().email({ message: 'Please enter a valid email.' }).trim(),
  password: z
    .string()
    .min(8, { message: 'Be at least 8 characters long' })
    .regex(/[a-zA-Z]/, { message: 'Contain at least one letter.' })
    .regex(/[0-9]/, { message: 'Contain at least one number.' })
    .regex(/[^a-zA-Z0-9]/, {
      message: 'Contain at least one special character.',
    })
    .trim(),
})
 
export type FormState =
  | {
      errors?: {
        name?: string[]
        email?: string[]
        password?: string[]
      }
      message?: string
    }
  | undefined
  1. 사용자 생성 또는 자격 증명 확인
export async function signup(state: FormState, formData: FormData) {
  // 1. Validate form fields
  // ...
 
  // 2. Prepare data for insertion into database
  const { name, email, password } = validatedFields.data
  // e.g. Hash the user's password before storing it
  const hashedPassword = await bcrypt.hash(password, 10)
 
  // 3. Insert the user into the database or call an Auth Library's API
  const data = await db
    .insert(users)
    .values({
      name,
      email,
      password: hashedPassword,
    })
    .returning({ id: users.id })
 
  const user = data[0]
 
  if (!user) {
    return {
      message: 'An error occurred while creating your account.',
    }
  }
 
  // TODO:
  // 4. Create user session
  // 5. Redirect user
}

세션 관리

세션의 2가지 유형

  1. Stateless : 세션을 브라우저의 쿠키에 저장 → 보안성이 떨어질 수 있음
  2. Database : 세션을 데이터베이스에 저장 → 안전하지만 리소스 사용 증가

데이터베이스 세션

데이터베이스 세션을 만들고 관리하려면 다음 단계를 따라야 합니다.

  1. 세션과 데이터를 저장할 데이터베이스에 테이블을 만듭니다(또는 인증 라이브러리가 이를 처리하는지 확인합니다).
  2. 세션을 삽입, 업데이트, 삭제하는 기능을 구현합니다.
  3. 사용자의 브라우저에 저장하기 전에 세션 ID를 암호화하고, 데이터베이스와 쿠키가 동기화된 상태를 유지하도록 합니다(권장)
import cookies from 'next/headers'
import { db } from '@/app/lib/db'
import { encrypt } from '@/app/lib/session'
 
export async function createSession(id: number) {
  const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
 
  // 1. Create a session in the database
  const data = await db
    .insert(sessions)
    .values({
      userId: id,
      expiresAt,
    })
    // Return the session ID
    .returning({ id: sessions.id })
 
  const sessionId = data[0].id
 
  // 2. Encrypt the session ID
  const session = await encrypt({ sessionId, expiresAt })
 
  // 3. Store the session in cookies for optimistic auth checks
  cookies().set('session', session, {
    httpOnly: true,
    secure: true,
    expires: expiresAt,
    sameSite: 'lax',
    path: '/',
  })
}

권한 부여

사용자가 인증되고 세션이 생성되면 애플리케이션 내에서 사용자가 액세스하고 수행할 수 있는 작업을 제어하는 권한 부여를 구현할 수 있습니다.

권한 확인에는 두 가지 주요 유형이 있습니다.

  1. Optimistic
  2. 쿠키에 저장된 세션 데이터를 사용하여 사용자가 경로에 액세스하거나 작업을 수행할 권한이 있는지 확인합니다. 이러한 확인은 UI 요소를 표시/숨기거나 권한이나 역할에 따라 사용자를 리디렉션하는 것과 같은 빠른 작업에 유용합니다.
  3. 보안
  4. 사용자가 데이터베이스에 저장된 세션 데이터를 사용하여 경로에 액세스하거나 작업을 수행할 권한이 있는지 확인합니다. 이러한 검사는 보다 안전하며 민감한 데이터 또는 작업에 액세스해야 하는 작업에 사용됩니다.

Middleware를 사용한 낙관적 검사(선택 사항)

권한에 따라 미들웨어를 사용하고 사용자를 리디렉션하려는 경우가 있습니다 .

  • 낙관적 검사를 수행하려면 Middleware가 모든 경로에서 실행되므로 리디렉션 논리를 중앙 집중화하고 허가받지 않은 사용자를 사전 필터링하는 좋은 방법입니다.
  • 사용자 간에 데이터를 공유하는 정적 경로를 보호합니다(예: 유료 콘텐츠).

그러나 미들웨어는 사전 페치된 경로를 포함한 모든 경로에서 실행되므로 쿠키에서만 세션을 읽고(낙관적 검사) 성능 문제를 방지하기 위해 데이터베이스 검사를 피하는 것이 중요합니다.

import { NextRequest, NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { cookies } from 'next/headers'
 
// 1. Specify protected and public routes
const protectedRoutes = ['/dashboard']
const publicRoutes = ['/login', '/signup', '/']
 
export default async function middleware(req: NextRequest) {
  // 2. Check if the current route is protected or public
  const path = req.nextUrl.pathname
  const isProtectedRoute = protectedRoutes.includes(path)
  const isPublicRoute = publicRoutes.includes(path)
 
  // 3. Decrypt the session from the cookie
  const cookie = cookies().get('session')?.value
  const session = await decrypt(cookie)
 
  // 4. Redirect to /login if the user is not authenticated
  if (isProtectedRoute && !session?.userId) {
    return NextResponse.redirect(new URL('/login', req.nextUrl))
  }
 
  // 5. Redirect to /dashboard if the user is authenticated
  if (
    isPublicRoute &&
    session?.userId &&
    !req.nextUrl.pathname.startsWith('/dashboard')
  ) {
    return NextResponse.redirect(new URL('/dashboard', req.nextUrl))
  }
 
  return NextResponse.next()
}
 
// Routes Middleware should not run on
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|.*\\\\.png$).*)'],
}

미들웨어는 초기 검사에 유용할 수 있지만, 데이터를 보호하는 유일한 방어선이 되어서는 안 됩니다. 대부분의 보안 검사는 데이터 소스에 최대한 가깝게 수행해야 합니다. 자세한 내용은 데이터 액세스 계층을 참조하세요.

데이터 엑세스 계층(DAL) 생성

(• DAL은 데이터베이스와 직접 상호작용하여 데이터를 읽고 쓰는 계층입니다.)

데이터 요청과 권한 부여 논리를 중앙에서 관리하려면 DAL을 만드는 것이 좋습니다.

DAL에는 사용자가 애플리케이션과 상호 작용할 때 사용자의 세션을 확인하는 기능이 포함되어야 합니다. 최소한 이 기능은 세션이 유효한지 확인한 다음 추가 요청을 하는 데 필요한 사용자 정보를 리디렉션하거나 반환해야 합니다.

예를 들어, verifySession() 함수를 포함하는 DAL에 대한 별도 파일을 만듭니다. 그런 다음 React의 캐시를 사용합니다.

import 'server-only'
 
import { cookies } from 'next/headers'
import { decrypt } from '@/app/lib/session'
 
export const verifySession = cache(async () => {
  const cookie = cookies().get('session')?.value
  const session = await decrypt(cookie)
 
  if (!session?.userId) {
    redirect('/login')
  }
 
  return { isAuth: true, userId: session.userId }
})

그런 다음 데이터 요청, 서버 작업, 경로 핸들러에서 함수를 호출 할 수 있습니다.

export const getUser = cache(async () => {
  const session = await verifySession()
  if (!session) return null
 
  try {
    const data = await db.query.users.findMany({
      where: eq(users.id, session.userId),
      // Explicitly return the columns you need rather than the whole user object
      columns: {
        id: true,
        name: true,
        email: true,
      },
    })
 
    const user = data[0]
 
    return user
  } catch (error) {
    console.log('Failed to fetch user')
    return null
  }
})

데이터 전송 개체(DTO) 사용

(• DTO는 데이터를 캡슐화하여 계층 간에 전달하는 객체입니다.)

데이터를 검색할 때는 애플리케이션에서 사용할 필수 데이터만 반환하고 전체 객체는 반환하지 않는 것이 좋습니다. 예를 들어, 사용자 데이터를 가져오는 경우 비밀번호, 전화번호 등을 포함할 수 있는 전체 사용자 객체가 아닌 사용자의 ID와 이름만 반환할 수 있습니다.

그러나 반환된 데이터 구조를 제어할 수 없거나 전체 객체가 클라이언트에 전달되는 것을 피하려는 팀에서 작업하는 경우, 클라이언트에 노출해도 안전한 필드를 지정하는 등의 전략을 사용할 수 있습니다.

import 'server-only'
import { getUser } from '@/app/lib/dal'
 
function canSeeUsername(viewer: User) {
  return true
}
 
function canSeePhoneNumber(viewer: User, team: string) {
  return viewer.isAdmin || team === viewer.team
}
 
export async function getProfileDTO(slug: string) {
  const data = await db.query.users.findMany({
    where: eq(users.slug, slug),
    // Return specific columns here
  })
  const user = data[0]
 
  const currentUser = await getUser(user.id)
 
  // Or return only what's specific to the query here
  return {
    username: canSeeUsername(currentUser) ? user.username : null,
    phonenumber: canSeePhoneNumber(currentUser, user.team)
      ? user.phonenumber
      : null,
  }
}
728x90
반응형

+ Recent posts