728x90
반응형

로그인

1. 카카오 디벨로퍼 로그인

https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

2. 애플리케이션 추가

 

3. 플랫폼 도메인 설정 후 등록하러가기 버튼 클릭

4. 로그인 활성화 및 Redirect URI 설정

5. 동의 항목 설정

 

6. _app.tsx 파일에  script 추가

_app.tsx

const MyApp: NextPage<AppProps> = ({ Component, pageProps }: AppProps) => {


  return (
    <React.StrictMode>
      <Head>
        <title></title>
        <meta name="viewport" content="initial-scale=1.0, width=device-width" />

        <script src="https://developers.kakao.com/sdk/js/kakao.js"
          defer
        >
        </script>


      </Head >
      <ThemeProvider theme={Theme}>
          <Component {...pageProps} />
      </ThemeProvider>


    </React.StrictMode>
  );
};

 

7. 쿠키 생성 라우터 ( Back-end )

community.js

var express = require('express');
var router = express.Router();

const jwt = require("jsonwebtoken");

router.post("/login", async function (req, res, next) {

    const body = req.body;

    const tokenValue = {
        email: body.email
    }

    const token = jwt.sign({ isLogined: tokenValue }, "secretkey", {
        expiresIn: 86400000, // 토큰 제한시간
    });

    res.cookie("A12Ba5os9", token, {
        maxAge: 86400000, // 쿠키 제한시간
        path: "/",
        httpOnly: true,
    });

    return res.send({ success: true })
})

module.exports = router;

 

8.  컴포넌트에 구현

import { GetServerSideProps, NextPage } from "next";
import { useRouter } from "next/router";

const Community: NextPage<any> = (props: any) => {
    const router = useRouter();

    const kakaoInit = () => {
        const kakao = (window as any).Kakao;
        if (!kakao.isInitialized()) {
            kakao.init(process.env.NEXT_PUBLIC_KAKAO_SHARE_KEY);
        }

        return kakao;
    }

    const kakaoLogin = async () => {
        // 카카오 초기화
        const kakao = kakaoInit();

        // 카카오 로그인 구현
        kakao.Auth.login({
            success: () => {
                kakao.API.request({
                    url: '/v2/user/me', // 사용자 정보 가져오기
                    success: (res: any) => {
                        // 로그인 성공할 경우 
                        if (!res.kakao_account.email || res.kakao_account.email === "") {
                            alert("해당 계정의 이메일이 존재하지 않습니다.")
                        }
                        else {
                        	// 쿠키 생성
                            await axios.post("/api/community/login", { email: res.kakao_account.email })
                                .then(({ data }) => {
                                    if (data.success) {
                                        dispatch({ name: "email", value: res.kakao_account.email })
                                    }
                                    else {
                                        return alert(" 로그인에 실패하였습니다.")
                                    }
                                })
                        }
                        
                    },
                    fail: (error: any) => {
                        console.log(error);
                    }
                })
            },
            fail: (error: any) => {
                console.log(error);
            }
        })
    }

    return (
        <>
            <button onClick={(e: any) => { kakaoLogin(); }}>
                카톡으로 로그인
            </button>
        </>
    )
}

export default Community;

export const getServerSideProps: GetServerSideProps = async (context) => {
    return {
        props: {

        }
    }
}

/api 로 시작하는 라우터는 next.config.js 로 백엔드 프록시 설정을 해서 그렇습니다.

9. 확인하기


로그아웃

카카오 로그아웃은 이 사이트 뿐만 아니라 카카오 소셜 계정 자체를 로그아웃 시켜야 하는데,

https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#logout-of-service-and-kakaoaccount

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

카카오에서 제공하는 api를 사용해야 한다.

 

1. Logout Redirect URI 설정

 

2. 쿠키 삭제 라우터 ( Back-end )

var express = require('express');
var router = express.Router();

const jwt = require("jsonwebtoken");

