Post

TIL-Spring 숙련주차(1)

2024-05-21

오늘의 학습 🌠


1

Bean으로 수동 등록


  • Bean 수동 등록이란?
    • @Component를 사용하면 @ComponentScan에 의해 자동으로 스캔되어 해당 클래스를 Bean으로 등록 해줌.
    • 일반적으로 @Component를 사용하여 Bean을 자동으로 등록하는 것이 좋습니다.
      • 프로젝트의 규모가 커질 수록 등록할 Bean들이 많아지기 때문에 자동등록을 사용하면 편리함.
      • 비즈니스 로직과 관련된 클래스들은 그 수가 많기 때문에 @Controller, @Service와 같은 애너테이션들을 사용해서 Bean으로 등록하고 관리하면 개발 생산성에 유리함.
  • Bean 수동 등록은 언제 사용하는가?

    • 기술적인 문제공통적인 관심사를 처리할 때 사용하는 객체들을 수동으로 등록하는 것이 좋음.

    • 공통 Log처리와 같은 비즈니스 로직을 지원하기 위한 부가 적이고 공통적인 기능들을 기술 지원 Bean이라 부르고 수동 등록함.
    • 비즈니스 로직 Bean 보다는 그 수가 적기 때문에 수동으로 등록하기 부담스럽지 않음.
    • 또한 수동등록된 Bean에서 문제가 발생했을 때 해당 위치를 파악하기 쉽다는 장점이 있음.
  • 직접 수동 등록 해보기
    • 비밀번호를 암호화 할 때 사용하는 객체를 Bean으로 수동 등록해 보기
1
2
3
4
5
6
7
8
@Configuration  
public class PasswordConfig {  
  
    @Bean  
  public PasswordEncoder passwordEncoder() {  
        return new BCryptPasswordEncoder();  
    }  
}
  • Bean으로 등록하고자 하는 객체를 반환하는 메서드 선언

  • 메서드 위에 @Bean 추가, Bean을 등록하는 메서드가 속해있는 해당 Class 위에 @Configuration 추가.
  • Spring 서버가 뜰 때 Spring IoC 컨테이너에 ‘Bean’으로 저장

  • Bean에 저장 될 때는 대문자 P가 소문자 p로 바뀌어서 즉, passwordConfig로 저장됨.
  • 메서드명 또한 passwordEncoder로 저장됨.

  • PasswordEncoder에 들어가보면 interface임. 그러니까 구현체를 넣어줘야됨. PasswordEncoder 구현체 중에 BCryptPasswordEncode를 선택한 것.

  • PasswordEncoder Bean 등록을 한 다음에 가져다 사용하면 즉, DI(Dependency Injection) 주입 받으면 BCryptPasswordEncode 구현체가 등록이 됨.

  • BCrypt 는 Hash 함수로 비밀번호를 암호화 해주는 Hash 함수임. 강력한 Hash 메커니즘을 가지고 있어서 굉장히 많이 사용됨.

  • 위에서 PasswordEncoder를 Bean으로 등록했으니 주입 받아와서 사용해야 함.
1
2
@Autowired  
PasswordEncoder passwordEncoder;
  • @Autowired 를 사용하여 PasswordEncoder를 주입 받아오고 있음.
1
2
3
4
5
6
7
8
9
10
11
12
// 현재 password
String password = "Robbie's password";  
  
// 암호화  
String encodePassword = passwordEncoder.encode(password);  
System.out.println("encodePassword = " + encodePassword);  
  
String inputPassword = "Robbie";  
  
// 복호화를 통해 암호화된 비밀번호와 비교  
boolean matches = passwordEncoder.matches(inputPassword, encodePassword);  
System.out.println("matches = " + matches); // 암호화할 때 사용된 값과 다른 문자열과 비교했기 때문에 false
  • passwordEncoder.matches 매개변수 첫 번째는 String 값 그대로를 parameter로 넣고 두 번째는 암호화가 이루어진 encodePassword를 넣어줌.

  • 그런데 하나는 평문이고 하나는 암호화된 String인데 어떻게 비교할 수 있는가?

    • matches 내부에서 자동으로 비교함. 우리가 입력 받아온 문자열 평문을 matches 내부에서 암호화한 다음에 두 개를 일치하는지 비교하는 것


같은 타입의 Bean Type 이 2개일 때


1
2
3
4
5
package com.sparta.springauth.food;  
  
public interface Food {  
    void eat();  
}
1
2
3
4
5
6
7
8
9
10
11
12
package com.sparta.springauth.food;  
  
import org.springframework.stereotype.Component;  
  
