Next

Next13 미들웨어 사용법.

하림회사 2023. 8. 7. 20:59

프로젝트 최상단 루트에

 

middleware.js(tsx) 파일을 만들어서 작성하면

자동으로 미들웨어로서 기능으로 서버작업에 참견한다.

 

공부코드

import { getToken } from 'next-auth/jwt';
import { NextResponse } from 'next/server';

const middleware = async (request) => {

    const session = await getToken({ req: request }) // 로그인 정보 확인.
    if (request.nextUrl.pathname.startsWith('/write')) { // write 사이트 접속시 검사 
        if (session === null) { // 로그인안되면 다른url로 보내버린다.
            return NextResponse.redirect(new URL('http://localhost:3000/list'), request.url)
        }
        return NextResponse.next()
    }

    // 현재 요청중인 url    //리스트 페이지에 접속하면 실행 스타츠위츠는 list 시작하는 모든 페이지 검사
    if (request.nextUrl.pathname.startsWith('/list')) {
        console.log(new Date()) // 시간 알려줌
        console.log(request.headers.get('sec-ch-ua-platform'))//사용자 시스템 확인
        return NextResponse.next() // 리턴하고 꼭 써야함 별일없으니 통과시켜주세요 뜻
    }

    if (request.nextUrl.pathname.startsWith('/register')) {
        if (request.cookies.has('visited') === false) {
            const response = NextResponse.next()
            response.cookies.set({
                name: 'visited',
                value: 'true',
                maxAge: 3600,
                httpOnly: true
            })
            return response  //쿠키생성
        }
        return NextResponse.next()
    }

}

export default middleware;

 쿠키를 다루는 문법.

import { NextResponse } from 'next/server';
export async function middleware(request) {

  request.cookies.get('쿠키이름')  //출력
  request.cookies.has('쿠키이름')  //존재확인
  request.cookies.delete('쿠키이름')  //삭제
  
  const response = NextResponse.next()
  response.cookies.set({
    name: 'mode',
    value: 'dark',
    maxAge: 3600,
    httpOnly : true //자바스크립트로 쿠키를 조작하는것을 방지
  })  
  return response  //쿠키생성
}
더보기

 

서버는 요청이 들어오면 응답해주는 간단한 프로그램입니다. 

 

- DB 조회해보기 

- 로그인 안한 사람이 /어쩌구 하위 경로로 GET, POST요청 하는걸 막기

- 이상한 페이지 접속하면 다른 곳으로 이동시키기 

- 쿠키만들어주기 

 

그리고 서버는 이런 여러가지 짓거리가 가능합니다.

그런데 간혹 하나의 서버기능을 100만개의 서버 API에 동시에 적용하고 싶은 경우가 있습니다.

그럴 땐 middleware 쓰면 가능합니다. 

요청이랑 응답 사이에 간섭하는 코드라서 middleware 라고 부릅니다. 

 

 

 

 

 

middleware.js

 

 

(/middleware.js)

import { NextResponse } from 'next/server'

export async function middleware(request) {
  (실행할코드~~)
} 

Next.js에선 app폴더와 나란한 위치에

middleware.js 파일 만들고 함수 하나 만들면 middleware 작성이 가능합니다. 

그럼 여러분이

(1) 서버로 GET POST 등 요청을 날리거나 

(2) 페이지 접속할 때마다 위 코드가 실행됩니다. 

 

정확한 시점은 서버 API 실행이나 페이지 로드가 되기 전에 위 코드가 실행됩니다.

그래서 여러분 맘대로 요청을 가로채고 도청하고 간섭하고 할 수 있는 것임 

 

 

 

 

 

(/middleware.js)

import { NextResponse } from 'next/server'

export async function middleware(request) {
  console.log(request.nextUrl)  //유저가 요청중인 URL 출력해줌
  console.log(request.cookies)  //유저가 보낸 쿠키 출력해줌
  console.log(request.headers)  //유저의 headers 정보 출력해줌 
  NextResponse.next()  //통과
  NextResponse.redirect()  //다른페이지 이동
  NextResponse.rewrite()  //다른페이지 이동
} 

middleware 안에선 이런 여러가지 것들을 사용할 수 있습니다.

진짠지 궁금하면 한두개 실행해보십시오. 

 

- headers 에는 유저가 쓰는 브라우저, 언어, OS 정보 등이 들어있습니다.

- headers, cookies 하나 출력은 .get('항목이름') 뒤에 붙입시다. Map 자료형이라 그렇습니다. 

- NextResponse 를 잘 쓰면 유저를 별일 없이 통과시켜주거나 다른 곳으로 이동시킬 수 있습니다. 

- rewrite()는 다른 페이지 이동인데 브라우저 주소창에 뜨는 URL을 변경하지 않고 이동해줌 

 

 

 

 

 

1. 특정 페이지 접속하는 놈들 기록하기 

 

어떤 놈이 /list 페이지 들어올 때 마다

터미널에 유저정보, 현재시간 이런걸 출력해서 몰래 감청을 해보도록 합시다.

request라는 변수 출력해서 유심히 보면 유저 ip, geo 등 여러 정보들이 들어있는데 실제 사이트 배포시 사용가능하고

지금은 현재시간과 유저의 접속기기 OS 정도만 터미널에 출력해봅시다. 

 

 

(/middleware.js)

import { NextResponse } from 'next/server'

