시애틀에서 비행기를 타고 라스베이거스에 도착!!! 도착하자마자마 반겨주는 베가스 간판의 매장이 보인다

 

 

 

 

 

- TIL -

 

구현목표)

프로젝트를 빌드하여 GCP 서버에 배포하자!

 

 

 

구현과정)

 

1. 프로젝트를 빌드한다!

더보기

IntelliJ 를 사용하므로 IntelliJ 내에서 편하게 빌드할 수 있는 방법으로 빌드를 구현하였다.

 

프로젝트 디렉토리에 접근하여 ./gradlew build 명령어로도 빌드가 가능하다.

 

- IntelliJ 로 빌드하기 -

 

 1) 우측의 Gradle 버튼 클릭

2) 프로젝트 하위의 build 하위의 build 클릭

( 만약, 빌드 실패 시 아래 clean 을 클릭하여 이전 빌드 정보를 지운 뒤 다시 시도하거나 빌드 오류를 파악하여 해결해야 함 )

( 나는 DB 연결설정 때문에 오류가 발생하여 해결한 뒤 빌드를 성공하였음, 빌드 오류시 html 파일로 친절하게 오류 내용을 알려줌 )

3) 빌드 성공시 좌측 하단에 빌드 여부가 초록색 체크표시로 표시됨

4) 빌드된 파일은 프로젝트 내부의 build 디렉토리 하위 libs 디렉토리 하위에 저장된다.

( main-0.0.1-SNAPSHOT.jar 가 빌드된 파일 )

 

2. 빌드된 프로젝트 파일을 GCP 서버에 SSH 를 사용하여 전송한다!

더보기

GCP 에서는 웹으로 SSH 접속을 쉽게 할 수 있도록 지원한다.

 

1) GCP 에서 웹을 통해 SSH 접속

GCP VM 인스턴스 항목에서 사용될 서버 피씨로 SSH 연결...! 웹으로 SSH 연결을 하여 커맨드 입력이 된다니 AWS 에서도 놀랐지만 한번 더 놀랐다...!

 

 

 

2) SSH 를 통해 서버에 빌드된 jar 파일을 전송

( 파일 업로드를 클릭하여 파일을 GCP 의 서버로 전송 )

세상이 참 편해졌다, 원래는 SSH 도 커맨드 라인을 통해 접속했어야 했는데 이제는 파일 업로드까지 웹 상에서 파일을 선택하여 전송이 된다

 

 

 

3) 업로드된 파일을 프로젝트 디렉토리로 이동시키자!

업로드된 파일은 홈 디렉토리에 존재한다

 

업로드된 파일을 프로젝트 디렉토리로 옮겨주었다!

 

3. 빌드된 프로젝트를 실행시킨다!

더보기
java -jar [빌드된 파일명] 명령어를 사용하여 프로젝트를 실행!!

 

4. 정상적으로 빌드되었는지 테스트 진행!

더보기

테스트는 포스트맨으로 진행하였다.

포스트맨을 사용하여 할당받은 아이피의 8080 포트로 회원가입 요청을 보내니 응답이 오는 모습

( 409 Conflict 코드가 출력된 이유는 이미 동일한 요청을 스크린샷 찍기 전에 한번 보냈기 때문! )

 

 

 

해결해야 할 사항)

 

1) 구현하는 동안 추가적으로 진행해야할 사항은 우선 SSL 인증이 되어있지 않으므로 https 요청을 처리하지 못하는 문제점이 발생한 것이다.

위 문제점은 SSL 을 무료로 인증받는 방법을 사용하거나 다른 방안을 탐색할 것이다.

 

2) Nginx 의 리버스 프록시 기능을 사용하여 프론트도 동일한 서버에 배포를 진행해보고자 한다.

 

3) 도메인을 연결해보자.

 

 

이후 글에서는 위의 세가지를 처리해볼 것이다!

728x90

 

 

 

 

 

한국에는 판매하지 않는 기아의 텔루라이드 모델과 맥도날드의 Filet-O-Fish(필레오피쉬)

 

 

 

 

- TIL -

 

구현 목표)

