ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot + Spring Security + JWT + Redis + JPA
    Spring 2021. 4. 25. 01:41

    JPA

    1. 기본 개념

    ORM

    • Object-Relational Mapping
    • 객체와 관계형 데이터 베이스 매핑, 객체와 DB의 테이블이 매핑을 이루는 것
    • 객체가 테이블이 되도록 매핑 시켜주는 프레임워크이다
    • 프로그램의 복잡도를 줄이고 자바 객체와 쿼리를 분리할 수 있으며 트랜잭션 처리나 기타 데이터 베이스 관련 작업들을 좀 더 편리하게 처리할 수 있는 방법
    • 기존 쿼리 : Select * from member;
    • 이를 ORM을 사용하면 Member 테이블과 매핑된 객체가 member라고 할 때, member.findAll()이라는 메서드 호출로 데이터 조회가 가능하다

    JPA란

    • Java Persistence Api(자바 ORM 기술에 대한 API 표준 명세)
    • 한마디로 ORM을 사용하기 위한 인터페이스를 모아둔 것
    • 자바 어플리케이션에서 관계형 데이터 베이스를 사용하는 방식을 정의한 인터페이스이다
    • ORM에 대한 자바 API 규격이며 Hibernate, OpenJPA 등이 JPA를 구현한 구현체이다
    • Hibenate 이외에도 EclipseLink, DataNucleus, OpenJPA, TopLink 등이 있음
    • 결국 인터페이스이기 때문에 JPA를 사용하기 위해서는 JPA를 구현한 Hibernate, EclipseLink, DataNucleus 같은 ORM 프레임 워크를 사용해야 한다

    Hibernate

    • JPA를 사용하기 위해서 JPA를 구현한 ORM 프레임워크 중 하나
    • Hibernate는 JPA 명세의 구현체이다. javax.persistence.EntityManager와 같은 JPA의 인터페이스를 직접 구현한 라이브러리이다.

    JPA를 왜 사용할까

    • 신규 개발시 또는 신규 컬럼이 추가되어 작업을 할 때 등 crud sql을 계속 반복적으로 사용한다
    • 신규 컬럼 하나만 추가되더라도 DTO, DAO, 수정 작업이 매우 반복되는 작업임을 느꼈을 것이다
    • 차츰 데이터 베이스 중심 설계의 단점을 개선하여 효율적으로 개발할 수 있는 방법론에 관심이 많아지기 시작하였다. 이에 객체와 테이블을 매핑시켜주는 ORM이 주목 받기 시작했고, 자바에서는 JPA라는 표준 스펙을 정의하게 됨

    장점
    1) 생산성이 뛰어나고 유지보수가 용이하다.

    • 객체 지향적인 코드로 인해 더 직관적이고 비즈니스 로직에 더 집중할 수 있게 도와준다.
    • 객체지향적으로 데이터를 관리할 수 있기 때문에 전체 프로그램 구조를 일관되게 유지할 수 있다.
    • SQL을 직접적으로 작성하지 않고 객체를 사용하여 동작하기 때문에 유지보수가 더욱 간결하고, 재사용성도 증가하여 유지보수가 편리해진다.
    • DB컬럼이 추가될 때마다 테이블 수정이나 SQL 수정하는 과정이 많이 줄어들고, 값을 할당하거나, 변수 선언등의 부수적인 코드 또한 급격히 줄어든다.
    • 각각의 객체에 대한 코드를 별도로 작성하여 코드의 가독성도 올라간다.

    2) DBMS에 대한 종속성이 줄어든다.

    • DBMS가 변경된다 하더라도 소스, 쿼리, 구현 방법, 자료형 타입 등을 변경할 필요가 없다.
    • 즉 프로그래머는 Object에만 집중하면 되고, DBMS를 교체하는 작업에도 비교적 적은 리스크와 시간이 소요된다.
      특히 요즘은 탈Oracle을 하여 MariaDB 등의 무료, 오픈소스 기반의 DMBS로 변경하는 기업이 늘고 있는데 이럴때 개발자들이 신경쓸 부분이 현저히 줄어든다.

    단점
    1) 어렵다.

    • JPA의 장점을 살려 잘 사용하려면 학습 비용이 높은 편이다.
    • 복잡한 쿼리를 사용해야 할 때에 불리하다.
      업무 비즈니스가 매우 복잡한 경우 JPA로 처리하기 어렵고, 통계처리와 같은 복잡한 쿼리 자체를 ORM으로 표현하는데 한계가 있다.
      (실시간 처리용 쿼리에 더 최적화되어 있다고 봐도 무방할 것이다.)
    • 결국 기존 데이터베이스 중심으로 되어 있는 환경에서는 JPA를 사용하기도 어렵고, 힘을 발휘하기 어렵다.
    • 잘못사용할 경우 실제 SQL문을 직접 작성하는 것보다는 성능이 비교적 떨어질 수 있다.
    • 대용량 데이터 기반의 환경에서도 튜닝이 어려워 상대적으로 기존 방식보다 성능이 떨어질 수 있다.

    결국 업무 환경, 이러한 장단점을 고려하여 Mybatis를 사용할지 JPA를 사용할지 의사결정에 참고하면 좋을 것 같다.

    JPA 설정 방법 (기본 CRUD)

    1. Dependency 추가
    2. Entity 클래스 생성
    3. Repository 클래스 생성
    4. Service 생성
    5. Controller 생성
    6. Properties 설정

    JPA 참고 레퍼런스

    https://gmlwjd9405.github.io/2019/08/04/what-is-jpa.html

    https://engkimbs.tistory.com/category/Spring/Spring%20JPA?page=2


    Spring Security

    보안 용어

    • 접근 주체(Principal) : 보호된 리소스에 접근하는 대상
    • 인증(Authentication) : 보호된 리소스에 접근한 대상에 대해 이 유저가 누구인지, 애플리케이션의 작업을 수행해도 되는 주체인지 확인하는 과정
    • 인가(Authorize) : 해당 리소스에 대해 접근 가능한 권한을 가지고 있는지 확인하는 과정(
      After Authentication)
    • 권한 : 어떠한 리소스에 대한 접근 제한, 모든 리소스는 접근 제어 권한이 걸려있다. 즉 인가 과정에서 해당 리소스에 대한 제한된 최소한의 권한을 가졌는지 확인한다.

    Spring Security란

    • 스프링 시큐리티는 스프링 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다. 주로 서블릿 필터와 이들로 구성된 필터 체인으로의 위임 모델을 사용한다.
    • 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안 관련 로직을 작성하지 않아도 된다.

    Form 기반 로그인 과정

    1. 사용자가 form을 통해 로그인 정보를 입력하고 인증 요청을 보낸다.
    2. AuthenticationFilter가 HttpServletRequest에서 사용자가 보낸 아이디와 패스워드를 인터셉트한다.
      HttpServletRequest에서 꺼내온 사용자 아이디와 패스워드를 진짜 인증을 담당할 AuthenticationManager 인터페이스(구현체 - ProviderManager)에게 인증용 객체(UsernamePasswordAuthenticationToken)로 만들어줘서 위임
    3. AuthenticationFilter에게 인증용 객체를 전달받는다.
    4. 실제 인증을 할 AuthenticationProvider에게 Authentication객체를 다시 전달한다.
    5. DB에서 사용자 인증 정보를 가져올 UserDetailsService 객체에게 사용자 아이디를 넘겨주고 DB에서 인증에 사용할 사용자 정보를 UserDetails라는 객체로 전달 받는다.
    6. AuthenticationProvider는 UserDetails 객체를 전달 받은 이후 실제 사용자의 입력 정보와 UserDetails 객체를 가지고 인증을 시도한다.
    7. 인증이 완료되면 사용자 정보를 가진 Authentication 객체를 SecurityContextHolder에 담은 이후 AuthenticationSuccessHandle을 실행한다.(실패 시 AuthentiationFailureHandler를 실행)

    Security의 Filter들

    • SecurityContextPersistenceFilter : SecurityContextRepository에서 SecurityContext를 가져오거나 저장하는 역할을 한다.
    • LogoutFilter : 설정된 로그아웃 URL로 오는 요청을 감시하며, 해당 유저를 로그아웃 처리
    • AuthenticationFilter : 설정된 로그인 URL로 오는 요청을 감시하며, 유저 인증 처리
    • DefaultLoginPageGeneratingFilter : 인증을 위한 로그인폼 URL을 감시한다
    • BasicAuthenticationFilter : Http 기본 인증 헤더를 감시하여 처리
    • RequestCacheAwareFilter : 로그인 성공 후, 원래 요청 정보를 재구성하기 위해 사용
    • SecurityContextHolderAwareRequestFilter : HttpServletRequestWrapper를 상속한 SecurityContextHolderAwareRequestWapper 클래스로 HttpServletRequest 정보를 감싼다. SecurityContextHolderAwareRequestWrapper 클래스는 필터 체인상의 다음 필터들에게 부가정보를 제공한다.
    • AnonymousAuthenticationFilter : 이 필터가 호출되는 시점까지 사용자 정보가 인증되지 않았다면 인증토큰에 사용자가 익명 사용자로 나타난다.
    • SessionManagementFilter : 이 필터는 인증된 사용자와 관련된 모든 세션을 추적한다.
    • ExceptionTranslationFilter : 이 필터는 보호된 요청을 처리하는 중에 발생할 수 있는 예외를 위임하거나 전달하는 역할을 한다.
    • FilterSecurityInterceptor : 이 필터는 AccessDecisionManager 로 권한부여 처리를 위임함으로써 접근 제어 결정을 쉽게해준다.

    Authentication

    public interface Authentication extends Principal, Serializable { 
        Collection<? extends GrantedAuthority> getAuthorities(); // Authentication 저장소에 의해 인증된 사용자의 권한 목록 
        Object getCredentials(); // 주로 비밀번호 
        Object getDetails(); // 사용자 상세정보 
        Object getPrincipal(); // 주로 ID 
        boolean isAuthenticated(); //인증 여부 
        void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; 
    }
    • Principal은 Authentication 객체를 생성한다. 이 객체는 SecurityContext(내부 메모리)에 보관되고 사용되어 진다

    AuthenticationManager

    • 유저의 요청을 AuthenticationFilter에서 Authentication 객체로 변환해 AuthenticationManager에게 넘김, AuthenticationProvider가 실제 인증을 한 이후 인증이 완료되면 Authentication 객체를 반환
    • Spring Security는 ProviderManager라는 AuthenticationManage 인터페이스의 유일한 구현체를 제공한다. ProviderManager는 하나 또는 여러개의 AuthenticationProvider 구현체를 사용할 수 있다. AuthenticationProvider는 많이 사용되고 ProviderManager(AuthenticationManager의 구현체)와도 잘 통합되기 때문에 기본적으로 어떻게 동작하는 지 이해하는 것이 중요하다.

    인증 예외

    • 인증과 관련된 모든 예외는 AuthenticationException을 상속한다.
    • AuthenticationException은 개발자에게 상세한 디버깅 정보를 제공하기 위한 두개의 멤버 필드를 가진다.
      • authenticatoin : 인증 요청 관련 Authentication 객체를 저장하고 있다
      • extrainformation : 인증 예외 관련 부가 정보를 저장한다. 예를 들어 UsernameNotFoundException는
        인증에 실패한 유저의 id 정보를 저장하고 있다.

    많이 발생하는 예외

    - BadCredentialsException
    - LockedException
    - UsernameNotFoundException

    접근 권한 부여

    자동으로 설정된 Spring Security 필터 체인의 마지막 서블릿 필터는 FilterSecurityInterceptor 이다. 이 필터는 해당 요청의 수락 여부를 결정한다. FilterSecurityInterceptor 가 실행되는 시점에는 이미 사용자가 인증되어 있을 것이므로 유효한 사용자인지도 알 수 있다. Authentication 인터페이스에는 List getAuthorities() 라는 메소드가 있다는 것을 상기해 보자. 이 메소드는 사용자 아이디에 대한 권한 목록을 리턴한다. 권한처리시에 이 메소드가 제공하는 권한정보를 참조해서 해당 요청의 승인 여부를 결정하게 된다.
    Access Decision Manager 라는 컴포넌트가 인증 확인을 처리한다. AccessDecisionManager 인터페이스는 인증 확인을 위해 두 가지 메소드를 제공한다.

    • supports : AccessDecisionManager 구현체는 현재 요청을 지원하는지의 여부를 판단하는 두개의 메소드를 제공한다. 하나는 java.lang.Class 타입을 파라미터로 받고 다른 하나는 ConfigAttribute 타입을 파라미터로 받는다.
    • decide : 이 메소드는 요청 컨텍스트와 보안 설정을 참조하여 접근 승인여부를 결정한다. 이 메소드에는 리턴값이 없지만 접근 거부를 의미하는 예외를 던져 요청이 거부되었음을 알려준다.

    인증과정에서 발생할 수 있는 예상 가능한 에러를 처리하는 AuthenticationException 과 하위 클래스를 사용했던 것처럼 특정 타입의 예외 클래스들을 사용하면 권한처리를 하는 애플리케이션의 동작을 좀더 세밀하게 제어할 수 있다.
    AccessDecisionManager 는 표준 스프링 빈 바인딩과 레퍼런스로 완벽히 설정할 수 있다.디폴트 AccessDecisionManager 구현체는 AccessDecisionVoter 와 Vote 취합기반 접근 승인 방식을 제공한다.
    Voter 는 권한처리 과정에서 다음 중 하나 또는 전체를 평가한다.

    • 보호된 리소스에 대한 요청 컨텍스트 (URL 을 접근하는 IP 주소)
    • 사용자가 입력한 비밀번호
    • 접근하려는 리소스
    • 시스템에 설정된 파라미터와 리소스

    AccessDecisionManager 는 요청된 리소스에 대한 access 어트리뷰트 설정을 보터에게 전달하는 역할도 하므로 보터는 웹 URL 관련 access 어트리뷰트 설정 정보를 가지게 된다.
    Voter는 사용할 수 있는 정보를 사용해서 사용자의 리소스에 대한 접근 허가 여부를 판단한다. 보터는 접근 허가 여부에 대해서 세 가지 중 한 가지로 결정하는데, 각 결정은 AccessDecisionVoter 인터페이스에 다음과 같이 상수로 정의되어 있다.

    • Grant(ACCESS_GRANTED) : Voter 가 리소스에 대한 접근 권한을 허가하도록 권장한다.

    • Deny(ACCESS_DENIED) : Voter 가 리소스에 대한 접근 권한을 거부하도록 권장한다.

    • Abstain(ACCESS_ABSTAIN) : Voter 는 리소스에 대한 접근권한 결정을 보류한다. 이 결정 보류는 다음과 같은 경우에 발생할 수 있다.

      1. Voter 가 접근권한 판단에 필요한 결정적인 정보를 가지고 있지 않은 경우
      2.Voter 가 해당 타입의 요청에 대해 결정을 내릴 수 없는 경우

    Spring Security 레퍼런스

    https://galid1.tistory.com/576

    https://velog.io/@jayjay28/2019-09-04-1109-%EC%9E%91%EC%84%B1%EB%90%A8

    https://coding-start.tistory.com/153


    JWT

    개요

    • 웹 / 앱 개발을 하면 로그인 과정에서 반드시 만나게 되는 개념이 쿠키 - 세션이다.
    • 하지만 현재 요구사항을 충족시키지 못함
    • JSON 포맷을 이용한 web token
    • claim based token
    • 두 개체에서 JSON 객체를 이용해 self-contained 방식으로 정보를 안전하게 전달
    • 회원 인증, 정보 전달에 주로 사용
    • RFC 7519

    claim based

    • Claim : 사용자에 대한 프로퍼티 / 속성
    • 토큰 자체가 정보
    • Self-contained : 자체 포함, 즉 토큰 자체가 정보
    • key / value

    필요성

    • CSRF, 기존 시스템의 보안 문제
    • CORS, 도메인 확장시 api로서의 문제
    • Web, Mobile 등 다양한 클라이언트
    • Session의 한계
    • Scale out의 한계
    • REST API : Rest api는 Stateless를 지향

    세션의 문제

    • 서버 확장 시 세션 정보의 동기화 문제
      • 서버가 스케일아웃 돼서 여러 개가 생기면 각 서버마다 세션 정보가 저장된다
      • 로그인시(서버1), 새로고침(서버2)로 접근하면 서버는 인증이 안됐다고 판단한다
    • 서버 / 세션 저장소의 부하
      • 세션을 각 서버에 저장하지 않고 세션 전용 서버, db에 저장해도 문제가 생긴다
      • 모든 요청 시 해당 서버에 조회해야 한다. DB 부하를 야기할 수 있다.
    • 웹 / 앱 간의 상이한 쿠키 - 세션 처리 로직
      • 기존의 Client는 웹 브라우저가 유일했다. 그러나 이제는 모바일로 접근하는 경우도 처리해야 한다.
      • 웹 / 앱의 쿠키 처리 방법이 다르고 또 다른 Client가 생겨나면 쿠키 - 세션에 맞게 처리해야 함

    Token - Self - Contained & Stateless

    • 앞의 문제를 해결하는 최선의 방법은 토큰이다
    • 토큰은 서버의 상태를 저장하지 않는다. 토큰 자체로 정보를 가지고 있기 때문에 별도의 인증 서버가 필요없다. 따라서 요청을 받을 서버 자체에서 인증 프로세스를 수행할 수 있다.
    • 또한 JSON 포맷으로 통신하기 때문에 어떤 Client에서든 Data 통신에 Json을 이용하면 토큰을 이용할 수 있다.

    JWT’s Architecture

    • JWT의 기본 구조
      • Header
      • Payload
      • Signature
    • Header
      • JWT 웹 토큰의 헤더 정보
        {
        "alg" : "HS256",
        "type" : "JWT"
        }
    • Payload
      • 실제 토큰으로 사용하려는 데이터가 담기는 부분. 각 데이터를 Claim이라고 하며 다음과 같이 3가지 종류가 있다
        • Reversed claims
        • Public Claims
        • Private Claims
          {
          "name" : "hak",
          "age" : 26,
          }
    • Signature
      • Header와 Payload의 데이터 무결성과 변조 방지를 위한 서명
      • Header + Payload를 합친후, secret 키와 함께 Header의 해싱 알고리즘으로 인코딩
        HMACSHA256(
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),
        secret)
    • JWT 구조
      • JWT는 header, payload, signature 각각 json 형태의 데이터를 base 64 인코딩 후 합친다
      • 최종적으로 만들어진 토큰은 http 통신 간 이용되며, Authorization 이라는 key의 value로서 사용 된다

    JWT 의 단점 & 도입시 고려사항

    • Self-contained : 토큰 자체에 정보가 있다는 사실은 양날의 검이 될수 있다.
      • 토큰 길이 : 토큰 자체 payload 에 Claim set을 저장하기 때문에 정보가 많아질수록 토큰의 길이가 늘어나 네트워크에 부하를 줄 수 있다.
      • payload 암호화 : payload 자체는 암호화 되지 않고 base64로 인코딩한 데이터다.
      • 중간에 payload를 탈취하면 디코딩을 통해 테이터를 볼 수 있다.
      • JWE 를 통해 암호하하거나, payload에 중요 데이터를 넣지 않아야 한다.
    • Stateless : 무상태성이 때론 불편할 수 있다. 토큰은 한번 만들면 서버에서 제어가 불가능하다.
    • 토큰을 임의로 삭제할 수 있는 방법이 없기 때문에 토큰 만료시간을 꼭 넣어주는게 좋다.
    • Store Token : 토큰은 클라이언트 side에서 관리해야하기 때문에 토큰을 저장해야한다.

    Redis(Remote Dictionary Server)

    • 키 - 값 기반의 인메모리 데이터 저장소
    • 쿼리를 따로 할 필요 없이 결과를 바로 가져올 수 있음
    • 디스크에 데이터를 쓰는 구조가 아니라 메모리에서 데이터를 처리하기 때문에 속도가 빠르다
    • String, Lists, Sets, Sorted sets, Hashes 제공
    • 캐시 데이터 저장, 인증 토큰 저장, Ranking Board 등으로 사용됨

    Reference

    https://medium.com/@yongkyu.jang/springboot-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD%EA%B5%AC%EC%84%B1-2%EB%B2%88%EC%A7%B8-%EA%B8%80%EC%97%90-%EC%9D%B4%EC%96%B4-%EC%9D%B4%EB%B2%88%EC%97%90%EB%8A%94-%EC%9B%90%EB%9E%98-rest-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4%EC%84%9C-springboot%EA%B8%B0%EB%B0%98-api-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%84-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%ED%99%98%EA%B2%BD%EC%9D%84-%EA%B5%AC%EC%84%B1%ED%95%98%EB%8A%94-%EA%B1%B8%EB%A1%9C-de5997645b17

    댓글

Designed by Tistory.