레이블이 공부 기록인 게시물을 표시합니다. 모든 게시물 표시
레이블이 공부 기록인 게시물을 표시합니다. 모든 게시물 표시

2020년 8월 4일 화요일

간단하게 OAuth 2.0 사용해보기

간단하게 OAuth 2.0 사용해보기

  • OAuth란?

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

    • ID와 비밀번호 대신 엑세스 토큰기반으로 사용자를 식별할 수 있다. 토큰은 리소스 서버만 제공한다.

  • 기본 구조

    • Resource Owner : ID와 비밀번호를 이용해 Resource Client에게 권한을 인가하여 엑세스 토큰을 획득하게 될 주체

    • Resource Client : ResourceOwner로부터 사용 인가를 받아, 소유자 대신 엑세스 토큰을 획득하고 해당 토큰 을 통해 Resource Server의 API를 사용하는 주체

    • Resource Server : 보호된 리소스를 관리하며 리소스 클라이언트가 사용할 API를 제공하는 주체. 엑세스 토큰이 유효한지 확인하기 위해 Authorization Server와 통신을 주고 받기도함

    • Authorization Server : 엑세스 토큰과 인가 코드를 관리하는 서버. 엑세스 토큰 검증, 폐기

  • 기본 동작 구조

    • ex) 리소스 오너 : 사용자 / 리소스 클라이언트 : 쇼핑몰 웹(앱) / 리소스 서버, 인가 서버 : 카카오
    • 사용자 -> 쇼핑몰 실행 -> 로그인 -> 카카오 로그인 버튼 클릭
    • 웹 -> 카카오로 인증페이지 요청 -> 카카오 로그인화면 이동
    • 사용자는 카카오 ID,PW 입력(토큰 요청) -> 카카오는 인증페이지를 요청했던 쇼핑몰에 엑세스 토큰 발급
    • 쇼핑몰에선 해당 엑세스 토큰을 가지고 다시 카카오API 호출 -> 정상적인 토큰일 경우 사용자 정보 리턴
  • 내가 적용한 예시

    • 아래의 두 가지 방법 중 하나로 가능하다. 특히 Spring Security가 제공하는 OAuth2ClientAuthenticationProcessingFilter 를 사용하면 토큰받기, 사용자정보 받기 과정을 자동으로 할 수 있다.

    • REST API 직접 호출을 통한 구현

      • 카카오로부터 AccessToken 받기

        public JsonNode getKakaoAccessToken(String code) {
        
        
          final String RequestUrl = "https://kauth.kakao.com/oauth/token";
          final List<NameValuePair> postParams = new ArrayList<NameValuePair>();
        
        
          //포스트 파라미터의 grant_type이라는 명칭에 authorization_code를 추가한다 아래도 동일
          postParams.add(new BasicNameValuePair("grant_type", "authorization_code"));
          postParams.add(new BasicNameValuePair("client_id", kakaoClientId));
          postParams.add(new BasicNameValuePair("redirect_uri", redirectUri));
          postParams.add(new BasicNameValuePair("code", code));
        
        
          final HttpClient client = HttpClientBuilder.create().build();
          final HttpPost post = new HttpPost(RequestUrl);
        
        
          JsonNode returnNode = null;
        
        
          try {
              post.setEntity(new UrlEncodedFormEntity(postParams));
              final HttpResponse response = client.execute(post);
              ObjectMapper mapper = new ObjectMapper();
              returnNode = mapper.readTree(response.getEntity().getContent());
          } catch (UnsupportedEncodingException e) {
              e.printStackTrace();
          } catch (ClientProtocolException e) {
              e.printStackTrace();
          } catch (IOException e) {
              e.printStackTrace();
          }
        
        
          return returnNode;
        }
        
      • 받은 토큰을 통해 사용자 프로필 받기

        public JsonNode getKakaoUserProfile(String access_token) {
        
        
            final String RequestUrl = "https://kapi.kakao.com/v2/user/me";
        
        
            final HttpClient client = HttpClientBuilder.create().build();
            final HttpPost post = new HttpPost(RequestUrl);
        
        
            post.addHeader("Authorization", "Bearer " + access_token);
        
        
            JsonNode returnNode = null;
        
        
            try {
                final HttpResponse response = client.execute(post);
        
        
                ObjectMapper mapper = new ObjectMapper();
                returnNode = mapper.readTree(response.getEntity().getContent());
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        
        
            return returnNode;
        }
        
    • Security 필터 사용

      • SecurityConfig

        @Configuration
        @EnableWebSecurity
        public class SecurityConfig extends WebSecurityConfigurerAdapter {
        
        
            private final Filter ssoFilter;
        
        
            public SecurityConfig(Filter ssoFilter) {
                this.ssoFilter = ssoFilter;
            }
        
        
            @Override
            public void configure(WebSecurity web) throws Exception {
                web.ignoring().antMatchers("/resources/**","resources/**");
                web.httpFirewall(new DefaultHttpFirewall());
            }
        
        
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http.csrf().disable();
                http.authorizeRequests()
                        .antMatchers("/", "/course/admin/**").hasAuthority("ROLE_ADMIN")
                        .antMatchers("/", "/admin/**").hasAuthority("ROLE_ADMIN")
                        .and()
                            .formLogin()
                            .loginPage("/")
                            .loginProcessingUrl("/loginProcess.ajax")
                            .usernameParameter("id")
                            .passwordParameter("password")
                            .successForwardUrl("/loginCheck.ajax")
                            .permitAll()
                        .and()
                            .logout()
                            .logoutUrl("/logout")
                            .logoutSuccessUrl("/")
                            .deleteCookies("JSESSIONID")
                            .invalidateHttpSession(true)
                        .and()
                            .addFilterBefore(ssoFilter, BasicAuthenticationFilter.class)
                        .httpBasic();
            }
        
        
            @Bean
            public PasswordEncoder passwordEncoder(){
                // 버전차이로 PasswordEncoderFactories.createDelegatingPasswordEncoder(); 는 사용못하는 것 같음
                return new BCryptPasswordEncoder();
            }
        
        
        }
        
        
        
      • OAuthConfig

        @Configuration
        @EnableOAuth2Client
        @PropertySource(value = "classpath:socialInfo.properties")
        public class OAuthConfig {
        
        
            @Autowired
            @Qualifier("oauth2ClientContext")
            OAuth2ClientContext oauth2ClientContext;
        
        
            @Bean
            public Filter ssoFilter() {
                CompositeFilter filter = new CompositeFilter();
                List<Filter> filterList = new ArrayList<>();
        
        
                filterList.add(kakaoSsoFilter());
                filterList.add(googleSsoFilter());
                filter.setFilters(filterList);
        
        
                return filter;
            }
        
        
            public Filter kakaoSsoFilter() {
                OAuth2ClientAuthenticationProcessingFilter oauth2Filter = new OAuth2ClientAuthenticationProcessingFilter("/login/kakao/oauth");
                OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(kakaoClient(), oauth2ClientContext);
                oauth2Filter.setRestTemplate(oAuth2RestTemplate);
                oauth2Filter.setTokenServices(new UserInfoTokenServices(kakaoResource().getUserInfoUri(), kakaoClient().getClientId()));
                oauth2Filter.setAuthenticationSuccessHandler(kakaoSuccessHandler());
        
        
                return oauth2Filter;
            }
        
        
            public Filter googleSsoFilter() {
                OAuth2ClientAuthenticationProcessingFilter oauth2Filter = new OAuth2ClientAuthenticationProcessingFilter("/google/googleSignInCallback");
                OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(googleClient(), oauth2ClientContext);
                oauth2Filter.setRestTemplate(oAuth2RestTemplate);
                oauth2Filter.setTokenServices(new UserInfoTokenServices(googleResource().getUserInfoUri(), googleClient().getClientId()));
                oauth2Filter.setAuthenticationSuccessHandler(successHandler());
        
        
                return oauth2Filter;
            }
        
        
            @Bean
            public AuthenticationSuccessHandler successHandler(){
                return (request, response, authentication) -> {
        
        
                    response.sendRedirect("/login/googleSignIn");
                };
            }
        
        
            @Bean
            public AuthenticationSuccessHandler kakaoSuccessHandler(){
                return (request, response, authentication) -> {
        
        
                    response.sendRedirect("/login/kakaoSignin");
                };
            }
        
        
            @Bean
            @ConfigurationProperties(prefix = "google.client")
            public OAuth2ProtectedResourceDetails googleClient() {
                return new AuthorizationCodeResourceDetails();
            }
        
        
            @Bean
            @ConfigurationProperties(prefix = "google.resource")
            public ResourceServerProperties googleResource() {
                return new ResourceServerProperties();
            }
        
        
            @Bean
            @ConfigurationProperties(prefix = "kakao.client")
            public OAuth2ProtectedResourceDetails kakaoClient() {
                return new AuthorizationCodeResourceDetails();
            }
        
        
            @Bean
            @ConfigurationProperties(prefix = "kakao.resource")
            public ResourceServerProperties kakaoResource() {
                return new ResourceServerProperties();
            }
        
        
            @Bean
            public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
                FilterRegistrationBean registration = new FilterRegistrationBean();
                registration.setFilter(filter);
                registration.setOrder(-100);
                return registration;
            }
        }
        

