1. 실제로 취업을 할때 필요한 자격요건
    1. 기본기가 있는 사람  ( 기본기란? 이론적인 부분을 알아야 함 - 컴퓨터 지식)
    2. 최대한 다양한 기술 많이 사용해보기 - CRUD 많이 하기보다 한 기술을 깊게 파보기
    3. 블로그 - 트러블 슈팅
    4. 깃헙 잔디심기 - 매일 ! 꾸준히
  2. 항해99를 끝내고 보통 어떻게 취업을 다들 하시는지? - 협력사보다 지원해서 많이 가는 편
  3. 항해99 수료 후 다른 분야로 가신분이 있다면 어떤 분야로 가시는지 - PM 직무
  4. 코딩 공부는 구조파악이 중요하다.
  5. 큰 회사는 CS 지식이 필요
  6. 면접을 포기하지말고 끝까지 가야함.
  7. 면접도 연습이고 할수록 기술이 늘기때문에 힘들더라도 최대한 많이 면접을 볼 것
  8. 취업이 급해서 아무 회사를 가기 보다 내가 생각하는 조건에 맞는 회사 가기
  9. 요즘은 코틀린 / 고랭 유행

비전공자로 자바 백엔드 개발자 시작하기 : https://jojoldu.tistory.com/505

 

(2021) 1. 비전공자로 자바 백엔드 개발자 시작하기

저는 개인적으로 이런 이야기를 하는 것을 썩 좋아하진 않습니다. 어떤 사람의 커리어나, 그 사람의 현재 위치는 운이 굉장히 큰 영향을 끼쳤다고 믿기 때문입니다. 그 사람이 했던 방식, 했던

jojoldu.tistory.com

 

월드잡 

https://www.worldjob.or.kr/new_index.do

 

월드잡플러스

한국산업인력공단 운영, 해외취업, 해외진출정보, 해외채용공고, K-Move스쿨, 해외취업정착지원금 등

www.worldjob.or.kr

 

 

로켓펀치

https://www.rocketpunch.com/

 

로켓펀치 - 비즈니스 네트워크

국내 최대 비즈니스 네트워크 '로켓펀치'입니다. 프로필을 만들고 비즈니스와 커리어를 성장시킬 수 있는 많은 정보를 만나보세요!

www.rocketpunch.com

 

원티드

https://www.wanted.co.kr/

 

원티드 - 나다운 일의 시작

AI 채용, 연봉 정보, 이력서, 커리어 콘텐츠까지 커리어 성장에 필요한 모든 것, 원티드에서 만나보세요.

www.wanted.co.kr

 

링크드인

https://www.linkedin.com/feed/

 

회원가입 | LinkedIn

5억 명 회원들이 함께 하는 글로벌 비즈니스 세상 비즈니스 인맥을 쌓고 넓히세요. 커리어 계발에 유용한 정보와 기회의 문으로 들어오세요.

www.linkedin.com

점핏

https://www.jumpit.co.kr/

 

점핏

개발자 커리어 점프, 점핏

www.jumpit.co.kr

 

프로그래머스 커리어

https://career.programmers.co.kr/

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

career.programmers.co.kr

합격자소서 

https://linkareer.com/

 

대외활동 공모전 대학생 인턴 채용 | 링커리어

대학생 대외활동 공모전 인턴 채용 동아리 정보를 한번에! 서포터즈 마케팅 광고 공모전 봉사활동 등 링커리어 통해 추천받으세요!

linkareer.com

 

1. 사전 준비

① 노션 : https://www.notion.so/1-882180dd274943b683676575e8aae4dd

 

1조 기술멘토링 사전노트

코드 컨벤션

www.notion.so

 

② 준비한 질문 

  • GameSet에서 발언권이 바뀔 때 마다 DB에 접근을 해야될 것 같은데 속도나 효율성 때문에 Redis를 사용하고자 하는데 방향성이 맞을까요?
  • 한 방에 정답이 최대 4개인데 키워드 리스트를 Json화 시켜서 문자열로 DB에 저장을 했는데 이거를 다시 검증하려고 하면 배열화 시켜야하는 번거로움이 있는데, 조금 더 효율적인 방법이 있을까요?
  • queryDSL 예시를 찾아보던 중에 여러 queryDSL문 생성 후에 마지막에 flush / clear 로 DB에 저장하는 사례가 많았는데, 이 방법이 DB 접근을 최소화하기 위한게 맞을까요?
  • 실무에서 flush / clear를 사용할 일이 별로 없다는데 잘 이해가 안됩니다….ㅠㅠ (데이터를 DB로 보내는 다른 방법: 트랜잭션 commit() / JPQL로 만들어진 Query 실행)
  • Redis가 메모리 파편화 때문에 생각보다 많은 메모리를 차지하는데, Redis 메모리를 효율적으로 사용하는 방법이 있을까요?
  • WebRTC를 시그널링 서버를 구축하는 방향으로 구현은 성공했는데 최대 4명의 유저간 통신이 이루어지는 저희 서비스에서도 SFU 서버 통신을 고려해야 할까요?

 

