1. JwtUtil

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtil {
    private final RefreshTokenRepository refreshTokenRepository;
    private final UserDetailsServiceImpl userDetailsService;
    private static final String BEARER_PREFIX = "Bearer ";
    private static final long ACCESS_TIME = 60 * 60 * 1000L; // ACCESS_TIME 1시간
    private static final long REFRESH_TIME = 7 * 24 * 60 * 60 * 1000L;  // REFRESH_TIME 7일
    public static final String ACCESS_TOKEN = "Access_Token";
    public static final String REFRESH_TOKEN = "Refresh_Token";
    @Value("${jwt.secret.key}")
    private String secretKey;
    private Key key;
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    @PostConstruct
    public void init() {
        byte[] bytes = Base64.getDecoder().decode(secretKey);
        key = Keys.hmacShaKeyFor(bytes);
    }

    // header 토큰을 가져오는 기능
    public String getHeaderToken(HttpServletRequest request, String type) {
        return type.equals("Access") ? request.getHeader(ACCESS_TOKEN) :request.getHeader(REFRESH_TOKEN);
    }

    // 토큰 생성
    public TokenDto createAllToken(String email) {
        return new TokenDto(createToken(email, "Access"), createToken(email, "Refresh"));
    }

    public String createToken(String email, String type) {
        //현재 시각
        Date date = new Date();
        //지속 시간
        long time = type.equals("Access") ? ACCESS_TIME : REFRESH_TIME;

        return Jwts.builder()
                .setSubject(email)
                .setExpiration(new Date(date.getTime() + time))
                .setIssuedAt(date)
                .signWith(key, signatureAlgorithm)
                .compact();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException e) {
            throw new CustomException(LOGIN_WRONG_SIGNATURE_JWT_TOKEN);
        } catch (ExpiredJwtException e) {
            throw new CustomException(LOGIN_EXPIRED_JWT_TOKEN);
        } catch (UnsupportedJwtException e) {
            throw new CustomException(LOGIN_NOT_SUPPORTED_JWT_TOKEN);
        } catch (IllegalArgumentException e) {
            throw new CustomException(LOGIN_WRONG_FORM_JWT_TOKEN);
        } catch (SignatureException e) {
            throw new CustomException(SIGNATURE_EXCEPTION);
        }
    }

    // refreshToken 토큰 검증
    public Boolean refreshTokenValidation(String token) {

        // 1차 토큰 검증
        if(!validateToken(token)) return false;

        // DB에 저장한 토큰 비교
        Optional<RefreshToken> refreshToken = Optional.ofNullable(refreshTokenRepository.findByEmail(getUserInfoFromToken(token)));

        return refreshToken.isPresent() && token.equals(refreshToken.get().getRefreshToken());
    }

    // 토큰에서 loginId 가져오는 기능
    public String getUserInfoFromToken(String token) {
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getSubject();
    }

    // 인증 객체 생성
    public Authentication createAuthentication(String email) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(email);
        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }
}

 

JwtUtil = 토큰에 관련된 암호화, 복호화, 검증 로직이 이루어지는 곳

1. init()

  • @PostConstruct 어노테이션은 특정 클래스의 메소드에 붙여서 해당 클래스의 객체 내 모든 의존성(Bean) 들이 초기화 된 직후에 딱 한 번만 실행되도록 해준다. 만약 객체에 의존성이 하나도 없더라도 실행된다.
  • WAS가 뜰 때 서비스가 생성되며 초기화 메소드가 실행된다.
  • 깃헙엔는 올라가지 않는 application-local.properties 파일에에 저장되어 있는 Jwt Secret Key를 복호화(디코딩 = 암호화의 반댓말)한다.

2. getHeaderToken()

  • 헤더부분에 타입에 따라 Access Token 과 Refresh Token을 가져오는 메소드
  • JwtAuthFilter 클래스에 사용된다.