참고 및 출처

2020년 7월 12일 일요일

젠킨스 자동배포 연습해보기

까먹지말자. 메모!


CI/CD?

  • 애플리케이션 개발 단계를 자동화하여 새로운 코드 통합으로 인한 문제를 해결할 수 있다.
  • 빌드, 테스트, 배포 프로세스에 대해 자동화와 모니터링이 가능하다.

CI란? (Continuous Integration)

  • 지속적 통합
  • 개발한 소스를 특정 시점에 통합(빌드, 테스트 등)하는 것이 아닌, 지속적으로 통합을 진행한다. (자동화 프로세스!)
  • 어플리케이션을 변경할 때 자동으로 빌드 및 테스트 가능
  • 유명한 CI 툴은 Jenkins 가 있다.

CD란? (Continuous Deploy or Delivery)

  • 지속적 배포
  • 변경사항을 저장소에서 테스트서버나 운영서버에 자동으로 반영한다.

Travis VS Jenkins

Travis
  • 장점

    • 전용 서버가 필요하지 않다. (자체 호스팅)
    • github과 연동이 편하다.
  • 단점

    • 제한된 옵션 제공
    • 느린 속도
    • private 유료

Jenkins
  • 장점

    • 많은 플러그인 제공
  • 단점

    • 별도의 서버가 필요함
    • 젠킨스에 대한 관리 필요

  • 간단히 혼자 해보기는 travis가 재미있을 것 같지만, 학습과 실습이 목적이니 Jenkins를 선택한다!

  • 일단 로컬로 도커를 이용해서 연습해보자.

  • 학습목적이니 일단 내장서버를 이용해서 실행해보자

    Once the spring-boot plugin has been applied to your project it will automatically attempt to rewrite archives to make them executable using the bootRepackage task. You should configure your project to build a jar or war (as appropriate) in the usual way.

    // build.gradle 에 다음 내용을 추가해준다.
    // plugin, 생성할 war명 과 실행할 메인 클래스 명을 넣는다.
    apply plugin: 'war'
    
    
    ...
    
    
    war {
        baseName = 'NoN'
        version =  '0.0.1-SNAPSHOT'
        manifest{
            attributes(
                    'Main-Class': 'com.edu.EduApplication'
            )
        }
    }
    