router.post("/login", async function (req, res, next) {

    const body = req.body;

    const tokenValue = {
        email: body.email
    }

    const token = jwt.sign({ isLogined: tokenValue }, "secretkey", {
        expiresIn: 86400000, // 토큰 제한시간
    });

    res.cookie("A12Ba5os9", token, {
        maxAge: 86400000, // 쿠키 제한시간
        path: "/",
        httpOnly: true,
    });

    return res.send({ success: true })
})

router.post("/logout", async function (req, res, next) {

    res.clearCookie("A12Ba5os9");

    return res.send({ success: true })
})


module.exports = router;

 

3. 컴포넌트 구현

import { GetServerSideProps, NextPage } from "next";
import { useRouter } from "next/router";

const Community: NextPage<any> = (props: any) => {
    const router = useRouter();

    const kakaoInit = () => {
        const kakao = (window as any).Kakao;
        if (!kakao.isInitialized()) {
            kakao.init(process.env.NEXT_PUBLIC_KAKAO_SHARE_KEY);
        }

        return kakao;
    }

    const kakaoLogin = async () => {
        // 카카오 초기화
        const kakao = kakaoInit();

        // 카카오 로그인 구현
        kakao.Auth.login({
            success: () => {
                kakao.API.request({
                    url: '/v2/user/me', // 사용자 정보 가져오기
                    success: (res: any) => {
                        // 로그인 성공할 경우 
                        if (!res.kakao_account.email || res.kakao_account.email === "") {
                            alert("해당 계정의 이메일이 존재하지 않습니다.")
                        }
                        else {
                        	// 쿠키 생성
                            await axios.post("/api/community/login", { email: res.kakao_account.email })
                                .then(({ data }) => {
                                    if (data.success) {
                                        dispatch({ name: "email", value: res.kakao_account.email })
                                    }
                                    else {
                                        return alert(" 로그인에 실패하였습니다.")
                                    }
                                })
                        }
                    },
                    fail: (error: any) => {
                        console.log(error);
                    }
                })
            },
            fail: (error: any) => {
                console.log(error);
            }
        })
    }
    
    const kakaoLogout = async () => {
        // 쿠키 제거
        await axios.post("/api/community/logout", state)
            .then(({ data }) => {
                if (data.success) {
                    dispatch({ type: "init" })
                    // 카카오 로그아웃 페이지
                    router.push(`https://kauth.kakao.com/oauth/logout?client_id=${process.env.NEXT_PUBLIC_KAKAO_SHARE_KEY}&logout_redirect_uri=http://localhost:3001`);
                }
                else {
                    return alert(" 로그아웃에 실패하였습니다.")
                }
            })
    }

    return (
        <>
            <button onClick={(e: any) => { kakaoLogin(); }}>
                카톡으로 로그인
            </button>
            <button onClick={(e: any) => { kakaoLogout(); }}>
                로그아웃
            </button>
        </>
    )
}

export default Community;

export const getServerSideProps: GetServerSideProps = async (context) => {
    return {
        props: {

        }
    }
}

로그인을 했을 경우 SSR로 props를 넘기면 쿠키 데이터를 읽어 로그인 된 상태를 구현할 수 있다. 

( 로그인 or 로그아웃 버튼 보이게끔 하는거 구현 가능 )

4. 로그아웃 

  • 이 서비스만 로그아웃 : state 가 초기화 되었고, 쿠키가 삭제되었으므로 사이트는 로그아웃 됨. ( 하지만 로그인할 때 전에 했던 방식으로 로그인 됨 )
  • 카카오계정과 함께 로그아웃 : 소셜 계정이 로그아웃 되어 다른 계정으로 로그인 가능

 

728x90
반응형
728x90
반응형

1. reset

아직 원격 저장소에 push하기 전이라면 reset을 사용할 수 있다.

없애고 싶은 커밋이 있을 때, reset을 이용하면 해당 커밋을 흔적도 없이 지울 수 있다.

 

문법은 아래와 같다.

$ git reset --옵션 커밋해시

옵션으로는 hard, soft, mixed 등이 있고,

커밋해시에는 돌아가고 싶은 커밋의 해시 주소를 입력하면 된다.

 