3. createToken() / createAllTolen()

  • 유저 정보(email)를 넘겨받아서 Access Token 과 Refresh Token 을 생성하는 메소드
  • 타입에따라 각각 유효 시간을 저장해주고, Token Dto에 담아준다.
  • KakaoService, MemberService 에서 로그인한 후  response Header에 JWT 토큰 추가할 때 사용된다.

4. validateToken/refreshTokenValidation

  • 토큰 정보를 검증한다.
  • JwtAuthFilter의 doFilterInternal()에서 토큰 만료 여부를 확인할 때 사용된다.

5. getUserInfoFromToken()

  • 토큰에서 유저정보(email) 가져오는 메소드
  • JwtAuthFilter의 doFilterInternal()에서 토큰이 유효한 경우 토큰정보를 setAuthentication()메소드를 이용하여 Authentication에 저장해 줄 때 사용

6. createAuthentication()

  • 인증 객체를 생성하는 메소드
  • JwtAuthFilter의 setAuthentication() 에서 객체를 Authentication에 저장할 때 사용

 

2. JwtAuthFilter

@Slf4j
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {
    public final JwtUtil jwtUtil;

    @Override //토큰 유효성 검사 후 Authentication 에 세팅
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String accessToken = jwtUtil.getHeaderToken(request, "Access");
        String refreshToken = jwtUtil.getHeaderToken(request, "Refresh");

        if (accessToken != null) {
            if (!jwtUtil.validateToken(accessToken)) {
                jwtExceptionHandler(response, "AccessToken Expired", HttpStatus.UNAUTHORIZED);
                return;
            }
            setAuthentication(jwtUtil.getUserInfoFromToken(accessToken));

        } else if (refreshToken != null) {
            if (!jwtUtil.refreshTokenValidation(refreshToken)) {
                jwtExceptionHandler(response, "RefreshToken Expired", HttpStatus.BAD_REQUEST);
                return;
            }
            // 3. 토큰이 유효하다면 토큰에서 정보를 가져와 Authentication 에 세팅
            setAuthentication(jwtUtil.getUserInfoFromToken(refreshToken));
        }
        // 4. 다음 필터로 넘어간다
        filterChain.doFilter(request, response);
    }

    public void setAuthentication(String email) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Authentication authentication = jwtUtil.createAuthentication(email);
        context.setAuthentication(authentication);
        SecurityContextHolder.setContext(context);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    public void jwtExceptionHandler(HttpServletResponse response, String message, HttpStatus statusCode) {
        response.setStatus(statusCode.value());
        response.setContentType("application/json; charset=UTF-8");
        try {
            String json = new ObjectMapper().writeValueAsString(new GlobalResponseDto<>(StatusCode.BAD_REQUEST_TOKEN));
            response.getWriter().write(json);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}

 

1. doFilterInternal()

  • 실제 필터링 로직을 수행하는 곳이다.
  • Request Header 에서 Access Token 과 Refresh Token을 꺼내온 후 토큰 유효 여부를 판단한다.
  • 토큰이 유효하면 Authentication에 넣어준다.
  • 가입/로그인/재발급을 제외한 모든 Request 요청은 이 필터를 거치기 때문에 토큰 정보가 없거나 유효하지 않으면 정상적으로 수행되지 않는다.

 

2. setAuthentication()

  • 토큰이 유효한 경우 토큰정보를 Authentication에 저장해 준다.

3. jwtExceptionHandler()

  • 토큰이 유효하지 않을 경우 핸들러를 통해 에러처리를 해준다.

 

3. Controller

@RestController
@RequiredArgsConstructor
@RequestMapping(produces = "application/json; charset=utf8")
public class MemberController {
    private final MemberService memberService;
    private final KakaoService kakaoService;

    // 로그인
    @PostMapping(value = "/auth/login")
    public ResponseEntity<?> login(@RequestBody SignupRequestDto signupRequestDto,
                                   HttpServletResponse response) {
        return ResponseUtil.response(memberService.login(signupRequestDto, response));
    }
    
    
    // 로그아웃
    @PostMapping("/auth/signout")
    public ResponseEntity<?> signout(@AuthenticationPrincipal UserDetailsImpl userDetails){
        return memberService.signout(userDetails.getMember().getEmail());
    }

    // 토큰 재발행
    @GetMapping("/auth/issue/token")
    public ResponseDto<String> issuedToken(@AuthenticationPrincipal UserDetailsImpl userDetails, HttpServletResponse response){
        return memberService.issuedToken(userDetails.getMember().getEmail(), response);
    }
}

 

4. Service

@RequiredArgsConstructor
@Service
@Slf4j
public class MemberService {
    private final PasswordEncoder passwordEncoder;
    private final RepositoryService repositoryService;
    private final JwtUtil jwtUtil;
    private final RefreshTokenRepository refreshTokenRepository;

    // 로그인
    @Transactional
    public MemberResponseDto login(SignupRequestDto signupRequestDto, HttpServletResponse response) {
        String email = signupRequestDto.getEmail();
        String password = signupRequestDto.getPassword();

        Member member = repositoryService.findMemberByEmail(email).orElseThrow(
                () -> new CustomException(StatusCode.LOGIN_MATCH_FAIL)
        );

        if (!passwordEncoder.matches(password, member.getPassword())) {
            throw new CustomException(StatusCode.BAD_PASSWORD);
        }

	// user email 값을 포함한 토큰 생성 후 tokenDto 에 저장
        TokenDto tokenDto = jwtUtil.createAllToken(signupRequestDto.getEmail());

        // user email 값에 해당하는 refreshToken 을 DB에서 가져옴
        Optional<RefreshToken> refreshToken = Optional.ofNullable(refreshTokenRepository.findByEmail(member.getEmail()));

        if (refreshToken.isPresent()) {
            refreshTokenRepository.saveRefreshToken(refreshToken.get().updateToken(tokenDto.getRefreshToken()));
        } else {
            RefreshToken newToken = new RefreshToken(tokenDto.getRefreshToken(), signupRequestDto.getEmail());
            refreshTokenRepository.saveRefreshToken(newToken);
        }

        setHeader(response, tokenDto);

        return new MemberResponseDto(member);
    }
    
    // 토큰 재발행
    public ResponseDto<String> issuedToken(String email, HttpServletResponse response){
        response.addHeader(JwtUtil.ACCESS_TOKEN, jwtUtil.createToken(email, "Access"));
        return ResponseDto.success("토큰재발행 성공");
    }
    
	// 로그인하면 Header에 토큰 담아주기
    private void setHeader(HttpServletResponse response, TokenDto tokenDto) {
        response.addHeader(JwtUtil.ACCESS_TOKEN, tokenDto.getAccessToken());
        response.addHeader(JwtUtil.REFRESH_TOKEN, tokenDto.getRefreshToken());
    }
    

    // 로그아웃
    public ResponseEntity<?> signout(String email) {
        // 해당 유저의 refreshtoken 이 없을 경우
        if(refreshTokenRepository.findByEmail(email) == null){
            throw new CustomException(StatusCode.INVALID_TOKEN);
        }
        // 자신의 refreshtoken 만 삭제 가능
        String memberIdrepo = refreshTokenRepository.findByEmail(email).getEmail();
        if(email.equals(memberIdrepo)){
            refreshTokenRepository.deleteRefreshToken(email);
            return ResponseUtil.response(StatusCode.SIGNOUT_OK);
        }else{
            return ResponseUtil.response(StatusCode.BAD_REFRESH_TOKEN);
        }
    }
}

 

 

※ 필요한 부분만 포스팅 했기 때문에 전체 코드는 아래 깃헙 링크를 참조해 주세요 ! ( 아직 수정중.... )

https://github.com/namoldak/Backend

 

GitHub - namoldak/Backend

Contribute to namoldak/Backend development by creating an account on GitHub.

github.com

 

 

현재 구현하고 있는 서비스에 Redis를 사용하고 있기 때문에 사용하는 ubuntu 서버에 Redis를 설치해 줘야만 애플리케이션이 제대로 동작한다. 설치 순서는 아래와 같다

 

1. ubuntu 서버에 아래 redis 설치 명령어 입력 

sudo apt-get install redis-server

 

 

 

2. 아래 명령어로 redis.conf 파일을 열어서 bind 부분을 0.0.0.0 ::1 로 수정. (원래는 127.0.0.1::1 로 설정되어있음)

 sudo nano /etc/redis/redis.conf

 

 

 

 

3. control + x 누르면 아래와 같이 뜨는데, Y를 입력하고 엔터를 치면 설치 및 환경 설정 완료 ! 

 

지난 번에 개념을 공부했으니 오늘은 Github을 이용하여 CI를 구현하는 방법을 포스팅 : ) 미래의 나를 위한 포스팅이다.

 

 

