OAuth2.0 / SSO / JWT (항해일지 19일차)

김형준·2022년 5월 27일
0

TIL&WIL

목록 보기
19/45
post-thumbnail

1. 학습일지

🔗 학습 코드 GitHub

1) OAuth2.0

  • 다양한 플랫폼 환경에서 권한 부여를 위한 산업 표준 프로토콜이다.
  • 쉽게 말해 인증과 권한을 획득하는 것인데, 이러한 방식은 third party Application에 아이디와 비밀번호를 제공하고 싶지 않은 요구에 의해 탄생되었다.
  • 다시 말해 인터넷 사용자들이 비밀번호를 제공하지 않고
    다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의
    접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는,
    접근 위임을 위한 개방형 표준이다.
  • 사용자가 애플리케이션에게 모든 권한을 넘기지 않고 사용자 대신 서비스를 이용할 수 있게 해주는 HTTP 기반의 보안 프로토콜이다.

2) Kakao 계정 로그인 구현하기 (OAuth2.0)

  1. 카카오 developers에 앱(웹) 정보 등록하기
  2. 카카오 인가 코드 받아오기
https://kauth.kakao.com/oauth/authorize?client_id={REST_API_KEY}&redirect_uri={REDIRECT_URI}&response_type=code
  1. 카카오 developers에 등록한 콜백 URI대로 API 작성하기
  • Controller는 콜백의 query에 담긴 code @RequestParam으로 받고 서비스로 넘겨주며, 루트로 리다이렉트한다.

  • Service는 크게 두가지 기능을 순차적으로 실행한다.(절차적 프로그래밍)

    • 1) 인가 코드액세스 토큰 요청
      • 서버 대 서버 간 요청은 RestTemplate을 사용한다.
      • header와 body에 필요한 값을 담아주고 요청을보낸다.
      • 보낸 요청은 JSON 형태의 ResponseEntity로 돌아온다.
      • 이를 ObjectMapper를 사용하여 JsonNode로 파싱하고
      • 파싱된 JSON에서 "access_token"을 추출한다.
*ObjectMapper provides functionality for reading and writing JSON, 
either to and from basic POJOs (Plain Old Java Objects), 
or to and from a general-purpose JSON Tree Model (JsonNode), 
as well as related functionality for performing conversions.
  • 2) 받아온 액세스 토큰으로 카카오 API 호출
    • header에 토큰을 담아주고 HTTP 요청을 보낸다.
    • 역시 JSON 타입의 Response가 돌아오는데, body에 id, nickname, email 등 원하는 정보가 담겨있다.
    • 하나씩 추출하여 변수에 담아주고 사용한다.
  1. 리팩토링
  • service에 절차적 프로그래밍 방식으로 작성한 코드들 함수화하기.
  • 계속해서 new연산을 통해 생성하는 객체들 @Bean으로 등록하여 재사용하기
    • HttpHeaders , RestTemplate, ObjectMapper를 WebSecurityConfig에서 빈으로 등록한다.
    • 이 후 service의 멤버변수로 넣어주고, @Autowired 붙은 생성자에 넣어줘 DI한다.
    • 복습해보자면, 생성자에 new를 넣어 생성할 때마다 new를 해주는 것은 강한결합의 형태다.
    • 따라서 등록된 빈을 가져와 사용하며(@Autowired를 통한 DI) 느슨한 결합의 형태로 바꾸는 것.
    • 멤버 변수를 갖다 쓰며 재사용성을 높이고, 메모리 낭비를 줄인다.
    • ❗❗❗ 단, 계속해서 재사용하기 때문에 headers의 경우 보내는 로직에 clear()를 추가해줘야 한다.
    @Bean
    public HttpHeaders httpHeaders() {
        return new HttpHeaders();
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }
  1. 카카오 사용자 정보로 회원가입 설계하기
  • 테이블 설계 옵션

    • 1) 카카오 User 를 위한 테이블 (ex. KakaoUser) 을 하나 더 만든다
      • 장점: 결합도가 낮아짐: 성격이 다른 유저 별로 분리하여 각 테이블의 변화가 서로 영향을 주지 않는다.
      • 단점: 구현 난이도가 올라감: 기능 구현 시 회원 별로 다른 테이블을 참조해야 한다.
    • 2) 기존 회원 테이블에 카카오 User 추가하기
      • 장점: 구현이 단순해짐
      • 단점: 결합도가 높아진다: 폼 로그인을 통해 카카오 로그인 사용자의 username, password로 입력해서 로그인한다면?
  • 2번 째 방식을 택하여 구현했다.

  • 문제는 username부분에 닉네임을 넣어서 카카오에서 같은 닉네임을 사용 시 중복된다.

    • 즉, 중복을 허용하지 않는 username의 특성 상 올바르지 않은 타입이었다.
    • 나는 이를 이메일의 아이디와 도메인 값을 섞어서 대체하였다.
    • makeUserNameForKakao(email); 구현부.
    private String makeUserNameForKakao(String email) {
          String[] emailArr = email.split("@");
          String emailId = emailArr[0];
          String[] domainArr = emailArr[1].split(".");
          String domainVal = Character.toString(domainArr[0].charAt(0))
                  + Character.toString(domainArr[0].charAt(domainArr[0].length()-1));
    
          return emailId + domainVal;
      }
  • DB에서 해당 kakaoId를 가진 회원 정보가 없다면 카카오에서 받은 정보를 기반으로 회원가입.

  1. 카카오 로그인 시 강제 로그인 구현
  • 로그인 성공 시 사용자 정보(UserDetails)는 SecurityContext에 저장된다.
    • (User ,UserDetails , UsernamePasswordAuthenticationToken , SecurityContext 작은 순서대로 나열)