옵션에 대해 간략하게 살펴보자.

 

1) hard

돌아가려는 커밋 이후의 모든 내용을 지워 버린다.

staging area와 working directory 모두 돌아가려는 커밋과 동일해진다.

 

2) soft

돌아가려는 커밋으로 되돌아가고, HEAD가 돌아가려는 커밋을 새롭게 가리키게 된다.

staging area와 working directory는 아무런 변화도 없다.

 

3) mixed

돌아가려는 커밋으로 되돌아가고, HEAD가 돌아가려는 커밋을 새롭게 가리키게 된다.

staging area는 돌아가려는 커밋과 동일해지고, working directory는 아무 변화가 없다.

 

그다음 git push를 해준다.

$ git push --force --set-upstream origin master

git log를 다시 찍어보면 revert가 잘 된 것을 확인할 수 있을 것이다.

 

 

2. revert

이미 원격 저장소에 push한 상태라면 reset은 사용할 수 없고 revert를 사용해야 한다.

이것이 reset과 revert의 차이이다.

 

그렇다면 revert의 사용방법에 대해 알아보자.

 

우선 git log로 해시주소를 파악해야 한다.

git log 찍고, 커밋이 다음의 commit1>2>3의 순서로 발생했다고 해보자.

오른쪽에 뜨는 것이 git hash이다.

commit3        54c6547
commit2        a516d21
commit1        4567289    // 되돌아가려는 커밋

 

내가 돌아가려는 커밋이 commit1이면,

commit3를 먼저 revert하고 이후 commit2를 revert하는 방식으로 순차적으로 진행해야 한다.

아래와 같이 입력하면 된다.

$ git revert 54c6547
$ git revert a516d21

 

그다음 git push를 해준다.

$ git push --force --set-upstream origin master

 

git log를 다시 찍어보면 revert가 잘 된 것을 확인할 수 있을 것이다.

728x90
반응형
728x90
반응형
    const [windowWidth, setWindowWidth] = useState(0);

    const resizeWindow = () => {
        setWindowWidth(window.innerWidth)
    }

    useEffect(() => {
        setWindowWidth(window.innerWidth)
        window.addEventListener("resize", resizeWindow)
        return () => {
            window.removeEventListener("resize", resizeWindow)
        }
    }, [windowWidth])

 

상태를 만들고 window 사이즈를 측정해서 쉽게 만들 수 있다.

728x90
반응형
728x90
반응형

1. 인스턴스 서버에 도커 설치

$ sudo wget -qO- http://get.docker.com/ | sh

 

2. 인스턴스 서버에서 도커 로그인

$ docker login

 

3. 도커 시작

$ sudo systemctl start docker

 

4. 도커허브에서 이미지 pull 

ex) Next.js

$ docker pull 도커허브아이디/web_client:버전정보

 

ex) Spring

$ docker pull 도커허브아이디/java_server:버전정보

 

5. 이미지 확인

$ docker images

 

 

6. Docker run

포트는 프로젝트에 맞게 설정해야함.

$ docker run -p 8083:8083 -d --rm --name java_server ID/Repository

 

$ docker run -p 80:3000 -d --rm --name web_client ID/Repository

 

728x90
반응형
728x90
반응형

1. env 수정

 

ex) /next.config.js

/** @type {import('next').NextConfig} */
const path = require('path');
const withImages = require('next-images');

module.exports = {
  reactStrictMode: true,
  async rewrites() {
    if (process.env.NODE_ENV === "production") {
      return [
        {
          source: process.env.PRODUCTION_JAVA_SERVER_PATH,
          destination: process.env.PRODUCTION_JAVA_SERVER_URL,
        }

      ];
    } else {
      return [
        {
          source: process.env.JAVA_SERVER_PATH,
          destination: process.env.JAVA_SERVER_URL,
        }
      ];
    }
  },
};

 

 

/.env.local

NODE_ENV = 'development'