오늘은 프론트로부터 이메일 정보를 넘겨받아 백에서 랜덤한 회원가입 코드를 생성한 뒤 전달 받은 회원의 이메일로 회원가입 코드를 전송하는 코드를

작성해보려 한다.

 

보통은 이메일 인증을 redis 와 같은 인메모리 DB 로 구현하지만 나는 사용 중인 주 DB 인 MySQL 을 사용하여 이메일 인증 정보를 저장하고 사용하고자 하였다.

 

코드의 흐름은 간단하게 아래와 같이 구현하고자 했다.

 

더보기

1. 사용자가 회원가입시 자신의 이메일을 작성한 뒤 인증번호 받기 버튼을 클릭

 

2. 프론트에서 작성된 이메일 정보를 백엔드에게 전달

 

3. 백엔드에서 이메일 정보를 전달 받은뒤 회원가입된 이메일인지 아닌지 확인

( 이미 가입된 회원이라면 상태코드 409 반환 )

 

4. 한 이메일을 기준으로 5분에 3번까지만 인증 시도를 허용할 것이므로 DB 를 시간 기준으로 출력하여 5분 내에 회원가입 시도가 5회 이상인지 확인

( 5회 이상으로 시도한 회원이라면 상태코드 429 반환 )

 

5. 가입되지 않은 이메일 정보라면 랜덤 회원가입 코드를 생성하여 DB 에 이메일 정보와 인증 시간 값을 저장한 뒤 회원가입 코드를 전송

 

위 과정 중 오늘 구현할 부분은 회원가입 시도 횟수 체크 부분인 4번을 제외한 모든 부분을 구현해 볼 것이다.

 

 

 

 

 

구현 코드)

더보기

1. Controller

package com.randomchat.main.controller.register;

import com.randomchat.main.dto.register.EmailVerificationDTO;
import com.randomchat.main.service.email.EmailVerificationService;
import com.randomchat.main.service.register.RegisterService;
import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RequiredArgsConstructor
@RestController
@RequestMapping("/register")
public class EmailVerificationController {

    private final EmailVerificationService emailVerificationService;
    private final RegisterService registerService;

    @PostMapping("/email/verification")
    public ResponseEntity<String> emailVerification(@RequestBody EmailVerificationDTO emailVerificationDTO) throws MessagingException, IOException {

        String email = emailVerificationDTO.getEmail();

        // 1. 이메일 인증이 들어온 이메일 정보로 가입된 계정이 있다면 deny
        if(registerService.checkEmailDuplication(email)) return ResponseEntity.status(409).body("이미 가입된 이메일입니다.");

        // 2. 이메일 인증이 들어온 기준 시간으로 부터 5분 내로 DB 내에 5회 인증 요청이 있다면 deny
        // emailVerificationService.esExceededLimit(email);

        // 3. 이메일 인증용 난수 생성
        String verificationCode = emailVerificationService.createCode();

        // 4. 이메일을 발송한 뒤 이메일 인증 데이터 DB 에 저장
        emailVerificationService.sendEmail(email, verificationCode); // 인증 이메일 발송
        emailVerificationService.saveEmailVerification(email, verificationCode); // DB 에 인증 내용 저장

        return ResponseEntity.ok("이메일이 발송되었습니다.");
    }
}

 

2. RegisterService ( JPA 쿼리 메소드를 사용하여 동일한 메일이 있는지 체크 )

package com.randomchat.main.service.register;

import com.randomchat.main.domain.users.Users;
import com.randomchat.main.dto.register.RegisterDTO;
import com.randomchat.main.repository.UsersRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class RegisterService {
    private final UsersRepository usersRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    public void register(RegisterDTO registerDTO) {
        String email = registerDTO.getEmail();
        String password = registerDTO.getPassword();

        // 받아온 비밀번호 정보를 암호화
        registerDTO.setPassword(bCryptPasswordEncoder.encode(registerDTO.getPassword()));

        Users users = new Users();
        Users user = users.createUser(registerDTO);
        usersRepository.save(user);
    }

    public boolean checkEmailDuplication(String email) {
        return usersRepository.existsByEmail(email);
    }

    public boolean checkNicknameDuplication(String nickname) {
        return usersRepository.existsByNickname(nickname);
    }
}

 

