JWT 토큰 인증이란? /Lombok? /DAT,DTO? /JPA 연관관계란??

이선우·2024년 7월 29일
0

CS 공부

목록 보기
3/10

2024-08-03 2주동안 새로 알게된 개념 정리


JWT 토큰 인증이란? (쿠키 VS 세션 VS 토큰)

서버가 클라이언트의 인증을 확인하는 방식은 대표적으로 쿠키, 세션, 토큰 3가지 방식이 있다고 한다. 이 세가지가 헷갈려 알아보았고, 하나씩 설명을 해보겠다.

쿠키는 Key-Value 형식의 문자열 덩어리이다.
클라이언트가 어떠한 웹사이트를 방문할 경우, 그 사이트가 사용하고 있는 서버를 통해 클라이언트의 브라우저에 설치되는 작은 기록 정보 파일이다. 각 사용자마다의 브라우저에 정보를 저장해 고유 정보 식별을 가능하게 한다.

쿠키의 인증 방식은

  1. 클라이언트가 서버에 접속을 한다
  2. 서버는 클라이언트의 요청에 대한 응답을 작성할 때, 클라이언트 측에 저장하고 싶은 정보를 응답 헤더의 Set-Cookie에 담는다
  3. 이후 해당 클라이언트는 요청을 보낼 때마다, 매번 저장된 쿠키를 요청 헤더의 Cookie에 담아 보내 서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별한다
  • 요청 시 쿠키의 값을 그대로 보내기 때문에 유출 및 조작 당할 위험이 존재해 보안에 취약하다
  • 쿠키에는 용량 제한이 있어 많은 정보를 담을 수 없다
  • 웹 브라우저마다 쿠키에 대한 지원 형태가 달라 브라우저 간 공유가 불가능하다
  • 쿠키의 사이즈가 커질수록 네트워크에 부하가 심해진다

Session 인증

쿠키의 보안적인 이슈를 해결하기 위해 세션은 비밀번호 등 클라이언트의 민감한 인증 정보를 브라우저가 아닌 서버 측에 저장하고 관리한다.
서버의 메모리나 서버의 로컬 파일, 데이터베이스에 저장한다

세션 객체는 Key에 해당하는 SESSEION ID와 이에 대응하는 Value로 구성되어 있으며, Map 형태로 저장된다.

세션의 인증 방식은
1. 유저가 웹사이트에서 로그인하면 세션이 서버 메모리/ 데이터베이스 상에 세션을 식별하기 위한 Session Id를 기준으로 정보를 저장한다
2. 서버에서 브라우저에 쿠키에 Session Id를 저장한다
3. 쿠키에 정보가 담겨있기 때문에 브라우저는 해당 사이트에 대한 모든 Request에 Session ID를 쿠키에 담아 전송한다
4. 서버는 클라이언트가 보낸 Session Id와 서버 메모리로 관리하고 있는 Session Id를 비교하여 인증을 수행한다

Session 방식의 단점

  • 쿠키를 포함한 요청이 외부에 노출되더라도 세션 ID 자체는 유의미한 개인정보를 담고 있지 않지만 해커가 세션 ID 자체를 탈취하여 클라이언트인척 위장할 수 있는 한계가 존재한다
  • 서버에서 세션 저장소를 사용하므로 요청이 많아지면 서버에 부하가 심해진다

Token 인증

토큰 기반 인증 시스템은 클라이언트가 서버에 접속을 하면 서버에서 해당 클 클라이언트에게 인증 되었다는 의미로 토큰을 부여한다. 이 토큰은 유일하며 토큰을 발급받은 클라이언트는 또 다시 서버에 요청을 보낼 때 요청 헤더에 토큰을 심어서 보낸다. 그러면 서버에서는 클라이언트로부터 받은 토큰을 서버에서 제공한 토큰과의 일치 여부를 체크하여 인증 과정을 처리한다

기존의 세션기반 인증과의 차이점은 세션기반 인증은 서버가 파일이나 데이터베이스에 세션정보를 가지고 있어야 하고 이를 조회하는 과정이 필요하기 때문에 많은 오버헤드가 발생하지만 토큰 인증 방식은 서버가 아닌 클라이언트에 저장되기 때문에 메모리나 스토리지 등을 통해 세션을 관리했던 서버의 부담을 덜 수 있다.

토큰은 앱과 서버가 통신 및 인증할 때 가장 많이 사용된다. 그 이유는 웹에는 쿠키와 세션이 있지만 앱에서는 없기 때문이다

