SEB_BE 67일차 - OAuth2 인증 2

subimm_·2022년 11월 30일
0

코드스테이츠

목록 보기
67/83

💡 오늘의 학습목표

  • OAuth2 샘플 애플리케이션 구현
  • OAuth2와 JWT를 이용한 샘플 애플리케이션 구현

📔 OAuth2 인증을 위한 사전 작업

📖 구글 API 콘솔에서의 OAuth2 설정

  • 구글 API 및 서비스 콘솔에서 OAuth2 시스템을 이용하기 위한 클라이언트 ID와 Secret 생성
    https://console.cloud.google.com/apis
    1. 프로젝트 생성

    2. OAuth2 동의 화면 만들기
  • 앱정보 - 앱이름, 사용자이메일, 개발자이메일 작성
    3. 사용자 인증 정보 생성
  • 사용자 인증 정보 만들기 - OAuth 클라이언트 ID
  • 클라이언트 ID 비밀번호 보관

📔 Hello, OAuth2 샘플 애플리케이션 구현

  • SSR 방식

📖 의존성 추가

  • (1) HTML 화면을 구성하기 위한 템플릿인 타임리프를 추가

📖 보호된 웹 페이지

  • SSR 방식의 웹 애플리케이션은 HTML로 렌더링되는 페이지가 존재
    샘플 애플리케이션은 OAuth2 인증을 통해 보호되는 간단한 HTML 페이지 포함되어 있다.
  • HelloHomeController
  • hello-oauth2 화면에 대한 뷰를 리턴하는 코드
    SSR 방식의 핸들러 메서드의 리턴 타입이 String 이면 뷰 이름을 리턴하며, 최종적으로 hello-oauth2.html을 웹 브라우저로 전송

📖 OAuth2 인증을 위한 SecurityConfiguration 설정

✔ Spring Boot의 자동 구성을 이용한 OAuth2 인증 설정

  • Spring Boot의 자동 구성을 이용한 방법
  • SecurityConfiguration(V1)
  • (1) 인증된 request에 대해서만 접근하도록 추가
  • (2) OAuth2 로그인 인증 활성화

✔ OAuth2 클라이언트 등록 정보 추가

  • application.yml
  • (1) OAuth2 클라이언트 생성 시, 클라이언트 ID
  • (2) 클라이언트 보안 비밀번호, 클라이언트 Secret
  • 실무에서는 시스템 환경 변수에 설정

✔ 실행 후 접속

✔ Configuration을 통한 OAuth2 인증 설정

  • 자동 구성을 통한 설정 뿐만 아니라, Configuration을 통해 Bean을 등록함으로써 OAuth2의 인증을 설정할 수 있다.
  • SecurityConfiguration(V2)
  • (1), (2) application.yml 파일에 설정되어 있는 구글의 ID와 Secret을 로드
  • (3) ClientRegistrationRepository 를 Bean으로 등록
    • Spring Boot 자동 구성 기능을 이용할 경우, 내부적으로 Bean이 생성되지만, 지금은 Configuration을 통해 직접 등록
    • (3-1) private 메서드인 clientRegistration()을 호출하여 인스턴스를 리턴 받는다.
    • (3-2) 구현 클래스인 InMemoryClientRegistrationRepository의 인스턴스를 생성
  • (4) ClientRegistration 인스턴스를 생성하는 private 메서드
    • (4-1) Spring Security에서는 CommonOAuth2Provider enum 제공. 내부적으로 빌더 패턴을 이용해 ClientRegistration 인스턴스를 제공하는 역할을 한다.
    • ClientRegistration은 OAuth2 Client에 대한 등록 정보를 표현하는 객체
      구글의 API 콘솔에서 등록했던 정보가 포함

📖 인증된 Authentication 정보 확인

  • 구글의 OAuth2 인증이 성공적으로 수행 되었는지 최종 확인
  • 인증이 정상 수행되면 SecurityContext에 인증된 Authentication이 저장되는 특성

1. SecurityContext를 이용하는 방법

  • HomeController(V2)
  • (1) 인증된 Authentication 객체를 통해 Principal 객체를 얻는다.
    OAuth2로 로그인 인증을 수행했으므로 Principal은 OAuth2User 객체로 캐스팅 가능
  • (2) OAuth2User 객체에 저장되어 있는 사용자 정보 중 사용자의 이메일 정보를 얻고 있다.
  • 인증 후, 정상적으로 이메일 주소가 출력된다면 인증 성공

2. Authentication 객체를 핸들러 메서드 파라미터로 전달 받는 방법

  • HomeController(V3)