3. emailVerificationService

 

- createCode 메소드 -

1) 난수에 사용될 문자들을 미리 선언 후 RANDOM 클래스의 nextInt 메소드를 사용하여 문자 길이만큼의 수 내에서 랜덤 숫자를 생성

2) 랜덤 숫자를 다시 CHARACTERS 문자열의 인덱스로 접근하여 랜덤한 문자를 뽑아 StringBuilder 에 추가한 뒤 반복문이 다 돌면 빌드

 

- renderJspToString 메소드 -

1) templates 디렉토리에 있는 jsp 파일을 불러와 Resource 객체로 생성

2) 생성된 객체의 정보를 UTF-8 으로 인코딩된 String 으로 변환

3) String 으로 변환된 내용 중 ${verificationCode} 문자를 실제 인증 코드로 변경

 

- sendEmail 메소드 -

1) 이메일을 전송하기 위해 Mimemessage 객체를 생성

2) Mimemessage 를 쉽게 설정하도록 도와주는 헬퍼 클래스인 MimemessageHelper 클래스를 선언하여 다음과 같이 설정

 

true : HTML 형식을 지원하도록 설정

UTF-8 : 이메일 내용의 인코딩 방식을 설정

 

3) 헬퍼 플래스를 사용하여 이메일 주소, 제목, 내용을 다음과 같이 설정

 

setTo : 수신자 이메일 작성

setSubject : 메일 제목 설정

setText : 메일 본문을 설정, true 로 HTML 형식 적용

setFrom : 발신자의 이메일 주소를 설정

package com.randomchat.main.service.email;

import com.randomchat.main.domain.email.EmailVerification;
import com.randomchat.main.repository.EmailVerificationRepository;
import com.randomchat.main.repository.UsersRepository;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.util.Random;

@Service
@RequiredArgsConstructor
public class EmailVerificationService {

    private final UsersRepository usersRepository;
    private final EmailVerificationRepository emailVerificationRepository;
    private final ResourceLoader resourceLoader;
    private final JavaMailSender mailSender;
    private static final Random RANDOM = new SecureRandom();
    private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; // 난수에 사용될 문자


    public String createCode() {
        StringBuilder stringBuilder = new StringBuilder();

        // 10 자리 난수를 생성할 것이므로 10회 반복
        for (int i = 0; i < 10; i++) {
            // 난수에 사용될 문자의 길이만큼의 랜덤한 값을 추출
            int randomIndex = RANDOM.nextInt(CHARACTERS.length());
            // 랜덤한 index 의 문자를 추출하여 문자열로 추가
            stringBuilder.append(CHARACTERS.charAt(randomIndex));
        }

        return stringBuilder.toString();
    }

    public void saveEmailVerification(String email, String verificationCode) {
        EmailVerification emailVerification = new EmailVerification();
        EmailVerification createEmailVerification = emailVerification.createEmailVerification(email, verificationCode);
        emailVerificationRepository.save(createEmailVerification);
    }

    public String renderJspToString(String verificationCode) throws IOException {
        // JSP 파일 내용을 String 으로 읽어오기
        Resource resource = resourceLoader.getResource("classpath:/templates/emailForm.jsp");
        String content = new String(Files.readAllBytes(resource.getFile().toPath()), StandardCharsets.UTF_8);

        // 인증 코드를 내용에 삽입하여 리턴
        return content.replace("${verificationCode}", verificationCode);
    }

    public void sendEmail(String email, String verificationCode) throws IOException, MessagingException {
        String emailContent = renderJspToString(verificationCode);

        MimeMessage message = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

        helper.setTo(email);
        helper.setSubject("랜덤 채팅의 이메일 인증 번호입니다.");
        helper.setText(emailContent, true);  // HTML 형식으로 보낼 때 true 설정
        helper.setFrom("youngho3358@gmail.com");

        mailSender.send(message);
    }
}

 

