4 인증 백엔드 통합

4.1 REST API 인증 기법

4.1.1 Basic 인증

  1. Basic 인증

    • 모든 HTTP 요청에 아이디와 비밀번호를 같이 보내는 것

    • 최초 로그인한 후 HTTP 요청 헤더의 Authorization: 부분에 ‘Basic <ID>:<Password>’처럼 아이디와 비밀번호를 콜론으로 이어붙인 후 Base64로 인코딩한 문자열을 함께 보냄

      Authorization: Basic aGVsb93b3JsZEBnbWFpbC5jbC5jb206MTIzNA==
    • 이 HTTP 요청을 수신한 서버는 인코딩된 문자열을 디코딩해 아이디와 비밀번호를 찾아냄

    • 사용자 정보가 저장된 데이터베이스 또는 인증 서버의 레코드와 비교함

    • 데이터베이스의 레코드는 아이디와 비밀번호가 일치하면 요텅받은 일을 수행하고, 아니면 거부함

  • Basic 인증의 문제점

    • 아이디와 비밀번호를 노출

      • 중간에 누군가 HTTP 요청을 가로채 문자열을 디코딩하면 아이디와 비밀번호를 알아낼 수 있음 (MITM)

      • HTTP와 사용하기엔 취약하여, 반드시 HTTPS와 사용해야 함

    • 사용자를 로그아웃시킬 수 없음

    • 사용자의 계정 정보가 있는 저장 장소, 인증 서버와 인증 DB에 과부하가 걸릴 확률이 높음

      • 서버가 단일 장애점이 됨

    !! 사진사진

4.1.2 토큰 기반 인증

  1. 토큰 기반 인증

    • 토큰(Token)은 사용자를 구별할 수 있는 문자열

    • 토큰은 최초 로그인 시 서버가 만들어 줌.

      • 서버가 자기만의 노하우로 토큰을 만들어 반환하면 클라이언트는 이후 요청에 아이디와 비밀번호 대신 토큰을 계속 넘겨 자신이 인증된 사용자임을 알리는 것

      • 토큰을 기반으로 하는 요청은 헤더에 위와같이 Authorization: Bearer <TOKEN>을 명시

      • 서버는 이 토큰을 받고 어떤 형태로든 인증을 해야 함

      • (토큰 이읆만 바뀐 세션)

    • ex

      • 서버가 랜덤한 문자와 숫자를 섞어 UUID로 토큰을 작성해 넘긴다고 가정

      • 서버는 토큰을 생성해 인증 서버를 통해 저장

      • 요청을 받을 때마다 헤더의 토큰을 서버의 토큰과 비교해 클라이언트를 인증

  • Basic Auth vs 토큰 기반 인증

    • 아이디와 비밀번호를 매번 네크워크를 통해 전송해야 할 필요가 없음

      • 이는 보안 측면에서 좀 더 안전함

    • 서버가 토큰을 마음대로 생성할 수 있으므로 사용자의 인가 정보(예 : User, Admin 등) 또는 유효 시간을 정해 관리 가능

      • 디바이스마다 다른 토큰을 생성해 주고 디바이스마다 유효 시간을 다르게 정하거나 임의로 로그아웃을 할 수도 있음

4.1.3 JSON 웹 토큰

  1. JSON 웹 토큰

    • 서버에서 전자 서명된 토큰을 이용하면 인증에 따른 스케일 문제를 해결 가능

    • {header}.{payload}.{signature}로 구성

      • Header

        • typ : Type을 줄인 말로 토큰의 타입

        • alg : Algorithm을 줄인 말로 토큰의 서명을 발행하는 데 사용된 해시 알고리듬의 종류 의미

      • Payload

        • sub : Subject를 줄인 말로 토큰의 주인 의미. 우리 애플리케이션에서는 사용자의 이메일로 토큰의 주인을 판별함. sub는 ID처럼 유일한 식별자여야 함

        • iss : Issuer를 줄인 말로 토큰을 발행한 주체를 의미

        • iat : issued at을 줄인 말로 토큰이 발행된 날짜와 시간

        • exp : expirarion을 줄인 말로 토큰이 만료되는 시간

      • Sugnature

        • 토큰을 발행한 주체 Issuer가 발행한 서명으로 토큰의 유효성 검사에 사용

  • 토큰 기반 인증 vs JWT 토큰 기반 인증

    • JWT 은 서버가 헤더와 페이로드를 생성한 후 전자 서명을 함

  • 전자 서명

    • {헤더}.{페이로드}와 시크릿키를 이용해 해시 함숫에 돌린 암호화한 결과 값

    • 시크릿키란 나만 알고 있는 문자열, 비밀번호 같은 것

  • JWT 토큰 생성과 인증

    • 생성

      • 최초 로그인 시 서버는 아이디와 비밀번호를 서버에 저장된 아이디와 비밀번호에 비교해 인증

      • 만약 인증된 사용자인 경우 사용자의 정보를 이용해 {헤더}.{페이로드} 부분을 작성

      • 이후 자신의 시크릿키로 {헤더}.{페이로드}.{서명}으로 이어붙이고 Base64로 인코딩한 후 반환

    • 인증

      • 누군가 토큰으로 리소스 접근을 요청

      • 서버는 토큰을 Base64로 디코딩

      • 디코딩해서 얻은 JSON을 {헤더}.{페이로드}와 {서명} 부분으로 나눔

      • 서버는 {헤더}.{헤이로드}와 자신이 갖고 있는 Secret으로 전자 서명을 만들 후 방금 만든 전자 서명을 HTTP 요청이 갖고 온 {서명} 부분과 비교해 토큰의 유효성 검사

    • 이는 인증 서버에 부하를 일으키지 않음. 즉 더 이상 인증 서버가 단일 장애점이 아님

