Dependency 의존관계란?


'A가 B를 의존한다.' 라는 표현은 어떤 의미일까? 토비의 스프링에서는 다음과 같이 정의한다.

 

의존대상 B가 변하면, 그것이 A에 영향을 미친다.

 

즉, B의 기능이 수정된다면 A에게까지 영향을 미친다.

 

다음 예시를 보며 이해해보자.

"햄버거 가게 요리사는 햄버거 레시피에 의존한다." 는 말은 햄버거 레시피가 변경되었을 때, 변경된 레시피에 따라서 요리사는 햄버거 만드는 방법을 바꿔야한다. 레시피의 변화가 요리사의 행위에 영향을 미쳤기 때문에 "요리사는 레시피에 의존한다"고 말할 수 있다.

 

코드로 표현해보면 다음과 같다.

class BurgerChef {
    private HamBurgerRecipe hamBurgerRecipe;

    public BurgerChef() {
        hamBurgerRecipe = new HamBurgerRecipe();        
    }
}

여기에서 HamBurgerRecipe만을 의존할 수 있는 구조로 되어있다. 만약 더 다양한 햄버거 레시피를 의존 받을 수 있게 구현하려면 인터페이스로 추상화해야한다.

 

이를 코드로 표현해보면 다음과 같다.

class BurgerChef {
    private BurgerRecipe burgerRecipe;

    public BurgerChef() {
        burgerRecipe = new HamBurgerRecipe();
        //burgerRecipe = new CheeseBurgerRecipe();
        //burgerRecipe = new ChickenBurgerRecipe();
    }
}

interface BugerRecipe {
    newBurger();
    // 이외의 다양한 메소드
} 

class HamBurgerRecipe implements BurgerRecipe {
    public Burger newBurger() {
        return new HamBerger();
    }
    // ...
}

class CheeseBurgerRecipe implements BurgerRecipe {
	public Buerger newBurger() {
    	return new CheeseBurger();
    }
    // ...
}

의존관계를 인터페이스로 추상화하게 되면, 더 다양한 의존 관계를 맺을 수 있고 실제 구현 클래스와의 관계가 느슨해지고, 결합도가 낮아진다.

 

그럼 DI(Dependency Injection)은 뭔가?


위에 코드를 보면 BurgerChef 내부적으로 의존관계인 BurgerRecipe가 어떤 값을 가질지 직접 정하고 있다. 만약 어떤 BurgerRecipe를 만들지를 버거 가게 사장님이 정하는 상황을 가정해보자. 즉, BurgerChef가 의존하고 있는 BurgerRecipe를 외부(사장님)에서 결정하고 주입하는 것이다.

 

이처럼 의존관계를 외부에서 결정하고 주입하는 것이 DI(의존관계 주입)이다.

DI를 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아진다.

 

DI 구현 방법


DI는 의존관계를 외부에서 결정하는 것이기 때문에, 클래스 변수를 결정하는 방법들이 곧 DI를 구현하는 방법이다. 런타임 시점의 의존관계를 외부에서 주입해 DI 구현이 완성된다.

 

"Burger 레스토랑 주인이 어떤 레시피를 주입하는지 결정하는 예시로 설명하고자 한다."

 

[생성자 이용]

class BurgerChef {
    private BurgerRecipe burgerRecipe;

    public BurgerChef(BurgerRecipe burgerRecipe) {
        this.burgerRecipe = burgerRecipe;
    }
}

class BurgerRestaurantOwner {
    private BurgerChef burgerChef = new BurgerChef(new HamburgerRecipe());

    public void changeMenu() {
        burgerChef = new BurgerChef(new CheeseBurgerRecipe());
    }
}

 

[메소드 이용]

class BurgerChef {
    private BurgerRecipe burgerRecipe = new HamburgerRecipe();

    public void setBurgerRecipe(BurgerRecipe burgerRecipe) {
        this.burgerRecipe = burgerRecipe;
    }
}

class BurgerRestaurantOwner {
    private BurgerChef burgerChef = new BurgerChef();

    public void changeMenu() {
        burgerChef.setBurgerRecipe(new CheeseBurgerRecipe());
    }
}

 

 

DI의 장점은 뭘까?


1. 의존성이 줄어든다.

의존성이 높을 수록 대상이 변화했을 때, 이에 맞게 다른 것도 수정해야한다. DI로 구현했을 경우, 주입받는 대상이 변하더라도 그 구현 자체를 수정할 일이 없거나 줄어들게 된다.

 