2. 피드백 정리

① SA대시보드 피드백

 

② 1주차 피드백 (1월 7일 토요일)

1) 깃허브 

  • 코딩컨벤션 : 에어비엔비 참고 (스탠다드 처럼 쓰임 )  https://github.com/airbnb/javascript
  • 깃플로우 전략 : 순서가 중요( 하위 브랜치에서 상위 브랜치로 가야함 ) + Master branch 에는 배포된 코드만 있어야 한다
    • Hot fix branch - 실제로 현업에서 발생하는 경우는 많지 않음
    • 브랜치는 Commit 단위로 나누기 ( 커밋 로그를 나눠서 )

2) 기술스택

  • AWS - 기술스택이 아니고 인프라로 빼야함 
  • OpenVidu - 이것도 기술 스택 아님 

3) ERD

  • 현재 ERD - ERD라고 보기 어려움 ( 몇대 몇으로 맵핑되는지 보여야함 +  PK/FK 가 보여야함)
  • 개선할 부분
    • GameRoom - Member 사이에 관계를 맺어주기
    • GameRoomMember 도메인을 GameRoomAttendee로 변경
      • Id(pk), RoomId(fk), MemberId(fk) 요소가 만들어진 테이블 만들어주기
      • relation table 역할 : 진짜 필요한 데이터만 갖고 있어야 한다.
      • PK : Id, FK : RoomId, MemberId, nickname
    • 게임룸이랑 멤버요소는 지워줘도 괜찮음
    • 게임룸 안에 chatroom 을 뺴도 괜찮을 듯 (존재 이유가 명확해야 하는데 불명확 하기 때문 )
    • GameRoom <- GameRoomMember -> Member
    • 게임룸이 게임룸 멤버 정보를 들고있으면 안된다 ! 

 

4) 질문에 대한 답변 

 

1. Openvidu와 같은 미디어 서버를 사용해야 할까요?

  • 사용자가 많아지면 SFU를 사용하는게 맞지만 4명~8명처럼 소수라면 P2P 시그널링 서버를 쓰는게 좋다 : 성능적으로 좋다
  • 실시간성이 중요한건지 서버의 부하를 줄이는건지 선택하는게 중요
  • 완벽한 이상적인 해결책은 없다 ! 따라서 서비스에 따라 기술스택을 선택하면 된다 

2. Redis가 메모리 파편화 때문에 생각보다 많은 메모리를 차지하는데, Redis 메모리를 효율적으로 사용하는 방법이 있을까요?

  • Redis는 현업에서 아주 많이 사용하는 메모리 DB, 메모리 파편화라는 것을 신경쓸 필요가 없을 정도로 잘 구성이 되어있음
  • 메모리 파편화 해결 : 효율적인 자료구조로 사용 - 리스트나 맵보다 스트링으로 저장한다던지 하는 방법 

3. 한 방에 정답이 최대 4개인데 키워드 리스트를 Json화 시켜서 문자열로 DB에 저장을 했는데 이거를 다시 검증하려고 하면 배열화 시켜야하는 번거로움이 있는데, 조금 더 효율적인 방법이 있을까요?

  • 효율성을 생각하면 콜룬을 추가해주는게 맞지만 사실 추후 확장성을 고려했을때는 Json 형식이 맞다! 

4. 실무에서 flush / clear를 사용할 일이 별로 없다는데 사용한 예제를 봤다. 이유가 뭘까요?

  • flush / clear 명시적으로 사용할 수 있지만 거의 코드로 사용하지 않음
  • 암시적으로 자동으로 반영이 되기 때문에 굳이 써줄 필요가 없다. 
  • hibernate 에 auto commit 기능이 있기 때문에 알아서 처리가 된다. 

5) 그 외 조언

  • 자료 찾을때 블로그보는게 좋지는 않음 - 최대한 공식 문서 / 영어로 찾아보기
  • 잘짠 코드/ 좋은 코드 : 일관성 있는 코드,  간결함 = 여러명이 찐 코드여도 한사람이 짠 코드처럼 보이는 코드 
  • 프론트엔드 : 리덕스를 사용하기보다 리액트에서 최대한 관리하는게 좋다. 리덕스 사용을 최소화하기 - stateful (x) stateless (o)

 

