Bean 수동 등록
@Component를 사용하면 @ComponentScan에 의해 자동으로 스캔되어 해당 클래스를 Bean으로 등록 해준다
- 일반적으로 @Component를 사용하여 Bean을 자동으로 등록하는 것이 좋다.
공통 로그처리와 같은 비즈니스 로직을 지원하기 위한 부가 적이고 공통적인 기능들을
기술 지원 Bean이라 부르고 수동등록 이라고 한다
수동등록된 Bean에서 문제가 발생했을 때 해당 위치를 파악하기 쉽다는 장점이 있다
Bean 수동 등록하는 방법
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- Bean으로 등록하고자하는 객체를 반환하는 메서드를 선언하고 @Bean을 설정합니다.
- Bean을 등록하는 메서드가 속한 해당 클래스에 @Configuration을 설정합니다.
- Spring 서버가 뜰 때 Spring IoC 컨테이너에 'Bean'으로 저장됩니다.
// 1. @Bean 설정된 메서드 호출
PasswordEncoder passwordEncoder = passwordConfig.passwordEncoder();
// 2. Spring IoC 컨테이너에 빈 (passwordEncoder) 저장
// passwordEncoder -> Spring IoC 컨테이너
'Bean' 이름: @Bean 이 설정된 메서드명
-> public PasswordEncoder passwordEncoder() {..} → passwordEncoder
Bean 등록해보기
비밀번호를 암호화할 때 사용하는 PasswordEncoder의 구현체 BCryptPasswordEncoder를 Bean으로 수동등록
PasswordConfig
package com.sparta.springauth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
등록한 passwordEncoder ‘Bean’을 사용하여 문자열을 암호화
PasswordEncoderTest
package com.sparta.springauth;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;
@SpringBootTest
public class PasswordEncoderTest {
@Autowired
PasswordEncoder passwordEncoder;
@Test
@DisplayName("수동 등록한 passwordEncoder를 주입 받아와 문자열 암호화")
void test1() {
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
}
}
같은 타입의 Bean이 2개라면
같은 타입 Bean 등록
1. Food Interface
package com.sparta.springauth.food;
public interface Food {
void eat();
}
2. food > Chicken
package com.sparta.springauth.food;
import org.springframework.stereotype.Component;
@Component
public class Chicken implements Food {
@Override
public void eat() {
System.out.println("치킨을 먹습니다.");
}
}
3. Pizza
package com.sparta.springauth.food;
import org.springframework.stereotype.Component;
@Component
public class Pizza implements Food {
@Override
public void eat() {
System.out.println("피자를 먹습니다.");
}
}
Food 타입의 Bean 객체 Chicken, Pizza를 등록
테스트를 위해 테스트 코드를 작성
@SpringBootTest
public class BeanTest {
@Autowired
Food food; //food 오류 : Food 타입의 Bena 객체가 하나 이상 있습니다
}
food 필드에 Bean을 주입해줘야하는데 같은 타입의 Bean 객체가 하나 이상이기 때문에
어떤 Bean을 등록해줘야할지 몰라 오류가 발생한 것
해결방법
1. 등록된 Bean 이름 명시하기
@SpringBootTest
public class BeanTest {
@Autowired
Food pizza;
@Autowired
Food chicken;
}
@Autowired가 기본적으로는 Bean Type(Food)으로 DI를 지원하며 연결이 되지않을 경우 Bean Name(pizza, chicken)으로 찾는 다는 것을 알 수 있습니다.
2. @Primary 사용하기
@Component
@Primary
public class Chicken implements Food {
@Override
public void eat() {
System.out.println("치킨을 먹습니다.");
}
}
Chicken 클래스에 @Primary를 추가
@SpringBootTest
public class BeanTest {
@Autowired
Food food;
}
@Primary가 추가되면 같은 타입의 Bean이 여러 개 있더라도 우선 @Primary가 설정된 Bean 객체를 주입 해줍니다
3. Qualifier 사용하기
@Component
@Qualifier("pizza")
public class Pizza implements Food {
@Override
public void eat() {
System.out.println("피자를 먹습니다.");
}
}
Pizza 클래스에 @Qualifier("pizza") 를 추가
@SpringBootTest
public class BeanTest {
@Autowired
@Qualifier("pizza")
Food food;
}
주입하고자하는 필드에도 @Qualifier("pizza") 를 추가해주면 해당 Bean 객체가 주입됩니다
테스트 코드
@SpringBootTest
public class BeanTest {
@Autowired
@Qualifier("pizza")
Food food;
@Test
@DisplayName("Primary 와 Qualifier 우선순위 확인")
void test1() {
// 현재 Chicken 은 Primary 가 적용된 상태
// Pizza는 Qualifier 가 추가된 상태입니다.
food.eat();
}
}
같은 타입의 Bean들에 Qualifier와 Primary가 동시에 적용되어있다면 Qualifier의 우선순위가 더 높습니다.
- 하지만 Qualifier는 적용하기 위해서 주입 받고자하는 곳에 해당 Qualifier를 반드시 추가해야합니다.
- 따라서 같은 타입의 Bean이 여러 개 있을 때는 범용적으로 사용되는 Bean 객체에는 Primary를 설정하고 지엽적으로 사용되는 Bean 객체에는 Qualifier를 사용하는 것이 좋습니다.
인증(Authentication)
- 인증은 해당 유저가 실제 유저인지 인증하는 개념입니다.
- 여러분의 스마트폰에 지문인식, 이용하는 사이트에 로그인 등과 같이, 실제 그 유저가 맞는지를 확인하는 절차 입니다.
인가(Authorization)
- 인가는 해당 유저가 특정 리소스에 접근이 가능한지 허가를 확인하는 개념입니다. 예를들어 관리자 페이지-관리자 권한 같은 것들을 들 수 있습니다.
우리가 자주 하는 로그인은 인증을 할 때(비밀번호 입력하고 제출 할 때)이고
회원/비회원 여부에 따라 다른 권한을 받는 것이 인가입니다.
웹 애플리케이션 인증은 Http라는 프로토콜을 이용하여 통신 (비연결성과 무상태로 이루어짐)
비연결성(Connectionless)은 서버와 클라이언트가 연결되어 있지 않다는 것 입니다.
채팅이나 게임 같은 것들을 하지 않는 이상 서버와 클라이언트는 실제로 연결되어 있지 않습니다.
그 이유는 리소스를 절약하기 위해서 인데, 만약 서버와 클라이언트가 실제로 계속 연결되어있다면 클라이언트는 그렇다고 쳐도, 서버의 비용이 기하급수적으로 늘어나기 때문입니다.
서버는 실제로 하나의 요청에 하나의 응답을 내버리고 연결을 끊어버리고있다 라고 생각하시면 좋습니다.
무상태(Stateless)는 서버가 클라이언트의 상태를 저장하지 않는다는 것입니다.
기존의 상태를 저장하는 것들도 마찬가지로 서버의 비용과 부담을 증가시키는 것 이기 때문에 기존의 상태가 없다고 가정하는 프로토콜을 이용해 구현되어 있습니다.
실제로 서버는 클라이언트가 직전에, 혹은 그 전에 어떠한 요청을 보냈는지 관심도 없고 전혀 알지 못합니다.
쿠키
- 클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일 입니다.
- 크롬 브라우저 기준으로 '개발자도구' 를 열어 보면
- Application - Storage - Cookies 에 도메인 별로 저장되어 있는게 확인 됩니다.
- 구성요소
- Name (이름): 쿠키를 구별하는 데 사용되는 키 (중복될 수 없음)
- Value (값): 쿠키의 값
- Domain (도메인): 쿠키가 저장된 도메인
- Path (경로): 쿠키가 사용되는 경로
- Expires (만료기한): 쿠키의 만료기한 (만료기한 지나면 삭제됩니다.)
세션
- 서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용됩니다.
- 서버에서 클라이언트 별로 유일무이한 '세션 ID' 를 부여한 후 클라이언트 별 필요한 정보를 서버에 저장합니다.
- 서버에서 생성한 '세션 ID' 는 클라이언트의 쿠키값('세션 쿠키' 라고 부름)으로 저장되어 클라이언트 식별에 사용됩니다.
쿠키 다루기
쿠키 생성
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
cookie.setPath("/");
cookie.setMaxAge(30 * 60);
// Response 객체에 Cookie 추가
res.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage());
}
}
- new Cookie(AUTHORIZATION_HEADER, cookieValue);
- Cookie에 저장될 Name과 Value를 생성자로 받는 Cookie 객체를 생성합니다.
- setPath("/"), setMaxAge(30 * 60)
- Path와 만료시간을 지정합니다.
- 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를 가져옵니다.
세션 다루기
Servlet에서는 유일무이한 '세션 ID'를 간편하게 만들수 있는 HttpSession을 제공 해줍니다.
HttpSession생성
createSession
@GetMapping("/create-session")
public String createSession(HttpServletRequest req) {
// 세션이 존재할 경우 세션 반환, 없을 경우 새로운 세션을 생성한 후 반환
HttpSession session = req.getSession(true);
// 세션에 저장될 정보 Name - Value 를 추가합니다.
session.setAttribute(AUTHORIZATION_HEADER, "Robbie Auth");
return "createSession";
}
- HttpServletRequest를 사용하여 세션을 생성 및 반환할 수 있습니다.
- req.getSession(true)
- 세션이 존재할 경우 세션을 반환하고 없을 경우 새로운 세션을 생성합니다.
- 세션에 저장할 정보를 Name-Value 형식으로 추가합니다.
- 반환된 세션은 브라우저 Cookie 저장소에 ‘JSESSIONID’라는 Name으로 Value에 저장됩니다.
HttpSession 읽기
getSession
@GetMapping("/get-session")
public String getSession(HttpServletRequest req) {
// 세션이 존재할 경우 세션 반환, 없을 경우 null 반환
HttpSession session = req.getSession(false);
String value = (String) session.getAttribute(AUTHORIZATION_HEADER); // 가져온 세션에 저장된 Value 를 Name 을 사용하여 가져옵니다.
System.out.println("value = " + value);
return "getSession : " + value;
}
- req.getSession(false)
- 세션이 존재할 경우 세션을 반환하고 없을 경우 null을 반환합니다.
- session.getAttribute(”세션에 저장된 정보 Name”)
- Name을 사용하여 세션에 저장된 Value를 가져옵니다.
'TIL' 카테고리의 다른 글
TIL 240523 JDBC (0) | 2024.05.24 |
---|---|
TIL 240522 NAVER Open API (0) | 2024.05.23 |
TIL 240520 JPA (0) | 2024.05.21 |
TIL 250517 (0) | 2024.05.20 |
TIL 0516 (0) | 2024.05.17 |