2. 재사용성이 높은 코드가 된다.

기존에 BurgerChef 내부에서만 사용되었던 BurgerRecipe를 별도로 구분하여 구현한다면, 다른 클래스에서 재사용할 수 있다.

 

3. 가독성이 높아진다.

기능들을 별도로 분리하게 되어 자연스럽게 가독성이 높아진다.

 

 

관점 지향 프로그래밍 (AOP)란 뭘까?


AOP는 OOP를 돕는 보조적인 기술로, 관심사의 분리의 문제를 해결하기 위해 만들어진 프로그래밍 패러다임이다.

여러 객체에 공통으로 적용할 수 있는 기능을 분리함으로써 재사용성을 높일 수 있다.

 

만약 메서드를 실행할 때마다 공통적으로 확인하고 싶은 정보가 있는데, 모든 메서드에 해당 로그 코드를 작성하면 코드가 길어질 뿐만아니라 가독성이 떨어질 것이다.

 

이를 AOP로 분리시킨다면..

 

@Aspect
@Component
public class ParameterAop {

    //com/example/aop/controller 패키지 하위 클래스들 전부 적용하겠다고 지점 설정
    @Pointcut("execution(* com.example.aop.controller..*.*(..))")
    private void cut() {}

    //cut() 메서드가 실행 되는 지점 이전에 before() 메서드 실행
    @Before("cut()")
    public void before(JoinPoint joinPoint) {
		
        //실행되는 함수 이름을 가져오고 출력
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        System.out.println(method.getName() + "메서드 실행");

        //메서드에 들어가는 매개변수 배열을 읽어옴
        Object[] args = joinPoint.getArgs();
		
        //매개변수 배열의 종류와 값을 출력
        for(Object obj : args) {
            System.out.println("type : "+obj.getClass().getSimpleName());
            System.out.println("value : "+obj);
        }
    }

    //cut() 메서드가 종료되는 시점에 afterReturn() 메서드 실행
    //@AfterReturning 어노테이션의 returning 값과 afterReturn 매개변수 obj의 이름이 같아야 함
    @AfterReturning(value = "cut()", returning = "obj")
    public void afterReturn(JoinPoint joinPoint, Object obj) {
        System.out.println("return obj");
        System.out.println(obj);
    }
}

AOP 클래스를 구현함으로써 이전보다 코드가 훨씬 간결해지고 가독성이 높아지는 것을 확인할 수 있다.

 

'정리 > Spring' 카테고리의 다른 글

[Spring] REST API  (0) 2021.09.09
[Spring] Spring MVC  (0) 2021.09.09
[Spring] Spring이란?  (0) 2021.09.09
[Spring Boot + Vue.js] 구글로그인  (0) 2021.08.18
[Spring Boot] 이메일 인증 회원가입하기 구현  (2) 2021.07.23

API란?

  • Application Programming Interface
  • 응용 프로그램에서 사용할 수 있도록, 운영체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스

👉🏻 OPEN API는 뭘까?

  • 프로그래밍에서 사용할 수 있는 개방되어 있느 상태의 인터페이스
  • NAVER, KAKAO 등 포털 서비스 사이트 등 다양한 데이터를 외부 응용 프로그램에서 사용할 수 있도록 OPEN API를 제공

REST란?

  • Representational State Transfer의 약어로 하나의 URL은 하나의 고유한 리소스를 대표하도록 설계된다는 개념에 전송방식을 결합해서 원하는 작업 지정

  • HTTP URI를 통해 제어할 자원을 명시하고 HTTP Method를 통해 해당 자원을 제어하는 명령을 내리는 방식의 아키텍처

  • HTTP Method
    • POST : 리소스 생성
    • GET : 리소스 조회
    • PUT : 리소스 수정
    • DELETE : 리소스 삭제

👉🏻GET / POST 차이

  • GET방식 : 클라이언트에서 서버로 어떤 리소스로부터 필요한 정보를 요청하기 위해 사용하는 메서드 EX) 게시판 게시물 조회
    • 데이터 URL주소 끝에 파라미터로 쿼리 스트링을 포함하여 전송
    • 요청 길이에 제한있다.
    • 파라미터에 붙여서 전달하기 때문에 보안에 취약하다.
    • 멱등이다.

  • POST방식 : 클라이언트에서 서버로 리소스를 생성하거나 업데이트하기 위해 데이터를 보낼 때 사용하는 메서드 EX) 게시판 글 작성
    • 데이터 HTTP 메시지 BODY부분에 담아서 전송
    • 요청 길이에 제한이 없다.
    • BODY에 담아서 보내기 때문에 데이터 노출되지 않아 보안에 강함
    • 멱등이 아니다.


    멱등이 뭐야❓
    • 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질
    • 즉, GET은 리소스를 조회만하기 때문에 여러 번 요청하더라도 응답은 똑같을 것이다. 하지만 POST는 리소스를 새로 생성하거나 업데이트할 때 사용되기 때문에 멱등이 아니라고 볼 수 있다.