4. emailForm.jsp

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Email Verification</title>
    <style>
        * {
            text-align: center;
        }
        body {
            font-family: Arial, sans-serif;
            text-align: center;
        }
        h1 {
            color: #4CAF50;
        }
        .code {
            font-size: 24px;
            font-weight: bold;
            color: #333;
        }
        p {
            font-size: 16px;
        }
        .footer {
            font-size: 14px;
            color: #999;
        }
    </style>
</head>
<body>
    <h1>이메일 인증</h1>
    <p>RandomChat 가입을 환영합니다!</p>
    <p>아래의 인증코드를 입력하여 회원가입을 완료해주세요.</p>
    <div class="code">${verificationCode}</div>
    <p class="footer">감사합니다.</p>
</body>
</html>

 

5. application.yml 설정

gmail 의 smtp 서버를 활용

 

 

 

 

 

구현 후기)

 

우선은 jsp 를 활용하여 치환하면 더 편할거란 생각으로 jsp 파일을 사용하였는데 단순하게 파일을 문자열로 읽어와서 문자를 치환하는 형태이기 때문에

jsp 파일을 사용할 필요가 없었다.

결국은 간단하게 txt 파일이나 html 파일로도 구현한 부분이었다...

 

그리고 아래는 redis 를 사용하여 구현하였을때의 장, 단점과

주 DB 인 MySQL 로 구현하였을때의 , 점이다.

구분 Redis 사용 MySQL 사용
장점 - 속도: 메모리 기반으로 매우 빠르게 데이터 처리 가능.
- TTL 지원: 인증 코드의 유효 기간 설정을 TTL(Time To Live)로 간편하게 관리.
- 부하 분산: 주 DB에 부하를 덜어주어 시스템의 성능을 최적화.
- 통합성: 기존 시스템(DB)에 통합되어 추가적인 외부 스토리지 필요 없음.
- 데이터 영속성: 인증 기록이 데이터베이스에 저장되어 추적 및 분석 가능.
- 트랜잭션 지원: 인증 관련 데이터와 다른 테이블의 데이터 간 연관성을 보장.
단점 - 데이터 영속성 부족: Redis는 메모리 기반이므로 서버 재시작 시 데이터 손실 위험.
- 추가 인프라 필요: Redis 설치 및 관리 필요.
- 데이터 용량 제한: 메모리 용량에 제한이 있으므로 대규모 데이터 저장 시 비효율적.
- 속도: Redis에 비해 상대적으로 느린 데이터 처리 속도.
- 복잡성 증가: TTL 구현을 별도로 코딩해야 하며 인증 코드 만료 관리가 번거로움.
- 부하 증가: 인증 관련 데이터가 많아지면 주 DB에 부하가 가중될 가능성.
적합한 상황 - 대량의 인증 요청을 처리해야 하는 고성능 시스템.
- 인증 요청 처리 시간이 매우 중요한 서비스.
- 인증 데이터가 비교적 적고, 인증 기록을 영속적으로 저장해야 하는 경우.
- 이미 MySQL 환경에서 동작 중이며 추가적인 데이터베이스 사용이 부담스러운 경우.

 

 

 

 

 

728x90

 

728x90

 

 

 

 

 

시애틀에 있는 스타벅스 리저브 1호점!! 시애틀의 스타벅스 리저브에서는 맥주를 판매한다 :)

 

 

 

로스팅될 원두가 이동되는 모습이다.

 

 

 

 

 

- TIL -

 

구현 목표)

AWS 의 RDS 서비스를 이용하다가 17만원이 과금된 적이 있으므로 AWS 서비스는 뒤로하고 Google 의 GCP 서비스를 이용해 볼 생각이다.

 

구글에서 제공하는 클라우드 서비스인 GCP 서버를 활용하여 구현중인 Spring Boot 프로젝트를 배포해볼 예정이다.

배포될 서버는 API 서버이며 GCP 무료 서버 설정은 아래 블로그를 참고하였다.

 

 

[Linux] GCP 무료 티어 서버 만들기