4.2 User 레이어 구현

4.2.1 UserEntity.java

4.2.2 UserRepository.java

4.2.3 UserService.java

4.2.4 UserController.java

4.3 Spring Security 통합

4.2에서 인증 및 인가를 구현하려고 사용자에 관련된 클래스을 작성

  • 문제점

    • 로그인 여부를 저장하지 않음

    • API 들이 사용자를 인증하지 않음

    • 패스워드 암호화하지 않음

4.3.1 JWT 생성 및 반환 구현

  • 토큰 발행 : JWT library

  • 로그인 시 토큰을 반환

JWT를 이용해 인증하는 방법 -> 사진

1.jjwt 라이브러리 디펜던시에 추가

  • security 패키지 생성

    • 인증을 위한 클래스 관리

    • 인가를 위한 클래스 관리

2. 토큰 발행

3. 로그인 시 토큰 반환

4.3.2 스프링 시큐리티와 서블릿 구현

  • API 가 실행될 때마다 사용자를 인증해 주는 부분 구현 : 스프링 시큐리티

  • 스프링 시큐리티

    • 토큰 인증을 위해 컨트롤러 메서드의 첫 부분마다 인증 코드 작성해야하는 문제점

    • 이증과 인가를 위한 다양한 기능 제공

    • 서블릿 필터의 집합

  • 서블릿 필터

    • 서블릿 실행 전에 실행되는 클래스

    • 스프링이 구현하는 디스패처 서블릿이 실행되기 전에 항상 실행

    • 1) 개발자는 서블릿 필터를 구현하고 2) 서플릿 필터를 서블릿 컨테이너가 실행하도록 설정

<서블릿 컨테이너의 서블릿 필터 pic>

  • 서블릿 필터에서 스프링 시큐리티의 위치와 우리가 구현할 필터의 위치

    • 스프링 시큐리티가 FilterChainProxy라는 필터를 서블릿 필터에 끼워 줌

    • 이 FilterChainProxy 클래스 안에서 내부적으로 필터 실행

    • 이 필터들이 스프링이 관리하는 스프링 빈 필터

      • 상속하는 필터는 OncePerRequestFilter

      • 필터 설정하기 위해 상속하는 클래스는 WebSecurityConfigurerAdapter

4.3.3 JWT를 이용한 인증 구현

서블릿 필터 구현

  1. 스프링 시큐리티 디펜던시를 build.gradle에 추가

  1. OncePerRequestFilter를 상속하는 JwtAuthenticationFilter 구현

  • parseBearerToken() : 요청의 헤더에서 Bearer 토큰을 가져옴

  • TokenProvider를 이용해 토큰을 인증하고 UsernamePasswordAuthenticationToken을 작성. 이 오브젝트에 사용자의 인증 정보를 저장하고 SecurityContext에 인증된 사용자 등록

    • 요청을 처리하는 과정에서 사용자가 인증됐는지 여부 혹은 인증괸 사용자가 누군지 알아야 할 때가 있기 때문

  • SecurityContext

    • SecurityContextHolder의 createEmptyContext() 메서드를 이용해 생성

    • 생성한 컨텍스트에 인증 정보인 authentication을 넣고 다시 SecurityContextHolder에 컨텍스트로 등록

    • SecirutyContextHolder는 기본적으로 ThreadLocal에 저장

      • ThreadLocal에 저장되므로 Thread마다 하나의 컨텍스트를 관리할 수 있음

      • 같은 스레드 내라면 어디에서든 접근 가능

4.3.4 스프링 시큐리티 설정

서블릿 컨테이너에 이 서블릿 필터를 사용하라고 알려줘야 함

  • 스프링 시큐리티에 JwtAuthenticationFilter를 사용하라고 알려줘야 함

4.3.5 TodoController에서 인증된 사용자 사용하기

4.3.6 패스워드 암호화

패스워드 암호화 부분은 스프링 시큐리티가 제공하는 BCryptPasswordEncoder 사용

  • UserService

  • BCryptPasswordEncoder는 값은 값을 인코딩하더라도 할 때마다 값이 다르고 패스워드에 랜덤하게 의미 없는 값을 붙여 결과를 생성

  • 이런 의미 없는 값을 보안 용어로 Salt라고 하고, Salt를 붙여 인코딩하는 것을 Salting이라고 함

  • matches() 메서드 : Salt를 고려해 두 값을 비교

  • UserController

Last updated