Token 인증 방식의 순서는
1. 사용자가 아이디와 비밀번호로 로그인을 한다
2. 서버 측에서 클라이언트에게 유일한 토큰을 발급한다
3. 클라이언트는 서버 측에서 전달받은 토큰을 쿠키나 스토리지에 저장해 두고, 서버에 요청을 할 때마다 해당 토큰을 HTTP 요청 헤더에 포함시켜 전달한다
4. 서버는 전달받은 토큰을 검증하고 요청에 응답한다

Token 방식의 단점

  • 쿠키/세션과 다르게 토큰 자체의 데이터 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해질 수 있다.
  • Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다
  • 토큰을 탈취당하면 대처하기 어렵다

JWT (JSON Web Token) 이란?

JWT란 인증에 필요한 정보들을 암호화시킨 JSON 토큰을 의미한다. JWT 토큰을 HTTP 헤더에 실어 서버가 클라이언트를 식별하는 방식이다.

사용자가 JWT를 서버로 전송하면 서버는 서명을 검증하는 과정을 거치게 되며, 검증이 완료되면 요청한 응답을 돌려준다

JWT의 구조는 .을 구분자로 나누어지는 세 가지 문자열의 조합이다

Header에는 JWT에서 사용할 타입과 해시 알고리즘의 종류가 담겨있으며, Payload는 서버에서 첨부한 사용자 권한 정보와 데이터가 담겨있다.
Signature에는 Header, Payload를 Base64 URL-safe Encode를 한 이후 Header에 명시된 해시함수를 적용하고 개인키로 서명한 전자서명이 담겨있다

JWT를 이용한 인증과정은
1. 사용자가 ID, PW를 입력하여 서버에 로그인 인증을 요청한다
2. 서버에서 클라이언트로부터 인증 요청을 받으면, Header, PayLoad, Signature를 정의한다
3. 클라이언트는 서버로부터 받은 JWT를 로컬 스토리지에 저장하고 API를 서버에 요청할 때 Authorization header에 Access Token을 담아서 보낸다
4. 서버가 할 일은 클라이언트가 Header에 담아서 보낸 JWT가 내 서버에서 발행한 토큰인지 일치 여부를 확인하고 인증이 통과되었으면 페이로드에 있는 유저의 정보들을 select해서 클라이언트에 돌려준다
5. 클라이언트가 서버에 요청을 했는데, 만일 액세스 토큰의 시간이 만료되면 클라이언트는 리프레시 토큰을 이용해 서버로부터 새로운 액세스 토큰을 발급 받는다

JWT 장점

  • Header와 Payload를 가지고 Signature를 생성하므로 데이터 위변조를 막을 수 있다.
  • 인증 정보에 대한 별도의 저장소가 필요없다.
  • JWT는 토큰에 대한 기본 정보와 전달할 정보 및 토큰이 검증됬음을 증명하는 서명 등 필요한 모든 정보를 자체적으로 지니고 있다.
  • 클라이언트 인증 정보를 저장하는 세션과 다르게, 서버는 무상태(StateLess)가 되어 서버 확장성이 우수해질 수 있다.
  • 토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능하다. (쿠키와 차이)
  • OAuth의 경우 Facebook, Google 등 소셜 계정을 이용하여 다른 웹서비스에서도 로그인을 할 수 있다.
  • 모바일 어플리케이션 환경에서도 잘 동작한다. (모바일은 세션 사용 불가능)

JWT 단점

  • Self-contained : 토큰 자체에 정보를 담고 있으므로 양날의 검이 될 수 있다.
  • 토큰 길이 : 토큰의 Payload에 3종류의 클레임을 저장하기 때문에, 정보가 많아질수록 토큰의 길이가 늘어나 네트워크에 부하를 줄 수 있다.
  • Payload 인코딩 : payload 자체는 암호화 된 것이 아니라 BASE64로 인코딩 된 것이기 때문에, 중간에 Payload를 탈취하여 디코딩하면 데이터를 볼 수 있으므로, payload에 중요 데이터를 넣지 않아야 한다.
  • Store Token : stateless 특징을 가지기 때문에, 토큰은 클라이언트 측에서 관리하고 저장한다. 때문에 토큰 자체를 탈취당하면 대처하기가 어렵게 된다.

의존 역전 원칙 - DIP(Dependency Inversion Principle)

DIP 원칙이란 객체에서 어떤 Class를 참조해서 사용해야하는 상황이 생긴다면, 그 Class를 직접 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 / 인터페이스)로 참조하라는 원칙이다

객체들이 서로 정보를 주고 받을 때는 의존 관계가 형성되는데, 이 때 객체들은 나름대로의 원칙을 갖고 정보를 주고 받아야 하는 약속이 있다.