👉🏻Swagger란?

  • 간단한 설정으로 프로젝트의 API 목록을 웹에서 확인 및 테스트 할 수 있게 해주는 라이브러리

'정리 > Spring' 카테고리의 다른 글

[Spring] DI & AOP  (0) 2021.09.24
[Spring] Spring MVC  (0) 2021.09.09
[Spring] Spring이란?  (0) 2021.09.09
[Spring Boot + Vue.js] 구글로그인  (0) 2021.08.18
[Spring Boot] 이메일 인증 회원가입하기 구현  (2) 2021.07.23

Spring MVC란?

👉🏻 model, view, controller의 합성어로 소프트웨어 디자인 패턴

  • Model : Controller에서 받은 데이터를 저장하는 역할
  • View : jsp 파일등과 같이 화면에 시각적으로 표현해주는 역할
  • Controller : 사용자의 요청을 받고 응답을 하는 로직을 담당. 접근한 url에 따라 요청사항을 파악하고 그에 맞는 데이터를 model에 의뢰하고 view에 반영해서 사용자에게 보여주는 역할

👉🏻Spring MVC 요청흐름

  • Request → DispatcherServlet (web.xml) → Controller → Logic처리(service, db접근) → View 전달 → response

  • DispatcherServlet : 모든 request를 받는 관문. request를 실제로 처리할 Controller에게 클라이언트의 요청을 전달하고, controller가 리턴한 결과값을 View에게 전달하여 알맞은 응답 생성
  • Controller : 클라이언트의 요청을 처리한 뒤, Model을 호출하고 그 결과를 DispatcherServlet에게 알려줌
  • View : Controller의 처리결과를 보여울 응답화면 생성

👉🏻VO, DTO, DAO란?

  • VO(Value Object) : 실제 데이터만 저장하는 클래스
  • DTO(Data Transfer Object) : 데이터를 주고 받기 위해 사용하는 클래스
  • DAO(Data Access Object) : DB에 접근하여 실제 데이터를 조회/조작하는 클래스 Repository 또는 Mapper에 해당

👉🏻MVC Pattern 특징

  • 어플리케이션의 확장을 위해 model, view, controller 세 영역으로 분리
  • 컴포넌트의 변경이 다른 영역의 컴포턴트에 영향을 미치지 않는다. = 유지보수 용이하다.
  • 컴포넌트 간의 결합성이 낮아 프로그램 수정이 용이하다. = 확장성이 뛰어나다.

장점

  • 화면과 비즈니스 로직을 분리해서 작업 가능
  • 영역별 개발로 확장성 뛰어남
  • 표준화된 코드를 사용하므로 공동작업 용이 및 유지보수성 좋음

단점

  • xml를 기반으로하는 프로젝트 설정은 많은 시간 소요
  • Tomcat과 같은 WAS 별도 설치

해결책( = Spring Boot)

  • 자동설정을 도임하여 DispatcherServlet과 같은 설정 시간 감소
  • spring-boot-starter로 외부 도구 사용
  • 내장 톰캣 제공하여 별도의 WAS 필요 X


웹 서버 VS WAS❓

  • 웹 서버(Apache) : 클라이언트가 웹 브라우저의 어떠한 페이지 요청을 하면 웹 서버에서 그 요청을 받아 정적 컨텐츠(HTML, CSS 등 즉시 응답가능한 컨텐츠)를 제공하는 서버
  • WAS(Tomcat) : 웹 서버 단독으로 처리할 수 없는 데이터베이스의 조회나 다양한 로직 처리가 필요한 동적 컨텐츠를 제공하는 서버

👉🏻그럼 웹 서버는 동적 컨텐츠를 제공하지 못하나?

  • 웹 서버가 동적 컨텐츠 요청을 받으면 WAS에게 해당 요청을 넘겨주고, WAS에어 처리한 결과를 클라이언트에게 전달해주기도 한다!

