2021년 2월 14일 일요일

메이크 타임 책 메모

메이크 타임 메모

심심해서 읽은 책.

'매일 하이라이트를 지정하여 그 하이라이트를 방해없이 집중해서 진행하자'

하루를 메일, SNS, 미디어에 끌려다녀 하루를 만족스럽게 보내지 못한 사람들을 위한 책이다.

미라클 모닝과 뭔가 비슷한 느낌...


  • 우리가 배운 첫번 째 교훈은 우선순위가 높은 하나의 목표로 하루를 시작하면 마법 같은 무언가가 일어난다는 것이다.

    • 스프린트의 각요일에 우리는 한 가지 중요한 일에 주의를 집중했다.
  • 메이크 타임의 네 단계 프로세스

    • 하이라이트 : 초점을 선택하는 것으로 하루하루를 시작하라.

      • 물론 하이라이트가 그날 하는 유일한 일은 아니다. 하지만 그 일이 당신의 최우선이 될 것이다.
    • 초집중 : 방해꾼을 물리쳐 하이라이트를 처리할 시간을 만들어라.

    • 에너지 충전 : 뇌를 충전하기 위해 몸을 돌보아라

      • 운동, 음식, 잠, 조용한 휴식, 직접 사람을 만나 대화하며 배터리를 충전한다
    • 돌아보기 : 시스템을 조절하고 개선하라

  • 메이크 타임의 목표는 수도승이 되곘다는 맹세가 아니라 실행할 수 있고 융통성 있는 일련의 습관이다.

  • 하루가 끝날 무렵 누군가가 "오늘의 하이라이트는 뭐였나요?"라고 물어보면 뭐라고 대답하고 싶은가? 하루를 돌아봤을 때 어떤 활동이나 성취나 순간을 음미하고 싶은가? 그것이 바로 당신의 하이라이트이다.

  • 오늘 절대적으로 꼭 해야 하는 무언가가 있다면 그 일을 하이라이트로 정하라.

  • 하이라이트는 60~90분이 가장 적합하다. 의미 있는 무언가를 하기에 충분할 뿐만 아니라 스케줄에서 만들어내기에도 적당한 시간이다.

  • 복리와 비슷하다. 하이라이트에 오래 집중을 유지할수록 그 일이 더 매력적으로 느껴져 더 잘할 수 있다.

  • 막힌 채로 있어라. 포기하지 말라

    • 텅 빈 스크린을 물끄러미 바라보거나 종이에 써보거나 이리저리 걸어다니뇌 지금 하는 프로젝트에 초점을 유지하라.
    • 뇌의 조용한 부분들은 일을 진행하면서 앞으로 나아가고 있다.
    • 결국 막힌 부분이 풀릴 것이다.
  • 주말에 늦잠을 자는 것은 몸속 시계에 혼란을 주고 본래 부족하던 잠에서 회복하기가 훨씬 더 어려워질 수 있다.

    • 매일 같은 시간에 알람을 설정하라
  • 일정표를 싹 비울 필요는 없다. 특별한 무언가에 주의를 집중할 60~90분만 있으면 된다.

    • 목표는 중요한 일을 할 시간을 만들고 더 균형을 잡고 오늘을 더 즐기는 것이다.

2021년 1월 18일 월요일

[인프런] 자바 ORM 표준 JPA 프로그래밍 - 기본편 수강 후기

[인프런] 자바 ORM 표준 JPA 프로그래밍 - 기본편 수강 후기

인프런에서 구매한 김영한님 첫번째 강의다. (2020년 10월 완강했지만 후기는 지금 씀...)

https://www.inflearn.com/course/ORM-JPA-Basic

대충 구글링해서 사용했던 JPA를 이렇게 쓰면 안되겠다고 생각하던 중 유튜브에서 봤던 강의가 생각났고, 인프런에 강의 또한 생겼다는 사실도 알게되어 바로 알아보게 되었다.