설치과정 메모


  • 도커에 jenkins 설치하기

    • 명령어로 설치할 수 도 있지만, Docker가 제공해주는 관리도구(Kitematic)로 쉽게 설치할 수 있다.


    • 도커 설치 중..


    • 설치 후 다음 명령어로 어떤 포트로 실행되고있는 지 볼 수 있다.


    • 물론 Kitematic에서도 확인 가능


    • 중간에 패스워드 입력화면은 Kitematic 로그 자세히 보면 패스워드가 있다. 그걸 입력하자.

      • 못봤다면... 도커에서 비밀번호에서 확인해보자

        docker exec -it jenkins /bin/bash
        
        
        cat /var/jenkins_home/secrets/initialAdminPassword

    • localhost:32819 로 접속 (잘 모를때는 추천 플러그인을 설치해보자)


    • 플러그인들이 모두 설치실패한다?


    • 젠킨스 버전 문제라고한다. 높은 버전으로 변경하자.

      docker exec -it -u 0 jenkins bash
      
      
      wget http://updates.jenkins-ci.org/download/war/2.235.1/jenkins.war
      
      
      mv ./jenkins.war /usr/share/jenkins/
      
      
      chown jenkins:jenkins /usr/share/jenkins/jenkins.war
      
      
      도커 젠킨스 재시작

    • 새로운 Item 클릭 


    • 소스관리 탭에서 자신의 프로젝트 git 주소와 Credentials - Add 자신의 git 정보를 입력한다.


    • build 유발 - GitHub hook trigger for GITScm polling 체크


    • 빌드 후 배포를 하기위해 다음 작업을 추가한다. Build - Add build step - Execute Shell - 다음 쉘 추가


  • 현재 젠킨스를 도커에 띄웠으므로 접속주소가 localhost 이다. github에서 요청을 보내야한다. 외부에서 로컬에 접속가능하게 하기 위해 ngrok 을 사용한다.

    • 설치 후 다음 명령어로 사용 가능

      ngrok http 32819
      
      
      // 세션만료 없애기 (기본 8시간), AuthToken 은 ngrok 홈페이지에서 로그인 후 받을 수있다.
      ngrok http 32819 --authtoken={AuthToken}
      
    • 생성된 주소를 사용하면 된다!!


  • Github에 이벤트가 있을 때 젠킨스로 요청을 보내야하므로 Webhooks를 설정해준다.

    • Gibhub - Settings - Webhooks - Add Webhook
    • 젠킨스 주소 뒤에 /github-webhook/ 를 붙여준다. (ngrok에서 생성된 주소 사용하면 됨)

  • README.md 파일에 build 상태 보여주기

    • 젠킨스 플러그인에서 Embeddable Build Status Plugin 다운로드
      • Item - Embeddable Build Status - MarkDown 부분 복사 - github README.md 파일에 입력

      • 다음과 같이 README.md 파일에 표시되는 것을 확인할 수 있다! (빌드상태가 계속 반영된다. 다만, ngrok을 사용하고 있기때문에 주소가 자꾸 바뀐다. 별도의 젠킨스 서버가 필요할 듯하다.)

  • 이제 저장소에 푸시가 있을 때 젠킨스가 자동으로 빌드하는 것을 볼 수 있다. 이를 활용해서 서버에 설치하고 배포작업을 상세화, 단계화하여 적용하면 될 것 같다. (실무에선 어떤 프로세스로 사용할까? ㅠ)


참고

2019년 6월 9일 일요일

기존프로젝트 개선 기록

스프링 스터디가 파토가 났다. 특정 시점부터는 깨질 것을 예상하고 있었다.
나의 문제도 컸지만, 과정을 진행하면서 정하다보니 별로 좋지 않았고, 뭔가 예전에 혼자 스프링 프로젝트를 만들었던 것과 별반 다를게 없는 듯한 느낌이 있었다. (제대로 코딩도 못하고 끝났지만...)
그래서 내가 공부하고싶은 것을 토이프로젝트를 통해 진행하는 것이 좋다고 판단하였는데 새로운 주제를 정하거나, 기존의 스프링 프로젝트를 시작하는 것은 매우 시작하기 귀찮았다.
그렇다면 기존에 했던 것을 내가 직접 원하는 기술을 활용하여 리펙토링하는 것은 어떨까??
지금도 그때와 별반 다를거없는 실력이지만, 에전에 한 코드를 다시보면 고치고 싶은 것들이 상당히 많이 있다. 그래서 취업직전 스터디프로젝트를 했던 인프런 모방 프로젝트를 리펙토링 하기로하였다.
거의 모든코드를 뜯어 고친다는 생각으로 해보자.

  1. AS IS
    소스: https://github.com/cotgyu/NoN
    • 개발환경
      • 이클립스로 개발하였음.
      • 스프링부트 1.5(이 당시 스터디원분께서 환경구축을 거의 하였음)
      • mybatis(mysql)
      • maven
      • 서버엔 띄워보진 않았음
      • git (처음으로 git 브랜치 사용해봄)
    • 주요기능
      • 소셜로그인
      • 강좌 기능
      • 강의 추천알고리즘
        • 머하웃 (엑셀파일을 통한 수동갱신)
  2. TO DO
    소스: https://github.com/cotgyu/NoN_ver2
    • 개발환경
      • IntelliJ
      • 스프링부트 1.5 (부트 설정에 대해 찾아보자)
      • JPA(mybatis) (aws서버에있는 mybatis 쓰자)
      • gradle(maven과 차이점과 학습해야할 이유 찾아보자)
      • GCP (구글클라우드 플랫폼)에 띄우기, CI로 마스터 빌드 시 배포될 수 있도록 구축하기
      • git (브랜치 활용해보자_개선할 기능마다 브랜치따서 관리해보기)
      • 각 기능 개선하면서 모든코드에 대해 테스트 케이스를 작성할 것
        https://www.slideshare.net/OKJSP/okkycon-tdd
      • REST API를 적용할 수 있는 곳이 있을까?
    • 주요기능에 대한 예상 개선방안
      • 소셜로그인 정리
        • 팀원이 개발하였지만, 어디에 적용되어있는 것을 그대로 가져와서 사용한 느낌이었다. 보기편하고, 로그인기능을 제대로할 수 있도록 뜯어고치자.
      • 강좌 기능
        • 소스 스타일 개선할 것, 사용자가 본 강의 등을 표시할 수 있도록하자.
      • 강의 추천알고리즘
        • 현재 엑셀파일을 통해서 추천알고리즘을 적용하는데, 수동으로 진행하고있다. 이 엑셀파일로하는 방식 교체, 수동이 아닌 배치 등을 통해 자동화된 추천순위 갱신을 해보자.
  3. 진행 순서
    • 모든 과정은 정리&메모를 바탕으로 진행한다. 큰 틀마다 브랜치를 생성하고, 각 정리한 메모 또한 git에 등록하자.
    • 사용환경 지식 정리
      • ORM - JPA란?, 왜 씀?
      • gradle 이란?, 왜 씀?
    • 기존 프로젝트 실행
      • github에 새로운 저장소를 파서 기존프로젝트 등록
    • 환경 변경
      • 기존 설정파일 분석
      • gradle 프로젝트로 변경? vs gradle 프로젝트로 생성하여 모든 소스들을 직접 등록?
      • JPA 설정 후 작동 테스트(쿼리변경은 각 기능마다 따로 따로진행할 것)
    • 기능 리펙토링
      • 소셜로그인
      • 강좌 기능 (이 쯤에 배포환경을 구성해보자)
      • 강의 추천 알고리즘