@Component  
public class Chicken implements Food {  
    @Override  
  public void eat() {  
        System.out.println("치킨을 먹습니다.");  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
package com.sparta.springauth.food;  
  
import org.springframework.stereotype.Component;  
  
@Component  
public class Pizza implements Food {  
    @Override  
  public void eat() {  
        System.out.println("피자를 먹습니다.");  
    }  
}
  • @Autiwired 하고 Food food; 하면 오류가 생김.

  • 치킨을 주입할지 피자를 주입할지 모르기 때문에.

  • 어떻게 해결하는가?
  • 1번 방법 🎈
    • 등록이 된 Bean의 이름을 명시
      • Chicken 클래스라면 @Autowired Food chicken;

      • Pizza 클래스라면 @Autowired Food pizza;

  • 여기서 알 수 있는 사실은 @Aturowired는 Bean의 타입(Food의 타입)을 기준으로 DI를 지원함. 하지만 연결이 되지 않으면 그때는 Bean의 이름으로 찾음.

  • 2번 방법 🎈
    1
    2
    3
    4
    5
    6
    7
    8
    
    @Component  
    @Primary  
    public class Chicken implements Food {  
      @Override  
    public void eat() {  
          System.out.println("치킨을 먹습니다.");  
      }  
    }
    
  • @Primary가 추가되면 같은 타입의 Bean이 여러 개 있더라도 우선적으로 @Primary가 적용이 되어있는 Bean 객체를 주입해 줌.

  • 3번 방법 🎈
1
2
3
4
5
6
7
8
@Component  
@Qualifier("pizza")  
public class Pizza implements Food {  
    @Override  
  public void eat() {  
        System.out.println("피자를 먹습니다.");  
    }  
}
1
2
3
4
5
6
7
8
9
@Autowired  
@Qualifier("pizza")  
	Food food;  
  
@Test  
@DisplayName("테스트")  
void test1() {  
    food.eat();  
}
  • @Qualifier(“pizza”)를 통해 Bean 객체 주입
1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootTest  
public class BeanTest {  
  
    @Autowired  
	@Qualifier("pizza")  
    Food food;  
  
    @Test  
 @DisplayName("Primary와 Qualifier 우선순위 확인")  
    void test1() {  
        food.eat();  
    }  
}
  • 같은 타입의 Bean 들에 Primary와 Qualifier가 동시에 걸려있다고 하면 Qualifier의 우선순위가 더 높음. 하지만 Qualifier는 주입받고자 하는 곳에 반드시 @Qualifier("pizza") 이와 같이 붙여줘야 됨.

  • 같은 타입의 Bean이 여러개 있을 때는 범용적으로 사용되는 Bean 객체는 Primary 임. 지엽적으로 사용되는 Bean 객체는 @Qualifier 임.

  • 예를들어 치킨이 주문을 95% 이상 차지한다면 그러면 치킨에 Primary를 걸어주는 게 좋음. 피자는 5%이니 지엽적으로 @Qualifier("pizza") 를 붙여서 수고로움을 최소화(5%밖에 안되니)할 수 있음.

  • Spring에서는 좁은 범위의 설정이 우선순위가 높음. 큰 범위 - @Primary, 좁은 범위 - @Qualifier임. 따라서 @Qualifier가 우선순위가 높음.


인증, 인가


  • 인증(Authentication)
    • 인증은 해당 유저가 실제 유저인지 인증하는 개념

    • 지문인식, 사이트 로그인 등과 같이 실제 그 유저가 맞는지를 확인하는 절차

  • 인가(Authorization)
    • 인가는 해당 유저가 특정 리소스에 접근이 가능하니 허가를 확인하는 개념. 예시로 관리자 페이지- 관리자 권한 같은 것
1
2
3
📌 인증과 인가의 헷갈리는 부분
	우리가 자주하는 로그인은 인증을 할 때(비밀번호를 입력할 때 제출할 때)이고
	회원/비회원 여부에 따라 다른 권한을 받는 것이 인가
  • 웹 어플리케이션 인증

  • 일반적으로 서버-클라이언트 구조, 실제로 이 두가지 요소는 아주 멀리 떨어져 있음.
  • 그리고 Http 라는 프로토콜을 이용하여 통신하는데, 그 통신은 비연결성 무상태로 이루어짐

    • 비연결성은 서버와 클라이언트가 연결되어 있지 않다는 것을 의미. 채팅이나 게임 같은 것들을 하지 않는 이상 서버와 클라이언트는 실제로 연결되어 있지 않음. 이유는 리소스를 절약하기 위해서임. 만약 서버와 클라이언트가 실제로 계속 연결되어있다면 서버의 비용이 기하급수적으로 늘어나기 때문

    • 그래서 서버는 실제로 하나의 요청에 하나의 응답을 내버리고 연결을 끊어버리고 있다라고 생각하면 좋음.

    • 무상태는 서버가 클라이언트의 상태를 저장하지 않는다는 것.

  • 인터넷 사용시 이전의 정보들이 잘 있는 것처럼 연속성있게 사용하는 것처럼 보여요!
    • 실제로 그렇게 느껴지기 위해 많은 노력을 기울이며 url을 계층적으로 설계하고 다음 요청에 대한 api url을 이전 계층에만 둔다면 연속적으로 사용하고 있다고 느낄 수 있음.
  • 그렇다면 인증과 같이 해당 유저가 인증을 통과했다는 사실은 상태값이 아닌가?
    • 비연결성, 무상태 프로토콜에서 “유저가 인증되었다”라는 정보를 유지시켜야 한다는 과제를 어떻게 해결했는지 관점에서 보시면 좋을 것 같음.
  • 인증 방식
    • 쿠키-세션 방식의 인증

      1

    • JWT(JSON Web Token) 기반 인증 1


쿠키와 세션


  • 쿠키와 세션 모두 HTTP에 상태 정보를 유지(Stateful)하기 위해 사용됨. 즉, 쿠키와 세션을 통해 서버에서는 클라이언트 별로 인증 및 인가를 할 수 있게 됨.

  • 쿠키
    • 클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일
  • 세션
    • 서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용됨.

    • 서버에서 클라이언트 별로 유일무이한 ‘세션 ID’를 부여한 후 클라이언트 별 필요한 정보를 서버에 저장함
    • 서버에서 생성한 ‘세션 ID’는 클라이언트의 쿠키값(‘세션 쿠키’라고 부름)으로 저장되어 클라이언트 식별에 사용됨.
 쿠키세션
설명클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용
저장 위치클라이언트 (웹 브라우져)웹 서버
사용 예사이트 팝업의 “오늘 다시보지 않기” 정보 저장로그인 정보 저장
만료 시점쿠키 저장 시 만료일시 설정 가능 (브라우져 종료시도 유지 가능)다음 조건 중 하나가 만족될 경우 만료됨 1. 브라우져 종료 시까지 2. 클라이언트 로그아웃 시까지 3. 서버에 설정한 유지기간까지 해당 클라이언트의 재요청이 없는 경우
용량 제한브라우져 별로 다름 (크롬 기준) - 하나의 도메인 당 180개 - 하나의 쿠키 당 4KB(=4096byte)개수 제한 없음 (단, 세션 저장소 크기 이상 저장 불가능)
보안취약 (클라이언트에서 쿠키 정보를 쉽게 변경, 삭제 및 가로채기 당할 수 있음)비교적 안전 (서버에 저장되기 때문에 상대적으로 안전)
  • 위 그림을 보면 세션 ID가 필요한데 Servlet에서는 유일무이한 ‘세션 ID’를 간편하게 만들수 있는 HttpSession을 제공 해줌.

RestTemplate


  • 지금까지는 Client 즉, 브라우저로부터 요청을 받는 서버의 입장에서 개발을 진행해옴.

  • 서비스 개발을 진행하다보면 라이브러리 사용만으로는 구현이 힘든 기능들이 많이 존재함.
  • 예를 들어 우리의 서비스에서 회원가입을 진행할 때 사용자의 주소를 받아야 한다면?
    • 주소를 검색할 수 있는 기능을 구현해야하는데 직접 구현을 하게되면 많은 시간과 비용이 들어감.
  • 이 때, 카카오에서 만든 주소 검색 API를 사용한다면 해당 기능을 간편하게 구현할 수 있음.

  • 이럴 때 우리의 서버는 Client의 입장이 되어 Kakao 서버에 요청을 진행해야함.
  • Spring에서는 서버에서 다른 서버로 간편하게 요청할 수 있도록 RestTemplate 기능을 제공하고 있음.

Static


  • Static은 ‘정적인, 고정된’이라는 뜻을 가지고 있음. 이러한 이름을 가지는 이유는 바로 static이 앞에 붙는 변수나 메서드는 어떤 객체에 소속되는 것이 아닌, 클래스에 고정되어 있는 변수나 메서드이기 때문

    • Static 사용 이유
  • 메모리에 고정적으로 할당된다.

    • Static이 붙지 않은 메서드나 변수의 경우 객체가 생성될 때마다 호출되어 서로 다른 값을 가지고 있을 수 있습니다. 그렇기 때문에 각 객체들에서 공통적으로 하나의 값이 유지되어야 할 경우 static을 유용하게 사용할 수 있음.
  • static 메서드 내에서는 인스턴스 변수를 사용할 수 없다.

    • Static 메서드는 프로그램 실행과 동시에 메모리에 올라가기 때문에 인스턴스 변수는 사용할 수 없음. 인스턴스 변수는 객체를 생성해야만 사용이 가능하기 때문에 객체를 생성하기 전에 먼저 메모리에 올라가는 static 메서드에서는 사용할 수 없는 것. 그래서 메서드에 static을 붙여주고 싶다면, 해당 메서드 내부에 인스턴스 변수나 인스턴스 메서드를 사용하는 부분이 있는지 먼저 확인해주어야 함.

추가 🕤


1

  • 다 자라서 자기 일에 책임을 질 수 있는 사람인 저는 오늘부터 Spring 숙련 주차에 들어간만큼 열심히..해야겠어요~

🐱‍🏍— —🤸🏻‍♀️ ~~~ 야~호~

This post is licensed under CC BY 4.0 by the author.