// 강제 로그인 처리
UserDetails userDetails = new UserDetailsImpl(kakaoUser);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
  • 처리 과정
    • 1) UserDetails 생성하여 파라미터에 User 넣어주기
    • 2) UsernamePasswordAuthenticationToken 생성하여 파라미터에 UserDetails, null(credentials), userDetails.getAuthorities() 넣기
    • 3) SecurityContextHolder에서 getContext()로 컨텍스트 가져와서 UsernamePasswordAuthenticationToken set해주기

3) JWT 로그인 구현하기

  • 배경 지식

    • 서버가 1대인 경우 : Session1이 모든 Client의 정보를 소유한다.

    • 서버가 2대 이상인 경우: 서버의 대용량 트래픽을 처리하기 위해 서버 2대 이상 운용하는 경우

      • 이 경우 Session마다 다른 Client 정보를 소유하고 있을 수 있다.
      • 만약 Client가 본인의 정보가 저장되지 않은 세션에 로그인 요청을 하면 로그인 실패 발생
      • 해결 방법은 크게 2가지인데,
        • Sticky Session: Client 마다 요청하는 Server를 고정한다. 그러나 로드밸런서의 사용 목적이 사라진다.
        • Session Storage 생성: 모든 세션에 저장되는 Client 정보를 저장하는 세션 저장소를 생성한다.

    • 세션 저장소 생성

      • Session storage 가 모든 Client 의 로그인 정보 소유

  • JWT 사용!

    • 로그인 정보를 Server 에 저장하지 않고, Client 에 로그인정보를 JWT 로 암호화하여 저장 → JWT 통해 인증/인가

    • 모든 서버에서 동일한 Secret Key 소유

    • Secret Key를 통한 암호화 / 위조 검증 (복호화 시)

    • 장점

      • 동시 접속자가 많을 경우 서버 측 부하를 낮춘다.
      • Client, Server가 다른 도메인을 사용할 때 ( 카카오 OAuth2 로그인도 JWT를 사용한다.)
        -> 도메인이 다르면 CORS 문제가 생길 수도 있는데 JWT는 이를 해결할 수 있다.
    • 단점

      • 구현의 복잡도 증가
      • JWT에 담은 내용이 커진다면 네트워크 비용이 증가한다. (클라이언트 -> 서버)
      • 기생성된 JWT를 일부만 만료시킬 방법이 없다.
      • Secret Key 유출 시 JWT 조작 가능

  • JWT 구조

  • HEADER / PAYLOAD / VERIFY SIGNATURE

    • HEADER: 사용한 알고리즘의 종류
    • PAYLOAD: 토큰에 담기는 데이터
    • VERIFY SIGNATURE: 점(.)을 구분자로 해서 헤더와 페이로드를 합친 문자열을 서명한 값이다. 헤더에 정의된 알고리즘과 비밀 키를 이용해 생성하고 Base64 URL-Safe로 인코딩한다.

  • JWT 사용 흐름

    • 1) Client가 username, password로 로그인 성공 시

      • 로그인 정보 → JWT 로 암호화 (Secret Key 사용)
      • JWT 를 Client 응답에 전달
      • Client 에서 JWT 저장 (쿠키, Local storage 등)
    • 2) Client 에서 JWT 통해 인증하는 방법

      • JWT 를 API 요청 시마다 Header 에 포함
        Content-Type: application/json
        Authorization: Bearer

      • Server에서 Client 가 전달한 JWT 위조 여부 검증 (Secret Key 사용) / JWT 유효기간이 지나지 않았는지 검증

      • 검증 성공시, JWT → "로그인 정보" (UserDetailsImpl) 만들어 사용


  • JWT 로그인 이해
    • 1) JWTAuthFilter: API 요청 Header에 전달되는 JWT 유효성 인증
      • 모든 API에 대해 JWTAuthFilter가 JWT 확인
      • 로그인 전 허용이 필요한 API는 예외처리 필요 -> FilterSkipMatcher
    • 2) FormLoginFilter: 회원 폼 로그인 요청 시 username / password 인증

2. 코멘트

  • 오늘은 스프링 심화 강의 2주차까지 수강했다. OAuth2.0 방식으로 카카오 계정으로 내가 만든 애플리케이션에 로그인하는 것을 구현해봤는데, 신선한 경험이었다. 로우코드 플랫폼으로 SSO를 구현할 때는 SAML 방식으로 구현했었는데, OAuth 방식으로도 구현해보며 조금 더 공부한 것 같다.
  • 아직 이해만 한 수준이지 자유 자재로 스프링 시큐리티 부터 SSO까지 구현 가능한 수준은 아닌 것 같다. 복습하고 응용해보며 익숙해지자
  • 추가적으로 마지막 강의에서 JWT에 대한 설명과 더불어 코드를 주셨는데, 논리 흐름에 따라 해독해나가는건 과제인 것 같다. 한참을 여러 파일을 읽어보며 해석해보려 했지만, 애초에 어떠한 논리 흐름인지 파악 못해서 불가능했다.
  • 내일은 3주차 강의를 시작해보며, JWT에 대한 공부를 마저 할 예정이다.
  • 목표는 나만의 셀렉샵에 스프링 시큐리티 + JWT 적용 시키기! 화이팅!
profile
BackEnd Developer

0개의 댓글