1. Actions -> new workflow -> Java with Gradle [configure] 클릭

 

2. <>Edit new file 에 코드 추가 -> Start commit

 

Test용으로 생성한 파일 

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
    - name: Grant execute permission for gradlew       // 이부분은 추가로 작성해줘야 한다
      run: chmod +x ./gradlew            			   // Build전에  gradlew 권한을 부여하는 워크플로우
# Build     
    - name: Build with Gradle
      uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
      with:
        arguments: build

 

아래는 우리 프로젝트에 맞게 커스텀되어있는 코드

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle

on:
  pull_request:
    branches: [ "dev" ]

env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: my-cicd-s3-bucket
  CODE_DEPLOY_APPLICATION_NAME: my-codedeploy-app
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: my-codedeploy-deployment-group

permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
      # 기본 체크아웃
      - name: Checkout
        uses: actions/checkout@v3

      # JDK 11 세팅
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      # 키 정보 있는 properties 따로 관리
      - name: make application.properties
        if: true
        run: |
          cd ./src/main/resources
          touch ./application.properties
          echo "${{ secrets.PROPERTIES }}" > ./application.properties
        shell: bash

      # gradlew 권한 설정
      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew
        shell: bash

      # build
      - name: Build with Gradle
        run: ./gradlew clean build -x test
        shell: bash

      # aws 인증
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2

      # 압축파일 형태로 전달
      - name: Make zip file
        run: zip -qq -r ./$GITHUB_SHA.zip .
        shell: bash

      # S3 bucket으로 copy
      - name: Upload to AWS S3
        run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip

      # S3 bucket에 있는 파일을 대상으로 CodeDeploy 실행
      - name: Deploy to AWS EC2 from S3
        run: aws deploy create-deployment --application-name $CODE_DEPLOY_APPLICATION_NAME --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name $CODE_DEPLOY_DEPLOYMENT_GROUP_NAME --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip

 