👉🏻 그럼 WAS를 쓰면 되는거 아닌가?

  • WAS는 DB조회 및 다양한 로직처리하는 데 집중하기 때문에 단순한 정적 컨텐츠를 웹 서버에 맡겨 기능을 분리시킴으로써 서버 부하 방지!
  • 만약 WAS가 정적 컨텐츠 요청까지 처리한다면, 동적 컨텐츠 처리가 지연되면서 수행 속도도 느려질뿐더러 이로 인해 페이지 노출 시간이 지연되는 문제가 발생하여 효율성이 크게 떨어진다.

👉🏻 그럼 Apache Tomcat은 다른건가?

  • Tomcat에 정적 컨텐츠를 처리하는 기능이 추가되어, Tomcat가 Apache기능을 포함하고 있어 Apache Tomcat이라고 부른다!

👀참고

https://codechasseur.tistory.com/25 (웹서버 WAS차이)

https://minchoi0912.tistory.com/93 (Spring MVC)

'정리 > Spring' 카테고리의 다른 글

[Spring] DI & AOP  (0) 2021.09.24
[Spring] REST API  (0) 2021.09.09
[Spring] Spring이란?  (0) 2021.09.09
[Spring Boot + Vue.js] 구글로그인  (0) 2021.08.18
[Spring Boot] 이메일 인증 회원가입하기 구현  (2) 2021.07.23

Spring이란?

  • Java언어 기반의 프레임워크
  • Java로 다양한 어플리케이션을 만들기 위한 프로그래밍 틀

프레임워크❓

  • 프로그램 기본 구조(뼈대)
  • 원하는 기능 구현에만 집중하여 빠르게 개발할 수 있도록 기본적으로 필요한 기능을 갖추고 있는 것.

쉽게 이해할 수 있도록 집을 짓는 것으로 비유를 해보자.

사용자가 집을 직접 짓기 위해 설계도를 직접 그리고 각각의 기초 작업들을 일일이 하는 것 보다, 전문가들의 도움을 받아 작업을 하면 더욱 쉽고 효율적으로 집을 지을 수 있는 것처럼 프레임워크를 사용한다면 프레임워크가 제공하는 여러 기능들을 사용해서 빠르고 효율적으로 프로그램을 구축할 수 있을 것이다.

 

프레임워크를 사용하면 뭐가 좋을까❓


개발 프로세스 간소화

프레임워크에서 제공하는 여러 도구와 패키지는 개발자가 스크립트를 처음부터 작성할 필요가 없고, 이미 만들어진 코드를 재사용하게 되므로 재사용성을 높이고, 시간과 비용을 아낄 수 있다.

 

유지보수 용이

프레임워크를 사용한다면 코드가 체계적이고 상대적으로 정형화되기 때문에, 개발자가 중간에 교체되더라도 위험이 적고, 이후 소스코드의 유지보수도 상당히 용이해진다.

 

보안

개발자는 일반적으로 SQL Injection, CSRF 등 외부 공격을 방어하기 위한 추가적인 소스코드를 작성해야하지만, 프레임워크를 사용하면 이러한 일들을 할 필요가 없다. 대부분의 프레임워크와 함께 제공되는 보안 기능들은 개발자가 웹 사이트 혹은 애플이케이션을 보호할 수 있는 방법을 제공하기 때문이다.

 

그럼 프레임워크의 단점은 없을까❓


사전학습

다양한 기능을 제공하고, 미리 만들어져있는 기능을 사용하기 위해서는 학습이 필요하다. 새로운 프레임워크를 학습하고 사용하기 위해서는 많은 공부가 필요하다.

 

제약사항

제공하고 있는 기능들 외에, 원하는 옵션을 추가하는 것에 굉장히 보수적일 수 있다.

사용법이 정해져 있고, 기본적으로 설계된 구조를 바탕으로 코드를 작성하고 기능을 완성해야하기 때문에 코드를 유연하게 개발하는데 있어서 한계가 있을 수 있다.

 

크기

프레임워크는 많은 기능을 제공한다. 이는 개발하면서 필요하지 않은 기능도 포함된다는 것을 의미하고, 불필요한 기능이 메모리를 차지하기 때문에 리소스 낭비로 이어질 수 있다.

 

 