PRODUCTION_JAVA_SERVER_PATH = '/java/:path*'
PRODUCTION_JAVA_SERVER_URL = 'http://사용하는 IP 주소:8083/:path*'

JAVA_SERVER_PATH = '/java/:path*'
JAVA_SERVER_URL = 'http://localhost:8083/:path*'

 

2. Dockerfile 생성

프로젝트 루트 경로에 만들어준다.

/Dockerfile

# 위에서 도커 허브 node 이미지를 기반으로 로컬로 다운로드 및 캐싱 되었기 때문에 이미지를 가져올 수 있다.
FROM node:18.4.0

# 만약 컨테이너 안의 이미지의 경로가 /app 이런식으로 되어있다면 작업할 div 경로를 설정할 수도 있다.
# 설정해주면 COPY 의 두번째 경로를 ./ 이것으로 했을 때 자동으로 /app 경로가 된다.
WORKDIR /app

# package.json 파일을 복사한다. 만약 다시 빌드할 때 변경사항이 없을 경우 npm install까지 그냥 넘어간다.
COPY package.json /app

# 이미지를 받으면 npm install을 자동으로 해줌
RUN npm install


# 어떤 파일이 이미지에 들어가야 하는지 
# 첫 번째 .은 이 프로젝트의 모든 폴더 및 파일들 (Dockerfile을 제외한)
# 두 번째 .은 파일을 저장할 컨테이너 내부 경로 (ex /app)
COPY . /app

# 배포환경으로 설정
ENV NODE_ENV=production

RUN npm run build

# 도케에게 우리가 서버를 실행할 포트를 말해준다.
EXPOSE 3000

# 이미지가 생성될 때 실행되지 않고 컨테이너가 실행될 때 수행하는 명령어
CMD ["npm","start"]

 

당연한 말이지만 개발 서버와 node 버전을 맞춰주어야 한다. 

 

3. dockerignore 생성

/.dockerignore

.node_modules

.next

 

4. next.config.js 수정

module.exports = {
  output: 'standalone'
}

 

5. 이미지 빌드

$ docker build -t 도커허브아이디/web-client:버전정보 .

web_client 는 임의로 제가 지은 이름입니다.

docker 프로그램에서 확인할 수도 있다.

 

혹시 맥북에서 빌드하고 linux/amd64 서버에 배포할 예정이라면 

docker buildx build --platform=linux/amd64 -t 도커허브아이디/web_client:버전정보 .

 

6. Dockerhub Repository 생성

 

7. 도커허브 업로드

# 업로드
docker push 도커허브아이디/web_client

 

 


Server

 

1. 인스턴스 서버에 도커 설치

$ sudo wget -qO- http://get.docker.com/ | sh

 

2. 인스턴스 서버에서 도커 로그인

$ docker login

 

3. 도커 시작

$ sudo systemctl start docker

 

4. 도커허브에서 이미지 pull 

$ docker pull 도커허브아이디/web_client:버전정보

 

5. 이미지 id 확인

$ docker images

 

6. 컨테이너 실행

$ docker run -p 80:3000 -d --rm 도커허브아이디/web_client
728x90
반응형
728x90
반응형

1. Dockerfile 작성

프로젝트 루트경로에 작성해준다.

FROM openjdk:11
# FROM amazoncorretto:11 ==> amazon corretto 11 사용할 경우
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
# ENTRYPOINT ["java","-jar","-Dspring.profiles.active=prod","/app.jar"]
# => 설정파일을 분리해서 사용할 때
# java -jar -Dspring.profiles.active=prod app.jar

 

2. Build

# Spring Boot 빌드
./gradlew build -x test

 

3. 이미지 생성

# gradle linux/amd64 옵션은 맥북 M1을 위한 옵션
$ docker build --build-arg DEPENDENCY=build/dependency -t 도커허브 ID/Repository --platform linux/amd64 .

# maven
$ docker build -t 도커허브 ID/Repository --platform linux/amd64 .

# 확인
$ docker images

 

Spring Boot 2.3.x 버전 이상인 경우 Dockerfile 작성 없이 Plugin으로 이미지 생성이 가능하다.

