Spring 숙련 - 3주

ayboori·2023년 6월 20일
0

Spring

목록 보기
5/24

Bean

Bean 수동 등록

@Component를 사용하면 @ComponentScan에 의해 자동으로 스캔되어 해당 클래스를 Bean으로 등록 해준다. 자동으로 등록하는 것이 좋다.

수동 등록하는 경우
기술적인 문제나 공통적인 관심사를 처리할 때 사용하는 객체들
공통 로그처리와 같은 비즈니스 로직을 지원하기 위한 부가 적이고 공통적인 기능 (기술 지원 Bean)

- 비즈니스 로직 Bean 보다는 그 수가 적기 때문에 수동으로 등록하기 부담스럽지 않습니다.
- 또한 수동등록된 Bean에서 문제가 발생했을 때 해당 위치를 파악하기 쉽다는 장점이 있습니다.

문법

1) Bean 으로 등록하고자 하는 클래스 선언
2) 위에 @Bean Annotation 추가
3) Bean을 등록하는 메서드가 속한 해당 클래스에 @Configuration을 설정

@Configuration
public class PasswordConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Spring 서버 실행 시 IoC 컨테이너에 Bean으로 저장된다.

같은 타입의 Bean이 2개라면?

하나의 인터페이스를 2개의 클래스로 구현해서 Bean으로 등록할 수 있다

그러나 그 인터페이스를 @Autowired (주입 받기) 하려고 하면 에러가 발생한다
둘 중 무엇을 주입할지 몰라서 우리에게 묻는다

1) 테스트 시 등록된 Bean의 이름을 명시해준다. (= 구현한 객체 이름)

    @Autowired
    Food pizza; // 이미 구현된 객체 이름과 동일
    
    @Autowired
    Food chicken;

Bean Type으로 찾았을 때 연결이 되지 않으면 Bean Name(pizza, chicken)으로 찾는다.

2) 구현체 둘 중 하나에게 @Primary 부여 > 우선 사용

@Component
@Primary
public class Chicken implements Food {
    @Override
    public void eat() {
        System.out.println("치킨을 먹습니다.");
    }
}

3) @Qualifier로 이름을 지정한 구현체를 지정 > 우선 사용
1. Pizza 구현 시 클래스 위에 @Qualifier("pizza") 작성

@SpringBootTest
public class BeanTest {

    @Autowired
    @Qualifier("pizza") // 2. 주입할 객체 위에 작성
    Food food;
}

Primary / Qualifier 중 우선순위는 Qualifier가 더 높다
그러나 주입받고자 하는 곳에 Qualifier를 무조건 작성해야 한다.
즉, 많이 사용하는 객체에 Primary, 지역적으로 사용하는 객체에 Qualifier를 붙이는 게 좋다


인증 / 인가

인증(Authentication)

  • 해당 유저가 실제 유저인지 인증 (지문인식, 로그인, ...)

인가(Authorization)

  • 해당 유저가 특정 리소스에 접근이 가능한지 허가

웹 애플리케이션 인증

  1. 일반적으로 서버-클라이언트 구조로 되어있고, 실제로 이 두가지 요소는 아주 멀리 떨어져 있습니다.
  2. 그리고 Http 라는 프로토콜을 이용하여 통신하는데, 그 통신은 비연결성(Connectionless) 무상태(Stateless) 로 이루어집니다.

    비연결성 : 채팅이나 게임 같은 것들을 하지 않는 이상 서버와 클라이언트는 실제로 연결되어 있지 않다. 리소스를 절약하기 위해서
    무상태 : 서버가 클라이언트의 상태를 저장하지 않는다

연결된 것처럼 느낄 수 있게끔 URL을 통해 구현하고 있다.
그렇다면 다음 페이지에서는 내가 로그인한 것을 어떻게 알고 있을까?

인증 방식

1) 쿠키 - 세션 방식

  • 서버가 ‘특정 유저가 로그인 되었다’는 상태를 저장
  • 유저의 인증과 관련된 최소한의 정보를 저장해서 로그인을 유지시킨다
  1. 사용자가 로그인 요청을 보낸다.
  2. 서버는 DB의 유저 테이블을 확인하여 ID, Password를 대조한다.
  3. 실제 유저테이블의 정보와 일치한다면 인증을 통과한 것으로 보고 “세션 저장소”에 해당 유저가 로그인 되었다는 정보를 넣습니다.
  4. 세션 저장소에서는 유저의 정보와는 관련 없는 난수인 session-id를 발급합니다.
  5. 서버는 로그인 요청의 응답으로 session-id를 내어줍니다.
  6. 클라이언트는 그 session-id를 쿠키라는 저장소에 보관하고 앞으로의 요청마다 세션아이디를 같이 보냅니다. (주로 HTTP header에 담아서 보냅니다!)
  7. 클라이언트의 요청에서 쿠키를 발견했다면 서버는 세션 저장소에서 쿠키를 검증합니다.
  8. 만약 유저정보를 받아왔다면 이 사용자는 로그인이 되어있는 사용자겠죠?
  9. 이후에는 로그인 된 유저에 따른 응답을 내어줍니다.