6월 1주차 기록

커밋 로그 구분해서 알아보기 쉽게하기

  feat (feature)
  fix (bug fix)
  docs (documentation)
  style (formatting, missing semi colons, …)
  refactor
  test (when adding missing tests)
  chore (maintain)

ORM ? JPA?

  • Object-Relational Mapping
  • 객체와 관계형 DB를 매핑해주는 것으로, '객체-테이블' 을 매핑시킬 수 있게 도와준다. ORM을 통해 객체와 데이터베이스의 변형에 유연하게 대처할 수 있고, 관계형 데이터베이스에 제약을 최대한 받지 않으면서, 관계형 데이터베이스를 객체처럼 표현 가능하게 할 수 있다.
  • SQL이 아닌 메서드로 데이터 조작 가능
  • 객체 간의 관계를 바탕으로 SQL이 자동으로 생성됨
  • DB변경과 같은 변화에 쉽게 대처가능?
  • JPA : ORM을 위한 표준기술 (JPA을 통해 ORM을 쓸 수 있다? 더 정리가 필요해보이는뎅...)
  • 백기선님 JPA강의소개 중 왜 JPA를 학습해야하는가?
    왜 JPA를 학습해야 하는가?
    1) 도메인 주도 개발이 가능합니다.
    2) 애플리케이션의 코드가 SQL 데이터베이스 관련 코드에 잠식 당하는 것을 방지하고 도메인 기반의 프로그래밍으로 비즈니스 로직을 구현하는데 집중할 수 있습니다.
    3) 그리고 개발 생산성에 좋으며, 데이터베이스에 독립적인 프로그래밍이 가능하고, 타입 세이프한 쿼리 작성 그리고 Persistent Context가 제공하는 캐시 기능으로 성능 최적화까지 가능합니다.
    

gradle이란??

  • 빌드배포툴로 ANT와 MAVEN의 단점을 보완했다고 한다. 안드로이드 스튜디오 공식 빌드 시스템이.
  • XML이 아닌 Groovy 언어를 활용한 DSL(도메인특화언어?)을 스크립트로 사용. 때문에 다양한 기능을 스크립트안에서 직접 구현가능.
  • 상속구조를 이용하여 멀티모듈 구현이 가능하다.
  • Maven의 길고 떨어지는 가독성 개선(xml 사용안함)
  • 성능향상을 위해 다양한 기능 제공 (증분빌드, 작업결과 캐싱, 병렬 실행 등이 가능하다고 함.)

기본 gradle 구조

 gradlew
 gradlew.bat
 gradle/wrapper/gradle-wrapper.jar
               /gradle-wrapper.propeties
 build.gradle
 settings.gradle   

그래들 변환 과정 기록

  • brew 설치 (맥에서 gradle 자동설치를 위해)
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  • gradle 설치
  brew install gradle
  • 메이븐 gradle 로 자동 변환 프로젝트 pom 파일위치에서
  gradle init --type pom
  • 프로젝트에서 메이븐관련 파일 삭제
  • import 모듈 -> gradle -> build.gradle 선택
  • gradle init --type pom 을 통해 자동으로 생성되는 build.gradle
(상당히 복잡해여서 좀 더 간단하게 개선하고 싶음. 근데 뭘보고 개선해야할까? 일단 그대로?)
  plugins {
      id 'java'
      id 'maven-publish'
  }

  repositories {
      mavenLocal()
      maven {
          url = 'http://repo.maven.apache.org/maven2'
      }
  }

  dependencies {
      compile 'org.springframework.boot:spring-boot-starter-integration:1.5.10.RELEASE'
      compile 'org.springframework.boot:spring-boot-starter-jdbc:1.5.10.RELEASE'
      ...
      compileOnly 'org.apache.tomcat.embed:tomcat-embed-jasper:8.5.27'
  }

  group = 'com.Library'
  version = '0.0.1-SNAPSHOT'
  description = 'NoN_ver2'
  sourceCompatibility = '1.8'

  publishing {
      publications {
          maven(MavenPublication) {
              from(components.java)
          }
      }
  }

  tasks.withType(JavaCompile) {
      options.encoding = 'UTF-8'
  }

TODO : build.gralde 수정해보기


6월 2주차 기록

JPA 학습하기