GCP에서 무료 티어 서버를 만들어보자. 1. GCP 홈페이지 방문 구글 계정 로그인 후 아래 GCP 홈페이지에 들어간다. https://cloud.google.com/free?hl=ko 들어가서 '무료로 시작하기' 버튼을 클릭한다. 그러면

velog.io

참고가 된 블로그에 무한한 감사를 드립니다

 

 

 

구현 과정)

1. GCP 에서 VM(가상머신) 을 생성

2. JAVA 설치

더보기
설치된 패키지를 업데이트

 

apt 저장소를 사용하여 openjdk 21 버전을 설치

 

java -version 명령어를 사용하여 설치된 자바 버전을 확인

3. 빌드된 파일을 보관할 디렉토리 생성

더보기
빌드 파일을 보관할 randomchat 경로 생성

4. MySQL 설치 및 설정

더보기
설치된 패키지를 업데이트

 

mysql 설치

 

mysql 포트 허용

 

mysql 데몬 실행 및 서버 재부팅 시 자동 실행 설정

 

mysql root 계정으로 접속

 

root 계정 비밀번호 변경 후 변경사항 FLUSH PRIVILEGES 로 적용

 

sudo 를 제외하고 mysql 을 root 권한으로 접속 >> 비밀번호 입력 후 엔터

 

DB 관리용 유저 아이디를 생성 ( 빨간색 : 비밀번호 ), root 권한을 모두 부여

 

vi 편집기를 사용하여 외부에서 sql 서버에 접속가능하게끔 설정

 

기존의 127.0.0.1 인 로컬에서만 접속 가능하던 아이피를 외부에서도 접근 가능하게 0.0.0.0 으로 수정 후 저장

 

sql 데몬을 종료 후 재시작

 

mysql 서버의 시간을 서울 시간 기준으로 변경

 

데몬 재실행

 

사용자 계정으로 로그인 ( 비밀번호 입력 )

 

Spring 프로젝트에서 jpa 를 활용하여 사용할 데이터베이스 생성 후 종료

5. 기존 프로젝트에 MySQL 의존성 추가 및 DB 접속 설정 파일 작성

더보기
build.gradle 에 mysql 의존성 추가

 

기존에 사용하던 개발용 H2 데이터베이스의 설정 값을 주석처리한 뒤 mysql 서버의 값으로 변경

6. VM 방화벽 설정

 

 

이제 MySQL 설정과 JAVA 설치는 모두 끝났으니 다음 글에서는 프로젝트 파일을 jar 파일로 변경하여 가동중인 GCP 서버에 jar 파일을 전송하여 서버를

돌려볼 것이다.

728x90

 

 

 

 

 

Seattle 의 Pike Place Market 과 껌벽....! 껌벽은 사람들이 씹던 껌을 벽에 덕지덕지 붙여 생긴 관광 명소라고 한다 시에서 껌벽을 한번 싹 청소하였지만 이후에도 계속 관광객들이 껌을 붙여서 다시 더러운 거리로 원상복구가 되었다고.....

 

 

 

 

 

- TIL -

구현 목표)

사용자가 Token 을 가지고 API 의 엔드포인트에 접근했을때 Security 에서 JWTUtil 클래스를 거쳐 유저가 정상적인 토큰을 가지고 있는지 검증한 뒤 해당 Token 의 값을 기준으로 User 의 정보를 가져와 다른 클래스에서 필요할때 사용하고자 하였다.

 

 

 

문제 발생)

Token 에서 유저의 정보를 가져오는 방법으로는

 

1) payload 값에 유저에 대한 정보를 모두 넣어 서버에서 디코딩하여 확인하는 방법

2) SecurityContextHolder 에서 검증된 유저의 email 정보를 가지고 DB 를 한번 훑어서 유저의 정보를 가져오는 방법

 

위 두가지 방법이 존재하는 것으로 파악하였다, 위 두 가지 방법 중 나는 후자를 선택하여 구현하고자 하였는데 이유로는 사용자의 Local Strage 의 Token 정보가 항상 동기화 되어있지 않을 수 있다는 문제점이 있기 때문에 확실하게 DB 를 한번 거쳐서 동기화된 사용자의 정보를 출력하여 사용하고자 하였다.

 