💡스프링(Spring)의 특징 -⭐ 결합도를 낮춰 유연할 개발 가능하게 해준다!


  • 제어의 역전(IOC : Inversion of Control)
    • 프로그램의 생명주기에 대한 제어권이 웹 애플리케이션 컨테이너에 있다. 
    • 사용자가 직접 new 연산자를 통해 인스턴스를 생성하고 메서드를 호출하는 일련의 생명주기에 대한 작업들을 스프링에 위임
    • 객체관리 주체가 프레임워크(Container)가 되기 때문에 개발자는 로직에 집중할 수 있다는 장점이 있다.

 

  • 의존성 주입(DI)
    • 객체 사이에 필요한 의존 관계에 대해서 스프링 컨테이너가 자동으로 연결해 주는 것
    • DI를 이용하여 빈(Bean) 객체 관리, 스프링 컨테이너에 클래스를 등록하여 스프링이 클래스의 인스턴스를 관리해 준다.
    👉🏻스프링 컨테이너에 빈(Bean)을 등록하고 설정하는 방법
    1. XML 설정을 통한 DI - applicationContext.xml에서 설정
    2. 어노테이션을 이용한 DI
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //DI가 없는 예제
    @RestController
    public class Controller{
        Service service = new Service(); //객체간의 결합도↑
     
    @RequestMapping("/hello")
    public String hello(){
            return service.helloMessage();
        }
    }
    cs

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //DI가 있는 예제
     
    //service
    @Component
    public class Service{
        public String helloMessage(){
            return "hello~"
        }
    }
     
    //controller
    @RestController
    public class Controller{
        @Autowired
        Service service;
     
        @RequestMappint("/hello")
        public String hello(){
            return service.helloMessage();
        }
     
    }
    cs
    → 어노테이션으로 Service 객체의 인스턴스를 쉽게 얻을 수 있다. = 결합도↓

 

  • 관점 지향 프로그래밍(AOP)
    • 주요 핵심 기능과 핵심 기능 구현을 위한 부가적인 기능(로깅 작업, 데이터베이스 연결 및 트랜잭션 처리, 파일 입출력 등) 구현을 분리하여 각각의 관점별로 묶어서 개발하는 방식

그럼 왜 스프링 부트가 필요할까❓


  • Spring MVC 프레임워크를 사용할 때 applicationContext.xml이외에 다양한 설정을 통해 의존성을 주입해야한다.
  • 기본 프로젝트를 셋팅하는데 많은 시간 소요

그럼 Spring Boot는 뭐가 다를까❓


  • Spring Boot는 따로 설정할 필요 없이 단지 실행만 하면 된다!
  • spring-boot-starter-web을 사용하면 종속된 모든 라이브러리를 알맞게 찾아서 가져오기 때문에 종속성이나 호환 버전에 대해 신경 쓸 필요가 없다!

공부하면서 작성한 글입니다. 틀린 내용이 있다면 알려주세요!🙏🏻

 

 

👀 참고

https://dzone.com/articles/spring-vs-spring-boot

https://msyu1207.tistory.com/entry/Spring-VS-Spring-Boot-차이점을-알아보자

'정리 > Spring' 카테고리의 다른 글

[Spring] DI & AOP  (0) 2021.09.24
[Spring] REST API  (0) 2021.09.09
[Spring] Spring MVC  (0) 2021.09.09
[Spring Boot + Vue.js] 구글로그인  (0) 2021.08.18
[Spring Boot] 이메일 인증 회원가입하기 구현  (2) 2021.07.23

구글 로그인 

  • 구글 로그인 버튼 클릭
1
2
3
<a href="https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.email&response_type=code&client_id={client_id}&redirect_uri={redirect_uri}">
<img :src="require('@/assets/images/google-icon.png')"/>
</a>
cs

 

  • 구글 로그인 진행 후, 리다이렉트 페이지로 이동하면 url에 Authorization code 확인!

 

  • Authorization code axios를 통해 백엔드에 전달
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let googleCode = new URL(window.location.href).searchParams.get("code");
    if(googleCode != null){
      console.log("구글로그인 시도");
      store.dispatch("auth/requestGoogleToken", googleCode)
      .then(function(result){
        console.log(result)
        if (result.data.idToken) {
          console.log("구글 result: ", result.data.idToken)
          console.log("구글 email : ",result.data.email )
          localStorage.setItem("jwt", result.data.idToken);
          store.commit("auth/setToken", result.data.idToken);
          store.commit("auth/setEmail", result.data.email);
          store.commit("auth/setProvider","google"); // 로그아웃할때 방식 다 달라서 구분용
        }
        router.push({
          path: "/"
        });
      }).catch(function(error){
        console.log(error)
      })
    }