JPA 테스트

  • 저번에 사용한 JPA를 활용해서 JPA테스트를 해보자
    • 일단 저번에 블로그 따라했을 때는 service나 repository 등을 미리 생성한 다음 테스트코드에서 해당 클래스들을 autowired를 통해 테스트하였음. (존재하는 소스들에 대한 테스트)
    • JPA에 필요한 설정을 적용해보고 작동하는 지 확인해보자!!
      • 일단 test패키지에 JPA용 테스트코드를 작성하고 동작하는 지 확인해보자
      • /src/main/java/com/Edu/Domain/JPADomain.java
        @NoArgsConstructor(access = AccessLevel.PROTECTED) // 기본생성자 자동 추가 (protected Posts(){} 와 같음)
        @Getter //클래스 내 모든 필드의 getter 메소드 자동생성
        @Entity //테이블과 링크될 클래스임을 나타냄
        public class JPADomain {
            @Id // PK
            @GeneratedValue(strategy = GenerationType.IDENTITY )
            private Long id;
        
        
            @Column(length = 500, nullable = false) //테이블의 컬럼 ()와 같이 컬럼특징 설정 가능
            private String title;
        
        
            @Column
            private String content;
        
        
            @Builder // Builder란 ?
            public JPADomain(String title, String content){
                this.title  = title;
                this.content = content;
            }
        }
        
      • src/main/java/com/Edu/Domain/JPADomainRepository.java
        //DB Layer 접근자(DAO), JpaRepository<Entity, PK타입 > 을 상속하면 기본적인 CRUD 메소드가 자동 생성됨.
        public interface PostsRepository extends JpaRepository<Posts, Long> {
        
        
            //@Query 를 통해 JPA에서 제공하지 않는 메소드는 쿼리로 작성 가능
            @Query("SELECT p " +
                    "FROM Posts p " +
                    "ORDER BY p.id DESC")
            Stream<Posts> findAllDesc();
        }
        
      • src/test/java/com/Edu/Domain/TestJPADomainRepository.java
        @RunWith(SpringRunner.class)
        @SpringBootTest //기존 스프링test의 @ContextConfiguration 의 발전된 기능 ? 다양한 기능이 있으니 찾아볼 것
        public class TestJPADomainRepository {
        
        
            @Autowired
            JPADomainRepository jpaDomainRepository;
        
        
            @After
            public void cleanup(){
                jpaDomainRepository.deleteAll();
            }
        
        
            @Test
            public void saveTest(){
        
        
                //테스트 환경 구축
                jpaDomainRepository.save(JPADomain.builder()
                        .title("테스트title")
                        .content("테스트content")
                        .build());
        
        
                //테스트 행위
                List<JPADomain> postsList = jpaDomainRepository.findAll();
        
        
                //테스트 검증
                JPADomain posts = postsList.get(0);
                assertThat(posts.getTitle(), is("테스트title"));
                assertThat(posts.getContent(), is("테스트content"));
        
        
            }
        
        
            @Test
            public void insertTest(){
        
        
                //insert @Query 테스트
                jpaDomainRepository.insertQueryTest();
        
        
                //테스트 행위
                List<JPADomain> postsList = jpaDomainRepository.findAll();
        
        
                //테스트 검증
                JPADomain posts = postsList.get(0);
                assertThat(posts.getTitle(), is("2"));
                assertThat(posts.getContent(), is("2"));
            }
        
        
        }
        

발생했던 오류

  • 잉.. save가 안됨 왜? select는 되는 거 보니 DB연결은 된 것 같음
  • delete도 안됨. mysql 이랑 jpa 설정이 필요할 것 같음 -> 무슨설정? -> 음 Application에 설정된 어노테이션들이랑 연관이 있을까? -> DB 관련 설정 문제였음!!!
  • 오류 메모
    Caused by: java.sql.SQLException: Can not issue data manipulation statements with executeQuery().


    org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
    • -> 구글링해보니 class에 적용되어있는 @Transactional 확인해보고 javax.transaction.Transactional 을 org.springframework.transaction.annotation 으로 바꿔라인데 난 그래도안되는데?
    • -> update나 insert할 때 트랜잭션이 필요한 것 같음. 어떤 설정떄문에 트랜잭션이 적용되지않는 것이 아닐까?
    • -> 기존프로젝트에 존재하는 dataconfig 에 트잭잭션매니저 설정(DataConfig.java)이 있었고, 해당 설정을 주석처리 하니 됨!!
    • 약간 여기때문에 설정이 꼬여서 모든 insert, update, 등이 안되었던 것 같음. 해당 트랜잭션 매니저부분 주석처리하니 됨!!!


    mysql 한글 insert 시 ??? 입력되는 현상
    • 해당 오류는 저번에도 발생했던 것으로 기억하는데, 역시 메모안해놓으니 또 발생했을 때 또 삽질함.
        1. my.cnf 설정바꾸기 (난 my.cnf가 없던데 새로 추가해야하는건가 아니면 다른 위치에 있는 것일까?)
        1. my.cnf 위치도 모르겠고 커넥션할 때 &useUnicode=true&characterEncoding=utf8 해당 옵션 추가해줌

TODO builder란? , 기존에 사용했던 설정들 분석 필요


6월 3주차 기록

JPA 개념 익히기

  • 강의 들으면서 정리 중

builder 란?

  • @Builder : 해당클래스의 빌더패턴 클래스 생성
  • 빌더 패턴 : 객체를 생성할 때 사용하는 패턴
    • 이펙티브자바 : 아이템2-생성자에 매개변수가 많다면 빌더를 고려하라
      책에서는 점층적 생성자 패턴 -> 자바빈즈 패턴 -> 이 둘의 장점을 살린 빌더 패턴을 설명하고 있다. (자세한건 책으로...)
      • 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.
        // 각 값의 의미 파악 어려움. 매개변수가 몇개인지도 세야함.
        Ex ex = new Ex(100, 10, 1, 2, 1, 2, 3 ,4);
        
      • 빌더 패턴을 통해 쓰기 쉽고, 읽기 쉽게 만들자. (인 것 같다. 또한 빌더패턴을 통해 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓여지는 단점을 해결해주는 것 같다.)
        Ex ex = new Ex.Builder(100,10).name(1).age(2).number(1).code(2).name2(3).number2(4).code2();
        
  • Lombok 의 @Builder 어노테이션을 통해 빌더패턴코드를 간단하게 사용할 수 있는 듯하다. (Lombok 라이브러리가 여러가지 자동으로 생성해주는 것 같다!!)