③ 과제

  • 깃 , 깃허브랑, 깃플로우의 차이점
  • 스프링과 스프링부트의 차이점 ?
  • trunk based development ( TBD 새로 주신 키워드 - 학습해보기 )

한시간 반동안 열심히 피드백을 주신 김선우 개발자님 감사합니다 : )

덕분에 많이 궁금증이 해소되고 배울 수 있었어요. 받은 피드백 잘 기억해서 개선하는 개발자가 되겠습니다.

 

이번 포스트는 Custom한 예외처리 (StatusCode)를 이용하여 JWT토큰에 관련된 예외처리를 작성한 부분을 담으려 한다.


ExceptionTranslationFilter

ExceptionTranslationFilter는 2가지 종류의 예외를 처리    

  • FilterSecurityInterceptor(보안필터중 제일 마지막에 위치)가 발생시킴
  • 여기에서 인증,인가예외를 Throws함

 

    가. AuthenticationException - 인증예외처리

        -  AuthenticationEntryPoint 호출 : 로그인페이지 이동, 401오류 코드 전달

        -  인증예외가 발생하기 전의 요청 정보를 저장

             - RequestCache - 사용자의 이전 요청정보를 세션에 저장하고, 이를 꺼내오는 캐시 메커니즘

             - SavedRequest - 사용자가 요청했던 request파라미터 값들, 그 당시의 헤더값들 등이 저장 

 

    나. AccessDeniedException - 인가예외처리

         - AccessDeniedHandler에서 예외처리하도록 제공

 

 

AuthenticationEntryPoint란 ?

  • 인증 처리 과정에서 예외가 발생한 경우 예외를 핸들링하는 인터페이스
  •  AuthenticationException(인증되지 않은 요청)인 경우 AuthenticationEntryPoint를 사용하여 처리

AuthenticationEntryPoint 인터페이스 구현체 생성

 

SecurityConfig에서 .exceptionHandling() 메소드에 등록

 

전체 코드

1. JwtUtil

// 기능 : JWT 유틸
@Component
@RequiredArgsConstructor
public class JwtUtil {

    private final UserDetailsServiceImpl userDetailsService;
    public static final String AUTHORIZATION_HEADER = "Authorization";
    private static final String BEARER_PREFIX = "Bearer ";
    private static final long TOKEN_TIME = 60 * 60 * 1000L;

    @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);
    }

    // 토큰 생성
    public String createToken(String loginId){
        Date date = new Date();

        return BEARER_PREFIX +
                Jwts.builder()
                        .setSubject(loginId)
//                        .claim(AUTHORIZATION_KEY, role)
                        .setExpiration(new Date(date.getTime() + TOKEN_TIME))
                        .setIssuedAt(date)
                        .signWith(key, signatureAlgorithm)
                        .compact();
    }

    public String resolveToken(HttpServletRequest request){
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);

        if(StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)){
            return bearerToken.substring(7);
        }
        return null;
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException e) {
            log.info("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
        } catch (ExpiredJwtException e) {
            log.info("Expired JWT token, 만료된 JWT token 입니다.");
        } catch (UnsupportedJwtException e) {
            log.info("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
        } catch (IllegalArgumentException e) {
            log.info("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
        }
        return false;
    }

    public Claims getUserInfoFromToken(String token) {
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    }

    public Authentication createAuthentication(String email) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(email);
        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }
}

 

2. JwtAuthFillter