export async function middleware(request) {
  if (request.nextUrl.pathname == '/list') {
    console.log(new Date().toLocaleString())
    console.log(request.headers.get('sec-ch-ua-platform'))
    return NextResponse.next()
  }
} 

request.nextUrl.pathname 출력하면 현재 요청하는 URL이 나오는데

그게 /list와 일치하면 저런 내용 출력해보라고 코드짰을 뿐입니다.

sec-ch-ua-platform 출력하면 OS정보가 나오는지 어떻게 알았냐고요?

구글찾아보거나 request.headers 출력해보면 알 수 있습니다. 

(다만 사파리 브라우저로 접속시 사용불가)

 

 

 

 

if (request.nextUrl.pathname.startsWith('/list') ) { }

참고로 URL 뒤에 query string이라든지 그런걸 유저 맘대로 붙일 수 있기 때문에

URL을 검사할 땐 등호보다는 .startsWith() 사용하는게 좋습니다.

그럼 '/list' 라는 글자로 시작하는 URL을 전부 잡아낼 수 있습니다. 

'/list/어쩌구' 같은 하위경로도 잡아낼듯 

 

아무튼 그래서 이런 정보들을 DB에 보관하거나 날짜별 파일로 기록하는 사이트들도 있습니다. 

 

 

 

 

 

2. 로그인 안된 유저는 /write 페이지 접속 못하게 막자 

 

Next-auth를 이용해서 로그인기능을 구현해놨는데 

이걸 이용해서 middleware 에서도 로그인 여부를 판단할 수 있습니다. 

 

(.env 파일)

NEXTAUTH_SECRET=qwer1234 

일단 이거 하려면 .env파일에 NEXTAUTH_SECRET 아무거나 집어넣는 사전작업이 필요합니다.

[...nextauth].js 파일에 넣던 secret : 'qwer1234' 이런거 그대로 똑같이 사용하면 됩니다.

JWT 암호화해주는 키라 길게 할 수록 좋습니다. 

 

 

 

import { NextResponse } from 'next/server';
import { getToken } from "next-auth/jwt";

export async function middleware(request) {

  if (request.nextUrl.pathname.startsWith('/write')) {
    const session = await getToken({ req : request })
    console.log('세션', session)
    if (session == null) {
      return NextResponse.redirect(new URL('/api/auth/signin', request.url));
    }
  }
} 

(1) await getToken() 쓰면 현재 로그인한 유저의 정보를 출력할 수 있습니다. 

(2) JWT를 사용중일 때만 사용가능할텐데 

session 사용시엔 session 쿠키가 있는지,

그리고 그 session이 DB에 있는지 조회해보고 이상이 없는지 확인하는 코드로 비슷하게 구현가능합니다.

(3) redirect/rewrite 함수 안에는 그냥 저 자리에 이동할 URL만 잘 채우면 유저를 강제로 이동시킵니다.

 

 

 

NextResponse.redirect('http://localhost:3000/api/auth/signin'); 

▲ 이동시 뭔가 안되면 직접 절대경로로 작성하면 됩니다.

 

 

Q. /write 페이지 컴포넌트 안에서 if문 써도 똑같이 구현할 수 있는거 아님?  

- /write 말고 다른 막고싶은 페이지들이 더 있으면 여기 쓰는게 편할 수도 있습니다. 

 

 

 

 

 

 

 

 

3. 특정페이지 접속시 쿠키를 만들어보자 

 

다크모드 버튼만들 때 쿠키생성을 프론트엔드에서 했었는데 (useEffect)

그게 싫으면 서버에서 만들어줘도 됩니다. 

서버 API에서 만들어도 되고 middleware에서 만들어도 되는데 우리는 middleware를 써봅시다. 

 

 

import { NextResponse } from 'next/server';
export async function middleware(request) {

  request.cookies.get('쿠키이름')  //출력
  request.cookies.has('쿠키이름')  //존재확인
  request.cookies.delete('쿠키이름')  //삭제
  
  const response = NextResponse.next()
  response.cookies.set({
    name: 'mode',
    value: 'dark',
    maxAge: 3600,
    httpOnly : true
  })  
  return response  //쿠키생성
} 

여기선 이런 문법으로 쿠키들을 다룰 수 있습니다. 

 

브라우저에 있는 모든건 언제나 유저가 조작이 가능하기 때문에

조작하면 큰일나는 정보는 서버에 보관하도록 합시다. 

참고로 httpOnly를 true로 설정한 쿠키는 유저가 자바스크립트로 맘대로 조작이 불가능하게 막을 수 있습니다.

그래서 약간 더 안전할 수 있는데 크롬 개발자도구에서 직접 수정하는건 막을 수 없음

 

 

Q. 유저가 /register 페이지 방문시 visited=true 라는 쿠키를 생성해주려면 코드를 어떻게 짜야할까요?

같은 이름의 쿠키가 이미 있으면 아무 짓거리도 안해줘야합니다. 

 

언제까지 바짓가랑이 붙잡을 것임

 

 

import { NextResponse } from 'next/server';
export async function middleware(request) {

  if (request.nextUrl.pathname.startsWith('/register')) {
    
    if (request.cookies.has('visited') == false) {
      const response = NextResponse.next()
      response.cookies.set({
        name: 'visited',
        value: 'true',
        maxAge: 3600,
      })
      return response
    }
    return NextResponse.next()
    
  }
} 

return을 만나면 return 들어있던 함수실행이 조기종료되고 그 뒤에 있는 코드들을 실행하지 않습니다.