회원기능 개선하기

  • 소셜 로그인
    • 현재 구현되어있는 기능 : 회원가입(수정 등), 카카오로그인, 네이버로그인
    • 수정할 기능들? : 회원가입 간소화, 소셜로그인 정리 및 소스정리 (or 방식 수정)
      • 회원 가입 간소화
        • 기존의 member 테이블에서 최소한의 정보만 있는 user테이블 생성
        • 아마 완성된 기능에서 회원기능을 아예 다 가져와서 동작하게끔 수정한 것으로 보임.(이것저것 많음...) 필요없는 건 다 없애는 방향으로 진행할 것. 음 이메일인증기능도 쓰나본데..? 이메일 인증기능은 일단 간단한 회원가입을 위해 없애자 나중에 방식 한번 살펴보기
        • todo 회원가입창 모달창에서 할수 있게하고 소셜로그인 등 같이할수있게
          • 로그인창 하나만 만들고 회원가입은 그창에서 모달로 보여주기
    • 추가 기능 : 구글 로그인
    • 마지막에는 JPA 적용하도록 수정 (이 때 테이블도 바꾸자 )
TODO 회원기능 개선 후에는 기존 설정 한번 더 살펴보기. JPA 강의 계속보기. 로그 적용하기. 테스트까먹음..소셜로그인 개선시 에는 테스트 하기

6월 4주차 기록

  • 로그 설정하기
    • 스프링부트는 기본으로 logback을 지원하고 있으며, 기타 xml으로도 로그설정들을 조정할 수 있지만, application.properties 에서도 설정 가능한듯 싶다. 일단 레벨만 간단하게 조정하고, 기타 로그 저장위치 설정할 수있게 xml 등을 생성해보자(펫클리닉에서는 logback.xml 을 사용하고 있다.)
    logging.level.root=info
    
  • 카카오로그인
    • HOW? : 이전의 내가 적용했던 방식이랑 비교하고, 현재 어떤방식인 지 어떻게 사용하고 있는 지 기록하고 어떻게 바꿀껀지?? 적용하기. 이번엔 테스트 코드를 짜면서 해보자
    • AS IS
      1. 형식은 비슷한듯싶음. 토큰보내서 들어오는 정보 json으로 받아서 멤버에 받아서 처리하는 듯 싶다.
      2. 일단 세션에 멤버객체, id 저장해놓고, 기존에 id가 등록되었는 지 확인하고, 중복된사용자면 바로 홈화면, 중복된 사용자가 아니면 사용자정보를 insert하고 있음.
TODO : 구글로그인도 자세하게보다는 내가 한 방향을 기록만해놓기. JPA강의 계속~ , 로그설정 찾아보기

7월 1주차~3주차 기록

  • 개인적인 사정으로 공부를 못하고 있음(병원)

7월 4주차 기록

  • 구글로그인
    • HOW : 기존 프로젝트에는 구글로그인이 아닌 네이버로그인이 되어있는데, 이를 생략하고 구글로그인으로 진행해보았다.
      이전 스터디에서 해본 구글로그인 코드를 그대로 가져왔지만, root-context에 등록된 빈 사용문제, 코드 노출문제가 발생하여, 이를 개선해야한다. 일단 로그인 및 회원가입 기능은 동작
    • 외부 프로퍼티를 써서 인증에 사용되는 값들은 깃에 안올라가게 설정함
    • 카카오와 비슷한 방식으로 진행함... 너무 허접함.
      이건 정말 정보 보내고, 받아서 회원가입, 로그인 등만 억지로 하는 느낌?
    (구글에 나온 시큐리티와 OAuth 연동하여 계정 권한 관리하는 소스들이 많은데, 일단 나는 그냥 처음에 검색했던 소셜 로그인만 딱 되도록 한 느낌.. 전체적인 이해와 개선의 필요성이 있다. 예제나 레퍼런스같은게 있을까?)

TODO : JPA 강의 계속듣기, 회원가입 시 닉네임입력할 수있게, 로그인 팝업으로, 회원 수정 창 만들기
+ 부트 설정 공부필요함. 프로퍼티 하나 추가하려고했다가 엄청 삽질함(이 프로젝트에선 AppConfig.java 에서함)
+ 팝업로그인, 회원 수정 등이 끝나면 JPA 적용할 것


8월 4주차 기록

  • 로그인창은 팝업으로
    • 되도록이면 모달창이 낫겠징? 흠 매우 귀찮네; 음.... 카카오,구글인증창만 새창에서하는 것도 나쁘지는않을텐디 흠..
    • 새로운 페이지 띄워서 해당 페이지 접속 시 자동으로 카카오 url 타도록??
  • 회원가입 시 닉네임 입력할 수 있도록

11월 1주차 기록

  • 교육기간을 활용하여, 프로젝트를 진행해보자
  • 로그인창 모달창으로 만들기 O
  • 소셜 회원가입할때 닉네임입력할 수 있게할지?? X
    • 그냥 자동회원가입되도록 O
  • 로그인, 회원정보 수정 등 로그인에 필요한 로직 구현
    • 로그인 : ajax를 통해 로그인처리하기 O
    • 회원수정 : 되는지만 확인하고 수정 할 것 O
    • 비밀번호 찾기 : 인프런은 입력한 이메일로 비밀번호 수정 url을 보내주는듯
      • 그냥 초기화된 비밀번호를 메일로 전송하도록 O
  • 회원기능은 일단 마무리...
    • JPA를 바로 회원에 적용해볼지..
    • 모두 개선해보고나서 JPA로 전환할지..

TODO : JPA 적용하면서 간소화된 테이블로 교체할 것, 암호화 등 시큐리티 활용