유튜브에 있는 'JPA프로그래밍 기본기 다지기'의 유료버전이라고 볼 수 있다. (121,000)

좀 더 실습을 할 수 있으며, 자세한 설명을 들을 수 있다.

- JPA : Java Persistence API

  - 자바 진영의 ORM 기술 표준

- ORM? : Object-relational mapping (객체 관계 매핑)

  - 객체는 객체대로 설계
  - 관계형 데이터베이스는 관계형 데이터베이스대로 설계
  - ORM 프레임워크가 중간에서 매핑
  - 대중적인 언어에는 대부분 ORM 기술이 존재
  - JPA는 애플리케이션과 JDBC 사이에서 동작

솔직히 책은 두껍고, 딱딱할 것 같아 접하기 어려웠는데, 인강은 대단했다.

누구나 쉽게 이해할 수 있게 설명해주셨고 유명한 스타 인강강사처럼 명쾌했다.

책이 쉽게 손에 가지않는다?? 이 강의를 듣자!! (물론 나는 현업에서 사용 못해봄...^^)

2020년 11월 12일 목요일

미라클모닝 책 메모

 

미라클 모닝 책 메모

출근 길에 읽을 책이 없어서 방황하다가 예전에 군대에서 읽어본 것 같은 미라클 모닝책을 구매해보았다.
어찌보면 뻔한 '일찍 일어나서 하루를 시작하자' 이지만 항상 책에는 배울 점이 조금씩은 있는 것 같다.

나도 한 시간 일찍 일어나서 물 한 잔, 오늘 해야할 일 정리(trello), 가벼운 운동으로 하루를 시작해보고 있다.
나름 상쾌하게 하루가 시작된다.(물론 일찍 일어날 수 있다면...)


  • 이런 책의 목적은 고이 보존하는 게 아니라 그 책에서 뽑아낼 수 있는 가치를 극대화하는 데 있기 때문이다. 아무 때나 책을 다시 펼쳐서 전부 다 다시 읽지 않고도 핵심 내용을 금방 다시 볼 수 있도록 책들에 표시를 한다.

  • 평범함을 극복하는 법을 발견했다. 바로, 목적 있는 삶을 사는 것이다.

    • 평범함에 안주하게 되는 원인들을 물리치기 위해서는 삶의 목표가 필요하다. 마음을 움직이고 영감을 주며 매일 아침 잠에서 깨어나게 만드는 목표라면 어느 것이든 상관없다. 더 나은 삶을 살아가게 하는 목표라면 더할 나위 없다.

    • 삶의 목표는 아무 때나 바꿔도 괜찮다는 사실을 기억하자. 당신이 성장할수록 당신의 목표도 진화할 것이다. 핵심은, 어떤 목표든 간에 선택헤서 지금부터 목표에 맞춰 살아가는 것이다.

    • 지금부터 일주일 동안 삶의 목표에 대해 생각해보고, 명확하게 그려보자. 그리고 매일 확인할 수 있도록 잘 보이는 곳에 적어두자.

  • 긍정적이고 진취적인 사람들과 많은 시간을 보낸다면 성공을 부르는 그들의 태도와 습관이 당신에게 흡수될 것이다.

  • 당신을 믿고 존중하며, 목표지점까지 삶을 이끌어주는 사람들 찾아야 한다. 그런 사람은 우연히 당신 앞에 나타나지 않는다. 영향력 집단을 강화하는 사람을 적극적으로 찾아야 한다.

  • 지금까지 한 번도 성취하지 못했던 성공을 거두기 위해서 지금까지 한 번도 해보지 않았던 노력을 기꺼이 할 준비가 되어 있어야 한다.

  • 미라클 모닝의 여러 의도 중 하나는 생생한 에너지로 가득했던 아침의 기억을 되찾아주는 것이다. 그리고 그 기억을 삶 전체로 확대 시키는 것이다. 일어나야 하기 때문에 잠에서 꺠는 게 아니라 목표를 가지고 침대를 박차고 나오게 하는 것이다.

  • 책을 한 번만 읽고 그 책에서 얻을 수 있는 모든 가치를 내 것으로 만들기는 어렵다. 어느 분야든 통달하기 위해서는 반복이 요구된다. 어떤 아이디어나 전략, 혹은 기술도 마찬가지로 그것에 계속 반복해서 노출해야만 그것들이 잠재의식에 새겨질 수 있다.

  • 나만의 미라클 모닝을 시작하며 머리로만 생각했던 아이디어를 꺼내어 일기장에 옮겨 적는다. 이를 통해 생각만 했을 때에는 절대로 알 수 없었던 통찰력을 얻게 된다. 기록하기는 새로운 아이디어와 당면한 문재의 돌파구, 새로운 깨달음이나 교훈, 성장과 발전의 발자취를 미래에 다시 한 번 확인할 수 있도록 기억을 저장하는 역할을 한다.