3. 나는 이미 CI/CD 적용을 모두 마친상태이기 때문에 위의 코드를 저장하고 테스트용으로 브랜치를 만들어서 main 브랜치에 merge를 해주면 아래와 같이 "This branch is being deployed" 라는 문구를 볼 수 있다.

 

아직 CD 적용을 안 한 상태라면 아래와 같이 "Some checks haven't completed yet" 이라는 checking 문구를 볼 수 있다.

https://littlezero48.tistory.com/289

 

 

4. 다시 Actions 탭을 가보면 아래와 같이 진행 상황을 확인 할 수 있다. 다시 말하지만, 나는 이미 CD를 끝낸 상태로 포스트를 작성했기 때문에 상단에 "Deploy"라고 뜨지만, CI 까지만 진행한 경우 상단에 "build"라는 표시와 함께 아래와 비슷한 화면이 보인다 !

 

이로써 CI는 끝났다! 아주 간단함 ! 

 


[ 참고 자료 ]

 

https://littlezero48.tistory.com/289

 

CI/CD] Github Actions CI

📌 Workflow 선택 깃헙에서 Actions > gradle 검색 > java with Gradle 의 Cofigure 클릭 📌 Workflow 선택 📍 yml이 뭔 파일이지? Yet another Markup Language의 약자로 yml 또는 yaml 확장자로 사용된다. 여러 configuration을