# yml, properties를 여러개 사용하는 경우 profile을 지정하여 image를 생성한다.
$ ./gradlew bootBuildImage --imageName=ID/Repository

4. Docker 업로드

# 로그인
$ docker login

# 업로드
$ docker push ID/Repository

 

배포 팁

환경변수중에 SPRING_PROFILES_ACTIVE=prod 를 지정하면 자동으로 application-prod.properties 파일을 바라봅니다.

개발할 땐 application.properties 설정을 바라보고 docker로 운영 서버에 배포할 땐 -e SPRING_PROFILES_ACTIVE=prod 만 추가하면 됩니다.

728x90
반응형
728x90
반응형

1. 의존성 추가

dependencies {
   runtimeOnly 'mysql:mysql-connector-java'
}

 

2. application properties

#Database
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.profiles.active=local
spring.datasource.url= jdbc:mysql://db-d618t-kr.vpc-pub-cdb.ntruss.com:3306/pay_calculator_test?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=nomu9032
spring.datasource.password=jmk1994**
spring.datasource.hikari.connection-timeout= 3000
spring.datasource.hikari.validation-timeout= 3000
spring.datasource.hikari.minimum-idle= 5
spring.datasource.hikari.max-lifetime= 240000
spring.datasource.hikari.maximum-pool-size= 20

# Hibernate setting
spring.jpa.database= mysql
spring.jpa.database-platform= org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=validate
728x90
반응형
728x90
반응형

1. 버킷 생성

https://typo.tistory.com/entry/Nodejs-Multer-S3-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-AWS-S3-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-%EA%B5%AC%ED%98%84-1

 

Node.js | Multer 를 이용한 AWS S3 파일 업로드 구현(1)

1. aws에 접속하여 IAM 사용자를 생성한다 2. 권한으로 AmazonS3FullAccess를 할당해준다. 3. 해당 Access key ID, Secret access key를 알고 있어야 한다. 4. aws s3 화면에 접속해서 버킷을 만들어준다. 5...

typo.tistory.com

 

2. build.gradle 의존성 추가

implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

 

3. application.properties 추가

중요한 내용을 담기 때문에 .gitignore에 설정 파일을 추가해준다.

 

4. S3Config 

backend/aws/S3Config

package mobile.backend.aws;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class S3Config {
    @Value("${cloud.aws.credentials.accessKey}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secretKey}")
    private String secretKey;

    @Value("${cloud.aws.region.static}")
    private String region;

    @Bean
    public AmazonS3 amazonS3Client() {
        AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

        return AmazonS3ClientBuilder
                .standard()
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withRegion(region)
                .build();
    }
}

 

5. WebConfig

Config 파일을 한 곳에서 관리한다면 Class를 추가해준다.

package mobile.backend;

...

@Configuration
@Import({AopConfig.class, RedisConfig.class, S3Config.class})
public class WebConfig implements WebMvcConfigurer {

...

 

6. S3Uploader

backend/aws/S3Uploader

package mobile.backend.aws;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;

@Slf4j
@RequiredArgsConstructor    // final 멤버변수가 있으면 생성자 항목에 포함시킴
@Service
public class S3Uploader {
    private final AmazonS3Client amazonS3Client;

    @Value("${cloud.aws.s3.bucket}")
    private String bucket;

    // MultipartFile을 전달받아 File로 전환한 후 S3에 업로드
    public String upload(MultipartFile multipartFile, String dirName) throws IOException {
        File uploadFile = convert(multipartFile)
                .orElseThrow(() -> new IllegalArgumentException("MultipartFile -> File 전환 실패"));
        return upload(uploadFile, dirName);
    }


    private String upload(File uploadFile, String dirName) {
        String fileName = dirName + "/" + uploadFile.getName();
        String uploadImageUrl = putS3(uploadFile, fileName);

        removeNewFile(uploadFile);  // 로컬에 생성된 File 삭제 (MultipartFile -> File 전환 하며 로컬에 파일 생성됨)

        return uploadImageUrl;      // 업로드된 파일의 S3 URL 주소 반환
    }