// 기능 : JWT 필터
@Slf4j
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {
    public final JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String token = jwtUtil.resolveToken(request);

        if(token != null) {
            if(!jwtUtil.validateToken(token)){
                response.setStatus(StatusCode.INVALID_TOKEN.getHttpStatus().value());
                response.setContentType("application/json; charset=UTF-8");
                try {
                    String json = new ObjectMapper().writeValueAsString(new GlobalResponseDto(StatusCode.INVALID_TOKEN));
                    response.getWriter().write(json);
                } catch (Exception e) {
                    log.error(e.getMessage());
                }
                return;
            }
            // 3. 토큰이 유효하다면 토큰에서 정보를 가져와 Authentication 에 세팅
            Claims info = jwtUtil.getUserInfoFromToken(token);
            setAuthentication(info.getSubject());
        }
        // 4. 다음 필터로 넘어간다
        filterChain.doFilter(request, response);
    }

    public void setAuthentication(String loginId) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Authentication authentication = jwtUtil.createAuthentication(loginId);
        context.setAuthentication(authentication);
        SecurityContextHolder.setContext(context);
    }
    public void jwtExceptionHandler(HttpServletResponse response, String message, int statusCode) {
        response.setStatus(statusCode);
        response.setContentType("application/json");
        try {
            String json = new ObjectMapper().writeValueAsString(new GlobalResponseDto<>(StatusCode.INVALID_TOKEN));
            response.getWriter().write(json);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}

 

3. SecurityConfig

// 기능 : Spring Security 사용에 필요한 설정
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig {
    private final JwtUtil jwtUtil;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf().disable();

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.httpBasic().disable()
                .authorizeRequests()
                .antMatchers("/auth/**").permitAll()
                .antMatchers(HttpMethod.GET, "/**").permitAll()
                .antMatchers("/ws-stomp").permitAll()
                .antMatchers("/signal/**").permitAll()
//                .antMatchers("/swagger-ui.html", "/swagger-ui/**", "/api-docs", "/api-docs/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new JwtAuthFilter(jwtUtil),
                        UsernamePasswordAuthenticationFilter.class);
        http.cors();

        // Spring Security에서 발생하는 예외를 커스텀 핸들링
        http.exceptionHandling()
                // 인증부분  (여기서만 사용하기 때문에 익명함수 람다식 이용)
                .authenticationEntryPoint((request, response, authException) -> {
                    securityExceptionResponse(response, INVALID_TOKEN);
                });

        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {

        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://localhost:3000");
        config.addAllowedOrigin("https://S3 버켓이름.s3-website.ap-northeast-2.amazonaws.com"); 
        config.addAllowedOrigin("https://프론트엔드 서버주소.cloudfront.net"); 
        config.addExposedHeader(JwtUtil.AUTHORIZATION_HEADER);
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        config.setAllowCredentials(true);
        config.validateAllowCredentials();
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return source;
    }

    // 예외 응답처리 메소드
    public void securityExceptionResponse(HttpServletResponse response, StatusCode statusCode) throws IOException {
        response.setStatus(statusCode.getHttpStatus().value());
        response.setHeader("content-type", "application/json; charset=UTF-8");
        String json = new ObjectMapper().writeValueAsString(new GlobalResponseDto(statusCode));
        response.getWriter().write(json);
        response.getWriter().flush();
        response.getWriter().close();
    }
}

 


[ 참고 자료 ]

 

 

[SpringSecurity] 스프링시큐리티 - 인증인가 예외발생시 처리 (ExceptionTraslationFilter)

[SpringSecurity] 스프링시큐리티 - 인증인가 예외발생시 처리 (ExceptionTraslationFilter) 1. ExceptionTranslationFilter는 2가지 종류의 예외를 처리 --> FilterSecurityInterceptor(보안필터중 제일 마지막에 위치)가 발생

fenderist.tistory.com

 

AuthenticationEntryPoint

개인 공부 목적으로 작성한 포스팅입니다. 아래 출처를 참고하여 작성하였습니다. 1. AuthenticationEntryPoint란 ? 인증 처리 과정에서 예외가 발생한 경우 예외를 핸들링하는 인터페이스입니다. e.g. Au

batory.tistory.com

 

문제상황

  • WebRTC를 이용해 프론트엔드와 화상채팅을 구현하는 코드를 작성 중이다.
  • 채팅방에 들어오는 사람마다 session ID를 갖고있는데, 이 세션 아이디를 Redis DB에 저장하는 과정에서 아래와 같은 두 가지 에러가 발생했다.
  • Jackson ObjectMapper를 사용해서 객체타입인 세션을 스트링타입으로 변환해주고 다시 객체타입으로 변환하는 그런 로직에 사용했는데, 이 부분에서 에러가 터진 것 같다. (추측)

1. No serializer found for class org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

 

2. Null key for a Map not allowed in JSON (use a converting NullKeySerializer?)

 

원인 : 모름

해결 : 못함

 

 

[ 참고자료 ]

https://interconnection.tistory.com/137

 

Jackson ObjectMapper 정리

개요 Java 개발자라면 Jackson에서 제공하는 ObjectMapper와 자주 마주치게 됩니다. Java 클래스 내용을 확인하거나 내용물의 Parsing 과정에 필요한 커스터마이징이 존재하기 때문입니다. 물론 중요한 기

interconnection.tistory.com

https://jinseongsoft.tistory.com/248

 

[SpringBoot] Jackson 사용시 Could not write JSON: No serializer found for class.. 오류 발생 해결법

들어가며 Class를 Serialize 하는 과정에서 아래와 같은 에러가 발생하였다. Exception in thread "Thread-5" org.springframework.messaging.converter.MessageConversionException: Could not write JSON: No serializer found for class com.tact.

jinseongsoft.tistory.com

 

+ Recent posts