cs

 

  • Authorization code를 이용해 구글 access_token 및 id_token 발행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import org.springframework.stereotype.Service;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
 
@Service
public class GoogleService {
    public String getAccessToken (String authorize_code) {
        String access_Token = "";
        String id_Token = "";
        String reqURL = "https://oauth2.googleapis.com/token";
 
        try {
            URL url = new URL(reqURL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);
 
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
            StringBuilder sb = new StringBuilder();
            sb.append("grant_type=authorization_code");
            sb.append("&client_id="); //수정 할것
            sb.append("&redirect_uri="); //수정 할것
            sb.append("&client_secret="); //수정 할것
            sb.append("&code=" + authorize_code);
            bw.write(sb.toString());
            bw.flush();
            int responseCode = conn.getResponseCode();
            System.out.println("responseCode : " + responseCode);
 
            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = "";
            String result = "";
 
            while ((line = br.readLine()) != null) {
                result += line;
            }
            System.out.println("response body : " + result);
 
            JsonParser parser = new JsonParser();
            JsonElement element = parser.parse(result);
 
            access_Token = element.getAsJsonObject().get("access_token").getAsString();
            id_Token = element.getAsJsonObject().get("id_token").getAsString();
            System.out.println("access_token : " + access_Token);
            System.out.println(id_Token);
 
            br.close();
 
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
        return access_Token;
    }
  
}
cs

 