    // 1. 로컬에 파일생성
    private Optional<File> convert(MultipartFile file) throws  IOException {
        File convertFile = new File(file.getOriginalFilename());
        if(convertFile.createNewFile()) {
            try (FileOutputStream fos = new FileOutputStream(convertFile)) {
                fos.write(file.getBytes());
            }
            return Optional.of(convertFile);
        }
        return Optional.empty();
    }

    // 2. S3에 파일업로드
    private String putS3(File uploadFile, String fileName) {
        amazonS3Client.putObject(
                new PutObjectRequest(bucket, fileName, uploadFile)
                        .withCannedAcl(CannedAccessControlList.PublicRead)	// PublicRead 권한으로 업로드 됨
        );
        return amazonS3Client.getUrl(bucket, fileName).toString();
    }

    // 3. 로컬에 생성된 파일삭제
    private void removeNewFile(File targetFile) {
        if(targetFile.delete()) {
            log.info("파일이 삭제되었습니다.");
        }else {
            log.info("파일이 삭제되지 못했습니다.");
        }
    }

    // s3 파일 삭제
    public void delete(String fileName) {
        log.info("File Delete : " + fileName);
        amazonS3Client.deleteObject(bucket, fileName);
    }
}

 

7. Controller

클라이언트(React) 에서 보낸 데이터입니다.

const config = {
    headers: {
        "content-type": "multipart/form-data",
    },
};

const formData = new FormData();

formData.append("file_object", fileObject);

await axios.post('/java/file/upload', formData, config)

 

backend/module/file/FileController

...

    @PostMapping("/upload")
    public CommonResponse<String> upload(
            @RequestParam("file_object") MultipartFile fileObject
    ) throws IOException {
        log.info("multipartFile={}", fileObject);
        return new CommonResponse<String>(
                true,
                service.upload(
                        fileObject
                ));
    }
    
...

 

8. Service

backend/module/file/FileService

...

	@Autowired
    private S3Uploader s3Uploader;

...

public String upload( MultipartFile fileObject ) throws IOException {

        if(fileObject.isEmpty()) {
        }
        else {
            String storedFileName = s3Uploader.upload(fileObject,"images");
            log.info("fileName={}", storedFileName);
        }
        return "ok";
    }

 

9. main 함수 파일에 다음을 추가해준다.

@SpringBootApplication
public class BackendApplication {

	static {
		System.setProperty("com.amazonaws.sdk.disableEc2Metadata", "true");
	}

	public static void main(String[] args) {
		SpringApplication.run(BackendApplication.class, args);
	}

}

 

서버를 키고 파일 저장을 시도해보면 아래와 같이 성공한 것을 볼 수 있다.

728x90
반응형
728x90
반응형

아래 옵션으로 편리하게 request 데이터를 확인할 수 있다.

 logging.level.org.apache.coyote.http11=debug

 

package hello.upload.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@Slf4j
@Controller
@RequestMapping("/spring")
public class SpringUploadController {

    @Value("${file.dir}")
    private String fileDir;

    @GetMapping("/upload")
    public String newFile() {
        return "upload-form";
    }