또한 전자로 구현하는 경우 Token 정보를 받아서 처리해야 하기 때문에 HttpServletRequest 객체를 매개변수로 매번 넘겨야하는 번거로움이 있는 반면 후자로 구현하고자 하면 매개변수를 넘기지 않고도 Spring Security 에서 인증된 사용자에 대한 객체 정보를 저장하고 있는 SecurityContext 를 활용하여 유저에 대한 정보를 확인할 수 있기 때문에 코드가 간결해진다는 장점이 있다...!!

 

 

 

 

문제 해결)

1) payload 의 값을 기준으로 유저에 대한 정보를 확인하는 코드

JWT 를 파싱하여 디코딩한 뒤 유저 정보를 DTO 로 담아서 리턴하는 메소드

 

2) SecurityContextHolder 를 활용하여 User 정보를 불러온 코드

Token 정보를 기준으로 검증된 사용자의 정보가 담긴 SecurityContextHolder 의 유저 email 정보를 가지고 usersRepository 를 활용하여 DB 의 User 정보를 출력하여 사용하는 로직을 작성하였다.

 

728x90

 

 

 

 

 

시애틀의 명물인 스타벅스 1호점! 평소엔 사람이 줄을 서서 기다린다고 하지만 평일 낮과 평일 밤에 방문한 덕분인지 줄을 오래기다리지 않았다ㅎㅎ

 

 

 

 

 

- TIL -

구현 목표 )

JWT 내부의 payload 에 Role 값을 key, value 로 지정한 후 클라이언트에서 해당 JWT 를 가져와 서버로 검증할때 해당 유저의 정보를 가져와 DB 에서 값을 추출하여 접근 api 주소에 권한이 있는지 확인하여 처리하도록 구현하고자 하였다.

 

 

 

문제 발생 )

1. 우선 현재 프로젝트에서 관리하는 Role 값은 enum 타입으로 설정하여 관리중이고 Spring Security 에서는 GrantedAuthority 객체나 SimpleGrantedAuthority 객체를 사용하여 객체 값을 관리하므로 관리하는 객체 타입이 다른 점에서 발생할 수 있는 문제가 있다고 판단하였다.

 

2. 토큰이 정상정으로 발급됨을 확인한 뒤 HttpSecurity 클래스의 hasRole("USER") 메소드나 @PreAuthrize("hasAnyRole('USER')") 어노테이션을 사용하여 분명 권한 정보를 토큰에서 추출하여 비교한 뒤 검증해야하는데 모든 요청의 응답이 403 Forbidden 로 처리되는 문제가 발생했다.

 

 

 

문제 해결 )

1. Role 값의 타입 문제는 토큰을 구현하며 공식문서와 인터넷 서핑을 통해 찾아보니 어짜피 JWT 에 담기는 모든 내용은 문자열로 변환하여 저장되고 검증되므로 토큰을 구현하는데 있어서 현재 사용하는 클래스 타입에서 캐스팅을 통해 문자열로 변환한 뒤 저장해주면 된다는 해결책을 찾게 되었다.

UsernamePasswordAuthenticationFilter 클래스의 successfulAuthentication 메소드를 오버라이딩하여 payload 부분에 내가 넣고자하는 정보를 넣어서 저장, 이때 user.getRole() 메소드를 통해 가져온 값은 enum 타입이므로 name() 메소드를 사용하여 String 타입으로 캐스팅하여 토큰을 빌드하였다

 

 

2. 가장 큰 문제점이었던 권한이 정상적으로 검증되지 않았던 문제는 우선적으로 토큰에 정상적인 권한 값이 들어가있는지 토큰의 정보를 추출하여 확인해보아도 내가 설정한 USER 값이 정상적으로 출력되는 것을 확인할 수 있었다. 그래서 공식문서를 찾아보니 hasRole 을 사용하여 권한을 검증하면 ROLE_ 의 접두가사 권한의 앞에 붙는다는 사실을 알게되었고 유저의 Role 값을 검증함에 있어서 앞에 접두사를 붙여서 코드를 구현하니 정상적으로 200 OK 응답이 돌아오는 것을 확인하였다.