littlezero48.tistory.com

 

소영님 감사합니다 !! ㅎㅎ 소영님 블로그 없으면 아무것도 못할 1인 = 나

OAuth 란?

  • OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준이다
  • OAuth가 사용되기 전에는 인증방식의 표준이 없었기 때문에 기존의 기본인증인 아이디와 비밀번호를 사용하였는데, 이는 보안상 취약한 구조이다.
  • OAuth를 이용하면 인증을 공유하는 애플리케이션끼리는 별도의 인증이 필요없다. 따라서 여러 애플리케이션을 통합하여 사용하는 것이 가능하게 된다. 

 

생활코딩 :&nbsp;https://www.youtube.com/watch?v=BofCK1oWAyc&list=PLuHgQVnccGMA4guyznDlykFJh28_R08Q-&index=6

 

용어 설명

  • Resource Owner : 리소스의 권한을 가진 사용자. 사용자 혹은 유저(User)라고도 한다. ex) 나몰닭 유저
  • Client : 리소스 서버에 접근을 요청하는 어플리케이션 혹은 웹 사이트. 즉, 서비스를 제공자이며 Third Party Application 라고도 한다. ex) 나몰닭이 여기에 해당
  • Authorization Server : 액세스 토큰(Access Token)을 클라이언트에게 발급해주는 서버. 인가의 주체. ex) 카카오톡, 구글, 네이버 등
  • Resource Server : Authorization Server가 발급한 액세스 토큰(Access Token)을 확인하고 리소스를 제공. API 서버. ex) 이름, 나이 등 아이디 관련 정보(리소스)를 제공하는 서버. 카카오톡, 구글, 페이스북, Github 등
  • cf.) Authorization Server와 Resource Server를 묶어서 OAuth Provider라고도 한다.

  • Client ID : 우리가 운영하는 애플리케이션을 식별하는 아이디
  • Client Secret : Client ID에 대한 비밀번호 - 외부에 노출되어선 안된다.
  • Authorized redirect URIs : Resource Server로 부터 Authorized code를 받을 주소

 

OAuth2.0 승인 방식(Grant Type)의 종류

Authorization Code Grant Type

  • OAuth2.0 Grant Type에서 가장 잘 알려진 방식이며 중요한 보안 이점을 제공하는 방법
  •   클라이언트가 사용자 대신 특정 리소스에 접근을 요청할 때 사용된다. 클라이언트는 Access Token을 발급 받기 전, 인가코드(Authorization Code)를 사용자에 의해 받게 되고, 그 인가코드(Authorization Code)를 가지고 인가 서버(Authorization Server)에 요청을 보내면 리소스에 대한 Access Token을 발급 받을 수 있다.

 

Implicit Grant Type

  • Authorization Code Grant Type과 다르게, 인가코드(Authorization Code) 교환 단계 없이 Access Token을 즉시 반환받아 이를 인증에 이용하는 방식

Resource Owner Password Credentials Grant Type

  • 클라이언트가 암호를 사용하여 Access Token에 대한 사용자의 자격 증명을 교환하는 방식

Client Credentials Grant Type

  • 클라이언트가 컨텍스트 외부에서 Access Token을 얻어 특정 리소스에 접근을 요청할 때 사용하는 방식

 

OAuth 2.0 흐름

 

Sequence Diagram&nbsp;of Authorization Code Grant

 

 

1. Resoruce Owner인 User가 Client를 통해서 인가(Authorization)를 시작한다.

2. Client는 User를 Authorization Server로 포워드 해준다.