  • 결과
1
2
3
4
responseCode : 200
response body : {  "access_token": "ya29.a0ARrdaM-w7dZpSKr4865j6fZ4SQcAbyHw9B2jG0Rd7vLpjf45gHlmbJ8YSEg6klSy8ElFFW-JanHQxd2u8zs7aUKTPZdLY9K28mx1k7a4J0JXGjX-k6MYqd0GaiNN9EV5wvXa_gpS7i6M3R36dGOkyvky9Wje",  
"expires_in": 3599,  "scope": "https://www.googleapis.com/auth/userinfo.email openid",  "token_type": "Bearer",  
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjZlZjRiZDkwODU5MWY2OTdhOGE5Yjg5M2IwM2U2YTc3ZWIwNGU1MWYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiIyMjk1MTExNDAxMTgtMzFkNHZwMTYwYzdkZDFsZDRnMjcxODBmbXExcWVzZzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIyMjk1MTExNDAxMTgtMzFkNHZwMTYwYzdkZDFsZDRnMjcxODBmbXExcWVzZzguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTE3MjkxNDg1Mzc4Mzg3NTY3NTUiLCJlbWFpbCI6InduZHVzeDFAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiJBOE94NFhPa2oyblV0aU1VUmhUeFBnIiwiaWF0IjoxNjI5Mjk1Njk3LCJleHAiOjE2MjkyOTkyOTd9.gsTWNfzCEZYniwwI9a_CoDmn5Kd7SUbyJLYxG9GbdBFpeXiS7yR_Zl3J2kDtqieDUgbt2EMZP4MlDEGbB0kglYWJXbE54kmbVKlmzMKK41GoLvohNnSEQUJHgj0bPFkBqJm8Z3DLhmWQJmqDczinUbHmYSbvcHgflRSJs1_0xAM7-xtmVE55rJoR0JzBkIjKhXGsGue9791lP0M0fM9y6SJxmkLJLv-1c1eoKIgY4cTbIYVOs29TwKRSQGSwTqsVBq63WinYFrt0ECc_e4RB21wcmDZx2aQe_SDEw4iqayWdFEnSYiyyit6XIpkLYXdaa_3F4RWpxL1biuJgMqSBWA"}
cs

 

구글 id_token으로 로그인한 유저 정보 얻기

  • id_token을 이용해 구글 이메일, 프로필 사진 등 유저 정보 얻기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.babble.api.service;
 
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import org.springframework.stereotype.Service;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
 
@Service
public class GoogleService {
  
    public String getUserInfo (String id_token) {
        String reqURL = "https://oauth2.googleapis.com/tokeninfo?id_token="+id_token;
        String email="";
        try {
            URL url = new URL(reqURL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setDoOutput(true);
 
            int responseCode = conn.getResponseCode();
            System.out.println("responseCode : " + responseCode);
 
            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = "";
            String result = "";
 
            while ((line = br.readLine()) != null) {
                result += line;
            }
            System.out.println("response body : " + result);
 
            JsonParser parser = new JsonParser();
            JsonElement element = parser.parse(result);
            email = element.getAsJsonObject().get("email").getAsString();
 
 
            br.close();
 
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return email;
    }
 
}
cs

 

  • 결과 - reqURL 에 따라 이메일, 프로필, 토큰 만료시간 등 여러 유저 정보 확인 가능!
1
2
responseCode : 200
response body : {  "iss": "https://accounts.google.com",  "azp": "229511140118-31d4vp160c7dd1ld4g27180fmq1qesg8.apps.googleusercontent.com",  "aud": "229511140118-31d4vp160c7dd1ld4g27180fmq1qesg8.apps.googleusercontent.com",  "sub": "111729148537838756755",  "email": "wndusx1@gmail.com",  "email_verified": "true",  "at_hash": "A8Ox4XOkj2nUtiMURhTxPg",  "iat": "1629295697",  "exp": "1629299297",  "alg": "RS256",  "kid": "6ef4bd908591f697a8a9b893b03e6a77eb04e51f",  "typ": "JWT"}
cs

'정리 > Spring' 카테고리의 다른 글

[Spring] DI & AOP  (0) 2021.09.24
[Spring] REST API  (0) 2021.09.09
[Spring] Spring MVC  (0) 2021.09.09
[Spring] Spring이란?  (0) 2021.09.09
[Spring Boot] 이메일 인증 회원가입하기 구현  (2) 2021.07.23

현재 진행하고 있는 프로젝트에서 사용하고 있는 이메일을 이용해 회원가입을 진행할 수 있도록 구현했습니다.

네이버, 구글, 다음 등의 가입되어있는 이메일을 이용하여 해당 이메일로 인증번호를 보낸 뒤,

인증번호가 일치한 경우 회원가입이 될 수 있도록 했습니다.

 

저는 구글 메일을 이용해 구현했습니다.

 

 
 

구현 순서

1. dependency 추가

1. properties 추가

2. EmailConfig 추가

3. EmailService 추가

4. EmailServiceImpl 추가

5. Controller 추가

 

구현하기 전 간단한 설정이 필요합니다!!

 

https://www.google.com/settings/security/lesssecureapps에서 보안 수준이 낮은 앱의 액세스 활성화

활성화를 해줘야 메일을 받을 수 있어요!

 

dependency 추가

build.gradle

1
2
3
dependencies {
     implementation 'org.springframework.boot:spring-boot-starter-mail'
}
cs

 

 

properties

email.properties

1
2
3
4
5
6
7
8
9
10
11
mail.smtp.auth=true
mail.smtp.starttls.required=true
mail.smtp.starttls.enable=true
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.fallback=false
mail.smtp.port=465
mail.smtp.socketFactory.port=465
 
#admin 구글 계정
AdminMail.id = 
AdminMail.password =
cs

 

 

EmailConfig

EmailConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import java.util.Properties;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
 
 
@Configuration
@PropertySource("classpath:email.properties")
public class EmailConfig {
 
    @Value("${mail.smtp.port}")
    private int port;
    @Value("${mail.smtp.socketFactory.port}")
    private int socketPort;
    @Value("${mail.smtp.auth}")
    private boolean auth;
    @Value("${mail.smtp.starttls.enable}")
    private boolean starttls;
    @Value("${mail.smtp.starttls.required}")
    private boolean startlls_required;
    @Value("${mail.smtp.socketFactory.fallback}")
    private boolean fallback;
    @Value("${AdminMail.id}")
    private String id;
    @Value("${AdminMail.password}")
    private String password;
 
    @Bean
    public JavaMailSender javaMailService() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
        javaMailSender.setHost("smtp.gmail.com");
        javaMailSender.setUsername(id);
        javaMailSender.setPassword(password);
        javaMailSender.setPort(port);
        javaMailSender.setJavaMailProperties(getMailProperties());
        javaMailSender.setDefaultEncoding("UTF-8");
        return javaMailSender;
    }
    private Properties getMailProperties()
    {
        Properties pt = new Properties();
        pt.put("mail.smtp.socketFactory.port", socketPort);
        pt.put("mail.smtp.auth", auth);
        pt.put("mail.smtp.starttls.enable", starttls);
        pt.put("mail.smtp.starttls.required", startlls_required);
        pt.put("mail.smtp.socketFactory.fallback",fallback);
        pt.put("mail.smtp.socketFactory.class""javax.net.ssl.SSLSocketFactory");
        return pt;
    }
}
cs

 

 

EmailService

EmailService.java

1
2
3
public interface EmailService {
    String sendSimpleMessage(String to)throws Exception;
}
cs

 

EmailServiceImpl

EmailServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import java.util.Random;
 
import javax.mail.Message.RecipientType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
 
@Service
public class EmailServiceImpl implements EmailService{
 
    @Autowired
    JavaMailSender emailSender;
 
    public static final String ePw = createKey();
 
    private MimeMessage createMessage(String to)throws Exception{
        System.out.println("보내는 대상 : "+ to);
        System.out.println("인증 번호 : "+ePw);
        MimeMessage  message = emailSender.createMimeMessage();
 
        message.addRecipients(RecipientType.TO, to);//보내는 대상
        message.setSubject("Babble회원가입 이메일 인증");//제목
 
        String msgg="";
        msgg+= "<div style='margin:100px;'>";
        msgg+= "<h1> 안녕하세요 Babble입니다. </h1>";
        msgg+= "<br>";
        msgg+= "<p>아래 코드를 회원가입 창으로 돌아가 입력해주세요<p>";
        msgg+= "<br>";
        msgg+= "<p>감사합니다!<p>";
        msgg+= "<br>";
        msgg+= "<div align='center' style='border:1px solid black; font-family:verdana';>";
        msgg+= "<h3 style='color:blue;'>회원가입 인증 코드입니다.</h3>";
        msgg+= "<div style='font-size:130%'>";
        msgg+= "CODE : <strong>";
        msgg+= ePw+"</strong><div><br/> ";
        msgg+= "</div>";
        message.setText(msgg, "utf-8""html");//내용
        message.setFrom(new InternetAddress("properties email쓰세용!","Babble"));//보내는 사람
 
        return message;
    }
 
    public static String createKey() {
        StringBuffer key = new StringBuffer();
        Random rnd = new Random();
 
        for (int i = 0; i < 8; i++) { // 인증코드 8자리
            int index = rnd.nextInt(3); // 0~2 까지 랜덤
 
            switch (index) {
                case 0:
                    key.append((char) ((int) (rnd.nextInt(26)) + 97));
                    //  a~z  (ex. 1+97=98 => (char)98 = 'b')
                    break;
                case 1:
                    key.append((char) ((int) (rnd.nextInt(26)) + 65));
                    //  A~Z
                    break;
                case 2:
                    key.append((rnd.nextInt(10)));
                    // 0~9
                    break;
            }
        }
 
        return key.toString();
    }
    @Override
    public String sendSimpleMessage(String to)throws Exception {
        // TODO Auto-generated method stub
        MimeMessage message = createMessage(to);
        try{//예외처리
            emailSender.send(message);
        }catch(MailException es){
            es.printStackTrace();
            throw new IllegalArgumentException();
        }
        return ePw;
    }
 
}
cs

★ 저는 인증코드 일치 여부를 비교해주기 위해서 sendSimpleMessage() 메서드를 void가 아닌 String으로 만들어주었습니다!

 

 

Controller 

Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@PostMapping("/emailConfirm")
    @ApiOperation(value = "회원 가입시 이메인 인증", notes = "기존사용하고 있는 이메일을 통해 인증")
    @ApiResponses({
            @ApiResponse(code = 200, message = "성공"),
            @ApiResponse(code = 401, message = "인증 실패"),
            @ApiResponse(code = 404, message = "사용자 없음"),
            @ApiResponse(code = 500, message = "서버 오류")
    })
    public ResponseEntity<extends BaseResponseBody> emailConfirm(
            @RequestBody @ApiParam(value="이메일정보 정보", required = trueString email) throws Exception {
 
        String confirm = emailService.sendSimpleMessage(email);
 
        return ResponseEntity.status(200).body(BaseResponseBody.of(200, confirm));
    }
cs

★ 위에서 말했다시피, 인증코드 일치여부 확인을 위해 성공코드와 함께 인증코드 return

 

 

메일 확인

 

 

참고한 블로그

https://badstorage.tistory.com/38

'정리 > Spring' 카테고리의 다른 글

[Spring] DI & AOP  (0) 2021.09.24
[Spring] REST API  (0) 2021.09.09
[Spring] Spring MVC  (0) 2021.09.09
[Spring] Spring이란?  (0) 2021.09.09
[Spring Boot + Vue.js] 구글로그인  (0) 2021.08.18

+ Recent posts