여기서의 원칙이 DIP 원칙으로 추상성이 낮은 클래스보다 추상성이 높은 클래스와 통신을 해야 한다는 것이다

클라이언트가 상속 관계로 이루어진 모듈을 사용할 때, 하위 모듈을 직접 인스턴스를 가져다 쓰지 말라는 뜻이다 그 이유는 하위 모듈의 구체적인 내용에 클라이언트가 의존하게 되어 하위 모듈에 변화가 있을 때마다 클라이언트나 상위 모듈의 코드를 자주 수정해야 되기 때문이다.


생성자 어노테이션

생성자 어노테이션은 Lombok에서 제공하는 어노테이션으로 RequiredArgsConstructor, NoArgsConstructor, AllArgsConstructor 세 가지가 있다

@NoArgsConstructor

제일 기본인 생성자 어노테이션으로 아무 인수가 없는 생성자를 생성해준다. DTO를 DB와 연결해서 사용할 때나 API를 호출하는 곳에서 파라미터를 DTO로 받아올 때 기본적으로 아무 인수가 없는 생성자라도 있어야해서 DTO에 제일 많이 사용한다

@RequiredArgsConstructor

꼭 필요한 객체의 변수를 인수로 받는 생성자를 구현해준다. 꼭 필요한 객체의 변수란 final 또는 @NotNull 어노테이션이 붙은 변수를 의미한다

@AllArgsConstructor

해당 객체 내에 있는 모든 변수들을 인수로 받는 생성자를 만들어내는 어노테이션이다. 이 어노테이션보다 builder 패턴을 사용하는 것이 코드 가동성에 좋기 때문에 많이는 사용하지는 않는다


DAO와 DTO

DAT와 DTO를 설명하기 위해서는 Entity를 먼저 알아야 한다.

Entity란 DB의 테이블에 존재하는 Column들을 필드로 가지는 객체를 뜻한다. Entity는 DB의 테이블과 1대 1 대응이며, 테이블에 가지지 않는 칼럼을 필드로 가져서는 안된다.

@Entity
public class Employee{
	
    @Id
    private Long id;
   
    private String name;
    
    private int age;
}

DTO(Data Transfer Object)란?

DTO는 말 그대로 데이터를 Transfer하기 위한 객체이다. 클라이언트가 컨트롤러에 요청을 보낼 때도 RequestDto의 형식으로 데이터가 이동하고, Controller가 Client에게 응답을 보낼 때도 ResponseDto의 형태로 데이터를 보내게 된다.

DTO는 로직을 갖고 있지 않는 순수한 데이터 객체이며, 일반적으로 getter/setter 메서드만을 가진다. DTO는 로직을 갖고 있지 않는 순수한 데이터 객체이다.

DTO를 굳이 왜 사용해??

  1. View Layer와 DB Layer의 역할을 분리하기 위해서

    객체를 표현하기 위한 계층과 저장하는 계층의 역할을 분리하기 위해서 DTO를 사용한다

  2. Entity 객체의 변경을 피하기 위해서

    Entity 객체를 그대로 사용하면 프로그래머의 의도와 다르게 데이터가 변질될 수 있다

  3. View와 통신하는 DTO 클래스는 자주 변경된다

  4. Domain 모델링을 지키기 위해서

    도메인 설계를 잘했다고 하더라도 원하는 데이터를 표시하기가 쉽지 않다. 예를 들어 Entity 클래스의 특정 컬럼들을 조합하여 특정 포멧을 출력하고 싶을 때, Entity 클래스에 표현을 위한 필드나 로직이 추가되면 객체 설계를 망가뜨릴 수 있다.

DAO(Data Access Object)란??

DAO는 말 그대로 실제 DB에 접근하는 객체를 뜻한다. 한 마디로 JPA를 사용할 때 만드는 Repository가 DAO의 역할을 한다. 물론 DAO와 Repository에 차이점은 있다. 간단하게 이해를 하기 위해서 정리를 하면 하나의 Repository 내부에서 다수의 DAO를 호출하는 방식으로 Repository를 구현할 수 있다


JPA 연관관계

JPA 연관관계가 내가 2주 정도의 시간동안 팀프로젝트를 하면서 가장 이해를 하기 어려웠던 부분이고 이해하기도 어려웠던 개념이었다.

연관 관계 정의 규칙

  • 방향: 단방향, 양방향(객체 참조)
  • 연관 관계의 주인: 양방향일 때, 연관 관계에서 관리 주체
  • 다중성: 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)

단방향과 양방향

