본문 바로가기
TIL/Spring

23.06.20

by J1-H00N 2023. 6. 20.

'Spring Security' 프레임워크는 Spring 서버에 필요한 인증 및 인가를 위해 많은 기능을 제공

// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
  • CSRF(사이트 간 요청 위조, Cross-site request forgery)
    • 공격자가 인증된 브라우저에 저장된 쿠키의 세션 정보를 활용하여 웹 서버에 사용자가 의도하지 않은 요청을 전달하는 것입니다.
    • CSRF 설정이 되어있는 경우 html 에서 CSRF 토큰 값을 넘겨주어야 요청을 수신 가능합니다.
    • 쿠키 기반의 취약점을 이용한 공격 이기 때문에 REST 방식의 API 에서는 disable 가능합니다.

Spring Security - Filter Chain

  • Spring에서 모든 호출은 DispatcherServlet을 통과하게 되고 이후에 각 요청을 담당하는 Controller 로 분배됩니다.
  • 이 때, 각 요청에 대해서 공통적으로 처리해야할 필요가 있을 때 DispatcherServlet 이전에 단계가 필요하며 이것이 Filter 입니다.
  • Spring Security도 인증 및 인가를 처리하기 위해 Filter를 사용하는데 Spring Security는 FilterChainProxy를 통해서 상세로직을 구현

Form Login 기반 인증은 인증이 필요한 URL 요청이 들어왔을 때 인증이 되지 않았다면 로그인 페이지를 반환하는 형태

UsernamePasswordAuthenticationFilter

  • UsernamePasswordAuthenticationFilter는 Spring Security의 필터인 AbstractAuthenticationProcessingFilter를 상속한 Filter입니다.
  • 기본적으로 Form Login 기반을 사용할 때 username 과 password 확인하여 인증합니다.
  • 인증 과정
    1. 사용자가 username과 password를 제출하면 UsernamePasswordAuthenticationFilter는 인증된 사용자의 정보가 담기는 인증 객체인 Authentication의 종류 중 하나인 UsernamePasswordAuthenticationToken을 만들어 AuthenticationManager에게 넘겨 인증을 시도합니다.
    2. 실패하면 SecurityContextHolder를 비웁니다.
    3. 성공하면 SecurityContextHolder에 Authentication를 세팅합니다.

SecurityContext는 인증이 완료된 사용자의 상세 정보(Authentication)를 저장

Authentication : 현재 인증된 사용자를 나타내며 SecurityContext에서 가져올 수 있다

  • principal : 사용자를 식별합니다.
    • Username/Password 방식으로 인증할 때 일반적으로 UserDetails 인스턴스입니다.
  • credentials : 주로 비밀번호, 대부분 사용자 인증에 사용한 후 비웁니다.
  • authorities : 사용자에게 부여한 권한을 GrantedAuthority로 추상화하여 사용합니다.

UserDetailsService는 username/password 인증방식을 사용할 때 사용자를 조회하고 검증한 후 UserDetails를 반환합니다. Custom하여 Bean으로 등록 후 사용 가능

UserDetails는 UsernamePasswordAuthenticationToken 타입의 Authentication를 만들 때 사용되며 해당 인증객체는 SecurityContextHolder에 세팅됩니다. Custom하여 사용 가능

 

Spring Security를 사용한 뒤 로그인 방식

로그인 처리 과정

 

Security 권한 설정 방법

  1. 회원 상세정보 (UserDetailsImpl) 를 통해 "권한 (Authority)" 설정 가능합니다.
  2. 권한을 1개 이상 설정 가능합니다.
  3. "권한 이름" 규칙
    1. "ROLE_" 로 시작해야 함
      • 예) "USER" 권한 부여 → "ROLE_USER"
      •       "ADMIN" 권한 부여 → "ROLE_ADMIN"

Spring Security를 이용한 API 별 권한 제어 방법

  • Controller 에 "@Secured" 애너테이션으로 권한 설정이 가능합니다.
    • @Secured("권한 이름") 선언

 

RestTemplate

개발을 하다보면 서버에서 구현하기에는 너무 복잡하고 비효울적인 기능이 필요할 때가 있다. 그래서 서버가 client입장이 되어서 다른 서버에 요청을 보내야 하는데, Spring에서는 서버에서 다른 서버로 간편하게 요청할 수 있도록 RestTemplate 기능을 제공하고 있다.

 

네이버 api 사용해보기

 

 

지금까지는 entity를 통해 하나의 테이블과 소통했었으나, 실제 서비스에서는 하나의 테이블만 다룰 리 없기 때문에, 여러개의 테이블의 연관관계를 entity에서 표현하는 방법에 대해 알아야 한다.

 

연관관계

고객이 음식을 주문한다고 해보자

 

1. 고객 테이블

  • 1명의 고객이 여러개의 음식을 시킬 수 있기 때문에 고객과 음식은 1 대 N 관계다.
  • 고객 테이블에 음식 주문 번호를 넣으면 고객의 이름이 중복되는 문제가 발생한다.

2. 음식 테이블

  • 하나의 음식은 여러명의 고객에게 주문 될 수 있으므로 음식과 고객은 1 대 N 관계다.
  • 음식 테이블에 고객 주문 번호를 넣으면 위와 마찬가지로 음식 이름이 중복되는 문제가 발생한다.

 