1월 2주차 기록

  • 강좌를 리펙토링해보자
  • 해야할 것
    • 수강평부분
    • 수강한 강좌 체크(어디까지 수강했는지)
    • 강좌 추천부분 동작할 수 있도록(현재는 특정 로컬경로를 지정...)
    • 엄청 완성도 높은 것보다는 의도한 기능이 정확하게 돌아가는가..
    • 수강하는 강좌 볼 수 있는 페이지 (있음 강의 진척률 표시할 것 )
  • 수강평 부분(다른팀원이 개발한 영역)
    • html을 제이쿼리로 통제하여 생성하는 방식..
    • 동작만 할 수 있게 수정함. (화면이 보기좋은 형태는 아님..)
  • 수강한 강좌 체크 (어디까지 수강했는지)
    • 어케할지...
      • 인프런에서는 완강하면 체크표시되면서 강좌 열었을때 시작할 강좌부터 열어줌
    • 필요한 것
      • 해당 강의 체크 방법
        • 일단 테이블에 저장 필요. (데이터 중복은 불가피한데 어디까지?)
            1. 사용자/수강강좌/강좌 수 1,2,3,4,5
            1. 사용자/수강강좌/강좌 1 / 2/ 3 4 (데이터 중복..)
          • userid, cosno, lecno, indt, delyn
      • 완강인지 어케 파악?
        • 동영상 종료시점 알아야함. (거기에 맞춰서 해당 강좌번호 체크 )
      • 만약 사용자가 체크한다고한다면? > TDD를 할 수있지 않을까? REST인강 실습기회!!
        • 테이블읽어서 가장 최신 강의
        • 체크하면 ajax 로 바로 해당 강의 insert
        • 해제하면 delflag 처리
        • 현재 강의 표시.
TODO 수강한 강좌 체크 진행, 코스 이미지 분리, 테이블 데이터 delFlag, regDate

2월 4주차 기록

  • 테스트환경 구축
    • 테스트에는 h2 , 운영은 mysql 쓰도록 구성
      • 일단 mysql만..
    • 필요 테이블 생성
    • TDD 실습
      • 최신강의 체크
      • 체크한 강의 불러오기
      • 체크하기
      • 체크해제하기

3월 1주차 기록

  • 필요 테이블 생성함
  • TDD 실습
    • 최신강의, 체크한 강의 가져오기
    • 없는 사용자에 대한 error 테스트
    • 테스트 시 응답 json에 대해 좀 더 자세한 검증 필요 https://github.com/json-path/JsonPath 참조

3월 4주차 기록

  • 강의정보로 최신강의로 불러오기 (체크 이후가 제일 최신일지? 클릭할떄마다 뭔가를 남겨야할지?)
  • 강의 해제 ajax 추가
  • 강의기능은 이정도까지만 하고 마무리?
    • 체크한 값 받아서 체크표시하기 o
    • 체크 및 해제 할때마다 ajax 날리기 o
      • 테이블에 체크된 강의 있는 지 확인
      • 테이블에 없으면 insert
TODO 알고리즘 적용, 예외처리, 에러페이지 생성, 이미지 등 파일관리, 테스트 디비 생성

4월 2주차

  • 에러페이지, 예외처리 진행
  • 컨트롤러어드바이스 찿아보기
TODO 에러 처리 더 다듬기, 에러페이지 사진넣기, 404 등 상태코드 시 페이지 설정, 알고리즘적용, 파일관리, 테스트 디비 생성(필요할까?)

4월 3주차

  • 예외처리 핸들러를 통해 처리 시 exception 페이지로 이동, 로그 남기기
    • 회사 소스에서는 로그만 남기도록 되어있던데, 일부러 그런걸까?
  • 상태코드에 대한 설정 추가
TODO 알고리즘 적용(머하웃 다시 정리?), 파일관리

4월 4주차 & 5월 1주차

  • 수강평 없는 경우 예외 처리 할 걳
    • 일단 화면에서만 문구 변경으로 처리
  • 머하웃 로직 분석
    • 강좌를 클릭하였을 때 해당 강좌에 대한 추천강좌를 보여줄 것이기 때문에 아이템 기반 알고리즘을 사용하였음.
      • 사용자 기반 추천 : 사용자의 유사도를 비교. 선호 이력이 비슷한 다른 사용자의 선호 아이템 추천
      • 아이템 기반 추천 : 아이템의 유사도를 비교. 해당 아이템과 유사한 아이템 추천
      인터넷 예제를 통해 Tanimoto 유서도를 사용했지만, 검색해보니 tanimoto와 loglikeihood 는 선호도 상관 없이 사용자의 아이템 겹침을 통해 추천을 하는 것 같아 유클리드 거리 기반 유사도로 변경하였다.
    • 책도 있음! : 머하웃 완벽 가이드
    • 머하웃아이템 추천에는 유사도를 고를 수 있다. (유사도에 따라 추천아이템이 달라진다!!)
      • EuclideanDistanceSimilarity
        • 두 점 사이의 거리 계산
        • 사람들이 공통적으로 점수를 매긴 항목의 거리를 통해 유사도를 판단 (짧은수록 유사)
      • PearsonCorrelationSimilarity
        • 두 개의 연속적인 숫자열의 일대일 비교를 통해 경향성을 측정
        • 한 숫자열의 각 숫자가 다른 숫자열의 대응되는 값보다 얼마나 상대적으로 큰지 측정
      • LogLikelihoodSimilarity
        • 두 사용자의 공통 아이템 수에 대비해서 얼마나 두 사용자가 겹치지 않는지 표현
        • 타니모토 보다 성능이 좋다고 함
      • TanimotoCoefficientSimilarity
        • 선호값 전체를 무시하도록 구현된 유사도 측정 방식
        • 사용자의 선호 표현 여부만 확인. 아이템에 대한 선호도가 높은지 낮은지
  • 머하웃 참고
TODO 데이터소스 부분 뜯어고치기 (좀 더 표준적인 mybatis 로?, 책 참조해볼것 ), 파일관리 필요