UserDetails 클래스에서 getAuthorities 메소드를 오버라이딩하여 role 값을 추출하여 앞에 "ROLE_" 접두사를 붙여서 검증하게끔 설정하니 200 Ok 사인을 반환

728x90

 

 

 

 

 

시애틀에 있는 Amazon spheres...!!! 아마존에 개발 부서에 종사하는 지인분이 계셔서 덕분에 내부에 들어가서 구경할 수 있었다.

 

 

 

 

 

- TIL -

구현 목표 ) Spring Security 와 JWT(Json Web Token) 을 활용하여 로그인 시 토큰을 발행하고 프론트에서 넘어오는 요청을 토큰의 내부 payload 값을 확인하여 처리하는 로직을 구현하고자 하였다.

 

 

 

문제 발생 ) Spring Security 의 인가 구현 방식은 UsernamePasswordAuthenticationFilter 클래스가 form-data 형식의 데이터를 받아 처리하게끔 구현되어 있다, 하지만 현재 구현하고 있는 프로젝트의 경우 요청을 form-data 형식이 아닌 Json 데이터로 처리하기로 규약하였고 이를 위해 해당 클래스를 상속받아 커스텀 처리를 진행할 필요가 있었다.

 

 

 

문제 해결 ) UsernamePasswordAuthenticationFilter 클래스에서 로그인 정보를 처리하는 obtainEmailAndPassword 메소드를 선언하여 HttpServletRequest 로 요청된 form-data 의 이메일, 비밀번호가 담긴 로그인 정보를 json 형태로 파싱하여 오버라이딩할 attemptAuthentication 메소드에 로그인 정보를 DTO 형태로 전달하여 사용하게끔 코드를 작성하였다.

obtainEmailAndPassword 메소드 (form-data 를 json 형태로 파싱하여 DTO 에 담아 데이터를 반환)
파싱된 로그인 정보를 DTO 를 사용하여 전달받아 회원 정보를 검증할 수 있게끔 처리

 

 

 

보완점) 기본적으로 Spring Security 에서 로그인을 검증할때 사용되는 방식이 form-data 방식이다보니 추가적으로 데이터 검증이 필요하거나 토큰을 발급하는 데 있어서 많은 클래스 커스텀이 필요할 것으로 보인다, 또한 기존의 Spring Security 는 username 과 password 라는 변수 명으로 로그인 처리를 진행하게 되는데 현재 구현하고자 하는 프로젝트에서는 email 과 password 값으로 로그인을 진행할 것이므로 email 값을 받아 DB 와 검증 절차를 거치게 하는 로직의 추가 구현이 필요하다.

728x90

 

 

 

 

 

역시 미국은 총기의 나라였다... 권총부터 샷건까지 사격해볼 수 있는 기회가 또 언제 있으려나ㅎㅎ

 

 

 

 

 

- TIL -

1. Domain 설계 시 테이블 명 이슈

( User 테이블을 JPA 를 통해 설계하고 실행시키는 순간 User 테이블을 생성하는 쿼리에서 Syntax error 가 발생한 것을 확인 )

 

Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "create table [*]user (id bigint not null, email varchar(255) not null, gender enum ('FEMALE','MALE'), nickname varchar(255) not null, password varchar(255) not null, role varchar(255) not null, primary key (id))"; expected "identifier"; SQL statement:

 

> 테이블 생성 코드 자체에는 잘못된 부분이 없는 것을 확인, 해당 문제를 해결하기 위해 구선생님께 "jpa 테이블 생성 실패", "jpa JdbcSQLSyntaxErrorException" 등을 검색해보다가 한 가지 사실을 알게되었다...!

 

 

2. SQL 표준에는 USER 가 예약어로 설정되어 있다.

( SQL 쿼리에는 USER 라는 예약어가 설정되어 있으며, SELECT USER() 와 같은 쿼리를 실행시키면 현재 접속중인 사용자의 이름을 반환하게 된다 )

 