2020년 9월 16일 수요일

[인프런] 더 자바 - 코드를 조작하는 다양한 방법 수강후기

 백기선님 4번째 강의이다.

https://www.inflearn.com/course/the-java-code-manipulation#

예전에 구매했던 강의인데, 김영한님 JPA강의 시작 전 빨리 봐야겠다는 생각이 들어서 
빨리 완강해버렸다. 277분이기 때문에 마음만 먹으면 빠르게 완강 가능하다. 
이 강좌는 누군가 공짜로 설명해주지 않는 내용이다. (누가 이렇게 설명 해줄까...?)

강의는 자바 심화 과정으로 '자바는 어떻게 코드, 객체를 조작하는가?' 에 대해 설명한다.
강의목표는 자바 라이브러리와 프레임워크들이 제공하는 편의기능들의 원리에 대해 설명하고자 하는 것 같다.

  • 코드 조작이 어떻게 이루어지는 지 이해하기 위한 JVM 구조 설명
  • java코드를 조작하지 않고 어떻게 코드를 조작할 수 있는 지?
  • 어노테이션들은 어떻게 편의기능을 제공하는 지?

에 대해서 쉽고 전문적으로 예제와 함께 설명해준다.


최근에 자꾸 와닿는 느낀점은 시간에 따른 학습 이해도가 정말 다르다는 것이다. 
예전에 공부했던 내용을 나중에 다시 공부할 때 그동안의 경험이나, 인식(?)으로 학습 내용이 정말 확 와닿고 머리에 딱 들어오는 경우가 있다. 
너무 이해가 안된다고 끙끙 앓지 말고 실전 경험 후 자연스럽게 이해하는 것도 좋은 것 같다.

API를 설계하거나 만들 때 리플렉션, 프록시 부분은 꼭 다시 강의 복습해보자!!

2020년 9월 6일 일요일

개인의 시대가 온다 책 메모

 

개인의 시대가 온다 책 메모