    @PostMapping("/upload")
    public String saveFile(@RequestParam String itemName,
                           @RequestParam MultipartFile file, HttpServletRequest
                                   request) throws IOException {
        log.info("request={}", request);
        log.info("itemName={}", itemName);
        log.info("multipartFile={}", file);
        if (!file.isEmpty()) {
            String fullPath = fileDir + file.getOriginalFilename();
            log.info("파일 저장 fullPath={}", fullPath);
            file.transferTo(new File(fullPath));
        }
        return "upload-form";
    }

}
728x90
반응형
728x90
반응형

1. 의존성 추가

// jwt
	implementation 'io.jsonwebtoken:jjwt:0.9.1'

 

2. JwtTokenManager 

backend/auth/JwtTokenManager

package web.backend.auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtTokenManager {

    private static final String secret = "secretKey!!!";

    // 토큰 유효 기간
    public static final long JWT_TOKEN_VALIDITY = 60 * 60 * 24 * 1000L; //하루

    /**
     *  토큰 생성
     */
    public String generateToken(String id, Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)  // 정보 저장
                .setId(id)
                .setIssuedAt(new Date(System.currentTimeMillis()))  // 토큰 발행 시간 정보
                .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY)) // set Expire Time
                .signWith(SignatureAlgorithm.HS512, secret)// 사용할 암호화 알고리즘과
                // signature 에 들어갈 secret값 세팅
                .compact();
    }

    /**
     *  토큰 id 반환
     */
    public String getTokenIdFromToken(String token) {
        return getClaimFromToken(token, Claims::getId);
    }

    /**
     *  토큰이 만료되었는지 Boolean 반환
     */
    public Boolean isTokenExpired(String token) {
        final Date expiration = getClaimFromToken(token, Claims::getExpiration);
        return expiration.before(new Date());
    }

    /**
     *  토큰 자체에 대한 정보 추출(id, expire 등)
     */
    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token).getBody();// JWT payload 에 저장되는 정보단위
        return claimsResolver.apply(claims);
    }

    /**
     *  토큰 안의 모든 정보 추출
     */
    public Claims getClaimsFromToken(String token) {
        final Claims claims = Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token).getBody();// JWT payload 에 저장되는 정보단위
        return claims;
    }

}

 

3. Test

test/java/web/backend/auth/JwtTest

package web.backend.auth;

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

@Slf4j
public class JwtTest {

    @Autowired
    JwtTokenManager jwtTokenProvider = new JwtTokenManager();

    Map<String, Object> map = new HashMap<>();

    @BeforeEach
    public void before() {
        map.put("userId","teepo");
    }

    @Test
    public void test() {

        // 토큰 생성
        String tokenString = jwtTokenProvider.generateToken("1", map);
        log.info("tokenString={}",tokenString);
        assertNotNull(tokenString);

        // 토큰 Id
        String tokenId = jwtTokenProvider.getTokenIdFromToken(tokenString);
        log.info("tokenId={}", tokenId);
        assertEquals("1",tokenId);

        // 토큰이 만료되었는지
        Boolean tokenExpired = jwtTokenProvider.isTokenExpired(tokenString);
        log.info("tokenExpired={}",tokenExpired);
        assertEquals(false, tokenExpired);

        // 토큰 안의 모든 정보
        Claims tokenClaims = jwtTokenProvider.getClaimsFromToken(tokenString);
        log.info("tokenClaims={}", tokenClaims);

        // before 메소드에서 생성한 데이터 조회
        String value = tokenClaims.get("userId").toString();
        log.info("userId={}",value);
        assertEquals("teepo", value);

    }
}

 

4. 로그 확인

 


 

이번엔 클라이언트한테 요청을 받고, 쿠키 안에 Token을 넣은 뒤 확인해보자. 코드는 전 포스트에서 이어진다.

 

1. Controller

backend/module/user/UserController

    @PostMapping("/test")
    public CommonResponse<String> jwtGenerateTest(HttpServletResponse response, @RequestBody User user) {
        return new CommonResponse<String>(true, userService.jwtTest(response,user));
    }

 

2. Service

backend/module/user/UserService

    JwtTokenManager jwtTokenManager = new JwtTokenManager();

    public String jwtTest(HttpServletResponse response ,User user) {

        Map<String, Object> tokenMap = new HashMap<>();

        tokenMap.put("userId", user.getUserId());

        String tokenString = jwtTokenManager.generateToken("token1", tokenMap);

        log.info("tokenValue={}",jwtTokenManager.getClaimsFromToken(tokenString));


        Cookie cookie = new Cookie("token1",tokenString);
        cookie.setMaxAge(86400000); // 하루

        response.addCookie(cookie);

        return "ok";
    }

 

3. Postman

하단에 Cookies 버튼을 누르면 확인할 수 있다.

728x90
반응형

+ Recent posts