3. OAuth2User를 파라미터로 전달 받는 방법

  • HomeController(V4)

📖 Authorization Server로부터 전달 받은 Access Token 확인

1. OAuth2AuthorizedClientSevice를 DI 받는 방법

  • HomeController(V5)
  • OAuth2AuthorizedClientService 는 권한을 부여받은 Client 를 관리하는 역할
    이것을 이용하여 OAuth2AuthorizedClient가 보유하고 있는 Access Token에 접근할 수 있기 때문에 (1) 같이 DI 받는다.
  • (2) OAuth2AuthorizedClient 객체를 로드 . loadAuthorizedClient() 를 호출하면 내부적으로 Repository에서 OAuth2AuthorizedClient 를 조회한다.
  • (3) AccessToken 객체를 얻는다
    • (3-1) Access Token의 문자열 출력
    • (3-2) Token의 타입 출력
    • (3-3) 토큰으로 접근할 수 있는 리소스의 범위 목록 출력
    • (3-4) 토큰의 발행일시
    • (3-5) 토큰의 만료일시
      2. OAuth2AuthorizedClient를 핸들러 메서드의 파라미터로 전달 받는 방법
  • (1) OAuth2AuthorizedClientRepository 에 저장되어 있는 OAuth2AuthorizedClient를 파라미터로 전달 받음.
    • 하나 이상의 핸들러 메서드에서 OAuth2AuthorizedClient 사용해야 한다면 OAuth2AuthorizedClientService 를 DI 받아서 사용하는 것이 바람직함.

📔 OAuth2와 JWT를 이용한 샘플 애플리케이션 구현

  • 프론트엔드와 백엔드가 분리되어 있는 CSR 방식의 애플리케이션에 적용