> Oracle, MySQL 등의 쿼리에서는 예약어를 유연하게 처리할 수 있게끔 설정되어 있으나 H2 Database, postgresql, ms-sql 등 데이터베이스에서는 예약어에 대한 처리과정이 더 엄격하게 설정되어 있어 USER 라는 테이블을 생성하지 못한다고 한다.

 

 

3. 테이블 명을 USERS, MEMBER 등으로 변경하여 생성하기

 

Hibernate: create table users (id bigint not null, email varchar(255) not null, gender enum ('FEMALE','MALE'), nickname varchar(255) not null, password varchar(255) not null, role varchar(255) not null, primary key (id))

테이블 명을 USERS 로 변경한 뒤 코드를 실행 / 테이블이 정상적으로 생성된 것 확인

728x90

 

 

 

 

 

미국에서 산책시킨 귀여운 멍멍이... 할 줄 아는 개인기가 하나도 없었지만 귀여웠다

 

 

미국 여행을 1달 넘게 다녀오면서 개발에 소홀해진 느낌이 들어 오늘부터 TIL(Today I Learned) 포스트를 기재해보려고 한다.

( 포트폴리오와 이력서를 작성하느라 미국 여행도 정신이 없이 지나가버린 듯 하다.... )

 

국비교육 과정에서 함께 프로젝트를 한번 진행했던 팀원분과 같이 사이드 프로젝트로 랜덤 채팅을 구현하고 서비스해보기로 했다.

 

기본적으로 Login 기능과 Register 기능은 필수로 구현하기로 하였고 우선적으로 구현할 부분은 랜덤한 상대와 텍스트 채팅을 먼저 구현하고 텍스트 채팅 구현이 완료되면 영상통화도 같이 구현해보기로 하였다.

 

 

 

 

 

- TIL -

 

1. 처음 Spring 프로젝트를 생성하고 빌드

( 프로젝트를 구현하기 위해 추가된 의존성 : Lombok, Spring Web, Spring Security, Spring Data JPA, MySQL Driver, Spring Boot DevTools, H2 Database )

 

2. H2 Database 를 in-memory 를 사용하게끔 설정하여 주 메모리에 Database 를 활성화 하여 사용하고자 하였고 좌측 같이 설정 후 실행하여 H2 콘솔 주소(http://localhost:8080/h2-console) 로 접근하니 우측과 같이 Spring Security 에 설정된 Username / Password 를 요구하는 창이 출력되었다

( 나는 Security 에서 제공하는 폼 로그인 기능을 사용하지 않을 것이지만... 혹시 모르니 해당 주소만 인가를 얻을 수 있는 방법을 검색해보았다 )

application.yml 의 database 설정 / H2 콘솔 주소 접속 시 출력물

 

3. 검색을 통해 알아낸 바에 의하면 Spring Security 는 자동으로 SecurityFilterChain 빈을 찾아 해당 애플리케이션 요청에 대해 보안 필터 체인을 적용한다고 하는데 나는 이 SecurityFilterChain 부분을 구현하는 방법을 찾아 기본적인 폼 로그인 방법을 사용하지 않게끔 설정하기로 하였다.

jwt 를 사용하여 기본적인 인가 부분을 모두 설정할 것이기 때문에 formLogin 과 httpBasic 을 비활성화하였고, Session 과 Cookie 를 활용하여 인증을 구현할때 발생할 수 있는 csrf 보호 기능을 비활성화 하였다. 이후 localhost:8080/h2-console 하위 모든 경로의 요청을 인가가 필요없이 허용하도록 설정하였다.

 

4. 돌아왔구나 H2 태식이....

익숙한 실루엣의 H2 console 로그인 화면이 정상적으로 출력되는 것을 확인!

 

 

 

- 의문점 및 보완점 -

1. Restful API 서버를 구축하는데 있어서 인가 부분을 오롯이 토큰에만 의존하여 설계해도 되는지 의문이 생긴다

( 예를 들어, 토큰에 role 값을 payload 에 담아서 사용한다고 가정했을때 이 권한 부분이 변조될 위험성은 없는가? )

728x90

+ Recent posts