3. Authorization Server는 User에게 로그인창을 제공하여 User를 인증한다.

4. User가 ID/Password를 입력하여 로그인 하면, Client가 Resource에 대한 접근 권한을 요청한다.

5. Authorization Server는 Authorization Code를 Client에게 제공해준다.

6. Client는 Authorization Code로 Authorization Server에 Access Token을 요청한다.

7. Authorization Server는 Client에게 Access Token을 발급해준다.

8. Access Token을 이용하여 Resource Server의 자원에 접근할 수 있게 된다.

9. 이후 Access Token이 만료되면 Refresh Token을 이용하여 Access Token을 재발급 받을 수 있다.

 


[ 참고 자료 ]

 

https://thatisgood.tistory.com/entry/OAuth20-OAuth20-%EC%9D%B4%EB%A1%A0-%EC%A0%95%EB%A6%AC-01

 

[OAuth2.0] OAuth2.0 이론 정리 #01

OAuth란? OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는,

thatisgood.tistory.com

https://opentutorials.org/course/3405

 

WEB2 - OAuth 2.0 - 생활코딩

수업소개 사용자가 가입된 서비스의 API에 접근하기 위해서는 사용자로부터 권한을 위임 받아야 합니다. 이 때 사용자의 패스워드 없이도 권한을 위임 받을 수 있는 방법이 필요합니다. 이를 위

opentutorials.org

https://data-jj.tistory.com/53

 

REST-API 활용한 카카오 소셜 로그인 구현(feat. React)

프로젝트를 진행하면서 소셜 로그인 구현을 맡게 되었다. 다들 프론트엔드는 소셜 로그인에서 할게 많이 없다 쉽다~, 그중에서 카카오가 가장 쉽다~ 이렇게 얘기해서 방심했다. 그렇게 6일간의

data-jj.tistory.com

 

 

발표 관련

  • 멘토님이 우리가 배포한 사이트에 접속했을 때 카카오 로그인이 안되고 회원가입이 잘 안되는 상황이었다. 
    • 배포당시 프론트엔드에서 카카오로그인 리다이렉트 주소를 로컬 서버로 해놓았기 때문 ( 수정 완료 )
    • 회원가입 비밀번호 유효성검사 토스트가 띄워지지 않았던 상황이라 아마 비밀번호입력에 문제가 있었을 듯. ( 수정 완료 )
  • 발표는 찍어놓은 영상으로 하지말고 직접 페이지 들어가서 시연하는게 좋음. 영상을 활용하면 신빙성이 떨어진다.
  • 아키텍쳐 기술은 다 설명할 필요 없이 대략적 개요를 설명하고 디테일한 부분을 슬라이드 따로 빼서 좀 더 상세히 설명하는 식으로 하는게 좋다.
  • 발표는 스크립트 읽는것이 아니라 자연스럽게 하기

 

기술관련 질문 

  • 프론트엔드 서빙을 위한 서버를 따로 두었는지 ?
  • 프론트엔드와 백엔드 배포는 어떻게 하고 있는지 ? 
  • EC2 Nginx 를 사용해서 경량화를 하는게 어떤지 제안해 주심

 

백엔드 질문

  • 레디스를 휘발성을 가졌다고 표현하는 이유는 ?
  • 레디스의 특성을 설명해 보세요.
  • 코드상으로 Redis가 아니라 MySQL을 사용해도 충분한데 왜 굳이 Redis를 사용했는지 ?
  • 동시성 제어에 비관적 락을 사용하는 이유?
  • 무중단배포 어떻게 할 계획인지 ? 
  • 기술 선택 부분의 설명이 아쉽다. 이 부분을 잘 준비해야 면접 질문에 대비할 수 있다.  

 


피드백 받고 작성해본 기술 선택 부분 정리

 

https://www.notion.so/cef12775fbfa4eb6941094ce084dfb6c

 

중간회고 피드백

FE.

www.notion.so

 

+ Recent posts