📖 Frontend와 Backend 간의 OAuth 2 인증 처리 흐름

  • (1) 리소스 오너가 웹 브라우저에서 Google 로그인 링크 클릭
  • (2) Frontend애플리케이션에서 Backend애플리케이션의 http://localhost:8080/oauth2/authorization/google로 request를 전송,
    이 URI의 request는 OAuth2LoginAuthenticationFilter 가 처리
  • (3) Google의 로그인 화면을 요청하는 URI로 리다이렉트한다.
    Authorization Server가 백엔드 애플리케이션쪽으로 Authorization Code를 전송할 리다이렉트 URI(http://localhost:8080/login/oauth2/code/google) 를 쿼리 파라미터로 전달
    리다이렉트 URI는 Spring Security가 내부적으로 제공
  • (4) Google 로그인 화면 오픈
  • (5) 리소스 오너가 구글 로그인 인증 정보를 입력하여 로그인
  • (6) 로그인 성공하면 (3)에서 전달한 백엔드 리다이렉트 URI로 Authorization Code를 요청
  • (7) Authorization Sever가 백엔드 애플리케이션에게 Authorization Code를 응답으로 전송
  • (8)백엔드 애플리케이션이 Authorization Sever에게 Access Token을 요청
  • (9) Authorizatio Server가 백엔드 애플리케이션에게 Access Token을 응답으로 전송
    여기서의 Token은 구글 리소스 서버에게 리소스를 요청하는 용도로 사용
  • (10) 백엔드 애플리케이션이 리소스 서버에게 User Info를 요청
  • (11) 리소스 서버가 백엔드 애플리케이션에게 User Info를 응답으로 전송
  • (12) 백엔드 애플리케이션은 JWT로 구성된 Access Token과 Refresh Token을 생성한 후, 프론트엔드 애플리케이션에게 JWT(Access Token과 Refresh Token)를 전달하기위해 프론트엔드 애플리케이션(http://localhost?access_token={jwt-access-token}&refresh_token={jwt-refresh-token}) 으로 리다이렉트 한다.
  • (6) ~ (11) 까지는 Spring Security에서 내부적으로 처리해줌.

📖 Frontend 애플리케이션 준비

1. 아파치 웹서버 설치

  • 아파치 웹서버 다운로드 https://www.apachelounge.com/download/
  • Apache24 디렉토리를 C:\ 디렉토리로 이동
  • httpd.conf 파일을 오픈하여 SeverName 주석 해제 후 수정
    ServerName localhost:80
  • ApacheMonitor.exe 를 클릭하여 웹서버 실행, 바탕화면 오른쪽 하단 빠른 실행창에서 실행/ 중지 가능
  • http://localhost 로 접속하면 웹서버 실행

2. Frontend 샘플 애플리케이션을 아파치 웹 서버에 배포
C:\Apache24\htdocs 디렉토리로 위치

  • index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>OAuth2 + JWT Frontend</title>
</head>
<body>
    <h2>Welcome to OAuth 2.0 + JWT Spring Security</h2>
    <a href="http://localhost:8080/oauth2/authorization/google">Google로 로그인</a>
</body>
</html>
  • Google로 로그인 버튼을 클릭하면 백엔드 애플리케이션으로 request가 전송되고 로그인 화면 오픈
  • receive-token.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>OAuth2 + JWT My page</title>
</head>
<body>
    <script type="text/javascript">
        let accessToken = (new URL(location.href)).searchParams.get('access_token');
        let refreshToken = (new URL(location.href)).searchParams.get('refresh_token');

        localStorage.setItem("accessToken", accessToken)
        localStorage.setItem("refreshToken", refreshToken)

        location.href = 'my-page.html'
    </script>
</body>
</html>
  • 백엔드 애플리케이션에서 전달 받은 jWT Access Token과 Refresh Token을 웹 브라우저의 LocalStorage에 저장한 후 , my-page.html로 이동

  • my-page.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>OAuth2 + JWT My page</title>
</head>
<body>
    <h2>My Page</h2>
    <h3>아래의 토큰을 이용해서 Backend 애플리케이션의 리소스를 요청할 수 있습니다.</h3>
    <p>
        <span>Access Token: </span><span id="accessToken" style="color: blue"></span>
    </p>
    <p>
        <span>Refresh Token: </span><span id="refreshToken" style="color: blue"></span>
    </p>
    <script type="text/javascript">
        let accessToken = localStorage.getItem('accessToken')
        let refreshToken = localStorage.getItem('refreshToken');

        document.getElementById("accessToken").textContent = accessToken;
        document.getElementById("refreshToken").textContent = refreshToken;
    </script>
</body>
</html>
  • LocalStorage에 저장된 JWT Access Token과 Refresh Token을 로드해서 웹 브라우저에 표시

📖 Backend 애플리케이션에 OAuth2 인증 기능 적용

1. JwtTokenizer 추가

  • JWT 유닛 코드

2. application.yml 설정

  • (1), (2) scope 값 직접 지정하면 해당 범위만큼의 리소스를 Client(백엔드) 에게 제공

3. JwtVerificationFilter 추가

  • JWT 유닛 코드

4. AuthenricationSuccessHandler 구현

  • OAuth2 인증에 성공하면 호출되는 핸들러
    JWT 생성하고, Frontend쪽으로 JWT를 전송하기 위해 리다이렉트 하는 로직 구현

  • (1) SimpleUrlAuthenticationSuccessHandler 상속하면 리다이렉트를 손쉽게 할 수 있는 getRedirectStrategy().sendRedirect() 와 같은 API 사용 가능

  • (2) 필요한 객체 DI 받기

  • (3) Authentication 객체로부터 얻어낸 OAuth2User 객체로부터 Resource Owner의 이메일 주소 얻기

  • (4) CustomAuthorityUtils 이용해 권한 정보 생성

  • (5) Resource Owner의 이메일 주소를 DB에 저장
    백엔드 애플리케이션의 리소스(커피정보, 주문정보) 와 연관 관계를 맺기 위해 최소한의 정보 관리

  • (6) Access Token과 Refresh Token을 생성하여 Frontend 애플리케이션에게 전달하기 위해 Redirect 한다.

    • (6-1), (6-2) JWT Access Token과 Refresh Token을 생성.
    • (6-3) Frontend 애플리케이션 쪽의 URL 생성, createURI() 메서드에서 UriComponentBuilder 를 이용해 AccessToken과 RefreshToken을 포함한 URL 생성
    • 포트 설정을 하지 않으면 기본값은 80 포트
    • (6-4) sendRedirect() 메서드를 이용해 프론트엔드 애플리케이션쪽으로 리다이렉트 한다.

  • SecurityConfiguration

  • (1) OAuth2 로그인 설정에 .successHandler() 를 통해 인증이 성공한 뒤 실행되는 핸들러 추가
    OAuth2MemberSuccessHandler 객체를 생성하면서 OAuth2MemberSuccessHandler에서 필요한 의존 객체를 DI 하고 있다.

  • (2) JwtVerificztionFilter를 OAuth2LoginAuthenricationFilter 뒤에 추가

5. 기타 회원 정보 등록 및 수정 로직이 수정됨.

  • 레퍼런스 코드

https://github.com/codestates-seb/be-reference-spring-security-oauth2-basic

https://github.com/codestates-seb/be-reference-oauth2-frontend

profile
코린이의 공부 일지

0개의 댓글