퇴사 예정인 혹은 퇴사를 생각하는 사람들에게 해주는 경험 공유 느낌 책이다.
책 광고를 보고 끌려서 보게 되었다. 자신의 퇴사 후 비즈니스 경험을 소개하는 책이었고 회사에 있을 때 챙겨야할 것들 중요하게 유지해야하는 것들을 소개해준다.
거의 퇴사 후 자신의 비즈니스를 키우면서 필요한 내용같았고 책의 모든 내용이 와닿진 않았지만, 나름 가볍게 볼만했다.


  • <타임>이 예측했던 진정한 '개인의 시대'는 각자가 개인의 중요성을 꺠닫고, 스스로를 내세워면서 살아가고자 하는 가치가 만발하는 시대, 바로 '지금'이라고 생각한다.

  • 정규직보다 더 위에 있는 비정규직, '슈퍼 비정규직'의 탄생이며 바로 이것이 '프리랜서 이코노미'를 이끌어 가고 있다. 이러한 경제구조에서는 개인이 곧 회사가 된다.

  • 미래의 경제와 기업은 프로젝트의 단위에 따라서 사람을 고용하고, 그 프로젝트가 끝나면 팀은 해체된다.

  • '끊임없이 변하는 세상'에서 '고정적인 인건비'를 사용하는 일은 기업 입장에서 과도한 비용만 나가는 일이다. 그럴 바에야 차라리 외부의 전문 인력을 필요한 기간만 쓰는 것이 훨씬 더 장점이 많다.

  • 자발적인 비정규직이 늘어나는 이유를 좀 더 현식적인 이유에서 찾아보자. 그것은 바로 세계 경제의 구조가 그렇게 변하고 있기 떄문이다. 가장 큰 두 가지의 이유는 '새로운 기술' 과 '자본주의'의 본질적인 속성 때문이다.

  • 지금 시장이 어디로 향하고 있는지, 그 시장 안에서 무엇이 중요하게 여겨지는지를 반드시 따져보아야 한다. 그 안에서 자신만의 영역을 찾을 수 있다면, 새로운 시대와 함께 승승장구할 수 있는 비즈니스를 펼쳐나갈 수 있기 때문이다. 이것을 빅웨이브라고 표현하고 싶다.

  • 사업이 성공하려면 누군가가 소비를 해주어야 한다. 그리고 이 소비의 기준은 이제껏 우리가 알고 있는 가격이나 제품의 퀄리티 등으로 통칭되는 '경쟁력'이다. 가성비라는 말도 결국 이러한 경쟁력을 지칭하는 말이다.

  • 과거에는 제품 그 자체를 보거나 혹은 브랜드를 보고 제품을 구매했다면, 이제 새로운 세대의 소비는 제품이 가지고 있는 '가치'를 본다는 의미이다.

  • 가치를 중심으로하는 이러한 시장 질서는 '소비에서의 개인의 시대'가 왔다는 사실을 잘 보여준다. 가치란 결국 개인의 신념이고 철학이다. 과거의 소비 과정에서는 이런 개인의 신념과 철학이 끼어 들어갈 틈이 없었다. 하지만 이제는 소비에서도 개인의 모습이 전면적으로 드러나고 있다.

  • '가치'란 양쪽 면에서 파도치듯 다가오는 빅 웨이브다. 비즈니스의 방향성을 물론이고, 그것을 함께하는 사람들 사이에서도 더할 수 없는 중요성을 가지고 있다. 모든 일의 출발점에서 가치를 점검하고, 함꼐 일하기 전에 조직 내의 가치를 다지는 것. 바로 이것이 '가치 중심의 소비'라는 새로운 트랜드를 반영하는 일이다.

  • 개인의 시대가 우리에게 과거보다 훨씬 많은 자유와 힘을 주었지만, 그 힘이 '누구에게나' 주어지지는 않는다.

    • 개인의 시대가 주는 자유와 힘을 누리고자 한다면 그만큼 더 많은 노력을 기울여야 한다.
  • 환경이 제한하는 인식에서 벗어나라

    • 자신이 처한 환경은 자신의 인식을 제한한다.
    • 비록 내가 지금 직장인이라고 하더라도, 얼마든지 다양한 분야에 진출할 수 있고, 또 얼마든지 위아래의 직급을 오갈수 있다고 가정해야만 한다.
  • '해보지 않았으니까 모른다'는 것은 곧 '해보면 알 수 있다'는 새로운 가능성에 대한 메시지를 준다. 그런 점에서 이것은 자신 앞에 있는 벽을 문으로 바꾸는 열쇠다.

  • 우리가 죽을 때까지 새로운 것을 배우듯, 죽을 때까지 나 자신을 테스트하는 것을 멈춰선 안된다. 언제나 세상은 변하고, 예상이 불가능하고, 또 새로운 것이 등장하기 떄문이다.

  • 자신의 능력을 발휘해 일을 열심히 하고 싶다면 오히려 직업 자체는 많아져야 한다. 주변 영역으로 계속해서 확장해나가면서 새로운 분야를 습득해 나가는 것이기 때문이다.

    • 원소스 멀티 유저 : 하나의 콘텐츠를 개발해 그것을 기반으로 여러 분야에 활용하여 파급효과를 노리는 마케팅 전략
  • 회사에서 할 수 있는 것은 다 해볼 필요가 있다. 한히 말하는 '라인'도 타보고, '파벌싸움'도 해보자. 상사의 눈치를 보면서 기분을 맞추는 스킬도 체득하자. 회사에서 할 수 있는 것은 다 해볼 필요가 있다.

  • 누군가에게는 회사는 어떻게든 일을 줄이면서 아등바등 월급을 받는 공간이자 미래의 퇴사가 두려워 눈치를 보는 곳이다. 하지만 어떤 이에게는 회사는 자기계발을 위한 훌륭한 인재개발원이 되기도한다. 또한 그 안에서 최대한 성과를 내면서 회사와 윈윈의 관계를 만들어낼 수도 있다.

  • 자신의 멘토를 경쟁자로 삼는 것도 하나의 방법이다. '나는 언제쯤 저 사람을 따라갈 수 있을까?' 라는 존경의 시선을 자신에게 투영하먄, 자신에 대한 엄격한 기준을 세울 수도 있다.

  • 어떻게 보면 도태는 환경의 변화에 의한 것이라기보다 자기 스스로 만든 틀 안에 갇힐 때 발생한다.

  • 물건이나 서비스가 만들어지는 그 모든 프로세스에 대한 충분한 이해가 있어야한다. 이것은 특정 분야에 대한 지식을 통해 퇴사 이후 자신만의 전문적인 영역을 만들어가는 데에도 매우 중요하다.

  • 기획력은 흔히 '어떤 대상에 대한 목표를 설정하고, 그것을 이뤄내기 위해 가장 적합하고 효율적인 행동을 설계하는 것'을 의미한다.

  • 기획력을 높이는 수많은 방법이 있지만, 현장에서 일하며 느낀 중요한 한 가지는 '질문을 잘해야 한다'는 점이다.

    • 질문은 새로운 관점을 자극하는 훌륭한 방법이다. 늘 해오던 행동이라도 여기에 질문을 던지는 순간, 주위가 환기되고 다른 식으로 사고하게 된다.
    • 질문은 주어진 현상의 핵심과 본질로 접근할 수 있도록 도와준다.
  • 일이라는 것은 최종적으로 누군가의 평가를 받는 일이다. 그래서 모든 것을 다 완성한 뒤에 내놓는 것이 아니라 처음에는 전체의 레이아웃을 보여주면서 의향을 물어보고 ,그 다음에는 조금 더 디테일을 보이면서 최종적으로 일의 끝을 향해 가는 방식이다.

  • 넷플릭스의 가이드 (자유와 책임의 조직문화에 대한 가이드)

    • 어지간한 성과를 내는 사람들이라면 퇴직금을 많이 주면서 내보낸다.
    • 넷플릭스는 성과가 중심이다. 성과를 잃어버리는 순간, 모든 것은 수포로 돌아가고 만다. 그래서 탁월하지 않으면 퇴직금을 줘서라도 내보낸다. 추상적인 슬로건을 내세우는 것은 아무런 의미도 없다.
    • 회사에서의 진짜 가치는 그럴듯해 보이는 구호가 아닌, 누가 보상받고, 승진하고, 해고되는지로 나타난다.
    • 우리는 가족이 아니고 팀이다.
  • 충분한 정보의 전달과 정확한 일의 배분, 그리고 전체 팀의 지행을 골고루 알려주는 것이 바로 '맥락 속에서의 이해'이다.

  • '레드오션 안의 블루오션'이라는 관점을 가질 필요가 있다. 뭉뚱그려서 '거기는 레드오션이다.'라고 생각하지 말고, 그 안에 비어 있는 부분을 찾아낼 필요가 있다.

  • '1분짜리 자기소개','5분','10분'를 모두 준비해야 한다. 기회가 왔을 때 단 한순간도 주저함 없이 자신에 대해 제한된 시간 안에 줄줄 말할 수 있을 정도가 되어야 한다.

  • 만약 상대방이 나에 대해서 부정적으로 느끼게 되면 매력도가 현저하게 떨어지게 되고 함께 일하고 싶은 마음의 문이 닫힐 수 있다.

    • 상대방을 소비하는 사람이 되지 말아야한다.
    • 부정적인 기운을 내뿜는 사람이 되어서는 안된다.
    • 사심이 없는 사람이 되어야한다. 대화를 하다 보면 매사에 계산적이고 늘 자신의 욕망을 불어넣는 사람이 있다.
    • 상대방의 문제에 솔루션을 제공해주려는 마음 자세를 갖는
  • 아무리 열심히 일을 해도 일을 되게 만들지 않으면 소용이 없다. 일을 되게 한다는 것은 목표한 바를 전진하는 동력이다. 힘을 가해져야 목표를 향해 굴러간다. 그런 점에서 '지금 내가 하는 일이 목표를 향해 굴러가게 만드는 일인가?'를 계속 되돌아봐야한다.

  • 기존의 관습에서 벗어나 일이 다른 경로로 성공을 향해 도약하게 만드는 것, 바로 이것이 '일을 되게 하는 것'이라고 할 수 있다.

  • 독립 사업자를 꿈꾼다면, 돈과 노동에 대한 이러한 일반적인 관점을 해체해야 할 필요가 있다. 심지어 돈이 안되는 일도 해야 하는 것이 새로운 세계에서 필요한 관점이다.

  • 협업의 시대에서 협업의 정의의 대해 '자신의 생각과 노력을 집단의 뇌에 녹여 혼자 일할 때보다 더 나은 결과를 낳으려는 의지와 능력'이라고 했다.

  • 흔히 우리의 전통 문화인 품앗이를 '서로 힙을 합치는 것'이라고 보지만, 어떤 의미에서는 '최적의 타이밍을 놓치지 않으려는 집단행동'일 수도 있다. 사업도 결국 타이밍 싸움이다. 타임의 힘과 자원을 집중적으로 끌여들여 단시간에 작업을 끝내면 타이밍을 놓치지 않게 된다.

  • 나쁜 사람으로 인해 자꾸만 부정적인 영향을 받으면, 결국 그것이 나를 통해 좋은 사람에게도 영향을 미치기 때문이다. 결국, 힘빼기를 통해 최대한 관계를 해결하기 위해 노력하지만, 그것이 한계에 봉착했을 때는 남아 있는 더 좋은 사람들을 위해서 관계를 포기하는 결단도 내려야 한다.

  • 진정한 리더쉽 이론에서는 자신의 실수와 약점, 나약함을 솔직하게 인정할 때 지도자와 추종자 간에 훨씬 밀접한 관계가 형성된다.

  • 대게 상대에게 더 주는 4:6정도가 되어야 비로소 상대는 5:5로 받아들이곤 한다. 이는 기본적으로 인간의 삼라애 존재하는 '호혜성'에서 기인한다. 끊임없이 뭔가를 주고받아야 상대와 내가 동시에 성장하고 발전하게 된다. 그리고 이 호혜성의 무게 중심이 내가 아닌 상대방에게 주어질 때 관계는 좀 더 빠르게 발전해나갈 수 있다.

  • 우리가 정말 추구해야할 것은 '남들의 시선'이 아니라 얼마나 진짜 나를 단단하게 만들어나갈 수 있느냐이다.