2) JWT 기반 인증

  • JWT(JSON Web Token) : 인증에 필요한 정보들을 암호화시킨 토큰
  • JWT 기반 인증은 쿠키/세션 방식과 유사하게 JWT 토큰(Access Token)을 HTTP header에 실어 서버가 클라이언트를 식별합니다.
  1. 사용자가 로그인 요청을 보냅니다.
  2. 서버는 DB의 유저 테이블을 확인하여 ID, Password를 대조한다.
  3. 실제 유저테이블의 정보와 일치한다면 인증을 통과한 것으로 보고 유저의 정보를 JWT로 암호화 해서 내보냅니다.
  4. 서버는 로그인 요청의 응답으로 jwt 토큰을 내어줍니다.
  5. 클라이언트는 그 토큰을 저장소에 보관하고 앞으로의 요청마다 토큰을 같이 보냅니다.
  6. 클라이언트의 요청에서 토큰을 발견했다면 서버는 토큰을 검증합니다.
  7. 이후에는 로그인 된 유저에 따른 응답을 내어줍니다.

세션 저장소가 따로 없다는 것이 가장 큰 차이이다.
JWT는 토큰 자체를 브라우저에 던지기 때문에 더 효율적일 수 있다.


쿠키와 세션이란 무엇일까?

  • HTTP 에 상태 정보를 유지(Stateful)하기 위해 사용
  • 서버에서는 클라이언트 별로 인증 및 인가에 사용

쿠키

클라이언트에 저장될 목적으로 생성한 작은 정보를 답은 파일

  • 구성요소
    • Name (이름): 쿠키를 구별하는 데 사용되는 키 (중복될 수 없음)
    • Value (값): 쿠키의 값
    • Domain (도메인): 쿠키가 저장된 도메인
    • Path (경로): 쿠키가 사용되는 경로
    • Expires (만료기한): 쿠키의 만료기한 (만료기한 지나면 삭제됩니다.)

세션

  • 서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용
  • 서버에서 클라이언트 별로 unique 값의 '세션 ID' 부여 > 클라이언트 별 필요한 정보를 서버에 저장
  1. 클라이언트가 서버에 1번 요청
  2. 서버가 세션ID 를 생성하고, 쿠키에 담아 응답 헤더에 전달
    • 세션 ID 형태: "SESSIONID = 12A345"
  3. 클라이언트가 쿠키에 세션ID를 저장 ('세션쿠키')
  4. 클라이언트가 서버에 2번 요청
    • 쿠키값 (세션 ID) 포함하여 요청
  5. 서버가 세션ID 를 확인하고, 1번 요청과 같은 클라이언트임을 인지

쿠키와 세션 비교

쿠키 다루기

쿠키 생성

public static void addCookie(String cookieValue, HttpServletResponse res) {
    try {
        cookieValue = URLEncoder.encode(cookieValue, "utf-8").replaceAll("\\+", "%20"); 
        // Cookie Value 에는 공백이 불가능해서 encoding 진행

        Cookie cookie = new Cookie(AUTHORIZATION_HEADER, cookieValue); // Name-Value 생성자의 객체 생성
        // AUTHORIZATION_HEADER는 위에 생성된 String 상수
        
        // 경로, 만료시간 설정
        cookie.setPath("/");
        cookie.setMaxAge(30 * 60);

        // Response 객체에 Cookie 추가
        res.addCookie(cookie);
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e.getMessage());
    }
}
  • Servlet에서 만들어준 Response 객체에 데이터를 담으면 클라이언트로 반환된다
    (이전 강의에 관련 내용 있음)
  • HttpServletResponse 객체에 생성한 Cookie 객체를 추가하여 브라우저로 반환한다
    • 이렇게 반환된 Cookie는 브라우저의 Cookie 저장소에 저장
  • Cookie 생성은 범용적으로 사용될 수 있기 때문에 static 메서드로 선언합니다.

쿠키 읽기

@GetMapping("/get-cookie")
public String getCookie(@CookieValue(AUTHORIZATION_HEADER) String value) {
    System.out.println("value = " + value);

    return "getCookie : " + value;
}
  • @CookieValue("Cookie의 Name") : Cookie의 Name 정보를 토대로 Cookie의 Value를 가져온다

세션 다루기

세션 만들기 - HttpSession 생성

세션 ID를 간편하게 만들 수 있다.

@GetMapping("/create-session")
public String createSession(HttpServletRequest req) {
    // 세션이 존재할 경우 세션 반환, 없을 경우 새로운 세션을 생성한 후 반환
    HttpSession session = req.getSession(true);

    // 세션에 저장될 정보 Name - Value 를 추가합니다.
    session.setAttribute(AUTHORIZATION_HEADER, "Robbie Auth");

    return "createSession";
}
  • HttpServletRequest을 사용하여 세션 생성, 반환
  • 반환된 세션은 브라우저 Cookie 저장소에 ‘JSESSIONID’라는 Name으로 Value에 저장됩니다.

세션 가져오기 - HttpSession 읽기

@GetMapping("/get-session")
public String getSession(HttpServletRequest req) {
    // 세션이 존재할 경우 세션 반환, 없을 경우 null 반환, false 옵션 주기
    HttpSession session = req.getSession(false);

    String value = (String) session.getAttribute(AUTHORIZATION_HEADER); 
    // 가져온 세션에 저장된 Value 를 Name 을 사용하여 가져옵니다.
    System.out.println("value = " + value);

    return "getSession : " + value;
}
profile
프로 개발자가 되기 위해 뚜벅뚜벅.. 뚜벅초

0개의 댓글