ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 앵귤러, 스프링부트를 이용한 Oauth2 회원가입 문제
    Spring Framework 2021. 6. 23. 23:44
    반응형

    1. 개발환경

    프론트앤드 - 앵귤러2, 타입스크립트

    백엔드 - 스프링부트

    DB - JPA, MySQL

     

    2. 카카오 API를 활용한 Oauth2 회원가입 과정

     

    카카오 API를 활용한 Oauth2 회원가입은 사실 간단합니다. 

     

    1. 인가(인증) 코드 request/resposne
    2. 발급된 인가 코드로 Access Token request/response
    3. Token에서 사용자 정보 추출 후 회원가입
    4. Token 유효성 검증 --> 사용자 정보 추출 --> DB 검증 (로그인 시)

     

    결국 REST API를 바탕으로 요청하고 받고, 반복입니다. 

     

    3. 문제점

    카카오 Oauth2의 경우 인가(인증) 코드의 경우 발급 후 반드시 'redirect uri'를 입력해야 합니다. 인가 코드를 받을 클라이언트 혹은 서버가 반드시 존재해야 합니다. 여기서 해당하는 서버, 클라이언트의 주소는 redirect uri에 해당합니다. 

     

    기존 애플리케이션의 회원가입 과정입니다. 

     

    1. 클라이언트 "인가 코드 요청 위한 URI" request --> 서버 핸들러1
    2. 핸들러1 URI response --> 클라이언트에서 받은 후 window.open
    3. 카카오 API에서 인가 코드 발행 후 redirect --> 서버 핸들러2 (get)
    4. 회원가입 http post 위해 인가 코드 전달 RestTemplate.postForEntity(인가코드) --> 서버 핸들러3
    5. 회원가입 로직

    여기서 문제점은 카카오 API가 응답을 요청한 클라이언트로 하는 게 아니라, redirect uri를 통해서 새로운 주소로 합니다. 그래서 최초 요청한 클라이언트에서는 아무런 response를 받지 못하고 있었습니다. 

     

    4. 기존 코드

    클라이언트 (component.ts)

      // 카카오 회원 가입 합니다
      signUpKakaoButton(): Promise<any> {
        return this.http.get<ApiResponseSingle>(this.getKakaoAuthCode)
          .toPromise()
          .then(async response => { 
            window.open(response.data);
            // 카카오 redirect uri 때문에, 응답이 없습니다.
            return this.http.get(this.home);
          });
      }

    서버

    	@GetMapping(value = "/kakaoAuthCode")
        public ResponseEntity<SingleResult> kakaoSignUpButton() { // 카카오 인가 코드 URI 응답
            // 생략
            // loginUrl - 카카오 인가 코드에 필요한 URI 클라이언트로 응답
            return new ResponseEntity<>(responseService.getSingleResult(loginUrl), HttpStatus.OK);
        }
    
        @GetMapping(value = "/kakaoAuthCode/return")
        public ResponseEntity<String> authCodeReturn(@ApiParam(value = "카카오 인가 코드 (authorization code)", required = true) @RequestParam String code) {
        	// 생략
            // 클라이언트에서 인가 코드 요청 후 발급 되는 redirect uri 핸들러
            // 회원가입 위한 핸들러로 다시 넘깁니다. http post.
            ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
            return response;
        }
        @PostMapping(value = "/signUp/kakaoAuthCode")
        public ResponseEntity<CommonResult> signUpByKakaoAccessToken(@ApiParam(value = "카카오 인가 코드 (authorization code)", required = true) @RequestParam String code) {
    
            // 인가 코드를 바탕으로 access tocken 가지고 옵니다.
            String access_token = kakaoService.getKakaoTokenInfo(code).getAccess_token(); 
            
            // 만들어진 access tocken으로 회원 정보 가지고 옵니다.
            KakaoProfile profile = kakaoService.getKakaoProfile(access_token); 
    
            // Access Token 바탕으로 회원 정보 추출 후 검증 
            // 생략
            
            // ResponstEntity 응답을 보내지만, 받는 곳이 없습니다..
            return new ResponseEntity<>( 
                    responseService.getSuccessCreated(),
                    HttpStatus.CREATED
            );
        }

     

     

    5. 해결방법

    우연히 '생활코딩' 페이스북 그룹에서 'ActivatedRoute' 클래스를 알게 되었습니다.

     

    기본적으로 Angular2의 Router는 요청한 URL에 따라서 어떠한 페이지를 보여줄지 정하는 경로입니다. 그리고 'ActivatedRoute'는 현재 동작하는 Router의 인스턴스입니다. 

     

    왜 이것이 필요할까요?

     

    기본적으로 카카오에서 인가 코드를 URL의 query string에 파라미터로 보냅니다. 즉 서버에서 받던, 클라이언트 측에서 바로 받던 파라미터를 파싱 할 수 있으면 됩니다. 

     

    그렇기 때문에 서버에서 받으면 클라이언트에서는 응답을 받지 못하기 때문에, 클라이언트에서 인가 코드를 받아서 서버로 보내면 됩니다. 덕분에 클라이언트의 request, 서버의 response 구조가 자연스럽게 이루어집니다. 

     

    개선된 카카오 Oauth2 회원가입 과정

     

    1. 카카오 인가 코드 --> 클라이언트 
    2. "ActivatedRoute" 사용해서 클라이언트에서 URL query string 파싱
    3. 파싱 된 인가 코드 --> 서버로 request
    4. 서버에서 인가 코드를 바탕으로 Access Token 및 회원가입 로직 --> 클라이언트 response

     

    6. 개선된 코드

    클라이언트 (component.ts - redirect uri 목적지)

    // 생략 
    import {ActivatedRoute} from '@angular/router';
    
     @Component({ // 생략 })
     export class KakaoAuthComponent implements OnInit {
       private paramCode: string; // 카카오 인가 코드 저장
    
       constructor(private signService: SignService,
                   private route: ActivatedRoute) { }
    
       // 카카오 인가 코드(paramCode) 바탕으로 회원가입 메소드 입니다.
       signUpKakaoAuth(): any {
         this.signService.signUpKakaoAuth(this.paramCode);
       }
    
       // 카카오 API로 부터 받은 URL 파라미터에서 'code' 파싱 합니다.
       ngOnInit(): void {
         this.route.queryParams.subscribe(params => { // private route: ActivatedRoute
           this.paramCode = params['code']; // 'code'라는 이름의 파라미터를 파싱 후 저장
         });
         this.signUpKakaoAuth();
       }
     }

     

    클라이언트 (service.ts)

      /*
       * 카카오 회원가입 버튼 클릭 시 동작 합니다.
       * 서버에게 '카카오 인가코드 요청 URI'를 요청 합니다.
       */
      getKakaoAuthCode(): Promise<any> {
        return this.http.get<ApiResponseSingle>(this.getKakaoAuthCodeUrl)
          .toPromise()
          .then(response => {
            window.open(response.data);
          });
      }
    
      /*
       * 카카오 API가 발급한 인가 코드를 바탕으로 회원가입 핸들러(서버) 요청합니다.
       */
      signUpKakaoAuth(paramCode: string): Promise<any> {
        const params = new FormData();
        params.append('code', paramCode); // 인가코드
        return this.http.post(this.signUpKakaoAuthcode, params)
          .toPromise()
          .then(response => {
            alert(response + '성공!');
          });
      }

     

    서버의 코드는 인가 코드 URI 요청 후 받는 핸들러만 없어지고, 동일합니다.

    반응형

    'Spring Framework' 카테고리의 다른 글

    Redis, SpringBoot 연동하기  (0) 2021.07.16
    Entity, DTO 그리고 @Service  (0) 2021.07.13
    하이버네이트의 네이밍 전략  (0) 2021.04.29
    [Spring Framework]스프링 커맨드 객체  (0) 2021.01.01
    서블릿이란?  (0) 2020.09.15

    댓글

Designed by Tistory.