5월 2주차

  • 데이터 접근 부분 리펙토링

    • 너무 복잡한 형식으로 되어있는 것 같다.
    • 표준적인 mybatis - mapper 형식으로 변환해보자.
    • databaseConfig 부분 정리하기
  • @Mapper 는 어떻게 스프링 빈으로 와이어될 수 있을까?

  • MyBatis 장점

    • 자동적으로 Connection close() 가능
    • Mybatis 내부적으로 PreparedStatement 처리
    • #{prop}와 같이 속성을 지정하면 내부적으로 자동 처리
    • 리턴 타입을 지정하는 경우 자동으로 객체 생성 및 ResultSet 처리

@Configuration
@MapperScan(basePackages = { "com.Edu.Dao" })
@EnableTransactionManagement
public class DataSourceConfig {

    @Autowired
	private ApplicationContext applicationContext;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception{
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dataSource());
		sqlSessionFactoryBean.setConfigLocation(applicationContext.getResource("classpath:static/mybatis/mybatis-config.xml"));
		sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources("classpath:static/mybatis/mappers/mysql-*.xml"));

        return sqlSessionFactoryBean.getObject();
    }

    @Bean(destroyMethod = "close")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        DataSource dataSource = new DataSource();

        return dataSource;
    }
}

TODO 파일 관리, JPA, 시큐리티, 보안취약 , 유효값 검증?

5월 3주차 ~ 4주차

  • JPA 적용 준비

    • JPA + QueryDSL 조합하여 많이 쓰는 것 같음

    • QueryDSL 이란?

      • Jooq와 함께 가장 유명한 조회 프레임워크
      • 타입에 안전한 방식으로 HQL 쿼리를 실행하기 위한 목적으로 만들어짐
      • 쿼리 작성과정에서 코드 자동완성 기능을 통해 쿼리를 더 안전하고 빠르게 생성할 수 있음
  • Querydsl 참조

  • compilequerydsl 실행 시 @Entity 어노테이션 있는 클래스들만 generated 들에 Q~~.java 로 자동 생성됨 (후에 쓰임)

  • insert, update는 JPA로 ??

  • 어려운 쿼리만 querydsl 사용하고 일반적인 쿼리는 jpa 사용해서 처리할 것 !!

  • 이번주까지 변환 완료할 것 !

  • 테이블 연관관계 학습필요!

TODO 파일 관리, 시큐리티, 보안취약 , 유효값 검증?, 테이블 연관관계 학습필요! (JPA책 공부하자)

6월 2주차

  • 파일관리를 보통 어떻게하는가?

    • 로켓마켓 : FileUtils

      • 파일 전용테이블 있음
      • 특정 경로에 저장하도록 하였음 (환경설정 파일에 명시)
    • 스프링부트 책 :

  • 유효성 검사

    • rest api 에 대해 유효값 검증을 하자 !! (백기선님 강의 참고)

    • ResponseEntity 변경

    • rocketmarket에 있는 전송방법 참고하여서 dto 만들것

TODO 파일 관리, 시큐리티, 보안취약, 테이블 연관관계 학습필요! (JPA책 공부하자), CI 적용

7월 1주차

  • 스프링 시큐리티 적용

  • CI 장단점 비교하기

    • CI란?

    • Travis VS Jenkins

  • 도커 장점

    • 가상의 공간을 이미지로 만들어 저장할 수 있다. 이 이미지를 통해 컨테이너를 생성할 수 있다.

TODO 시큐리티 인증절차 제대로 구현필요, 테이블 연관관계 학습필요! (JPA책 공부하자), CI 적용

7월 2주차

  • 시큐리티 심화 적용

    • 로켓마켓은 어떻게 활용하고 있는가?

      • 이상함.. 비번체크하는부분이 없음..? -> 시큐리티에서 다해줌!!!!
    • 나의 방식 : 로그인 시 권한체크 성공하면 패스워드 인코더를 통한 비밀번호 체크해서 세션에 로그인 정보 담는 방식임 , 소셜부분은?

      • 아.. 시큐리티를 태우는 순간 DaoAuthenticationProvider에서 비번체크까지 해줌 ;;
      try {
          preAuthenticationChecks.check(user);
          additionalAuthenticationChecks(user,
                  (UsernamePasswordAuthenticationToken) authentication);
      }
      
      • 시큐리티와 소셜로그인 연동은 개선할 것이 많고 어렵당 ㅠㅠㅠ 다시해보자
  • 시큐리티 개선할떄 참고할 것

TODO 소셜로그인에 시큐리티 로그인 연동하기 , 테이블 연관관계 학습필요! (JPA책 공부하자), CI 적용

7월 3주차

  • 프로젝트 좀 정리하는 시간 갖고, 다음에 뭐 개선할지 정하기

  • 테이블 연관관계 or 소셜 연동 하는 것이 좋아보임.

  • 네이버클라우드에 젠킨스 깔자 (ㅋㅋ거기꺼 써도될듯)

    • 회사 선배계정에 젠킨스 설치함!! github README.md 파일에 build status 표시 중

TODO 소셜로그인에 시큐리티 로그인 연동하기 , 테이블 연관관계 학습필요! (JPA책 공부하자), CI 적용

7월 4주차

다중 구현 가능할듯!!

TODO 테이블 연관관계 학습필요! (JPA책 공부하자), build.Gradle 정리

8월 1주차

  • build.gradle 정리

    • 필요없는 부분 지우기
      • io.spring.gradle:dependency-management-plugin 으로 자동으로 버전이 설정됨. 필요없는 건 버전을 지우자
    • 로컷마켓 task 분석해보기
      • test : Junit5 을 사용하기 위함으로 보임
      • jooq : jooq 관련 설정
      • 나머지는 비슷...?
  • 테이블 연관관계 공부 하기

    • 책을 인쇄해서 가야할까? or 사야할까..흠
  • 테이블 연관관계는 책이나, 인강으로 학습이 필요해보인다. 나중에 새로운 프로젝트할 때 제대로 적용하자

끝. 부족한건 다음 프로젝트에서 ...