3. 주문 테이블

  • 주문 테이블을 만들어 두 테이블간의 연관 관계 문제를 해결한다
  • 고객과 음식 : 1 대 N
  • 음식과 고객 : 1 대 N
  • => 고객과 음식 : N 대 M
  • 이렇듯 N : M 관계인 테이블들의 연관 관계를 해결하기 위해 orders 테이블처럼 중간 테이블을 사용할 수 있다

 

테이블 간의 방향

  • 단방향은 예를 들어 users 테이블에서만 food 테이블을 참조할 수 있을 때
  • 양방향은 두 테이블이 서로를 참조할 수 있을 때를 말한다.

주문한 음식 정보를 users 테이블 기준으로 조회해보자

SELECT u.name as username, f.name as foodname, o.order_date as orderdate
FROM users u
         INNER JOIN orders o on u.id = o.user_id
         INNER JOIN food f on o.food_id = f.id
WHERE o.user_id = 1;

 반대로 고객 정보를 foods 테이블 기준으로 조회해보자

SELECT u.name as username, f.name as foodname, o.order_date as orderdate
FROM food f
         INNER JOIN orders o on f.id = o.food_id
         INNER JOIN users u on o.user_id = u.id
WHERE o.user_id = 1;

두 결과가 완전히 일치한다.

이렇듯 데이터베이스에서는 어떤 자료를 기준으로 하든 원하는 결과를 조회할 수 있다. 즉, 방향은 없다.

 

그렇다면 JPA Entity에서는 이러한 테이블간의 연관 관계를 어떻게 표현하고 있을까

 

음식:고객 = 1:N 관계로 표현해보자

음식

@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

고객

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user")
    private List<Food> foodList = new ArrayList<>();
}

자바 컬렉션을 사용하여 RDBMS에서는 거의 불가능했던

이와 같은 형태로 '표현'이 가능하다.

=> 그 이유 : 고객 Entity 입장에서는 음식 Entity의 정보를 가지고 있지 않으면 음식의 정보를 조회할 방법이 없다. 그래서 DB 테이블에 실제 컬럼으로 존재하지는 않지만 Entity 상태에서 다른 Entity를 참조하기 위해 이러한 방법을 사용하는 것이다. 이렇게 서로의 Entity를 참조하는 즉, 기존 데이터베이스에서는 없던 방향을 JPA Entity에서는 양방향 관계로 만들 수 있다.

 

고객 쪽에서 리스트를 아래와 같이 없애면

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

고객은 음식 데이터를 조회할 방법이 없다. 즉, 음식 Entity만 고객 Entity를 조회할 수 있다. 즉, 지금처럼 단방향 관계로도 설정할 수 있다.

 

<정리>

  • DB 테이블에서는 테이블 사이의 연관관계를 FK(외래 키)로 맺을 수 있고 방향 상관없이 조회가 가능합니다.
  • Entity에서는 상대 Entity를 참조하여 Entity 사이의 연관관계를 맺을 수 있습니다.
  • 하지만 상대 Entity를 참조하지 않고 있다면 상대 Entity를 조회할 수 있는 방법이 없습니다.
  • 따라서 Entity에서는 DB 테이블에는 없는 방향의 개념이 존재합니다.

 

1대1 관계

@OneToOne : 1대1 관계를 맺어주는 역할

  • 단방향 관계
    • 외래키의 주인 정하기
      • Entity에서 외래 키의 주인은 일반적으로 N(다)의 관계인 Entity 이지만 1 대 1 관계에서는 외래 키의 주인을 직접 지정해야한다.
      • 외래 키 주인만이 외래 키등록, 수정, 삭제할 수 있으며, 주인이 아닌 쪽은 오직 외래 키 읽기만 가능
      • @JoinColumn()은 외래 키의 주인이 컬럼명, null 여부, unique 여부 등을 지정할 수 있는 애너테이션
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
  • 양방향 관계
    • 외래키의 주인 정하기
      • 양방향 관계에서 외래 키의 주인을 지정해 줄 때 mappedBy 옵션을 사용
      • mappedBy의 속성값은 외래 키의 주인인 상대 Entity의 필드명을 의미
      • 마찬가지로 @JoinColumn()로 외래 키의 주인이 활용
      • 양방향 관계에서 mappedBy 옵션을 생략할 경우 JPA가 외래 키의 주인 Entity를 파악할 수가 없어 의도하지 않은 중간 테이블이 생성되기 때문에 반드시 설정해주시는게 좋다
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user; // 외래키
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToOne(mappedBy = "user") // 외래키 주인의 필드명
    private Food food;
}

 

다대1 관계

@ManyToOne : 다대1 관계를 맺어주는 역할

  • 단방향 관계
    • 음식이 N의 관계로 외래키의 주인
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
  • 양방향 관계
    • 양방향 참조를 위해 고객 Entity에서 Java 컬렌션을 사용하여 음식 Entity 참조
    • 음식 Entity가 N의 관계로 외래 키의 주인
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user")
    private List<Food> foodList = new ArrayList<>(); // 여러개의 음식 데이터를 저장하기 위해 컬렉션 사용
}

'TIL > Spring' 카테고리의 다른 글

23.07.10  (0) 2023.07.10
23.06.21  (0) 2023.06.21
23.06.19  (0) 2023.06.19
23.06.15  (0) 2023.06.15
23.06.14  (0) 2023.06.14