DB 테이블은 외래 키 하나로 양쪽 테이브 조인이 가능하다.
따라서 DB는 단방향이니 양방향이니 나눌 필요가 없다 그러나 객체는 참조용 필드가 있는 객체만 다른 객체를 참조하는 것이 가능하다.

그렇기 때문에 두 객체 사이에 하나의 객체만 참조용 필드를 갖고 참조하면 단방향 관계, 두 객체 모두가 각각 참조용 필드를 갖고 참조하면 양방향 관계라고 한다.

단방향과 양방향 관계를 결정하는 방법은 비즈니스 로직에서 두 객체가 참조가 필요한지 여부를 고민해보면 된다

Board.getPost()처럼 참조가 필요하면 Board -> Post 단방향 참조

post.getBoard()처럼 참조가 필요하면 Post -> Board 단방향 참조

이런식으로 두 객체가 서로 단방향 참조를 했다면 양방향 연관 관계가 되는 것이다.

그러면 무조건 양방향 관계를 하면 되잖아????

불필요한 연관관계 매핑으로 인해 복잡성이 증가할 수 있다

연관관계의 주인

두 객체가 양방향 관계일 때 연관 관계의 주인을 지정해야한다.
두 단방향 관계중 제어의 권한(외래 키를 비롯한 테이블 레코드를 저장, 수정, 삭제 처리)를 갖는 실질적인 관계가 어떤 것인지 JPA에게 알려주는 것이다.

연관관계의 주인은 두 객체 사이에서 조회, 저장, 수정, 삭제를 할 수 있지만 주인이 아니면 조회만 가능하다

연관 관계의 주인이 아닌 객체에서 mappedBy 속성을 사용해서 주인을 지정해줘야 한다

외래 키가 있는 곳을 연관 관계의 주인으로 정하면 된다!!

다중성

데이터베이스를 기준으로 다중성을 결정한다

다대일(N:1)

요구사항

  • 하나의 게시판(1)에는 여러 게시글(N)을 작성할 수 있다
  • 하나의 게시글은 하나의 게시판에만 작성할 수 있다
  • 게시글과 게시판은 다대일 관계를 갖는다

DB를 기준으로 다중성(게시글 N : 게시판 1)을 결정했고, 외래 키를 게시글이 관리하는 일반적인 형태이다. (다(N) 쪽이 외래 키를 갖는다!!)

다대일 단방향

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

    @Column(name = "TITLE")
    private String title;

    @ManyToOne
    @JoinColumn(name = "BOARD_ID")
    private Board board;
    //... getter, setter
}

@Entity
public class Board {
    @Id @GeneratedValue
    private Long id;
    private String title;
    //... getter, setter
}

단방향이기 때문에 다 쪽인 Post에만 @ManyToOne만 추가

다대일 양방향

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

    @Column(name = "TITLE")
    private String title;

    @ManyToOne
    @JoinColumn(name = "BOARD_ID")
    private Board board;
    //... getter, setter
}

@Entity
public class Board {
    @Id @GeneratedValue
    private Long id;
    private String title;

    @OneToMany(mappedBy = "board")
    List<Post> posts = new ArrayList<>();
    //... getter, setter
}

일대다(1:N)

일대다가 다대일의 그저 반대 입장으로 쓴 것이 아니라 다대일의 기준은 연관관계의 주인 다(N)쪽에 둔 것이고 일대다는 연관관계의 주인을 일(1)쪽에 둔 것이다.

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

    @Column(name = "TITLE")
    private String title;
  //... getter, setter
}

@Entity
public class Board {
    @Id @GeneratedValue
    private Long id;
    private String title;

    @OneToMany
    @JoinColumn(name = "POST_ID") //일대다 단방향을 @JoinColumn필수
    List<Post> posts = new ArrayList<>();
    //... getter, setter
}

@OneToMany에 양방향이 아니기 때문에 mappedBy를 쓰지 않고 @JoinColumn을 이용해서 조인을 한다

(1:N)은 절대로 실무에서 사용 금지 XX

일대일(1:1)

주 테이블에 외래키를 넣을 수도 있고, 대상 테이블에 외래키를 넣을 수도 있다.

일대일 단방향

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

    @Column(name = "TITLE")
    private String title;
    @OneToOne
    @JoinColumn(name = "ATTACH_ID")
    private Attach attach;
    //... getter,setter
}
@Entity
public class Attach {
    @Id @GeneratedValue
    @Column(name = "ATTACH_ID")
    private Long id;
    private String name;
  //... getter, setter
}

profile
백엔드 개발자 준비생

0개의 댓글