2020년 8월 17일 월요일

멀티모듈 실습해보기

 멀티모듈 실습해보기

  • 멀티모듈 프로젝트 ??

    • 기존의 단일 프로젝트들을 프로젝트 안의 모듈로서 갖을 수 있게 하여 다중 모듈을 한 프로젝트에서 관리한다.

    • 독립적인 프로젝트들이 모여 동작하는 한 시스템을 개발하는 경우 독립적인 시스템들이 동일하게 사용하는 부분을 동일하게 각각의 프로젝트에서 구현해야 한다. (ex_ Domain, Repository 등)

    • 이 경우 공통부분에 변경이 생기면 모든 독립적인 프로젝트에도 동일하게 변경을 해줘야한다.

      • 상당히 비효율적이다. 실수발생 가능성도 높다.
    • 멀티모듈을 통해서 루트프로젝트 내 여러곳에서 공통적으로 사용되는 부분을 모듈로 분리해서 관리할 수있다.

  • 문제점

      1. 그래들 6.4.1 오류 발생
      • 생성 시 6.4.1로 설정되었는데 빌드가 안된다! 검색을 해보면 인털리제이 이슈로 나온다

      • 버전을 낮췄다.. 6.4.1 -> 5.6.1

      • 프로젝트 경로에서 다음을 실행하면 된다.

      ./gradlew wrapper --gradle-version 5.6.1

      • 나는 gradle/wrapper/gradle-wrapper.properties 에서 버전을 바꿔주었다. 위의 방법이 맞는 방법 같다.

        distributionBase=GRADLE_USER_HOME
        distributionPath=wrapper/dists
        distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-bin.zip
        zipStoreBase=GRADLE_USER_HOME
        zipStorePath=wrapper/dists
        
      1. common에 생성한 repository나 domain을 자꾸 인식을 못하여 다음과 같이 Application에 명시해줌
      @SpringBootApplication(scanBasePackages = "com.example")
      @EntityScan("com.example.domain")
      @EnableJpaRepositories(basePackages={"com.example.repository"})
      
      • 해결! : 생성한 repository 경로가 다른 모듈과 달랐음!

        • 기존 common 모듈 : com.example.domain.Member.java
        • 기존 api 모듈 : com.example.multimodulestestproject.ModuleApiApplication.java
        • 변경 common모듈 : com.example.multimodulestestproject .domain.Member.java


  • 프로젝트 생성 시 입력하는 groupId와 artifactId

  • 의문점

    • MSA랑 멀티모듈이랑 다른걸까??

      • MSA 는 기능기반으로 분리하여 아예 의존성 없는 모듈간 통신으로 서비스를 하는 것 같고 멀티모듈은 그냥 방식인 것 같음.
      • 모놀리스방식도 멀티모듈이 가능한듯?
    • 애초에 모두 묶어서 프로젝트를 만들면?

      • 모두 합친 프로젝트에 자주 변경되는 부분이 있을텐데 그 부분 때문에 전체 재배포가 필요해진다.

      • 해당 부분을 분리하여 각각 만듦으로써 필요한 부분만 변경(배포)이 가능해진다.

  • 변경 전 build.gradle

plugins {
    id 'org.springframework.boot' version '2.3.2.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    compile 'org.springframework.boot:spring-boot-starter-web'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

test {
    useJUnitPlatform()
}
  • 변경 후 build.gradle

buildscript {
    ext {
        springBootVersion = '2.3.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        classpath "io.spring.gradle:dependency-management-plugin:1.0.9.RELEASE"
    }
}



subprojects {
    group = 'com.example'
    version = '0.0.1-SNAPSHOT'

    apply plugin: 'java'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'

    sourceCompatibility = '1.8'

    repositories {
        mavenCentral()
    }


    dependencies {
        testCompile group: 'junit', name: 'junit', version: '4.12'
    }
}

project(':module-api') {
    dependencies {
        compile project(':module-common')
    }
}

project(':module-web') {
    dependencies {
        compile project(':module-common')
    }
}

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

참고 및 출처